Repository: boa-dev/boa Branch: main Commit: 58a245878906 Files: 961 Total size: 8.9 MB Directory structure: gitextract_xo92gz2d/ ├── .cargo/ │ └── config.toml ├── .config/ │ └── nextest.toml ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ ├── custom.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── codecov.yml │ ├── dependabot.yml │ ├── labeler.yml │ ├── release.yml │ └── workflows/ │ ├── codeql.yml │ ├── labeler.yml │ ├── nightly_build.yml │ ├── pr_management.yml │ ├── pull_request.yml │ ├── release.yml │ ├── rust.yml │ ├── security_audit.yml │ ├── test262.yml │ ├── test262_comment.yml │ ├── test262_release.yml │ └── webassembly.yml ├── .gitignore ├── .husky/ │ └── pre-push ├── .prettierignore ├── .vscode/ │ ├── launch.json │ └── tasks.json ├── ABOUT.md ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-MIT ├── LICENSE-UNLICENSE ├── Makefile.toml ├── PR_MESSAGE_EXPECT_EXPRESSION.md ├── README.md ├── SECURITY.md ├── benches/ │ ├── Cargo.toml │ ├── benches/ │ │ └── scripts.rs │ ├── scripts/ │ │ ├── basic/ │ │ │ ├── call-loop.js │ │ │ ├── closure.js │ │ │ └── nested-loop.js │ │ ├── closures/ │ │ │ ├── create.js │ │ │ └── invoke.js │ │ ├── intl/ │ │ │ ├── collator-compare.js │ │ │ ├── collator-construction.js │ │ │ ├── datetimeformat-construction.js │ │ │ ├── datetimeformat-format.js │ │ │ ├── datetimeformat-with-options.js │ │ │ ├── datetimeformat_resolved_options.js │ │ │ ├── listformat-construction.js │ │ │ ├── listformat-format.js │ │ │ ├── numberformat-construction.js │ │ │ ├── numberformat-different-options.js │ │ │ ├── pluralrules-construction.js │ │ │ ├── pluralrules-select.js │ │ │ ├── segmenter-construction.js │ │ │ └── segmenter-segment.js │ │ ├── properties/ │ │ │ └── access.js │ │ ├── prototypes/ │ │ │ └── chain.js │ │ ├── strings/ │ │ │ ├── concat.js │ │ │ ├── replace.js │ │ │ ├── slice.js │ │ │ └── split.js │ │ └── v8-benches/ │ │ ├── README.md │ │ ├── crypto.js │ │ ├── deltablue.js │ │ ├── earley-boyer.js │ │ ├── navier-stokes.js │ │ ├── raytrace.js │ │ ├── regexp.js │ │ ├── richards.js │ │ └── splay.js │ └── src/ │ └── lib.rs ├── cli/ │ ├── ABOUT.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── debug/ │ │ ├── function.rs │ │ ├── gc.rs │ │ ├── limits.rs │ │ ├── mod.rs │ │ ├── object.rs │ │ ├── optimizer.rs │ │ ├── realm.rs │ │ ├── shape.rs │ │ └── string.rs │ ├── executor.rs │ ├── helper.rs │ ├── logger.rs │ └── main.rs ├── clippy.toml ├── core/ │ ├── ast/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── declaration/ │ │ │ │ ├── export.rs │ │ │ │ ├── import.rs │ │ │ │ ├── mod.rs │ │ │ │ └── variable.rs │ │ │ ├── expression/ │ │ │ │ ├── access.rs │ │ │ │ ├── await.rs │ │ │ │ ├── call.rs │ │ │ │ ├── identifier.rs │ │ │ │ ├── import_meta.rs │ │ │ │ ├── literal/ │ │ │ │ │ ├── array.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── object.rs │ │ │ │ │ └── template.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── new.rs │ │ │ │ ├── new_target.rs │ │ │ │ ├── operator/ │ │ │ │ │ ├── assign/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── op.rs │ │ │ │ │ ├── binary/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── op.rs │ │ │ │ │ ├── conditional.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── unary/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── op.rs │ │ │ │ │ └── update/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── op.rs │ │ │ │ ├── optional.rs │ │ │ │ ├── parenthesized.rs │ │ │ │ ├── regexp.rs │ │ │ │ ├── spread.rs │ │ │ │ ├── tagged_template.rs │ │ │ │ ├── this.rs │ │ │ │ └── yield.rs │ │ │ ├── function/ │ │ │ │ ├── arrow_function.rs │ │ │ │ ├── async_arrow_function.rs │ │ │ │ ├── async_function.rs │ │ │ │ ├── async_generator.rs │ │ │ │ ├── class.rs │ │ │ │ ├── generator.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── ordinary_function.rs │ │ │ │ └── parameters.rs │ │ │ ├── keyword/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── lib.rs │ │ │ ├── module_item_list/ │ │ │ │ └── mod.rs │ │ │ ├── operations/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── pattern.rs │ │ │ ├── position.rs │ │ │ ├── property.rs │ │ │ ├── punctuator/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── scope.rs │ │ │ ├── scope_analyzer.rs │ │ │ ├── source.rs │ │ │ ├── source_text.rs │ │ │ ├── statement/ │ │ │ │ ├── block.rs │ │ │ │ ├── if.rs │ │ │ │ ├── iteration/ │ │ │ │ │ ├── break.rs │ │ │ │ │ ├── continue.rs │ │ │ │ │ ├── do_while_loop.rs │ │ │ │ │ ├── for_in_loop.rs │ │ │ │ │ ├── for_loop.rs │ │ │ │ │ ├── for_of_loop.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── while_loop.rs │ │ │ │ ├── labelled.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── return.rs │ │ │ │ ├── switch.rs │ │ │ │ ├── throw.rs │ │ │ │ ├── try.rs │ │ │ │ └── with.rs │ │ │ ├── statement_list.rs │ │ │ └── visitor.rs │ │ └── tests/ │ │ └── scope.rs │ ├── engine/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ ├── benches/ │ │ │ ├── README.md │ │ │ ├── bench_scripts/ │ │ │ │ ├── arithmetic_operations.js │ │ │ │ ├── array_access.js │ │ │ │ ├── array_create.js │ │ │ │ ├── array_pop.js │ │ │ │ ├── boolean_object_access.js │ │ │ │ ├── clean_js.js │ │ │ │ ├── fibonacci.js │ │ │ │ ├── for_loop.js │ │ │ │ ├── mini_js.js │ │ │ │ ├── number_object_access.js │ │ │ │ ├── object_creation.js │ │ │ │ ├── object_prop_access_const.js │ │ │ │ ├── object_prop_access_dyn.js │ │ │ │ ├── regexp.js │ │ │ │ ├── regexp_creation.js │ │ │ │ ├── regexp_literal.js │ │ │ │ ├── regexp_literal_creation.js │ │ │ │ ├── string_code_point_sum.js │ │ │ │ ├── string_compare.js │ │ │ │ ├── string_concat.js │ │ │ │ ├── string_copy.js │ │ │ │ ├── string_object_access.js │ │ │ │ └── symbol_creation.js │ │ │ └── full.rs │ │ ├── src/ │ │ │ ├── bigint.rs │ │ │ ├── builtins/ │ │ │ │ ├── array/ │ │ │ │ │ ├── array_iterator.rs │ │ │ │ │ ├── from_async.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── array_buffer/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── shared.rs │ │ │ │ │ ├── tests.rs │ │ │ │ │ └── utils.rs │ │ │ │ ├── async_function/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── async_generator/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── async_generator_function/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── atomics/ │ │ │ │ │ ├── futex.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── bigint/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── boolean/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── builder.rs │ │ │ │ ├── dataview/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── date/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── tests.rs │ │ │ │ │ └── utils.rs │ │ │ │ ├── error/ │ │ │ │ │ ├── aggregate.rs │ │ │ │ │ ├── eval.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── range.rs │ │ │ │ │ ├── reference.rs │ │ │ │ │ ├── syntax.rs │ │ │ │ │ ├── tests.rs │ │ │ │ │ ├── type.rs │ │ │ │ │ └── uri.rs │ │ │ │ ├── escape/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── eval/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── finalization_registry/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── function/ │ │ │ │ │ ├── arguments.rs │ │ │ │ │ ├── bound.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── generator/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── generator_function/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── intl/ │ │ │ │ │ ├── collator/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── options.rs │ │ │ │ │ ├── date_time_format/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ ├── options.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── list_format/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── options.rs │ │ │ │ │ ├── locale/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ ├── options.rs │ │ │ │ │ │ ├── tests.rs │ │ │ │ │ │ └── utils.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── number_format/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ ├── options.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── options.rs │ │ │ │ │ ├── plural_rules/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── options.rs │ │ │ │ │ └── segmenter/ │ │ │ │ │ ├── iterator.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── options.rs │ │ │ │ │ └── segments.rs │ │ │ │ ├── is_html_dda.rs │ │ │ │ ├── iterable/ │ │ │ │ │ ├── async_from_sync_iterator.rs │ │ │ │ │ ├── iterator_constructor.rs │ │ │ │ │ ├── iterator_helper.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── tests.rs │ │ │ │ │ └── wrap_for_valid_iterator.rs │ │ │ │ ├── json/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── map/ │ │ │ │ │ ├── map_iterator.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── ordered_map.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── math/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── number/ │ │ │ │ │ ├── conversions.rs │ │ │ │ │ ├── globals.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── object/ │ │ │ │ │ ├── for_in_iterator.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── options.rs │ │ │ │ ├── promise/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── proxy/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── reflect/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── regexp/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── regexp_string_iterator.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── set/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── ordered_set.rs │ │ │ │ │ ├── set_iterator.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── string/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── string_iterator.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── symbol/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── temporal/ │ │ │ │ │ ├── calendar/ │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── duration/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── error.rs │ │ │ │ │ ├── instant/ │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── now.rs │ │ │ │ │ ├── options.rs │ │ │ │ │ ├── plain_date/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── plain_date_time/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── plain_month_day/ │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── plain_time/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── plain_year_month/ │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── tests.rs │ │ │ │ │ └── zoneddatetime/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── typed_array/ │ │ │ │ │ ├── builtin.rs │ │ │ │ │ ├── element/ │ │ │ │ │ │ ├── atomic.rs │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── object.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── uri/ │ │ │ │ │ ├── consts.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── weak/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── weak_ref.rs │ │ │ │ ├── weak_map/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ └── weak_set/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── bytecompiler/ │ │ │ │ ├── class.rs │ │ │ │ ├── declaration/ │ │ │ │ │ ├── declaration_pattern.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── declarations.rs │ │ │ │ ├── env.rs │ │ │ │ ├── expression/ │ │ │ │ │ ├── assign.rs │ │ │ │ │ ├── binary.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── object_literal.rs │ │ │ │ │ ├── unary.rs │ │ │ │ │ └── update.rs │ │ │ │ ├── function.rs │ │ │ │ ├── generator.rs │ │ │ │ ├── jump_control.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── module.rs │ │ │ │ ├── register.rs │ │ │ │ ├── statement/ │ │ │ │ │ ├── block.rs │ │ │ │ │ ├── break.rs │ │ │ │ │ ├── continue.rs │ │ │ │ │ ├── if.rs │ │ │ │ │ ├── labelled.rs │ │ │ │ │ ├── loop.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── switch.rs │ │ │ │ │ ├── try.rs │ │ │ │ │ └── with.rs │ │ │ │ └── utils.rs │ │ │ ├── class.rs │ │ │ ├── context/ │ │ │ │ ├── hooks.rs │ │ │ │ ├── icu.rs │ │ │ │ ├── intrinsics.rs │ │ │ │ ├── mod.rs │ │ │ │ └── time.rs │ │ │ ├── environments/ │ │ │ │ ├── mod.rs │ │ │ │ ├── runtime/ │ │ │ │ │ ├── declarative/ │ │ │ │ │ │ ├── function.rs │ │ │ │ │ │ ├── global.rs │ │ │ │ │ │ ├── lexical.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── module.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── private.rs │ │ │ │ └── tests.rs │ │ │ ├── error/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── host_defined.rs │ │ │ ├── interop/ │ │ │ │ ├── into_js_arguments.rs │ │ │ │ ├── into_js_function_impls.rs │ │ │ │ └── mod.rs │ │ │ ├── job.rs │ │ │ ├── lib.rs │ │ │ ├── module/ │ │ │ │ ├── loader/ │ │ │ │ │ ├── embedded.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── namespace.rs │ │ │ │ ├── source.rs │ │ │ │ └── synthetic.rs │ │ │ ├── native_function/ │ │ │ │ ├── continuation.rs │ │ │ │ └── mod.rs │ │ │ ├── object/ │ │ │ │ ├── builtins/ │ │ │ │ │ ├── jsarray.rs │ │ │ │ │ ├── jsarraybuffer.rs │ │ │ │ │ ├── jsasyncgenerator.rs │ │ │ │ │ ├── jsdataview.rs │ │ │ │ │ ├── jsdate.rs │ │ │ │ │ ├── jsfinalization_registry.rs │ │ │ │ │ ├── jsfunction.rs │ │ │ │ │ ├── jsgenerator.rs │ │ │ │ │ ├── jsgeneratorfunction.rs │ │ │ │ │ ├── jsmap.rs │ │ │ │ │ ├── jsmap_iterator.rs │ │ │ │ │ ├── jspromise.rs │ │ │ │ │ ├── jsproxy.rs │ │ │ │ │ ├── jsregexp.rs │ │ │ │ │ ├── jsset.rs │ │ │ │ │ ├── jsset_iterator.rs │ │ │ │ │ ├── jssharedarraybuffer.rs │ │ │ │ │ ├── jstypedarray.rs │ │ │ │ │ ├── jsweakmap.rs │ │ │ │ │ ├── jsweakset.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── datatypes.rs │ │ │ │ ├── internal_methods/ │ │ │ │ │ ├── immutable_prototype.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── string.rs │ │ │ │ ├── jsobject.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── operations.rs │ │ │ │ ├── property_map.rs │ │ │ │ ├── shape/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── property_table.rs │ │ │ │ │ ├── root_shape.rs │ │ │ │ │ ├── shared_shape/ │ │ │ │ │ │ ├── forward_transition.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ ├── template.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── slot.rs │ │ │ │ │ └── unique_shape.rs │ │ │ │ └── tests.rs │ │ │ ├── optimizer/ │ │ │ │ ├── mod.rs │ │ │ │ ├── pass/ │ │ │ │ │ ├── constant_folding.rs │ │ │ │ │ ├── dead_code_elimination.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── strength_reduction.rs │ │ │ │ └── walker.rs │ │ │ ├── property/ │ │ │ │ ├── attribute/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── mod.rs │ │ │ │ └── nonmaxu32.rs │ │ │ ├── realm.rs │ │ │ ├── script.rs │ │ │ ├── spanned_source_text.rs │ │ │ ├── string.rs │ │ │ ├── symbol.rs │ │ │ ├── sys/ │ │ │ │ ├── fallback/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── js/ │ │ │ │ │ └── mod.rs │ │ │ │ └── mod.rs │ │ │ ├── tests/ │ │ │ │ ├── async_generator.rs │ │ │ │ ├── class.rs │ │ │ │ ├── control_flow/ │ │ │ │ │ ├── loops.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── env.rs │ │ │ │ ├── function.rs │ │ │ │ ├── generators.rs │ │ │ │ ├── iterators.rs │ │ │ │ ├── job.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── operators.rs │ │ │ │ ├── promise.rs │ │ │ │ ├── spread.rs │ │ │ │ └── to_string.rs │ │ │ ├── try_into_js_result_impls.rs │ │ │ ├── value/ │ │ │ │ ├── conversions/ │ │ │ │ │ ├── convert.rs │ │ │ │ │ ├── either.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── nullable/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── serde_json.rs │ │ │ │ │ ├── try_from_js/ │ │ │ │ │ │ ├── collections.rs │ │ │ │ │ │ └── tuples.rs │ │ │ │ │ ├── try_from_js.rs │ │ │ │ │ └── try_into_js.rs │ │ │ │ ├── display/ │ │ │ │ │ ├── arguments.rs │ │ │ │ │ ├── array.rs │ │ │ │ │ ├── map.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── object.rs │ │ │ │ │ ├── primitives.rs │ │ │ │ │ ├── set.rs │ │ │ │ │ ├── typed_array.rs │ │ │ │ │ └── value.rs │ │ │ │ ├── equality.rs │ │ │ │ ├── hash.rs │ │ │ │ ├── inner/ │ │ │ │ │ ├── legacy.rs │ │ │ │ │ └── nan_boxed.rs │ │ │ │ ├── inner.rs │ │ │ │ ├── integer.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── operations.rs │ │ │ │ ├── tests.rs │ │ │ │ ├── type.rs │ │ │ │ └── variant.rs │ │ │ └── vm/ │ │ │ ├── call_frame/ │ │ │ │ └── mod.rs │ │ │ ├── code_block.rs │ │ │ ├── completion_record.rs │ │ │ ├── flowgraph/ │ │ │ │ ├── color.rs │ │ │ │ ├── edge.rs │ │ │ │ ├── graph.rs │ │ │ │ ├── mod.rs │ │ │ │ └── node.rs │ │ │ ├── inline_cache/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── mod.rs │ │ │ ├── opcode/ │ │ │ │ ├── args.rs │ │ │ │ ├── arguments.rs │ │ │ │ ├── await/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── binary_ops/ │ │ │ │ │ ├── logical.rs │ │ │ │ │ ├── macro_defined.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── call/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── concat/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── control_flow/ │ │ │ │ │ ├── jump.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── return.rs │ │ │ │ │ └── throw.rs │ │ │ │ ├── copy/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── define/ │ │ │ │ │ ├── class/ │ │ │ │ │ │ ├── getter.rs │ │ │ │ │ │ ├── method.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── setter.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── own_property.rs │ │ │ │ ├── delete/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── environment/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── function.rs │ │ │ │ ├── generator/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── yield_stm.rs │ │ │ │ ├── get/ │ │ │ │ │ ├── argument.rs │ │ │ │ │ ├── function.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── name.rs │ │ │ │ │ ├── private.rs │ │ │ │ │ └── property.rs │ │ │ │ ├── iteration/ │ │ │ │ │ ├── for_in.rs │ │ │ │ │ ├── get.rs │ │ │ │ │ ├── iterator.rs │ │ │ │ │ ├── loop_ops.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── meta/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── new/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── nop/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── object.rs │ │ │ │ ├── pop/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── push/ │ │ │ │ │ ├── array.rs │ │ │ │ │ ├── class/ │ │ │ │ │ │ ├── field.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── private.rs │ │ │ │ │ ├── environment.rs │ │ │ │ │ ├── literal.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── numbers.rs │ │ │ │ │ └── object.rs │ │ │ │ ├── rest_parameter/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── set/ │ │ │ │ │ ├── class_prototype.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── name.rs │ │ │ │ │ ├── private.rs │ │ │ │ │ └── property.rs │ │ │ │ ├── switch/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── templates/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── to/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── unary_ops/ │ │ │ │ │ ├── decrement.rs │ │ │ │ │ ├── increment.rs │ │ │ │ │ ├── logical.rs │ │ │ │ │ └── mod.rs │ │ │ │ └── value/ │ │ │ │ └── mod.rs │ │ │ ├── runtime_limits.rs │ │ │ ├── shadow_stack.rs │ │ │ ├── source_info/ │ │ │ │ ├── builder/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ └── tests.rs │ │ └── tests/ │ │ ├── assets/ │ │ │ ├── dir1/ │ │ │ │ ├── file1_1.js │ │ │ │ └── file1_2.js │ │ │ ├── file1.js │ │ │ └── gcd.js │ │ ├── gcd.rs │ │ ├── imports.rs │ │ ├── macros.rs │ │ └── module.rs │ ├── gc/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── cell.rs │ │ ├── internals/ │ │ │ ├── ephemeron_box.rs │ │ │ ├── gc_box.rs │ │ │ ├── gc_header.rs │ │ │ ├── mod.rs │ │ │ ├── vtable.rs │ │ │ └── weak_map_box.rs │ │ ├── lib.rs │ │ ├── pointers/ │ │ │ ├── ephemeron.rs │ │ │ ├── gc.rs │ │ │ ├── mod.rs │ │ │ ├── weak.rs │ │ │ └── weak_map.rs │ │ ├── test/ │ │ │ ├── allocation.rs │ │ │ ├── cell.rs │ │ │ ├── erased.rs │ │ │ ├── mod.rs │ │ │ ├── std_types.rs │ │ │ ├── weak.rs │ │ │ └── weak_map.rs │ │ └── trace.rs │ ├── icu_provider/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── data/ │ │ │ ├── icu_casemap.postcard │ │ │ ├── icu_collator.postcard │ │ │ ├── icu_datetime.postcard │ │ │ ├── icu_decimal.postcard │ │ │ ├── icu_list.postcard │ │ │ ├── icu_locale.postcard │ │ │ ├── icu_normalizer.postcard │ │ │ ├── icu_plurals.postcard │ │ │ ├── icu_segmenter.postcard │ │ │ └── icu_time.postcard │ │ └── src/ │ │ └── lib.rs │ ├── interner/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── fixed_string.rs │ │ ├── interned_str.rs │ │ ├── lib.rs │ │ ├── raw.rs │ │ ├── sym.rs │ │ └── tests.rs │ ├── macros/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── class.rs │ │ │ ├── embedded_module_loader.rs │ │ │ ├── lib.rs │ │ │ ├── module.rs │ │ │ ├── utils.rs │ │ │ └── value.rs │ │ └── tests/ │ │ └── str.rs │ ├── parser/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── error/ │ │ │ ├── mod.rs │ │ │ └── tests.rs │ │ ├── lexer/ │ │ │ ├── comment.rs │ │ │ ├── cursor.rs │ │ │ ├── error.rs │ │ │ ├── identifier.rs │ │ │ ├── mod.rs │ │ │ ├── number.rs │ │ │ ├── operator.rs │ │ │ ├── private_identifier.rs │ │ │ ├── regex.rs │ │ │ ├── spread.rs │ │ │ ├── string.rs │ │ │ ├── template.rs │ │ │ ├── tests.rs │ │ │ └── token.rs │ │ ├── lib.rs │ │ ├── parser/ │ │ │ ├── cursor/ │ │ │ │ ├── buffered_lexer/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ └── mod.rs │ │ │ ├── expression/ │ │ │ │ ├── assignment/ │ │ │ │ │ ├── arrow_function.rs │ │ │ │ │ ├── async_arrow_function.rs │ │ │ │ │ ├── conditional.rs │ │ │ │ │ ├── exponentiation.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── yield.rs │ │ │ │ ├── await_expr.rs │ │ │ │ ├── fpl_or_exp.rs │ │ │ │ ├── identifiers.rs │ │ │ │ ├── left_hand_side/ │ │ │ │ │ ├── arguments.rs │ │ │ │ │ ├── call.rs │ │ │ │ │ ├── member.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── optional/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── template.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── primary/ │ │ │ │ │ ├── array_initializer/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── async_function_expression/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── async_generator_expression/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── class_expression/ │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── function_expression/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── generator_expression/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── object_initializer/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── template/ │ │ │ │ │ │ └── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── tests.rs │ │ │ │ ├── unary.rs │ │ │ │ └── update.rs │ │ │ ├── function/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── mod.rs │ │ │ ├── statement/ │ │ │ │ ├── block/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── break_stm/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── continue_stm/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── declaration/ │ │ │ │ │ ├── export.rs │ │ │ │ │ ├── hoistable/ │ │ │ │ │ │ ├── async_function_decl/ │ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ │ └── tests.rs │ │ │ │ │ │ ├── async_generator_decl/ │ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ │ └── tests.rs │ │ │ │ │ │ ├── class_decl/ │ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ │ └── tests.rs │ │ │ │ │ │ ├── function_decl/ │ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ │ └── tests.rs │ │ │ │ │ │ ├── generator_decl/ │ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ │ └── tests.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── import.rs │ │ │ │ │ ├── lexical.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── expression/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── if_stm/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── iteration/ │ │ │ │ │ ├── do_while_statement.rs │ │ │ │ │ ├── for_statement.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── tests.rs │ │ │ │ │ └── while_statement.rs │ │ │ │ ├── labelled_stm/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── return_stm/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── switch/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── throw/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── try_stm/ │ │ │ │ │ ├── catch.rs │ │ │ │ │ ├── finally.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── variable/ │ │ │ │ │ └── mod.rs │ │ │ │ └── with/ │ │ │ │ └── mod.rs │ │ │ └── tests/ │ │ │ ├── format/ │ │ │ │ ├── declaration.rs │ │ │ │ ├── expression.rs │ │ │ │ ├── function/ │ │ │ │ │ ├── class.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ └── statement.rs │ │ │ ├── mod.rs │ │ │ └── test.js │ │ └── source/ │ │ ├── mod.rs │ │ ├── utf16.rs │ │ └── utf8.rs │ ├── runtime/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ ├── assets/ │ │ │ └── harness.js │ │ ├── build.rs │ │ ├── src/ │ │ │ ├── abort/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── base64/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── clone/ │ │ │ │ └── mod.rs │ │ │ ├── console/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── extensions.rs │ │ │ ├── fetch/ │ │ │ │ ├── fetchers.rs │ │ │ │ ├── headers.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── request.rs │ │ │ │ ├── response.rs │ │ │ │ └── tests/ │ │ │ │ ├── e2e.rs │ │ │ │ ├── headers.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── request.rs │ │ │ │ └── response.rs │ │ │ ├── interval/ │ │ │ │ └── tests.rs │ │ │ ├── interval.rs │ │ │ ├── lib.rs │ │ │ ├── message/ │ │ │ │ ├── mod.rs │ │ │ │ ├── senders.rs │ │ │ │ └── tests.rs │ │ │ ├── microtask/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── process/ │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── store/ │ │ │ │ ├── from.rs │ │ │ │ ├── mod.rs │ │ │ │ └── to.rs │ │ │ ├── text/ │ │ │ │ ├── encodings.rs │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── url/ │ │ │ │ └── tests.rs │ │ │ └── url.rs │ │ └── tests/ │ │ ├── clone/ │ │ │ ├── complex.js │ │ │ ├── date.js │ │ │ ├── errors.js │ │ │ ├── map.js │ │ │ ├── object.js │ │ │ ├── regexp.js │ │ │ ├── set.js │ │ │ ├── simple.js │ │ │ └── transfer.js │ │ └── clone.rs │ ├── string/ │ │ ├── ABOUT.md │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── builder.rs │ │ ├── code_point.rs │ │ ├── common.rs │ │ ├── display.rs │ │ ├── iter.rs │ │ ├── lib.rs │ │ ├── str.rs │ │ ├── tests.rs │ │ ├── type.rs │ │ └── vtable/ │ │ ├── mod.rs │ │ ├── sequence.rs │ │ ├── slice.rs │ │ └── static.rs │ └── wintertc/ │ ├── ABOUT.md │ ├── Cargo.toml │ └── src/ │ ├── abort/ │ │ └── mod.rs │ ├── base64/ │ │ └── mod.rs │ ├── clone/ │ │ └── mod.rs │ ├── console/ │ │ └── mod.rs │ ├── encoding/ │ │ └── mod.rs │ ├── events/ │ │ └── mod.rs │ ├── fetch/ │ │ └── mod.rs │ ├── lib.rs │ ├── microtask/ │ │ └── mod.rs │ ├── timers/ │ │ └── mod.rs │ └── url/ │ └── mod.rs ├── docs/ │ ├── boa_object.md │ ├── bytecompiler.md │ ├── debugging.md │ ├── native_object.md │ ├── profiling.md │ ├── shapes.md │ ├── string.md │ └── vm.md ├── examples/ │ ├── Cargo.toml │ ├── README.md │ ├── scripts/ │ │ ├── calc.js │ │ ├── calctest.js │ │ ├── enhancedglobal.js │ │ ├── helloworld.js │ │ └── modules/ │ │ ├── operations.mjs │ │ └── trig.mjs │ └── src/ │ └── bin/ │ ├── classes.rs │ ├── closures.rs │ ├── commuter_visitor.rs │ ├── derive.rs │ ├── host_defined.rs │ ├── jsarray.rs │ ├── jsarraybuffer.rs │ ├── jsasyncgenerator.rs │ ├── jsdate.rs │ ├── jsgeneratorfunction.rs │ ├── jsmap.rs │ ├── jspromise.rs │ ├── jsregexp.rs │ ├── jsset.rs │ ├── jstypedarray.rs │ ├── jsweakmap.rs │ ├── jsweakset.rs │ ├── loadfile.rs │ ├── loadstring.rs │ ├── module_fetch_async.rs │ ├── modulehandler.rs │ ├── modules.rs │ ├── properties.rs │ ├── runtime_limits.rs │ ├── smol_event_loop.rs │ ├── symbol_visitor.rs │ ├── synthetic.rs │ ├── tokio_event_loop.rs │ └── try_into_js_derive.rs ├── ffi/ │ └── wasm/ │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE-MIT │ ├── LICENSE-UNLICENSE │ ├── README.md │ ├── src/ │ │ └── lib.rs │ └── tests/ │ └── web.rs ├── flake.nix ├── make/ │ └── ci.toml ├── package.json ├── test262_config.toml ├── test_wpt_config.toml ├── tests/ │ ├── Cargo.toml │ ├── fuzz/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── fuzz_targets/ │ │ ├── bytecompiler-implied.rs │ │ ├── common.rs │ │ ├── parser-idempotency.rs │ │ └── vm-implied.rs │ ├── insta-bytecode/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── scripts/ │ │ │ ├── basic-loop.js │ │ │ ├── double-loop-function.js │ │ │ ├── loop-hoisting.js │ │ │ └── new.js │ │ └── src/ │ │ ├── lib.rs │ │ └── snapshots/ │ │ ├── insta_bytecode__compile_bytecode@basic-loop.js.snap │ │ ├── insta_bytecode__compile_bytecode@double-loop-function.js.snap │ │ ├── insta_bytecode__compile_bytecode@loop-hoisting.js.snap │ │ └── insta_bytecode__compile_bytecode@new.js.snap │ ├── macros/ │ │ ├── Cargo.toml │ │ └── tests/ │ │ ├── assets/ │ │ │ ├── fibonacci.js │ │ │ └── gcd_callback.js │ │ ├── class.rs │ │ ├── derive/ │ │ │ ├── from_js_with.rs │ │ │ └── simple_struct.rs │ │ ├── derive.rs │ │ ├── embedded/ │ │ │ ├── dir1/ │ │ │ │ ├── file3.js │ │ │ │ └── file4.js │ │ │ ├── file1.js │ │ │ └── file2.js │ │ ├── embedded.rs │ │ ├── fibonacci.rs │ │ ├── gcd_callback.rs │ │ ├── module.rs │ │ └── optional.rs │ ├── src/ │ │ └── lib.rs │ ├── tester/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── edition.rs │ │ ├── exec/ │ │ │ ├── js262.rs │ │ │ └── mod.rs │ │ ├── main.rs │ │ ├── read.rs │ │ └── results.rs │ └── wpt/ │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ └── src/ │ ├── fetcher/ │ │ └── mod.rs │ ├── lib.rs │ └── logger/ │ └── mod.rs ├── tools/ │ ├── gen-icu4x-data/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ └── scripts/ │ ├── Cargo.toml │ └── src/ │ └── bin/ │ └── regenerate-about.rs ├── typos.toml └── utils/ ├── small_btree/ │ ├── ABOUT.md │ ├── Cargo.toml │ └── src/ │ ├── entry.rs │ └── lib.rs └── tag_ptr/ ├── ABOUT.md ├── Cargo.toml └── src/ └── lib.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ # TODO: track https://github.com/rust-lang/rust/issues/141626 for a resolution [target.x86_64-pc-windows-msvc] rustflags = ['-Csymbol-mangling-version=v0'] [env] MIRIFLAGS = "-Zmiri-tree-borrows" ================================================ FILE: .config/nextest.toml ================================================ [profile.ci] # Don't fail fast in CI to run the full test suite. fail-fast = false ================================================ FILE: .editorconfig ================================================ root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_size = 4 indent_style = space [{Makefile,**.mk}] # Use tabs for indentation (Makefiles require tabs) indent_style = tab [{*.js,*.json,*.mjs}] indent_size = 2 [*.md] indent_size = 2 [flake.lock] # ================================================ FILE: .gitattributes ================================================ # 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 ================================================ FILE: .github/FUNDING.yml ================================================ open_collective: boa ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: "\U0001F41B Bug report" about: Create a report to help us improve title: "" type: "Bug" assignees: "" --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the issue, or JavaScript code that causes this failure. **Expected behavior** Explain what you expected to happen, and what is happening instead. **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. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Matrix space url: https://matrix.to/#/#boa:matrix.org about: Please ask and answer questions here. ================================================ FILE: .github/ISSUE_TEMPLATE/custom.md ================================================ --- name: Custom about: Open an issue in the repo that is neither a bug or a feature. title: "" labels: "" type: "" assignees: "" --- E.g.: I think we should improve the way the JavaScript interpreter works by... ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: "\U0001F680 Feature request" about: Suggest a new ECMAScript feature to be implemented, or a new capability of the engine. title: "" type: "Feature" labels: "" assignees: "" --- **ECMASCript feature** Explain the ECMAScript feature that you'd like to see implemented. **Example code** Give a code example that should work after the implementation of this feature. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ This Pull Request fixes/closes #{issue_num}. It changes the following: - - - ================================================ FILE: .github/codecov.yml ================================================ github_checks: annotations: false coverage: status: project: default: threshold: 5% # allow 5% coverage variance patch: off ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly labels: - "C-Dependencies" - "C-Actions" groups: ci-dependencies: applies-to: version-updates patterns: ["*"] update-types: - "minor" - "patch" - package-ecosystem: cargo directory: / schedule: interval: weekly labels: - "C-Dependencies" groups: rust-dependencies: applies-to: version-updates patterns: ["*"] update-types: - "minor" - "patch" - package-ecosystem: cargo directory: /tests/fuzz/ schedule: interval: weekly labels: - "C-Dependencies" groups: fuzz-dependencies: applies-to: version-updates patterns: ["*"] update-types: - "minor" - "patch" ================================================ FILE: .github/labeler.yml ================================================ C-Actions: - changed-files: - any-glob-to-any-file: - '.github/**' C-AST: - changed-files: - any-glob-to-any-file: 'core/ast/**' C-Benchmark: - changed-files: - any-glob-to-any-file: - 'benches/**' - 'core/engine/benches/**' C-Builtins: - all: - changed-files: - any-glob-to-any-file: - 'core/engine/src/builtins/**' - 'core/engine/src/object/builtins/**' - all-globs-to-all-files: - '!core/engine/src/object/builtins/intl/**' C-CLI: - changed-files: - any-glob-to-any-file: - 'cli/**' C-Dependencies: - changed-files: - any-glob-to-any-file: - '**/Cargo.lock' - '**/Cargo.toml' C-Documentation: - changed-files: - any-glob-to-any-file: - '**/*.md' C-FFI: - changed-files: - any-glob-to-any-file: - 'ffi/**' C-GC: - changed-files: - any-glob-to-any-file: - 'core/gc/**' C-Intl: - changed-files: - any-glob-to-any-file: - 'core/engine/src/builtins/intl/**' C-Javascript: - changed-files: - any-glob-to-any-file: - '**/*.js' C-Parser: - changed-files: - any-glob-to-any-file: - 'core/parser/**' C-Runtime: - changed-files: - any-glob-to-any-file: - 'core/runtime/**' C-Tests: - changed-files: - any-glob-to-any-file: - '**/tests/**' - '**/test*' C-VM: - changed-files: - any-glob-to-any-file: - 'core/engine/src/bytecompiler/**' - 'core/engine/src/vm/**' C-WebAssembly: - changed-files: - any-glob-to-any-file: - 'ffi/wasm/**' ================================================ FILE: .github/release.yml ================================================ # .github/release.yml changelog: exclude: authors: - dependabot categories: - title: Feature Enhancements labels: - A-Enhancement - title: Bug Fixes labels: - A-Bug - title: Performance Improvements labels: - A-Performance - A-Memory - title: Internal Improvements labels: - A-Internal - A-Technical Debt - title: Other Changes labels: - "*" ================================================ FILE: .github/workflows/codeql.yml ================================================ name: "CodeQL SAST Scanning" on: workflow_dispatch: schedule: - cron: '0 0 * * 0' # Run weekly on Sundays permissions: contents: read jobs: analyze: name: Analyze runs-on: ubuntu-latest timeout-minutes: 60 permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'rust' ] steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 with: languages: ${{ matrix.language }} build-mode: none - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/labeler.yml ================================================ name: "Pull Request Labeler" on: - pull_request_target permissions: contents: read jobs: labeler: permissions: contents: read pull-requests: write runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6 ================================================ FILE: .github/workflows/nightly_build.yml ================================================ name: Nightly Build permissions: contents: read # Schedule this workflow to run at midnight every day on: schedule: - cron: "0 0 * * *" workflow_dispatch: jobs: build: permissions: contents: write strategy: matrix: include: - target: x86_64-unknown-linux-gnu os: ubuntu-latest binary_extension: "" - target: aarch64-apple-darwin os: macos-14 - target: aarch64-unknown-linux-gnu os: ubuntu-24.04-arm binary_extension: "" - target: x86_64-pc-windows-msvc os: windows-latest binary_extension: ".exe" runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable targets: ${{ matrix.target }} - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Build run: cargo build --target ${{ matrix.target }} --release --locked --bin boa - name: Upload binaries to release uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: target/${{ matrix.target }}/release/boa${{ matrix.binary_extension }} asset_name: boa-${{ matrix.target }}${{ matrix.binary_extension }} tag: refs/tags/nightly overwrite: true prerelease: true ================================================ FILE: .github/workflows/pr_management.yml ================================================ name: PR Management on: pull_request_target: types: [opened, reopened, synchronize, closed] permissions: contents: read jobs: manage_pr: runs-on: ubuntu-latest timeout-minutes: 10 permissions: pull-requests: write issues: write steps: - name: Auto Add Label if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const labels = await github.rest.issues.listLabelsOnIssue({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number }); if (labels.data.every(label => label.name != "Waiting On Author")) { github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, labels: ['Waiting On Review'] }) } - name: Auto Remove Label if: github.event.action == 'closed' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 continue-on-error: true with: script: | try { await github.rest.issues.removeLabel({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, name: 'Waiting On Review' }); } catch (error) { console.log('Label "Waiting On Review" not found or could not be removed.'); } - name: Auto Assign Milestone if: github.event.action == 'opened' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | // Fetch open milestones and assign the closest one const { data: milestones } = await github.rest.issues.listMilestones({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', sort: 'due_on', direction: 'asc' }); if (milestones.length > 0) { const latestMilestone = milestones[0]; await github.rest.issues.update({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, milestone: latestMilestone.number }); } ================================================ FILE: .github/workflows/pull_request.yml ================================================ name: Benchmarks on: pull_request: branches: - main - releases/** permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: runBenchmark: if: contains(github.event.pull_request.labels.*.name, 'run-benchmark') name: run benchmark runs-on: ubuntu-latest timeout-minutes: 120 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - uses: boa-dev/criterion-compare-action@adfd3a94634fe2041ce5613eb7df09d247555b87 # v3.2.4 with: token: ${{ secrets.GITHUB_TOKEN }} branchName: ${{ github.base_ref }} cwd: ./core/engine ================================================ FILE: .github/workflows/release.yml ================================================ name: Publish Release on: release: types: [published] permissions: contents: read jobs: publish: name: Publish crates runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Set environment env: W_FLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-Dwarnings' || '' }} # Setting `RUSTFLAGS` overrides any flags set on .cargo/config.toml, so we need to # set the target flags instead which are cumulative. # Track https://github.com/rust-lang/cargo/issues/5376 run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Install cargo-workspaces uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 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 \ --no-git-commit \ skip npm_publish: name: Publish NPM package (wasm) runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable targets: wasm32-unknown-unknown - name: Install wasm-pack uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: wasm-pack - name: Build boa_wasm run: wasm-pack build --scope boa-dev ./ffi/wasm - name: Set-up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: "20" - name: Set-up npm config for publishing run: npm config set -- '//registry.npmjs.org/:_authToken' "${{ secrets.NPM_TOKEN }}" - name: Check if the npm version already exists run: | VERSION=$(jq -r '.version' ./ffi/wasm/pkg/package.json) if npm view @boa-dev/boa_wasm@$VERSION version > dev/null 2>&1; then echo "Version $VERSION already published. Skipping" echo "SKIP_PUBLISH=true" >> $GITHUB_ENV fi - name: Publish to npm if: env.SKIP_PUBLISH != 'true' run: npm publish ./ffi/wasm/pkg --access=public release-binaries: name: Publish binaries permissions: contents: write needs: publish strategy: fail-fast: false matrix: build: [linux, macos-arm64, win-msvc] include: - build: linux os: ubuntu-latest target: x86_64-unknown-linux-gnu binary_extension: "" - build: macos-arm64 os: macos-14 target: aarch64-apple-darwin binary_extension: "" - build: win-msvc os: windows-latest target: x86_64-pc-windows-msvc binary_extension: ".exe" runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable targets: ${{ matrix.target }} - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Build run: cargo build --target ${{ matrix.target }} --verbose --release --locked --bin boa - name: Upload binaries to release uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: target/${{ matrix.target }}/release/boa${{ matrix.binary_extension }} asset_name: boa-${{ matrix.target }}${{ matrix.binary_extension }} tag: ${{ github.ref }} ================================================ FILE: .github/workflows/rust.yml ================================================ name: Continuous integration on: pull_request: branches: - main - releases/** push: branches: - main - releases/** merge_group: types: [checks_requested] workflow_dispatch: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: W_FLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-D warnings' || '' }} jobs: fmt: name: Formatting runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Set environment run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable components: rustfmt - name: Format (rustfmt) run: cargo fmt --all --check typos: name: Typos runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Check for typos uses: crate-ci/typos@631208b7aac2daa8b707f55e7331f9112b0e062d # v1.44.0 clippy: name: Lint runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Set environment run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable components: clippy - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Install cargo-workspaces uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: cargo-workspaces - name: Clippy (All features) run: cargo workspaces exec cargo clippy --all-features --all-targets - name: Clippy (No features) run: cargo workspaces exec cargo clippy --no-default-features --all-targets - name: Clippy (Intl) run: cargo clippy -p boa_engine --features intl - name: Clippy (Annex-B) run: cargo clippy -p boa_engine --features annex-b - name: Clippy (Experimental) run: cargo clippy -p boa_engine --features experimental docs: name: Documentation runs-on: ubuntu-latest timeout-minutes: 60 env: RUSTDOCFLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-D warnings' || '' }} steps: - name: Set environment run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Generate documentation run: cargo doc -v --document-private-items --all-features msrv: name: MSRV runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Set environment run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false # Get the rust_version from the Cargo.toml - name: Get rust_version id: rust_version run: echo "rust_version=$(grep '^rust-version' Cargo.toml | cut -d' ' -f3 | tr -d '"')" >> $GITHUB_OUTPUT - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: ${{ steps.rust_version.outputs.rust_version }} - name: Check compilation run: cargo check --all-features --all-targets coverage: name: Coverage runs-on: ubuntu-latest timeout-minutes: 60 if: ${{ github.ref == 'refs/heads/main' || github.base_ref == 'main' }} needs: - fmt - typos - clippy - docs - msrv steps: - name: Set environment # Setting `RUSTFLAGS` overrides any flags set on .cargo/config.toml, so we need to # set the target flags instead which are cumulative. # Track https://github.com/rust-lang/cargo/issues/5376 run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Install cargo-tarpaulin uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: cargo-tarpaulin - name: Run tarpaulin run: cargo tarpaulin --workspace --features annex-b,intl_bundled,experimental --ignore-tests --engine llvm --out xml - name: Upload to codecov.io uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5 tests: name: Test runs-on: ${{ matrix.os }} timeout-minutes: 60 needs: - fmt - typos - clippy - docs - msrv env: RUSTUP_WINDOWS_PATH_ADD_BIN: 1 strategy: fail-fast: false matrix: include: - os: macos-14 - os: windows-latest - os: ubuntu-24.04-arm - os: ubuntu-latest steps: - name: Set environment if: ${{ matrix.os != 'windows-latest' }} # Setting `RUSTFLAGS` overrides any flags set on .cargo/config.toml, so we need to # set the target flags instead which are cumulative. # Track https://github.com/rust-lang/cargo/issues/5376 run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr '[:lower:]' '[:upper:]' | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Set environment if: ${{ matrix.os == 'windows-latest' }} run: | $target = (rustc -vV | Select-String '^host') -replace '^host:\s+', '' | ForEach-Object { $_.ToUpper().Replace('-', '_') } "CARGO_TARGET_${target}_RUSTFLAGS=$env:W_FLAGS" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Install Cargo insta uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: cargo-insta locked: true - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - 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 --features annex-b,intl_bundled,experimental,embedded_lz4 - name: Install latest nextest uses: taiki-e/install-action@7cb3ba7bc31801346db00d4a6d7008aabab5e986 # nextest - name: Test with nextest run: cargo nextest run --profile ci --cargo-profile ci --features annex-b,intl_bundled,experimental,embedded_lz4 - name: Test docs run: cargo test --doc --profile ci --features annex-b,intl_bundled,experimental - name: Test bytecode output run: cargo insta test -p insta-bytecode cross-tests: name: Test runs-on: ubuntu-latest timeout-minutes: 60 needs: - fmt - typos - clippy - docs - msrv strategy: matrix: include: - target: i686-unknown-linux-gnu steps: - name: Set environment # Setting `RUSTFLAGS` overrides any flags set on .cargo/config.toml, so we need to # set the target flags instead which are cumulative. # Track https://github.com/rust-lang/cargo/issues/5376 run: | target=$(echo ${{ matrix.target }} | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable target: ${{ matrix.target }} - name: Install Cross uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: cross git: https://github.com/cross-rs/cross - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Run tests run: | cross test --workspace --target ${{ matrix.target }} \ --profile ci \ --features annex-b,intl_bundled,experimental \ --exclude boa_macros \ --exclude boa_macros_tests miri: name: Miri runs-on: ubuntu-latest timeout-minutes: 120 needs: - fmt - typos - clippy - docs - msrv steps: - name: Set environment # Setting `RUSTFLAGS` overrides any flags set on .cargo/config.toml, so we need to # set the target flags instead which are cumulative. # Track https://github.com/rust-lang/cargo/issues/5376 run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust nightly with miri uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # nightly with: toolchain: nightly components: miri - name: Setup miri run: cargo miri setup - name: Run miri tests run: cargo miri test --workspace --exclude boa_cli --exclude boa_examples miri build-fuzz: name: Fuzzing runs-on: ubuntu-latest timeout-minutes: 60 needs: - fmt - typos - clippy - docs - msrv steps: - name: Set environment run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Install cargo-fuzz uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: cargo-fuzz - name: Build fuzz run: cd tests/fuzz && cargo fuzz build -s none --dev build-run-examples: name: Build & run examples runs-on: ubuntu-latest timeout-minutes: 60 needs: - fmt - typos - clippy - docs - msrv steps: - name: Set environment run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Install cargo-workspaces uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: cargo-workspaces - name: Build (All features) run: cargo workspaces exec cargo build --all-features --all-targets --profile ci - name: Build (No features) run: cargo workspaces exec cargo build --no-default-features --all-targets --profile ci - name: Run examples run: | cd examples cargo run -p boa_examples --bin 2>&1 \ | grep -E '^ ' \ | xargs -n1 sh -c 'cargo run -p boa_examples --profile ci --bin $0 || exit 255' run-semver-check: name: Check SemVer compatibility runs-on: ubuntu-latest timeout-minutes: 60 needs: - fmt - typos - clippy - docs - msrv steps: - name: Set environment run: | target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_') echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Check Semver uses: obi1kenobi/cargo-semver-checks-action@5b298c9520f7096a4683c0bd981a7ac5a7e249ae # v2 with: exclude: boa_wintertc ================================================ FILE: .github/workflows/security_audit.yml ================================================ name: Security audit on: schedule: - cron: "0 0 * * *" permissions: contents: read jobs: audit: runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/test262.yml ================================================ name: test262 on: pull_request: branches: - main - releases/** permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: run_test262: name: Run the test262 test suite runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: path: boa persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Checkout the data repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: repository: boa-dev/data path: data persist-credentials: false - name: Run the test262 test suite run: | cd boa mkdir -p ../results/test262 cargo run --release --bin boa_tester -- run -v -o ../results/test262 cd .. - name: Compare results shell: bash run: | cd boa base_results="../data/test262/refs/heads/main/latest.json" pr_results="../results/test262/pull/latest.json" output_dir="../results/outputs" test -f "$base_results" test -f "$pr_results" comment="$(./target/release/boa_tester compare "$base_results" "$pr_results" -m)" maincommit="$(jq -r '.c' "$base_results")" mkdir -p "$output_dir" { echo "" echo "### Test262 conformance changes" echo echo "$comment" echo echo "Tested main commit: [\`${maincommit}\`](${{ github.event.pull_request.base.repo.html_url }}/commit/${maincommit})" echo "Tested PR commit: [\`${{ github.event.pull_request.head.sha }}\`](${{ github.event.pull_request.head.repo.html_url }}/commit/${{ github.event.pull_request.head.sha }})" echo "Compare commits: ${{ github.event.pull_request.base.repo.html_url }}/compare/${maincommit}...${{ github.event.pull_request.head.sha }}" } > "$output_dir/comment.md" echo "${{ github.event.pull_request.number }}" > "$output_dir/pr_number.txt" - name: Upload results uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: test262-results path: results/outputs retention-days: 1 ================================================ FILE: .github/workflows/test262_comment.yml ================================================ name: test262_comment on: workflow_run: workflows: ["test262"] types: - completed permissions: contents: read pull-requests: write jobs: comment: name: Post results to PR runs-on: ubuntu-latest if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} steps: - name: Download results uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: test262-results github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} path: downloaded-results - name: Read results id: results shell: bash run: | echo "pr_number=$(cat downloaded-results/pr_number.txt)" >> $GITHUB_OUTPUT - name: Find Previous Comment uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4 id: previous-comment with: issue-number: ${{ steps.results.outputs.pr_number }} body-includes: "" - name: Update or create comment uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ steps.previous-comment.outputs.comment-id }} issue-number: ${{ steps.results.outputs.pr_number }} body-path: downloaded-results/comment.md edit-mode: replace ================================================ FILE: .github/workflows/test262_release.yml ================================================ name: Update Test262 Results on: release: types: - published push: branches: - main permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: update_test262_results: name: Update Test262 Results runs-on: ubuntu-latest timeout-minutes: 60 steps: # Checkout the main repository - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: path: boa # Install Rust toolchain - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable # Cache cargo dependencies - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 # Checkout the `data` repository - name: Checkout the data repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: repository: boa-dev/data token: ${{ secrets.DATA_PAT }} path: data # Run the Test262 test suite - name: Run the test262 test suite run: | cd boa cargo run --release --bin boa_tester -- run -v -o ../data/test262 # Commit and push results back to the `data` repo - name: Commit results run: | cd data git config user.name "GitHub Actions" git config user.email "actions@github.com" git add test262 git commit -m "Update Test262 results ( ${{ github.ref_name }} )" - name: Push changes uses: ad-m/github-push-action@4cc74773234f74829a8c21bc4d69dd4be9cfa599 # master with: # cannot use secrets.GITHUB_TOKEN since it only gives you # write permissions to the current repository. github_token: ${{ secrets.DATA_PAT }} repository: boa-dev/data directory: data ================================================ FILE: .github/workflows/webassembly.yml ================================================ name: Webassembly demo on: pull_request: branches: - main - releases/** push: branches: - main - releases/** merge_group: types: [checks_requested] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: check_style: name: Check webassembly demo style runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Check code formatting run: npx prettier --check . build: name: Build webassembly demo runs-on: ubuntu-latest timeout-minutes: 60 env: WASM_PACK_PATH: ~/.cargo/bin/wasm-pack steps: - name: Set environment env: W_FLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-Dwarnings' || '' }} # Setting `RUSTFLAGS` overrides any flags set on .cargo/config.toml, so we need to # set the target flags instead which are cumulative. # Track https://github.com/rust-lang/cargo/issues/5376 run: echo "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - name: Install wasm-pack uses: baptiste0928/cargo-install@f204293d9709061b7bc1756fec3ec4e2cd57dec0 # v3.4.0 with: crate: wasm-pack - name: Build Playground run: wasm-pack build ./ffi/wasm --verbose - name: Test (Chrome) run: wasm-pack test --headless --chrome ./ffi/wasm --verbose - name: Test (Firefox) run: wasm-pack test --headless --firefox ./ffi/wasm --verbose ================================================ FILE: .gitignore ================================================ # IDE .idea/ *.iml # Vim *.*.swp *.*.swo # Build target dist **/*.rs.bk node_modules .DS_Store yarn-error.log .vscode/settings.json .zed/settings.json # debug is used for testing changes locally /debug .boa_history # test262 testing suite test262 # wpt testing suite tests_wpt # Profiling *.string_data *.string_index *.events chrome_profiler.json *.mm_profdata profile.json.gz # Logs *.log # Yarn .yarn .yarnrc.yml # e2e test playwright-report test-results # dhat dhat-*.json perf.data* # Nix /.envrc /.direnv ================================================ FILE: .husky/pre-push ================================================ #!/bin/sh target=$(rustc -vV | awk '/^host/ { print $2 }' | tr '[:lower:]' '[:upper:]' | tr '-' '_') export CARGO_TARGET_${target}_RUSTFLAGS='-D warnings' if ! command -v cargo-make >/dev/null 2>&1; then echo "cargo-make is not installed. Install it with:" echo " cargo install cargo-make" exit 1 fi cargo make run-ci ================================================ FILE: .prettierignore ================================================ # Ignore artifacts: *.rs *.yml target node_modules core/engine/benches/bench_scripts/mini_js.js core/engine/benches/bench_scripts/clean_js.js ffi/wasm/pkg dist test262 playwright-report test-results # For some reason Prettier likes to reformat JSON lock files. flake.lock # Assets that should not be checked. benches/scripts/v8-benches ================================================ FILE: .vscode/launch.json ================================================ { // 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": "Debug Boa (Script mode)", "windows": { "program": "${workspaceFolder}/target/debug/boa.exe" }, "program": "${workspaceFolder}/target/debug/boa", "args": ["${workspaceFolder}/${input:filePath}", "--debug-object"], "sourceLanguages": ["rust"], "preLaunchTask": "Cargo Build boa_cli" }, { "type": "lldb", "request": "launch", "name": "Debug Boa (Module mode)", "windows": { "program": "${workspaceFolder}/target/debug/boa.exe" }, "program": "${workspaceFolder}/target/debug/boa", "args": [ "${workspaceFolder}/${input:filePath}", "--debug-object", "-m", "-r", "${workspaceFolder}/${input:modulePath}" ], "sourceLanguages": ["rust"], "preLaunchTask": "Cargo Build boa_cli" }, { "type": "lldb", "request": "launch", "name": "Debug Boa (Tester)", "windows": { "program": "${workspaceFolder}/target/debug/boa_tester.exe" }, "program": "${workspaceFolder}/target/debug/boa_tester", "args": ["run", "-s", "${input:testPath}", "-vvv", "-d"], "sourceLanguages": ["rust"], "preLaunchTask": "Cargo Build boa_tester" } ], "inputs": [ { "id": "filePath", "description": "Relative path to the file to run", "type": "promptString" }, { "id": "modulePath", "description": "Relative path to the module root directory", "type": "promptString" }, { "id": "testPath", "description": "Relative path to the test from the test262 directory", "type": "promptString" } ] } ================================================ FILE: .vscode/tasks.json ================================================ { // 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 Build", "command": "cargo", "args": ["build"], "group": "build", "presentation": { "clear": true } }, { "type": "process", "label": "Cargo Build boa_cli", "command": "cargo", "args": ["build", "-p", "boa_cli"], "group": "build", "presentation": { "clear": true } }, { "type": "process", "label": "Cargo Build boa_tester", "command": "cargo", "args": ["build", "-p", "boa_tester"], "group": "build", "presentation": { "clear": true } }, { "type": "process", "label": "Run JS file", "command": "cargo", "args": ["run", "--bin", "boa", "${file}"], "group": { "kind": "build", "isDefault": true }, "presentation": { "clear": true }, "options": { "env": { "RUST_BACKTRACE": "1" } }, "problemMatcher": [] }, { "type": "process", "label": "Run JS file (Profiler)", "command": "cargo", "args": ["run", "--features", "profiler", "${file}"], "group": "build", "options": { "env": { "RUST_BACKTRACE": "full" }, "cwd": "${workspaceFolder}/cli" }, "presentation": { "clear": true }, "problemMatcher": [] }, { "type": "process", "label": "Run JS file with VM trace", "command": "cargo", "args": ["run", "--bin", "boa", "--", "-t", "${file}"], "group": "build", "presentation": { "clear": true }, "problemMatcher": [] }, { "type": "process", "label": "Get AST for JS file", "command": "cargo", "args": ["run", "--bin", "boa", "--", "-a=Debug", "${file}"], "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: ABOUT.md ================================================ # 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_cli`**][cli] - Boa's CLI && REPL implementation - [**`boa_ast`**][ast] - Boa's ECMAScript Abstract Syntax Tree. - [**`boa_engine`**][engine] - Boa's implementation of ECMAScript builtin objects and execution. - [**`boa_gc`**][gc] - Boa's garbage collector. - [**`boa_icu_provider`**][icu] - Boa's ICU4X data provider. - [**`boa_interner`**][interner] - Boa's string interner. - [**`boa_macros`**][macros] - Boa's macros. - [**`boa_parser`**][parser] - Boa's lexer and parser. - [**`boa_runtime`**][runtime] - Boa's `WebAPI` features. - [**`boa_string`**][string] - Boa's ECMAScript string implementation. - [**`boa_wintertc`**][wintertc] - Boa's `WinterTC` (TC55) Minimum Common Web API implementation. - [**`tag_ptr`**][tag_ptr] - Utility library that enables a pointer to be associated with a tag of type `usize`. - [**`small_btree`**][small_btree] - Utility library that adds the `SmallBTreeMap` data structure. [boa-conformance]: https://boajs.dev/conformance [boa-web]: https://boajs.dev/ [boa-playground]: https://boajs.dev/playground [ast]: https://docs.rs/boa_ast/latest/boa_ast/index.html [engine]: https://docs.rs/boa_engine/latest/boa_engine/index.html [gc]: https://docs.rs/boa_gc/latest/boa_gc/index.html [interner]: https://docs.rs/boa_interner/latest/boa_interner/index.html [parser]: https://docs.rs/boa_parser/latest/boa_parser/index.html [icu]: https://docs.rs/boa_icu_provider/latest/boa_icu_provider/index.html [runtime]: https://docs.rs/boa_runtime/latest/boa_runtime/index.html [string]: https://docs.rs/boa_string/latest/boa_string/index.html [wintertc]: https://docs.rs/boa_wintertc/latest/boa_wintertc/index.html [tag_ptr]: https://docs.rs/tag_ptr/latest/tag_ptr/index.html [small_btree]: https://docs.rs/small_btree/latest/small_btree/index.html [macros]: https://docs.rs/boa_macros/latest/boa_macros/index.html [cli]: https://crates.io/crates/boa_cli ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG ## [v0.21.0 (2025-10-21)](https://github.com/boa-dev/boa/compare/v0.20...v0.21) ### Feature Enhancements - Build out Temporal's `ZonedDateTime` and `Now` by @nekevss in https://github.com/boa-dev/boa/pull/4068 - Add valueOf methods to the Temporal builtins by @nekevss in https://github.com/boa-dev/boa/pull/4079 - Add more `ZonedDateTime` method implementations by @nekevss in https://github.com/boa-dev/boa/pull/4095 - Cleanup CLI to use eyre + refactor patterns by @jedel1043 in https://github.com/boa-dev/boa/pull/4108 - Move methods of `JsString` to `JsStr` by @jedel1043 in https://github.com/boa-dev/boa/pull/4106 - Implement `Error.isError` by @jedel1043 in https://github.com/boa-dev/boa/pull/4114 - Improve implementation of example `JobQueue`s by @jedel1043 in https://github.com/boa-dev/boa/pull/4111 - Implement `PlainDate` string methods by @nekevss in https://github.com/boa-dev/boa/pull/4119 - Revamp `JobQueue` into `JobExecutor` and introduce `NativeAsyncJob` by @jedel1043 in https://github.com/boa-dev/boa/pull/4118 - Add `From>` for `JsString` by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4134 - Implement more `toString` and `toJSON` methods on Temporal builtins by @nekevss in https://github.com/boa-dev/boa/pull/4126 - Implement `Array.fromAsync` by @jedel1043 in https://github.com/boa-dev/boa/pull/4115 - Implement `toString` and `toJSON` methods for the remaining builtins `Duration`, `PlainMonthDay`, and `PlainYearMonth` by @nekevss in https://github.com/boa-dev/boa/pull/4135 - Bump temporal_rs and fix instant return by @nekevss in https://github.com/boa-dev/boa/pull/4142 - Implement toLocaleString and some general cleanup by @nekevss in https://github.com/boa-dev/boa/pull/4156 - add macos arm64 by @ahaoboy in https://github.com/boa-dev/boa/pull/4160 - Use NaN-boxing on value::InnerValue by @hansl in https://github.com/boa-dev/boa/pull/4091 - Split `Tagged` into a utility crate by @HalidOdat in https://github.com/boa-dev/boa/pull/3849 - Update JsValue::to_json to support undefined by @jamesthurley in https://github.com/boa-dev/boa/pull/4212 - Change signature of `from_async_fn` to allow capturing the context by @jedel1043 in https://github.com/boa-dev/boa/pull/4215 - Implement Set methods from ECMAScript Specification Features/#4128 by @Hemenguelbindi in https://github.com/boa-dev/boa/pull/4145 - Add a `#[boa_module]` macro to automatically implement a Module by @hansl in https://github.com/boa-dev/boa/pull/4277 - Implement small changes from Intl's 2026 spec by @jedel1043 in https://github.com/boa-dev/boa/pull/4290 - Bump rustc edition to 2024 and version to 1.88 by @jedel1043 in https://github.com/boa-dev/boa/pull/4315 - Implement backtrace information for errors by @HalidOdat in https://github.com/boa-dev/boa/pull/4292 - Add WPT as optional tests for boa_runtime by @hansl in https://github.com/boa-dev/boa/pull/4008 - Simplify SourcePositionGuard creation by @hansl in https://github.com/boa-dev/boa/pull/4327 - Make `JobExecutor::run_jobs_async` a plain async method by @jedel1043 in https://github.com/boa-dev/boa/pull/4331 - Introduce async `ModuleLoader`s by @jedel1043 in https://github.com/boa-dev/boa/pull/4328 - Use `AsyncFnOnce` in constructors of `NativeAsyncJob` by @jedel1043 in https://github.com/boa-dev/boa/pull/4333 - Add (optionally) Float16Array and f16round() support and add JsUint8ClampedArray by @hansl in https://github.com/boa-dev/boa/pull/4364 - Implement `Atomics.waitAsync` by @jedel1043 in https://github.com/boa-dev/boa/pull/4339 - Implement `Math.sumPrecise` by @nekevss in https://github.com/boa-dev/boa/pull/4383 - Add `Date.prototype.toTemporalInstant` from the Temporal proposal by @nekevss in https://github.com/boa-dev/boa/pull/4382 - Implement upsert methods for Map by @jasonmilad in https://github.com/boa-dev/boa/pull/4436 - Change JsObject default method to take `Instrinsics` parameter by @mdrokz in https://github.com/boa-dev/boa/pull/4466 - Cleanup `BuiltInConstructor` constants to ensure no additional allocations by @jedel1043 in https://github.com/boa-dev/boa/pull/4464 - Implement Upsert methods for weakMap: getOrInsert and getOrInsertComputed by @rrogerc in https://github.com/boa-dev/boa/pull/4459 ### Bug Fixes - Fix #4051, parse Arguments should expect `)` not `}` by @zzzdong in https://github.com/boa-dev/boa/pull/4058 - bug fix: ops that stay strictly at the EOF after assigns ops are ignored by @Nikita-str in https://github.com/boa-dev/boa/pull/4047 - Patch Temporal.PlainTime and Temporal.Duration constructors by @nekevss in https://github.com/boa-dev/boa/pull/4078 - Fix bugs on ephemeron and TypedArray.prototype.slice by @jedel1043 in https://github.com/boa-dev/boa/pull/4107 - Allow bool and null literals in export aliases by @jedel1043 in https://github.com/boa-dev/boa/pull/4113 - Allow referencing `super` within initializer of static private property by @jedel1043 in https://github.com/boa-dev/boa/pull/4121 - Fix truncation on max microseconds and nanoseconds by @nekevss in https://github.com/boa-dev/boa/pull/4139 - Fix issues with `to_temporal_time` and `ZonedDateTime.prototype.withPlainTime` by @nekevss in https://github.com/boa-dev/boa/pull/4154 - Some cleanup + order of operations fixes by @nekevss in https://github.com/boa-dev/boa/pull/4190 - Fix JsValue::to_json with cyclic values by @changhc in https://github.com/boa-dev/boa/pull/4176 - Fixed logo in documentation by @Razican in https://github.com/boa-dev/boa/pull/4208 - Enable `wasm_js` feature of getrandom in boa_engine crate by @HalidOdat in https://github.com/boa-dev/boa/pull/4241 - Fix panics on staging TypedArray.slice tests by @jedel1043 in https://github.com/boa-dev/boa/pull/4289 - Add the legacy enum-based JsValue implementation behind a flag by @hansl in https://github.com/boa-dev/boa/pull/4281 - Allow non-reserved keywords to be used as identifiers by @cijiugechu in https://github.com/boa-dev/boa/pull/4307 - Avoid fully awaiting futures in async event loops by @jedel1043 in https://github.com/boa-dev/boa/pull/4332 - Prevent evalutation of code with `--dump-ast` flag by @HalidOdat in https://github.com/boa-dev/boa/pull/4337 - Some general bug fixes for Temporal implementation by @nekevss in https://github.com/boa-dev/boa/pull/4349 - Fix UB in implementation of `NanBoxedValue` by @jedel1043 in https://github.com/boa-dev/boa/pull/4346 - fix(regexp): fix the capture group count assert to have the correct upper limit by @BDeuDev in https://github.com/boa-dev/boa/pull/4419 - Fix `contains_direct_eval` for ordinary function by @hpp2334 in https://github.com/boa-dev/boa/pull/4453 - Fix test262 comments on new PR by @jedel1043 in https://github.com/boa-dev/boa/pull/4465 ### Internal Improvements - Remove `try_break` macro in favour of question mark operator by @jedel1043 in https://github.com/boa-dev/boa/pull/4112 - Use cow-utils instead by @heygsc in https://github.com/boa-dev/boa/pull/4133 - Register VM by @HalidOdat in https://github.com/boa-dev/boa/pull/3798 - Bump MSRV to 1.84 by @jedel1043 in https://github.com/boa-dev/boa/pull/4165 - Avoid unnecessary calls of `to_string` in `cow_*` by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4166 - Apply rustc 1.85 lints by @jedel1043 in https://github.com/boa-dev/boa/pull/4170 - Some string cleanups by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4090 - Refactor bytecode representation by @raskad in https://github.com/boa-dev/boa/pull/4220 - Refactor registers to use the stack by @raskad in https://github.com/boa-dev/boa/pull/4263 - Bump Test262 hash commit and cleanup test features by @jedel1043 in https://github.com/boa-dev/boa/pull/4288 - Replace inner `Gc` with `Rc` for `SourceText` by @HalidOdat in https://github.com/boa-dev/boa/pull/4293 - Fix more Intl tests for latest ECMA402 spec by @jedel1043 in https://github.com/boa-dev/boa/pull/4304 - Fix lints for rustc 1.88 by @jedel1043 in https://github.com/boa-dev/boa/pull/4309 - Migrate `temporal_rs` from `0.0.9` to `0.0.10` by @HalidOdat in https://github.com/boa-dev/boa/pull/4318 - Mark the error path in `Call::operation` as cold. by @cijiugechu in https://github.com/boa-dev/boa/pull/4319 - Add arm64 linux nightly build by @nekevss in https://github.com/boa-dev/boa/pull/4321 - extract small_map as separate utility crate by @countradooku in https://github.com/boa-dev/boa/pull/4214 - Remove `Box` from `JsValue` for `JsString` by @HalidOdat in https://github.com/boa-dev/boa/pull/4329 - Add `repr(C)` on `Object` to prevent field reordering by @HalidOdat in https://github.com/boa-dev/boa/pull/4343 - Fix clippy lints for Rust 1.89 by @nekevss in https://github.com/boa-dev/boa/pull/4368 - Add Nix flake by @xubaiwang in https://github.com/boa-dev/boa/pull/4381 - Apply clippy fixes for Rust 1.90 by @hansl in https://github.com/boa-dev/boa/pull/4423 - Fix UB on unaligned ArrayBuffers by @jedel1043 in https://github.com/boa-dev/boa/pull/4427 - Use same cache key for builds and tests by @jasonwilliams in https://github.com/boa-dev/boa/pull/4426 ### Other Changes - Bug fix: regex started with `/=` parsed as `AssignDiv` by @Nikita-str in https://github.com/boa-dev/boa/pull/4048 - Add fast path for number to `JsString` conversion by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4054 - Bump the rust-dependencies group with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4069 - Add `fjcvtzs` instruction for `ARMv8.3` target by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4084 - Add a stress test to the parser to parser multi-millions tokens by @hansl in https://github.com/boa-dev/boa/pull/4086 - Bump the rust-dependencies group with 4 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4088 - Privatize `JsValue`'s internals and expose it through a JsVariant (with immutable reference) by @hansl in https://github.com/boa-dev/boa/pull/4080 - Skip creation of arguments object if possible by @raskad in https://github.com/boa-dev/boa/pull/4087 - Allow resizing of underlying ArrayBuffer from Rust by @hansl in https://github.com/boa-dev/boa/pull/4082 - Add inline cache for getting bindings from the global object by @raskad in https://github.com/boa-dev/boa/pull/4067 - Update Temporal ToIntegerIfIntegral, ToIntegerWithTruncation, and ToPositiveIntegerWithTruncation implementation by @nekevss in https://github.com/boa-dev/boa/pull/4081 - Adjust call to correct method for `PlainDateTime.prototype.since` by @nekevss in https://github.com/boa-dev/boa/pull/4096 - fix very minor typo shift -> unshift by @albertleigh in https://github.com/boa-dev/boa/pull/4097 - Bump the `temporal_rs` version and related changes by @nekevss in https://github.com/boa-dev/boa/pull/4098 - Bump test262 commit and changes to `boa_tester` to support sm changes by @nekevss in https://github.com/boa-dev/boa/pull/4099 - Bump the rust-dependencies group across 1 directory with 6 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4103 - Remove some clones and branches in hot `PropertyDescriptor` functions by @raskad in https://github.com/boa-dev/boa/pull/4104 - Bump syn from 2.0.93 to 2.0.95 in the rust-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4109 - Fix CI with the latest linter errors introduced in 1.84 by @hansl in https://github.com/boa-dev/boa/pull/4117 - Use `cow_to_ascii_uppercase` instead by @heygsc in https://github.com/boa-dev/boa/pull/4124 - Bump the rust-dependencies group across 1 directory with 7 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4131 - Set the array "length" property in `[[DefineOwnProperty]]` based on the array shape by @raskad in https://github.com/boa-dev/boa/pull/4101 - Bump the rust-dependencies group with 5 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4137 - Implement since and until methods for ZonedDateTime by @nekevss in https://github.com/boa-dev/boa/pull/4136 - Bump baptiste0928/cargo-install from 3.1.1 to 3.3.0 in the ci-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4138 - Make the HostHooks shareable between app and context by @hansl in https://github.com/boa-dev/boa/pull/4141 - Bump the rust-dependencies group across 1 directory with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4148 - Implement an internal time type and Clock trait by @hansl in https://github.com/boa-dev/boa/pull/4149 - `SourceText` collection & `toString()` for fns and methods by @Nikita-str in https://github.com/boa-dev/boa/pull/4038 - `setTimeout`, `setInterval` and `clearInterval` (and the same `clearTimeout`) implementations by @hansl in https://github.com/boa-dev/boa/pull/4130 - Bump the rust-dependencies group with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4151 - Bump the rust-dependencies group with 5 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4157 - Add trace function to Logger trait by @jamesthurley in https://github.com/boa-dev/boa/pull/4155 - Allow local parameters if mapped arguments object is not used by @raskad in https://github.com/boa-dev/boa/pull/4092 - Bump temporal_rs to Feb. 15 version + adjustments by @nekevss in https://github.com/boa-dev/boa/pull/4162 - Bump the rust-dependencies group with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4163 - Add `PlainDateTime.prototype.round` implementation from `temporal_rs` by @nekevss in https://github.com/boa-dev/boa/pull/4164 - Fix some engine specific bugs and bump version by @nekevss in https://github.com/boa-dev/boa/pull/4167 - Simplify date parser by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4143 - Build out remaining method stubs for temporal by @nekevss in https://github.com/boa-dev/boa/pull/4172 - Bump the rust-dependencies group with 4 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4174 - Prioritize drop of common types for nan-boxed `JsValue` by @HalidOdat in https://github.com/boa-dev/boa/pull/4178 - Add changes from YearMonth parsing update and MonthCode addition by @nekevss in https://github.com/boa-dev/boa/pull/4173 - implementation of static method compare for duration from temporal.rs by @lockels in https://github.com/boa-dev/boa/pull/4189 - Unify release workflows by @HalidOdat in https://github.com/boa-dev/boa/pull/4192 - Temporal bump and fixes by @nekevss in https://github.com/boa-dev/boa/pull/4193 - Bump ring from 0.17.9 to 0.17.13 by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4201 - Bump the rust-dependencies group across 1 directory with 20 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4204 - feat(examples): Add comprehensive JsPromise example by @created-by-varun in https://github.com/boa-dev/boa/pull/4198 - Temporal bump and implementation of toPlainYearMonth and toPlainMonthDay by @lockels in https://github.com/boa-dev/boa/pull/4207 - Bump the rust-dependencies group with 5 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4209 - Bump the rust-dependencies group with 4 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4216 - Fix Rust 1.86.0 lints and update dependencies by @raskad in https://github.com/boa-dev/boa/pull/4228 - Move interop and module utilities from boa_interop into boa_engine by @hansl in https://github.com/boa-dev/boa/pull/4218 - Bump the rust-dependencies group with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4229 - Temporal bump by @lockels in https://github.com/boa-dev/boa/pull/4231 - Bump the rust-dependencies group with 2 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4232 - Enable `PlainDate.prototype.toZonedDateTime` method by @nekevss in https://github.com/boa-dev/boa/pull/4233 - `PlainDateTime::toZonedDateTime` & `PlainDateTime::toPlainDate` implementations by @nekevss in https://github.com/boa-dev/boa/pull/4234 - Bump temporal_rs and add ZonedDateTime.prototype.round impl by @nekevss in https://github.com/boa-dev/boa/pull/4236 - Bump the rust-dependencies group across 1 directory with 6 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4239 - Bump the rust-dependencies group with 2 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4242 - Bump temporal_rs and remove dead code by @nekevss in https://github.com/boa-dev/boa/pull/4248 - Fix Rust 1.87.0 lints by @raskad in https://github.com/boa-dev/boa/pull/4249 - Avoid range checks in nan-boxing by @raskad in https://github.com/boa-dev/boa/pull/4251 - Update README.md to new version. by @tomoverlund in https://github.com/boa-dev/boa/pull/4254 - Fix the Set methods to pass the 262 tests by @hansl in https://github.com/boa-dev/boa/pull/4260 - Add support for LZ4 compression in embedded module loader by @hansl in https://github.com/boa-dev/boa/pull/4261 - Bump the rust-dependencies group across 1 directory with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4264 - Fix AST `contains` operation by @HalidOdat in https://github.com/boa-dev/boa/pull/4267 - Bump temporal_rs and fix some order of operation issues by @nekevss in https://github.com/boa-dev/boa/pull/4268 - Escape analyze function scopes on non-arrow functions by @HalidOdat in https://github.com/boa-dev/boa/pull/4266 - Remove local binding's initialized state in `CallFrame` by @HalidOdat in https://github.com/boa-dev/boa/pull/4269 - Improve `README.md` by @HalidOdat in https://github.com/boa-dev/boa/pull/4270 - Add a #[boa_class] proc macro attribute by @hansl in https://github.com/boa-dev/boa/pull/4271 - Bump ICU4X to 2.0 by @jedel1043 in https://github.com/boa-dev/boa/pull/4274 - Bump the rust-dependencies group with 2 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4278 - Add `Span`s to expression nodes by @HalidOdat in https://github.com/boa-dev/boa/pull/4273 - Bump the rust-dependencies group with 4 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4283 - Bump temporal_rs to v0.0.9 by @nekevss in https://github.com/boa-dev/boa/pull/4285 - remove boa_profiler and profiler calls. update docs. (#4272) by @Timkarx in https://github.com/boa-dev/boa/pull/4276 - Add a #[boa(rename = ...)] attribute to TryFromJs and TryIntoJs derive macros by @hansl in https://github.com/boa-dev/boa/pull/4286 - Add a js_value! macro to allow creation of JsValue from JSON-like DSL by @hansl in https://github.com/boa-dev/boa/pull/4282 - Bump baptiste0928/cargo-install from 3.3.0 to 3.3.1 in the ci-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4295 - Bump the rust-dependencies group with 4 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4296 - Update phf requirement from 0.11.2 to 0.12.1 in /tests/fuzz in the fuzz-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4297 - Fix `JsValue::mul` to handle zero times a negative integer by @Rafferty97 in https://github.com/boa-dev/boa/pull/4303 - Move the URL class to using the boa_class macro by @hansl in https://github.com/boa-dev/boa/pull/4294 - Hint branch predictor that surrogate pairs are rare by @cijiugechu in https://github.com/boa-dev/boa/pull/4312 - cli: use `mimalloc` as global allocator on windows by @cijiugechu in https://github.com/boa-dev/boa/pull/4314 - Refactor the `Binding` modifiers to use bitflags by @cijiugechu in https://github.com/boa-dev/boa/pull/4316 - Implement type erased `Gc` by @HalidOdat in https://github.com/boa-dev/boa/pull/4291 - inline `Gc::inner_ptr` by @cijiugechu in https://github.com/boa-dev/boa/pull/4317 - Shrink `JsStr` from 24 to 16 bytes by @HalidOdat in https://github.com/boa-dev/boa/pull/4322 - Bump tokio from 1.45.1 to 1.46.1 in the rust-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4324 - Refactor `JsObject` to always be size 8 by @raskad in https://github.com/boa-dev/boa/pull/4287 - Add a new `Nullable` type that deserialize null to `Nullable::::Null` by @hansl in https://github.com/boa-dev/boa/pull/4325 - Refactor `JsString`'s static string tagging by @HalidOdat in https://github.com/boa-dev/boa/pull/4334 - Bump the rust-dependencies group with 6 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4335 - Implement native backtrace positions by @HalidOdat in https://github.com/boa-dev/boa/pull/4306 - Fix documentation for NativeFunction::from_async_fn by @jedel1043 in https://github.com/boa-dev/boa/pull/4344 - Bump the rust-dependencies group with 2 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4347 - Bump temporal_rs version to v0.0.11 by @nekevss in https://github.com/boa-dev/boa/pull/4348 - Add an empty EmbeddedModuleLoader default by @hansl in https://github.com/boa-dev/boa/pull/4351 - Fix a camel case bug by @hansl in https://github.com/boa-dev/boa/pull/4352 - Temporal documentation update, part 1 by @nekevss in https://github.com/boa-dev/boa/pull/4353 - Temporal documentation update, part 2 by @nekevss in https://github.com/boa-dev/boa/pull/4354 - Temporal documentation, part 3 by @nekevss in https://github.com/boa-dev/boa/pull/4355 - Temporal documentation update, part 4 by @nekevss in https://github.com/boa-dev/boa/pull/4356 - Bump tokio from 1.46.1 to 1.47.0 in the rust-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4357 - `Fetch` API (beta) by @hansl in https://github.com/boa-dev/boa/pull/4338 - Remove `Box` from `Object`'s data member by @HalidOdat in https://github.com/boa-dev/boa/pull/4342 - Bump the rust-dependencies group with 6 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4361 - Add support for utf16(-le|be) and use the class API by @hansl in https://github.com/boa-dev/boa/pull/4358 - Bump temporal_rs version to v0.0.12 by @nekevss in https://github.com/boa-dev/boa/pull/4367 - Allow public access to the full position of a call frame by @hansl in https://github.com/boa-dev/boa/pull/4365 - Bump the rust-dependencies group with 5 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4374 - Bump slab from 0.4.10 to 0.4.11 by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4376 - Implement queueMicrotask() and a test for it by @hansl in https://github.com/boa-dev/boa/pull/4359 - Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4386 - Bump the rust-dependencies group with 4 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4387 - Improve object display by @xubaiwang in https://github.com/boa-dev/boa/pull/4377 - Fix broken PlainMonthDay.from tests by @nekevss in https://github.com/boa-dev/boa/pull/4389 - Fix final Temporal.Instant test in built-ins test suite by @nekevss in https://github.com/boa-dev/boa/pull/4388 - Remove alignment of property keys with identation by @hansl in https://github.com/boa-dev/boa/pull/4390 - Bump temporal_rs version to v0.0.14 by @nekevss in https://github.com/boa-dev/boa/pull/4391 - Update `Math.sumPrecise` to remove `SummationState` by @nekevss in https://github.com/boa-dev/boa/pull/4392 - Update phf requirement from 0.12.1 to 0.13.1 in /tests/fuzz in the fuzz-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4384 - Fix index access in `JumpTable` instruction by @HalidOdat in https://github.com/boa-dev/boa/pull/4372 - Bump the rust-dependencies group with 5 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4395 - Fix: prevent OOB panic in decodeURI by tightening percent-decoding bounds; add test for incomplete escape %E7%9A%8 (#4404) by @hamflx in https://github.com/boa-dev/boa/pull/4405 - Implement a `JsValueStore` as well as structuredClone and tests by @hansl in https://github.com/boa-dev/boa/pull/4366 - Bump baptiste0928/cargo-install from 3.3.1 to 3.3.2 in the ci-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4385 - Update hashbrown requirement from 0.15.5 to 0.16.0 in /tests/fuzz in the fuzz-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4396 - Replace the String producing Display logic to using a Formatter by @hansl in https://github.com/boa-dev/boa/pull/4393 - Propagate AST errors to the user by @hansl in https://github.com/boa-dev/boa/pull/4408 - Remove the boa_interop module by @hansl in https://github.com/boa-dev/boa/pull/4407 - Fix final `Temporal.PlainTime.from` tests by @nekevss in https://github.com/boa-dev/boa/pull/4411 - Bump the rust-dependencies group with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4413 - Bump actions/setup-node from 4 to 5 by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4412 - Various quality of life improvements to the CLI by @hansl in https://github.com/boa-dev/boa/pull/4414 - Implement `Date` and `RegExp` `JsValueStore` from/to by @hansl in https://github.com/boa-dev/boa/pull/4415 - Use async channels for Atomics.waitAsync implementation by @jedel1043 in https://github.com/boa-dev/boa/pull/4418 - Bump the rust-dependencies group across 1 directory with 7 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4421 - Unblock Context::run_jobs when no timeouts need to be run by @hansl in https://github.com/boa-dev/boa/pull/4416 - Fix 2 UBs, and cleanup `GcRefCell` by @hansl in https://github.com/boa-dev/boa/pull/4422 - Bump temporal_rs version to v0.0.16 by @nekevss in https://github.com/boa-dev/boa/pull/4425 - Fix the CLI when it is not attached to a TTY by @hansl in https://github.com/boa-dev/boa/pull/4424 - Bump the rust-dependencies group with 6 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4429 - Implement `JsValueStore` for `SharedArrayBuffer` and `postMessage` by @hansl in https://github.com/boa-dev/boa/pull/4417 - fix: comment typo by @jasonmilad in https://github.com/boa-dev/boa/pull/4430 - Editorconfig: specify indentation and dont overrule Makefile by @hansl in https://github.com/boa-dev/boa/pull/4446 - Add a simple cargo-make to the project by @hansl in https://github.com/boa-dev/boa/pull/4437 - Update to temporal_rs to 0.1 release by @nekevss in https://github.com/boa-dev/boa/pull/4433 - Add minimal CI task to cargo-make by @nekevss in https://github.com/boa-dev/boa/pull/4448 - Improve regular expression flags parsing by @hansl in https://github.com/boa-dev/boa/pull/4434 - Bump the rust-dependencies group with 4 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4449 - Only validate the RegExp, do not optimize/compile it by @hansl in https://github.com/boa-dev/boa/pull/4451 - Bump peter-evans/create-or-update-comment from 4 to 5 by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4456 - Bump peter-evans/find-comment from 3 to 4 by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4457 - Bump bytemuck from 1.23.2 to 1.24.0 in the rust-dependencies group by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4458 - Allow printing test262 comment on PRs from forks by @jedel1043 in https://github.com/boa-dev/boa/pull/4428 - perf: Improve `Math.sumPrecise` Performance by @Gumichocopengin8 in https://github.com/boa-dev/boa/pull/4462 - Bump the rust-dependencies group with 3 updates by @dependabot[bot] in https://github.com/boa-dev/boa/pull/4467 ### New Contributors - @zzzdong made their first contribution in https://github.com/boa-dev/boa/pull/4058 - @albertleigh made their first contribution in https://github.com/boa-dev/boa/pull/4097 - @heygsc made their first contribution in https://github.com/boa-dev/boa/pull/4124 - @jamesthurley made their first contribution in https://github.com/boa-dev/boa/pull/4155 - @lockels made their first contribution in https://github.com/boa-dev/boa/pull/4189 - @changhc made their first contribution in https://github.com/boa-dev/boa/pull/4176 - @created-by-varun made their first contribution in https://github.com/boa-dev/boa/pull/4198 - @tomoverlund made their first contribution in https://github.com/boa-dev/boa/pull/4254 - @Hemenguelbindi made their first contribution in https://github.com/boa-dev/boa/pull/4145 - @Timkarx made their first contribution in https://github.com/boa-dev/boa/pull/4276 - @Rafferty97 made their first contribution in https://github.com/boa-dev/boa/pull/4303 - @cijiugechu made their first contribution in https://github.com/boa-dev/boa/pull/4307 - @countradooku made their first contribution in https://github.com/boa-dev/boa/pull/4214 - @xubaiwang made their first contribution in https://github.com/boa-dev/boa/pull/4381 - @hamflx made their first contribution in https://github.com/boa-dev/boa/pull/4405 - @BDeuDev made their first contribution in https://github.com/boa-dev/boa/pull/4419 - @jasonmilad made their first contribution in https://github.com/boa-dev/boa/pull/4430 - @hpp2334 made their first contribution in https://github.com/boa-dev/boa/pull/4453 - @Gumichocopengin8 made their first contribution in https://github.com/boa-dev/boa/pull/4462 - @mdrokz made their first contribution in https://github.com/boa-dev/boa/pull/4466 - @rrogerc made their first contribution in https://github.com/boa-dev/boa/pull/4459 **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.20...v0.21 ## [0.20.0 (2024-12-05)](https://github.com/boa-dev/boa/compare/v0.19.1...v0.20.0) ### Feature Enhancements - Add a js_error! macro to create opaque errors by @hansl in https://github.com/boa-dev/boa/pull/3920 - Update `Instant` for new Temporal functionality by @nekevss in https://github.com/boa-dev/boa/pull/3928 - Add a way to add setters/getters in js_class! by @hansl in https://github.com/boa-dev/boa/pull/3911 - Fix lints from rustc 1.80.0 by @jedel1043 in https://github.com/boa-dev/boa/pull/3936 - Add a JsError::from_rust constructor to create native errors from Rust by @hansl in https://github.com/boa-dev/boa/pull/3921 - add some temporal methods by @jasonwilliams in https://github.com/boa-dev/boa/pull/3856 - Allow a custom Logger to be used as the backend for boa_runtime::Console by @hansl in https://github.com/boa-dev/boa/pull/3943 - Add more utility functions around modules and exports by @hansl in https://github.com/boa-dev/boa/pull/3937 - Allow trailing commas in js_class functions by @hansl in https://github.com/boa-dev/boa/pull/3964 - Implement `Atomics.pause` by @jedel1043 in https://github.com/boa-dev/boa/pull/3956 - Add a clone_inner method to allow cloning of inner data by @hansl in https://github.com/boa-dev/boa/pull/3968 - fix: ignore `debugger` statement by @shurizzle in https://github.com/boa-dev/boa/pull/3976 - Add support for boa(rename = "") in TryFromJs derive by @hansl in https://github.com/boa-dev/boa/pull/3980 - Add an "iter()" method to Js\*Array for convenience by @hansl in https://github.com/boa-dev/boa/pull/3986 - A simple module loader from a function by @hansl in https://github.com/boa-dev/boa/pull/3932 - Add a way for js_error! macro to create native errors with message by @hansl in https://github.com/boa-dev/boa/pull/3971 - Limit actions runs to 1 per branch and fix macos release by @jedel1043 in https://github.com/boa-dev/boa/pull/3996 - Add TextEncoder, TextDecoder implementations to boa_runtime by @hansl in https://github.com/boa-dev/boa/pull/3994 - Add TryFromJs for TypedJsFunction and more tests by @hansl in https://github.com/boa-dev/boa/pull/3981 - Add context to the console `Logger` trait by @hansl in https://github.com/boa-dev/boa/pull/4005 - Add a URL class to boa_runtime by @hansl in https://github.com/boa-dev/boa/pull/4004 - Add a display_lossy() to write a JsString lossily by @hansl in https://github.com/boa-dev/boa/pull/4023 - `TryIntoJs` trait and derive macro for it by @Nikita-str in https://github.com/boa-dev/boa/pull/3999 - console.debug() should use a debug Logger method by @hansl in https://github.com/boa-dev/boa/pull/4019 - `TryFromJs` from `JsMap` for `HashMap` & `BtreeMap` by @Nikita-str in https://github.com/boa-dev/boa/pull/3998 - Add string builder to build `JsString` by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/3915 ### Bug Fixes - Implement `Math.pow` function according to ECMAScript specification by @magic-akari in https://github.com/boa-dev/boa/pull/3916 - Fix temporal builtin properties by @nekevss in https://github.com/boa-dev/boa/pull/3930 - Fix wrong `neg` operation by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/3926 - Fix destructuring assignment evaluation order by @raskad in https://github.com/boa-dev/boa/pull/3934 - Fix various parser idempotency issues and parsing errors by @raskad in https://github.com/boa-dev/boa/pull/3917 - Implement new spec changes for `AsyncGenerator` by @jedel1043 in https://github.com/boa-dev/boa/pull/3950 - Refactor ast function types by @raskad in https://github.com/boa-dev/boa/pull/3931 - Fix `js_str` macro to correctly handle latin1 strings by @jedel1043 in https://github.com/boa-dev/boa/pull/3959 - Allow dead code for code that is newly detected as unused by @hansl in https://github.com/boa-dev/boa/pull/3984 - Allow warnings when running CI on release branches by @jedel1043 in https://github.com/boa-dev/boa/pull/3990 - docs: Fix link to examples by @it-a-me in https://github.com/boa-dev/boa/pull/4007 - `IntegerOrInfinity` `eq` bug fix by @Nikita-str in https://github.com/boa-dev/boa/pull/4010 ### Internal Improvements - Refactor `RawJsString`'s representation to make `JsString`s construction from string literal heap-allocation free by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/3935 - Split default icu data into lazily deserialized parts by @jedel1043 in https://github.com/boa-dev/boa/pull/3948 - Add clippy for denying print and eprints by @hansl in https://github.com/boa-dev/boa/pull/3967 - Refactor iterator APIs to be on parity with the latest spec by @jedel1043 in https://github.com/boa-dev/boa/pull/3962 - Add support for Trace, Finalize and JsData for Convert<> by @hansl in https://github.com/boa-dev/boa/pull/3970 - use with_capacity to reduce re-allocations fixes #3896 by @jasonwilliams in https://github.com/boa-dev/boa/pull/3961 - add nightly build by @jasonwilliams in https://github.com/boa-dev/boa/pull/4026 - Patch the indentation in nightly_build.yml by @nekevss in https://github.com/boa-dev/boa/pull/4028 - Update night build's rename binary step by @nekevss in https://github.com/boa-dev/boa/pull/4032 - Use upload-rust-binary-action for nightly release by @nekevss in https://github.com/boa-dev/boa/pull/4040 - Fix `ref` value in nightly and add target to nightly release by @nekevss in https://github.com/boa-dev/boa/pull/4042 - Reduce environment allocations by @raskad in https://github.com/boa-dev/boa/pull/4002 ### Other Changes - Implement more Temporal functionality by @nekevss in https://github.com/boa-dev/boa/pull/3924 - Add a Source::with_path method to set the path on a Source by @hansl in https://github.com/boa-dev/boa/pull/3941 - Add spec edition 15 to the tester by @jedel1043 in https://github.com/boa-dev/boa/pull/3957 - Rename as_promise to as_promise_object and add as_promise -> JsPromise by @hansl in https://github.com/boa-dev/boa/pull/3965 - Build out partial record functionality, property bag construction, and `with` methods by @nekevss in https://github.com/boa-dev/boa/pull/3955 - Enable CI for release branches by @jedel1043 in https://github.com/boa-dev/boa/pull/3987 - Add a display type for JsString to allow formatting without allocations by @hansl in https://github.com/boa-dev/boa/pull/3951 - Add TryIntoJsResult for vectors by @hansl in https://github.com/boa-dev/boa/pull/3993 - Add tests from WPT and fix them in the Console by @hansl in https://github.com/boa-dev/boa/pull/3979 - Update changelog for v0.19.1 by @jedel1043 in https://github.com/boa-dev/boa/pull/3995 - Implement register allocation by @HalidOdat in https://github.com/boa-dev/boa/pull/3942 - Implement scope analysis and local variables by @raskad in https://github.com/boa-dev/boa/pull/3988 - `JsValue::to_json` fix integer property keys by @Nikita-str in https://github.com/boa-dev/boa/pull/4011 - Some optimizations on `Error` by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4020 - Option::None should try into Undefined, not Null by @hansl in https://github.com/boa-dev/boa/pull/4029 - Some string optimizations by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/4030 - Add a JsPromise::from_result for convenience by @hansl in https://github.com/boa-dev/boa/pull/4039 - Fix misspelled permissions in nightly build action by @nekevss in https://github.com/boa-dev/boa/pull/4041 - Remove dockerfile from documentation by @4yman-0 in https://github.com/boa-dev/boa/pull/4046 - Bump dependencies with breaking changes by @jedel1043 in https://github.com/boa-dev/boa/pull/4050 - Migrate to fast-float2 by @jedel1043 in https://github.com/boa-dev/boa/pull/4052 ### New Contributors - @magic-akari made their first contribution in https://github.com/boa-dev/boa/pull/3916 - @shurizzle made their first contribution in https://github.com/boa-dev/boa/pull/3976 - @it-a-me made their first contribution in https://github.com/boa-dev/boa/pull/4007 - @Nikita-str made their first contribution in https://github.com/boa-dev/boa/pull/4010 - @4yman-0 made their first contribution in https://github.com/boa-dev/boa/pull/4046 **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.19...v0.20.0 ## [0.19.1 (2024-09-11)](https://github.com/boa-dev/boa/compare/v0.19...v0.19.1) ### Bug Fixes - Implement new spec changes for `AsyncGenerator` by @jedel1043 in https://github.com/boa-dev/boa/pull/3950 - Allow dead code for code that is newly detected as unused by @hansl in https://github.com/boa-dev/boa/pull/3984 - Allow warnings when running CI on release branches by @jedel1043 in https://github.com/boa-dev/boa/pull/3990 ### Internal Improvements - Add spec edition 15 to the tester by @jedel1043 in https://github.com/boa-dev/boa/pull/3957 - Enable CI for release branches by @jedel1043 in https://github.com/boa-dev/boa/pull/3987 **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.19...v0.19.1 ## [0.19.0 (2024-07-08)](https://github.com/boa-dev/boa/compare/v0.18...v0.19) ### Feature Enhancements - Add release binary striping by @Razican in https://github.com/boa-dev/boa/pull/3727 - Added NPM publish workflow by @Razican in https://github.com/boa-dev/boa/pull/3725 - Remove references to dev docs and npm dependencies by @jedel1043 in https://github.com/boa-dev/boa/pull/3787 - Cleanup tester deps and patterns by @jedel1043 in https://github.com/boa-dev/boa/pull/3792 - Build docs.rs docs with all features enabled by @jedel1043 in https://github.com/boa-dev/boa/pull/3794 - Add a new type Convert<> to convert values by @hansl in https://github.com/boa-dev/boa/pull/3786 - Add functions to create modules from a JSON value by @hansl in https://github.com/boa-dev/boa/pull/3804 - Add an embed_module!() macro to boa_interop by @hansl in https://github.com/boa-dev/boa/pull/3784 - Add a ContextData struct to inject host defined types from the context by @hansl in https://github.com/boa-dev/boa/pull/3802 - Implement object keys access by @HalidOdat in https://github.com/boa-dev/boa/pull/3832 - Group dependabot updates by @jedel1043 in https://github.com/boa-dev/boa/pull/3863 - Adding TryFromJs implementations for BTreeMap and HashMap by @hansl in https://github.com/boa-dev/boa/pull/3844 - Adding TryFromJs implementations for tuples by @hansl in https://github.com/boa-dev/boa/pull/3843 - Add a js_class to implement the Class trait without boilerplate by @hansl in https://github.com/boa-dev/boa/pull/3872 - Implement lossless TryFromJs for integers from f64 by @HalidOdat in https://github.com/boa-dev/boa/pull/3907 ### Bug Fixes - Close for-of iterator when the loop body throws by @raskad in https://github.com/boa-dev/boa/pull/3734 - Add default value handling for destructuring property access arrays by @raskad in https://github.com/boa-dev/boa/pull/3738 - Fix invalid syntax errors for allowed `let` as variable names by @raskad in https://github.com/boa-dev/boa/pull/3743 - Fix parsing of `async` in for-of loops by @raskad in https://github.com/boa-dev/boa/pull/3745 - Fix parsing of binding identifier in try catch parameters by @raskad in https://github.com/boa-dev/boa/pull/3752 - Add missing environment creation in initial iteration of for loop by @raskad in https://github.com/boa-dev/boa/pull/3751 - chore: Update README link to reflect new site paths by @NickTomlin in https://github.com/boa-dev/boa/pull/3793 - Fix order of `ToString` call in `Function` constructor by @HalidOdat in https://github.com/boa-dev/boa/pull/3820 - Fix CI for nextest step by @jedel1043 in https://github.com/boa-dev/boa/pull/3862 - Fix base objects in `with` statements by @raskad in https://github.com/boa-dev/boa/pull/3870 - Fix boa cli history by @raskad in https://github.com/boa-dev/boa/pull/3875 - Fix hashbang comments by using proper goal symbols by @raskad in https://github.com/boa-dev/boa/pull/3876 - Fix AsyncGenerator to correctly handle `return` inside `then` by @jedel1043 in https://github.com/boa-dev/boa/pull/3879 - Fix HomeObject for private class methods by @raskad in https://github.com/boa-dev/boa/pull/3897 - Fix evaluation order in destructive property assignments by @raskad in https://github.com/boa-dev/boa/pull/3895 ### Internal Improvements - Apply new clippy lints for rustc 1.77 by @jedel1043 in https://github.com/boa-dev/boa/pull/3759 - Change dependabot interval to weekly by @jedel1043 in https://github.com/boa-dev/boa/pull/3758 - Dense array storage variants for `i32` and `f64` by @HalidOdat in https://github.com/boa-dev/boa/pull/3760 - Optimize number to `PropertyKey` conversion by @HalidOdat in https://github.com/boa-dev/boa/pull/3769 - don't run test262 on push by @jasonwilliams in https://github.com/boa-dev/boa/pull/3774 - Check that `min <= max` in `clamp_finite` by @jedel1043 in https://github.com/boa-dev/boa/pull/3699 - Decouple `Context` from `ByteCompiler` by @HalidOdat in https://github.com/boa-dev/boa/pull/3829 - Implement latin1 encoded `JsString`s by @HalidOdat in https://github.com/boa-dev/boa/pull/3450 - Replace `js_str` with `js_string` in examples by @getong in https://github.com/boa-dev/boa/pull/3836 - Separate `JsString` into its own crate by @HalidOdat in https://github.com/boa-dev/boa/pull/3837 - Bump temporal_rs to latest commit by @jedel1043 in https://github.com/boa-dev/boa/pull/3880 - Remove `FormalParameterList` from `CodeBlock` by @HalidOdat in https://github.com/boa-dev/boa/pull/3882 ### Other Changes - Fix a few Duration code typos by @robot-head in https://github.com/boa-dev/boa/pull/3730 - Add a try_from_js implementation for Vec (accept any Array-like) by @hansl in https://github.com/boa-dev/boa/pull/3755 - Swap to Duration::round from temporal_rs by @robot-head in https://github.com/boa-dev/boa/pull/3731 - Cache `this` value by @HalidOdat in https://github.com/boa-dev/boa/pull/3771 - Allow deserialization of missing objects properties into Option<> by @hansl in https://github.com/boa-dev/boa/pull/3767 - Optimize Regex match check by @HalidOdat in https://github.com/boa-dev/boa/pull/3779 - Add a boa_interop crate by @hansl in https://github.com/boa-dev/boa/pull/3772 - Add a path to Module (and expose it in Referrer) by @hansl in https://github.com/boa-dev/boa/pull/3783 - Properly resolve paths in SimpleModuleLoader and add path to Referrer::Script by @hansl in https://github.com/boa-dev/boa/pull/3791 - Fix SimpleModuleLoader on Windows by @hansl in https://github.com/boa-dev/boa/pull/3795 - Add more utility traits and funtions to boa_interop by @hansl in https://github.com/boa-dev/boa/pull/3773 - Implement Promise.try() by @linusg in https://github.com/boa-dev/boa/pull/3800 - Implement TryFromJs for Either by @hansl in https://github.com/boa-dev/boa/pull/3822 - Fix Rust 1.78.0 Clippy lints by @HalidOdat in https://github.com/boa-dev/boa/pull/3838 - Switch from actions-rs/toolchain to dtolnay/rust-toolchain by @raskad in https://github.com/boa-dev/boa/pull/3845 - Replace archived github actions from actions-rs by @raskad in https://github.com/boa-dev/boa/pull/3848 - Add matrix badge and update communication to include matrix by @nekevss in https://github.com/boa-dev/boa/pull/3865 - Add groupCollapsed by @leoflalv in https://github.com/boa-dev/boa/pull/3867 - Bump ICU4X to 1.5 and cleanup Intl by @jedel1043 in https://github.com/boa-dev/boa/pull/3868 - Update regress to v0.10.0 by @raskad in https://github.com/boa-dev/boa/pull/3869 - Combine `HasProperty` and `Get` operations when possible by @raskad in https://github.com/boa-dev/boa/pull/3883 - Remove some environment clones by @raskad in https://github.com/boa-dev/boa/pull/3884 - Refactor call frame access to avoid panic checks by @raskad in https://github.com/boa-dev/boa/pull/3888 - Remove `Temporal.Calendar` and `Temporal.TimeZone` by @jedel1043 in https://github.com/boa-dev/boa/pull/3890 - Update Temporal rounding and implement additional methods by @nekevss in https://github.com/boa-dev/boa/pull/3892 - format code in comments by @jasonwilliams in https://github.com/boa-dev/boa/pull/3902 - Updates to temporal_rs version and temporal methods by @nekevss in https://github.com/boa-dev/boa/pull/3900 - Patch regression from change to to-relative-to-object by @nekevss in https://github.com/boa-dev/boa/pull/3906 - Add `get_unchecked` method to `JsString` and `JsStr` by @CrazyboyQCD in https://github.com/boa-dev/boa/pull/3898 - bump gc threshold by @jasonwilliams in https://github.com/boa-dev/boa/pull/3908 - update versions and ABOUT files by @jasonwilliams in https://github.com/boa-dev/boa/pull/3903 - Cleanup README.md and contributor documentation by @jedel1043 in https://github.com/boa-dev/boa/pull/3909 - Refactor environment stack to remove some panics by @raskad in https://github.com/boa-dev/boa/pull/3893 ### New Contributors - @robot-head made their first contribution in https://github.com/boa-dev/boa/pull/3730 - @hansl made their first contribution in https://github.com/boa-dev/boa/pull/3755 - @NickTomlin made their first contribution in https://github.com/boa-dev/boa/pull/3793 - @linusg made their first contribution in https://github.com/boa-dev/boa/pull/3800 - @getong made their first contribution in https://github.com/boa-dev/boa/pull/3836 - @leoflalv made their first contribution in https://github.com/boa-dev/boa/pull/3867 - @CrazyboyQCD made their first contribution in https://github.com/boa-dev/boa/pull/3898 **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.18...v0.19 ## [0.18.0 (2024-03-04)](https://github.com/boa-dev/boa/compare/v0.17...v0.18) ### Feature Enhancements - Format let-else expressions by @jedel1043 in https://github.com/boa-dev/boa/pull/3102 - Add regexp indices (`d` flag) support by @dirkdev98 in https://github.com/boa-dev/boa/pull/3094 - Add missing 'unscopables' to `Array.prototype[@@unscopables]` by @dirkdev98 in https://github.com/boa-dev/boa/pull/3111 - Updated Fuzzer dependencies and added them to Dependabot by @Razican in https://github.com/boa-dev/boa/pull/3124 - Implement `findLast` and `findLastIndex` on TypedArray by @dirkdev98 in https://github.com/boa-dev/boa/pull/3135 - Implement i128/u128 to JsBigInt conversions by @AlvinKuruvilla in https://github.com/boa-dev/boa/pull/3129 - Implement `String.prototype.isWellFormed` and `String.prototype.toWellFormed` by @raskad in https://github.com/boa-dev/boa/pull/3187 - Clarify usage section in `README.md` by @postmeback in https://github.com/boa-dev/boa/pull/3092 - Log traces even without message (boa_runtime) by @kelbazz in https://github.com/boa-dev/boa/pull/3193 - Implement ephemeron-based weak map by @jedel1043 in https://github.com/boa-dev/boa/pull/3052 - Improve bytecompiler bytecode generation. by @HalidOdat in https://github.com/boa-dev/boa/pull/3188 - Add `Instruction` and `InstructionIterator` by @HalidOdat in https://github.com/boa-dev/boa/pull/3201 - Add ECMAScript 14 to `boa_tester` by @jedel1043 in https://github.com/boa-dev/boa/pull/3273 - Bump `rust-version` to 1.71 by @jedel1043 in https://github.com/boa-dev/boa/pull/3290 - Lazily download `test262` repository by @HalidOdat in https://github.com/boa-dev/boa/pull/3214 - Implement `Gc::new_cyclic` by @jedel1043 in https://github.com/boa-dev/boa/pull/3292 - Implement `Intl.PluralRules` by @jedel1043 in https://github.com/boa-dev/boa/pull/3298 - Implement step 5 in `RegExp` constructor by @HalidOdat in https://github.com/boa-dev/boa/pull/3305 - Replace #[deny] with #[warn] by @jedel1043 in https://github.com/boa-dev/boa/pull/3309 - Bump ICU4X to 1.3 by @jedel1043 in https://github.com/boa-dev/boa/pull/3306 - Migrate to workspace deps by @jedel1043 in https://github.com/boa-dev/boa/pull/3313 - Implement `[[HostDefined]]` field on `Realm`s by @HalidOdat in https://github.com/boa-dev/boa/pull/2952 - Introduce experimental features by @jedel1043 in https://github.com/boa-dev/boa/pull/3318 - Introduce a `Class` map by @jedel1043 in https://github.com/boa-dev/boa/pull/3315 - Fix `Function.prototype.toString()` by @HalidOdat in https://github.com/boa-dev/boa/pull/3374 - First portion of the Temporal implementation by @nekevss in https://github.com/boa-dev/boa/pull/3277 - Update feature flags to specific feature flag by @nekevss in https://github.com/boa-dev/boa/pull/3376 - Implement `[[HostDefined]]` for `Module` and `Script` by @arexon in https://github.com/boa-dev/boa/pull/3381 - Implement synthetic modules by @jedel1043 in https://github.com/boa-dev/boa/pull/3294 - Prevent `test262` repository update if not needed by @HalidOdat in https://github.com/boa-dev/boa/pull/3386 - Implement `SharedArrayBuffer` by @jedel1043 in https://github.com/boa-dev/boa/pull/3384 - Add `Context::create_realm` by @johnyob in https://github.com/boa-dev/boa/pull/3369 - Introduce a thread safe version of `JsError` by @jedel1043 in https://github.com/boa-dev/boa/pull/3398 - Implement asynchronous evaluation of scripts by @jedel1043 in https://github.com/boa-dev/boa/pull/3044 - Feature `get/set $boa.limits.stack` by @HalidOdat in https://github.com/boa-dev/boa/pull/3385 - Implement `change-array-by-copy` methods by @jedel1043 in https://github.com/boa-dev/boa/pull/3412 - Implement the `array-grouping` proposal by @jedel1043 in https://github.com/boa-dev/boa/pull/3420 - Implement `Atomics` builtin by @jedel1043 in https://github.com/boa-dev/boa/pull/3394 - Migrate to workspace lints by @jedel1043 in https://github.com/boa-dev/boa/pull/3334 - Bump ICU4X to 1.4 and finish Intl impls with new APIs by @jedel1043 in https://github.com/boa-dev/boa/pull/3469 - Class: Switch `make_data` parameter from `this` to `new_target` by @johnyob in https://github.com/boa-dev/boa/pull/3478 - Add utility methods to the `Class` trait by @jedel1043 in https://github.com/boa-dev/boa/pull/3488 - Simplify `Icu` API by @jedel1043 in https://github.com/boa-dev/boa/pull/3503 - Add UTF-16 input parsing by @raskad in https://github.com/boa-dev/boa/pull/3538 - Remove allocations from `HostDefined::get_many_mut` by @jedel1043 in https://github.com/boa-dev/boa/pull/3606 - Implement getter for `ArrayBuffer` data by @HalidOdat in https://github.com/boa-dev/boa/pull/3610 - Implement non-erased `JsObject`s by @jedel1043 in https://github.com/boa-dev/boa/pull/3618 - Update regress to v0.8.0 and use UTF16 / UCS2 matching by @raskad in https://github.com/boa-dev/boa/pull/3627 - Cleanup 262 tester and stabilize some experimental features by @jedel1043 in https://github.com/boa-dev/boa/pull/3632 - Improve typing of `DataView` and related objects by @jedel1043 in https://github.com/boa-dev/boa/pull/3626 - Close sync iterator when async wrapper yields rejection by @jedel1043 in https://github.com/boa-dev/boa/pull/3633 - Implement resizable buffers by @jedel1043 in https://github.com/boa-dev/boa/pull/3634 - Implement stage 3 feature "arraybuffer-transfer" by @jedel1043 in https://github.com/boa-dev/boa/pull/3649 - Implement prototype of `NumberFormat` by @jedel1043 in https://github.com/boa-dev/boa/pull/3669 - Add example for async module fetches by @jedel1043 in https://github.com/boa-dev/boa/pull/3012 - Js typed array methods by @AngeloChecked in https://github.com/boa-dev/boa/pull/3481 - Create tool to regenerate `ABOUT.md` by @jedel1043 in https://github.com/boa-dev/boa/pull/3692 - Implement RegExp `v` flag by @raskad in https://github.com/boa-dev/boa/pull/3695 ### Bug Fixes - Allow escaped yield and await in labelled statement by @raskad in https://github.com/boa-dev/boa/pull/3117 - `TypedArray.prototype.values()` and `TypedArray.prototype[@@iterator]` should be equal by @HalidOdat in https://github.com/boa-dev/boa/pull/3096 - Fix TypedArrayConstructors tests by @raskad in https://github.com/boa-dev/boa/pull/3171 - Close iterator after generator return call while array destructuring assignment by @HalidOdat in https://github.com/boa-dev/boa/pull/3164 - Fix remaining TypedArray bugs by @raskad in https://github.com/boa-dev/boa/pull/3186 - Add early errors for `LexicalDeclaration` by @raskad in https://github.com/boa-dev/boa/pull/3207 - Fix switch statement `break` and `continue` return values by @raskad in https://github.com/boa-dev/boa/pull/3205 - Fix GitHub coverage workflow by @HalidOdat in https://github.com/boa-dev/boa/pull/3288 - Fix tagged template `this` in strict mode by @HalidOdat in https://github.com/boa-dev/boa/pull/3307 - fix: add 'static lifetime by @mattsse in https://github.com/boa-dev/boa/pull/3297 - Fix class inherit from `null` by @HalidOdat in https://github.com/boa-dev/boa/pull/3312 - Fix anonymous function name in cover assignment by @raskad in https://github.com/boa-dev/boa/pull/3325 - Add `NonMaxU32` as integer variant for `PropertyKey` by @raskad in https://github.com/boa-dev/boa/pull/3321 - Add missing class name binding by @raskad in https://github.com/boa-dev/boa/pull/3328 - Truncate environment stack on non-caught native error by @HalidOdat in https://github.com/boa-dev/boa/pull/3331 - Fix regular expression construction by @HalidOdat in https://github.com/boa-dev/boa/pull/3338 - Fix `super()` construction with default parameters by @HalidOdat in https://github.com/boa-dev/boa/pull/3339 - Fix static class element evaluation order by @raskad in https://github.com/boa-dev/boa/pull/3327 - Fix detection of runtime limits for accessors by @jedel1043 in https://github.com/boa-dev/boa/pull/3335 - Fix `Number.prototype.toFixed()` by @HalidOdat in https://github.com/boa-dev/boa/pull/2898 - Check `eval` realm before call by @HalidOdat in https://github.com/boa-dev/boa/pull/3375 - Evaluate all parts of `class` in strict mode by @HalidOdat in https://github.com/boa-dev/boa/pull/3383 - Fix var declaration for deleted binding locator by @raskad in https://github.com/boa-dev/boa/pull/3387 - Fix await flag in class constructor by @raskad in https://github.com/boa-dev/boa/pull/3388 - Fix compilation for targets without `AtomicU64` by @jedel1043 in https://github.com/boa-dev/boa/pull/3399 - Update `regex.match` spec and code by @raskad in https://github.com/boa-dev/boa/pull/3462 - `Context` independent `CodeBlock`s by @HalidOdat in https://github.com/boa-dev/boa/pull/3424 - Fix a Parser Idempotency Issue by @veera-sivarajan in https://github.com/boa-dev/boa/pull/3172 - Non recursive gc trace by @HalidOdat in https://github.com/boa-dev/boa/pull/3508 - Fix invalid return value when closing an iterator by @raskad in https://github.com/boa-dev/boa/pull/3567 - Implement Date parsing according to the spec by @raskad in https://github.com/boa-dev/boa/pull/3564 - `Date` refactor by @raskad in https://github.com/boa-dev/boa/pull/3595 - Fix regexp `toString` method by @raskad in https://github.com/boa-dev/boa/pull/3608 - Fix escaping in `RegExp.prototype.source` by @raskad in https://github.com/boa-dev/boa/pull/3619 - Fix line terminators in template strings by @raskad in https://github.com/boa-dev/boa/pull/3641 - Consider strict + no-strict tests as a single test by @jedel1043 in https://github.com/boa-dev/boa/pull/3675 - Preserve `.exe` suffix for Windows releases by @HalidOdat in https://github.com/boa-dev/boa/pull/3680 ### Internal Improvements - Move `RefCell` of `CompileTimeEnvironment`s to field `bindings` by @HalidOdat in https://github.com/boa-dev/boa/pull/3108 - Change `name` field type in `CodeBlock` to `JsString` by @HalidOdat in https://github.com/boa-dev/boa/pull/3107 - Refactor `Array.prototype.find*` and TypedArray variants to use `FindViaPredicate` by @dirkdev98 in https://github.com/boa-dev/boa/pull/3134 - Fix 1.71.0 lints by @RageKnify in https://github.com/boa-dev/boa/pull/3140 - Clippy updates: add panics and etc. by @nekevss in https://github.com/boa-dev/boa/pull/3235 - Remove unused class environments by @raskad in https://github.com/boa-dev/boa/pull/3332 - Improve highlighter performance by @jedel1043 in https://github.com/boa-dev/boa/pull/3341 - Cleanup `get_option` and calls to the function by @jedel1043 in https://github.com/boa-dev/boa/pull/3355 - Fix new lints for Rust 1.73 by @jedel1043 in https://github.com/boa-dev/boa/pull/3361 - Refactor compile time environment handling by @raskad in https://github.com/boa-dev/boa/pull/3365 - Update all dependencies by @jedel1043 in https://github.com/boa-dev/boa/pull/3400 - Optimize `shift` for dense arrays by @jedel1043 in https://github.com/boa-dev/boa/pull/3405 - Disallow changing type of already created objects by @jedel1043 in https://github.com/boa-dev/boa/pull/3410 - Merge `CodeBlock` constant pools by @HalidOdat in https://github.com/boa-dev/boa/pull/3413 - Move ordinary function `[[ConstructorKind]]` to `CodeBlock` by @HalidOdat in https://github.com/boa-dev/boa/pull/3439 - Move `FunctionKind` to `CodeBlock` by @HalidOdat in https://github.com/boa-dev/boa/pull/3440 - Unify generator and ordinary function creation by @HalidOdat in https://github.com/boa-dev/boa/pull/3441 - Move `arguments` object creation to bytecode by @HalidOdat in https://github.com/boa-dev/boa/pull/3432 - Move parameter environment creation to bytecode by @HalidOdat in https://github.com/boa-dev/boa/pull/3433 - Prevent `DefVar` opcode emit for global binding by @HalidOdat in https://github.com/boa-dev/boa/pull/3453 - Transition `Intl` types to `NativeObject` API by @jedel1043 in https://github.com/boa-dev/boa/pull/3491 - Reduce `WeakGc` memory usage by @HalidOdat in https://github.com/boa-dev/boa/pull/3492 - Migrate `Temporal` to its own crate. by @nekevss in https://github.com/boa-dev/boa/pull/3461 - Reestructure repo and CI improvements by @jedel1043 in https://github.com/boa-dev/boa/pull/3505 - Move `PromiseCapability` to stack by @HalidOdat in https://github.com/boa-dev/boa/pull/3528 - Fix rust 1.75 lints by @raskad in https://github.com/boa-dev/boa/pull/3540 - Remove double indirection in module types by @jedel1043 in https://github.com/boa-dev/boa/pull/3640 - Fix clippy warnings for rustc 1.76 by @jedel1043 in https://github.com/boa-dev/boa/pull/3668 - Migrate to `temporal_rs` crate by @nekevss in https://github.com/boa-dev/boa/pull/3694 ### Other Changes - Removed time 0.1 dependency, updated dependencies by @Razican in https://github.com/boa-dev/boa/pull/3122 - Add new CLI options to usage in README by @Razican in https://github.com/boa-dev/boa/pull/3123 - Find roots when running GC rather than runtime by @tunz in https://github.com/boa-dev/boa/pull/3109 - Re-enable must_use clippy rule by @tunz in https://github.com/boa-dev/boa/pull/3180 - Refactor environment, exception handling and jumping in VM by @HalidOdat in https://github.com/boa-dev/boa/pull/3059 - Refactor `Context::run()` method by @HalidOdat in https://github.com/boa-dev/boa/pull/3179 - Added examples by @postmeback in https://github.com/boa-dev/boa/pull/3141 - Use main stack for calling ordinary functions by @HalidOdat in https://github.com/boa-dev/boa/pull/3185 - Update license field following SPDX 2.1 license expression standard by @frisoft in https://github.com/boa-dev/boa/pull/3209 - Store active runnable and active function in `CallFrame` by @HalidOdat in https://github.com/boa-dev/boa/pull/3197 - Added MSRV check by @Razican in https://github.com/boa-dev/boa/pull/3291 - Reintroduce publish CI job by @jedel1043 in https://github.com/boa-dev/boa/pull/3308 - Format code snippets in docs by @jedel1043 in https://github.com/boa-dev/boa/pull/3317 - Remove direct conversion from `&str` to `JsValue`/`PropertyKey`. by @jedel1043 in https://github.com/boa-dev/boa/pull/3319 - `icu_properties` default features to true by @nekevss in https://github.com/boa-dev/boa/pull/3326 - Varying length instruction operands by @HalidOdat in https://github.com/boa-dev/boa/pull/3253 - Improve CI testing by @jedel1043 in https://github.com/boa-dev/boa/pull/3333 - Refactor function internal methods by @HalidOdat in https://github.com/boa-dev/boa/pull/3322 - Make environments opcodes use varying operands by @HalidOdat in https://github.com/boa-dev/boa/pull/3340 - Bump test262 by @jedel1043 in https://github.com/boa-dev/boa/pull/3349 - Refactor ordinary VM calling by @HalidOdat in https://github.com/boa-dev/boa/pull/3295 - Fix Array.join when the array contains itself by @ahaoboy in https://github.com/boa-dev/boa/pull/3406 - Rename master workflow to main by @Razican in https://github.com/boa-dev/boa/pull/3409 - Cleaned up a couple of Github action warnings by @Razican in https://github.com/boa-dev/boa/pull/3417 - Temporal duration update and cleanup by @nekevss in https://github.com/boa-dev/boa/pull/3443 - Progress on Duration's round/total method updates by @nekevss in https://github.com/boa-dev/boa/pull/3451 - Simplify all extensions APIs of `Context` by @jedel1043 in https://github.com/boa-dev/boa/pull/3456 - `[[HostDefined]]` Improvements by @johnyob in https://github.com/boa-dev/boa/pull/3460 - Make well_known_symbols functions pub by @tj825 in https://github.com/boa-dev/boa/pull/3465 - Use `Vec` for keeping track of gc objects by @HalidOdat in https://github.com/boa-dev/boa/pull/3493 - Implement `Inline Caching` by @HalidOdat in https://github.com/boa-dev/boa/pull/2767 - Migrate `ISO8601` parsing to `boa_temporal` by @nekevss in https://github.com/boa-dev/boa/pull/3500 - Implement erased objects by @jedel1043 in https://github.com/boa-dev/boa/pull/3494 - Build out ZonedDateTime, TimeZone, and Instant by @nekevss in https://github.com/boa-dev/boa/pull/3497 - `boa_temporal` structure changes and docs update by @nekevss in https://github.com/boa-dev/boa/pull/3504 - Refactor vm calling convention to allow locals by @HalidOdat in https://github.com/boa-dev/boa/pull/3496 - Temporal Parser Cleanup/Fixes by @nekevss in https://github.com/boa-dev/boa/pull/3521 - Refactor Temporal Calendar API for `AnyCalendar` and fields by @nekevss in https://github.com/boa-dev/boa/pull/3522 - Update `boa_temporal` Time Zone design by @nekevss in https://github.com/boa-dev/boa/pull/3543 - Implement `DifferenceInstant` and related refactor by @nekevss in https://github.com/boa-dev/boa/pull/3568 - Run `cargo update` on fuzz crate by @jedel1043 in https://github.com/boa-dev/boa/pull/3607 - Temporal `Instant` migration cont. and related changes by @nekevss in https://github.com/boa-dev/boa/pull/3601 - Temporal: Update `Date` builtin with `boa_temporal` and fixes by @nekevss in https://github.com/boa-dev/boa/pull/3614 - Temporal: Build out `Time` and its methods by @nekevss in https://github.com/boa-dev/boa/pull/3613 - Temporal: Enable temporal tests by @nekevss in https://github.com/boa-dev/boa/pull/3620 - Fix tests results upload by @raskad in https://github.com/boa-dev/boa/pull/3635 - Temporal: `DateTime` and `PlainDateTime` functionality by @nekevss in https://github.com/boa-dev/boa/pull/3628 - Temporal: Initial `PlainTime` build out by @nekevss in https://github.com/boa-dev/boa/pull/3621 - Ignore `Cargo.lock` in fuzzer by @jedel1043 in https://github.com/boa-dev/boa/pull/3636 - Temporal: attribute/property and custom calendar fixes by @nekevss in https://github.com/boa-dev/boa/pull/3639 - Docs: Update boa's main README.md by @nekevss in https://github.com/boa-dev/boa/pull/3650 - Bump time from 0.3.31 to 0.3.33 by @jedel1043 in https://github.com/boa-dev/boa/pull/3652 - Temporal: Refactor Calendar protocol for `JsObject`s by @nekevss in https://github.com/boa-dev/boa/pull/3651 - Simplify Temporal APIs by @jedel1043 in https://github.com/boa-dev/boa/pull/3653 - Implement inline caching tests and cleanup by @HalidOdat in https://github.com/boa-dev/boa/pull/3513 - Docs: Update README.md and add `boa_cli`'s README.md by @nekevss in https://github.com/boa-dev/boa/pull/3659 - Change `HostEnsureCanCompileStrings` to the new spec by @jedel1043 in https://github.com/boa-dev/boa/pull/3690 - Split ICU4X data generation from `boa_icu_provider` by @jedel1043 in https://github.com/boa-dev/boa/pull/3682 - Add a catch all for other categories not labelled by @jasonwilliams in https://github.com/boa-dev/boa/pull/3703 - Fix `temporal_rs` in Cargo.toml by @nekevss in https://github.com/boa-dev/boa/pull/3702 ## New Contributors - @AlvinKuruvilla made their first contribution in https://github.com/boa-dev/boa/pull/3129 - @tunz made their first contribution in https://github.com/boa-dev/boa/pull/3109 - @postmeback made their first contribution in https://github.com/boa-dev/boa/pull/3092 - @kelbazz made their first contribution in https://github.com/boa-dev/boa/pull/3193 - @frisoft made their first contribution in https://github.com/boa-dev/boa/pull/3209 - @mattsse made their first contribution in https://github.com/boa-dev/boa/pull/3297 - @arexon made their first contribution in https://github.com/boa-dev/boa/pull/3381 - @johnyob made their first contribution in https://github.com/boa-dev/boa/pull/3369 - @ahaoboy made their first contribution in https://github.com/boa-dev/boa/pull/3406 - @tj825 made their first contribution in https://github.com/boa-dev/boa/pull/3465 - @AngeloChecked made their first contribution in https://github.com/boa-dev/boa/pull/3481 **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.17...v0.18 ## [0.17.0 (2023-07-05)](https://github.com/boa-dev/boa/compare/v0.16...v0.17) ### Feature Enhancements - Implement `new.target` expression by @raskad in [#2299](https://github.com/boa-dev/boa/pull/2299) - Parse static async private methods in classes by @raskad in [#2315](https://github.com/boa-dev/boa/pull/2315) - Implement `JsDataView` by @nekevss in [#2308](https://github.com/boa-dev/boa/pull/2308) - Upgrade clap to 4.0, add value hints for zsh and fish by @Razican in [#2336](https://github.com/boa-dev/boa/pull/2336) - Implement `JsRegExp` by @nekevss in [#2326](https://github.com/boa-dev/boa/pull/2326) - Create new lazy Error type by @jedel1043 in [#2283](https://github.com/boa-dev/boa/pull/2283) - Fixed some documentation and clippy warnings in tests by @Razican in [#2362](https://github.com/boa-dev/boa/pull/2362) - Removed the "VM Implementation" headline for Test262 by @Razican in [#2364](https://github.com/boa-dev/boa/pull/2364) - Modified the `loadfile` example to show how Boa can read bytes by @Razican in [#2363](https://github.com/boa-dev/boa/pull/2363) - Implement `LabelledStatement` by @jedel1043 in [#2349](https://github.com/boa-dev/boa/pull/2349) - Split vm/opcode into modules by @nekevss in [#2343](https://github.com/boa-dev/boa/pull/2343) - Removed some duplicate code, added `ToIndentedString` by @Razican in [#2367](https://github.com/boa-dev/boa/pull/2367) - Document the AST by @jedel1043 in [#2377](https://github.com/boa-dev/boa/pull/2377) - Implement member accessors in initializer of for loops by @jedel1043 in [#2381](https://github.com/boa-dev/boa/pull/2381) - Implement `JsGenerator` and wrapper docs clean up by @nekevss in [#2380](https://github.com/boa-dev/boa/pull/2380) - Add named evaluation of logical assignments by @raskad in [#2389](https://github.com/boa-dev/boa/pull/2389) - Implement optional chains by @jedel1043 in [#2390](https://github.com/boa-dev/boa/pull/2390) - Implement delete for references by @jedel1043 in [#2395](https://github.com/boa-dev/boa/pull/2395) - Implement AST Visitor pattern (attempt #3) by @addisoncrump in [#2392](https://github.com/boa-dev/boa/pull/2392) - Implement async arrow functions by @raskad in [#2393](https://github.com/boa-dev/boa/pull/2393) - Pretty print promise objects by @jedel1043 in [#2407](https://github.com/boa-dev/boa/pull/2407) - Parser Idempotency Fuzzer by @addisoncrump in [#2400](https://github.com/boa-dev/boa/pull/2400) - Safe wrapper for `JsDate` by @anuvratsingh in [#2181](https://github.com/boa-dev/boa/pull/2181) - Fix some Date tests by @jedel1043 in [#2431](https://github.com/boa-dev/boa/pull/2431) - Boa Gc implementation draft by @nekevss in [#2394](https://github.com/boa-dev/boa/pull/2394) - VM Fuzzer by @addisoncrump in [#2401](https://github.com/boa-dev/boa/pull/2401) - Implement the `WeakRef` builtin by @jedel1043 in [#2438](https://github.com/boa-dev/boa/pull/2438) - Refactor the `Date` builtin by @jedel1043 in [#2449](https://github.com/boa-dev/boa/pull/2449) - Implement instruction flowgraph generator by @HalidOdat in [#2422](https://github.com/boa-dev/boa/pull/2422) - `JsArrayBuffer` take method and docs by @nekevss in [#2454](https://github.com/boa-dev/boa/pull/2454) - Set function names in object literal methods by @raskad in [#2460](https://github.com/boa-dev/boa/pull/2460) - Redesign Intl API and implement some services by @jedel1043 in [#2478](https://github.com/boa-dev/boa/pull/2478) - Cleanup `Context` APIs by @jedel1043 in [#2504](https://github.com/boa-dev/boa/pull/2504) - Prepare `Promises` for new host hooks and job queue API by @jedel1043 in [#2528](https://github.com/boa-dev/boa/pull/2528) - Implement host hooks and job queues APIs by @jedel1043 in [#2529](https://github.com/boa-dev/boa/pull/2529) - First batch of `no_std` support for some sub-crates by @jedel1043 in [#2544](https://github.com/boa-dev/boa/pull/2544) - Create `Source` to abstract JS code sources by @jedel1043 in [#2579](https://github.com/boa-dev/boa/pull/2579) - Move increment and decrement operations to `Update` expression by @raskad in [#2565](https://github.com/boa-dev/boa/pull/2565) - Implement binary `in` operation with private names by @raskad in [#2582](https://github.com/boa-dev/boa/pull/2582) - Module parsing by @Razican in [#2411](https://github.com/boa-dev/boa/pull/2411) - Implement `WeakSet` by @lupd in [#2586](https://github.com/boa-dev/boa/pull/2586) - Implement `WeakMap` by @raskad in [#2597](https://github.com/boa-dev/boa/pull/2597) - API to construct a `NativeFunction` from a native async function by @jedel1043 in [#2542](https://github.com/boa-dev/boa/pull/2542) - Add `--strict` flag to cli by @HalidOdat in [#2689](https://github.com/boa-dev/boa/pull/2689) - Add timeout to CI by @HalidOdat in [#2691](https://github.com/boa-dev/boa/pull/2691) - Add ES5 and ES6 Conformance calculation to boa_tester by @ZackMitkin in [#2690](https://github.com/boa-dev/boa/pull/2690) - Improve tester display for multiple editions by @jedel1043 in [#2720](https://github.com/boa-dev/boa/pull/2720) - Implement `JsPromise` wrapper by @jedel1043 in [#2758](https://github.com/boa-dev/boa/pull/2758) - Initial version of a JS -> Rust conversion trait. by @Razican in [#2276](https://github.com/boa-dev/boa/pull/2276) - Implement `escape` and `unescape` by @jedel1043 in [#2768](https://github.com/boa-dev/boa/pull/2768) - Implement debug object for CLI by @HalidOdat in [#2772](https://github.com/boa-dev/boa/pull/2772) - Make `Realm` shareable between functions by @jedel1043 in [#2801](https://github.com/boa-dev/boa/pull/2801) - Implement Annex-B string html methods by @HalidOdat in [#2798](https://github.com/boa-dev/boa/pull/2798) - Implement annex-b `trimLeft` and `trimRight` string methods by @HalidOdat in [#2806](https://github.com/boa-dev/boa/pull/2806) - Implement HTML comments and gate behind the `annex-b` feature by @jedel1043 in [#2817](https://github.com/boa-dev/boa/pull/2817) - Implement `Intl.Segmenter` by @jedel1043 in [#2840](https://github.com/boa-dev/boa/pull/2840) - Implement var initializers in for-in loops by @jedel1043 in [#2842](https://github.com/boa-dev/boa/pull/2842) - Improve debug output of `JsNativeError` and `Realm` by @jedel1043 in [#2894](https://github.com/boa-dev/boa/pull/2894) - Implement runtime limits for loops by @HalidOdat in [#2857](https://github.com/boa-dev/boa/pull/2857) - Implement runtime limits for recursion by @HalidOdat in [#2904](https://github.com/boa-dev/boa/pull/2904) - Implement annexB Block-Level Function Declarations by @raskad in [#2910](https://github.com/boa-dev/boa/pull/2910) - Implement module execution by @jedel1043 in [#2922](https://github.com/boa-dev/boa/pull/2922) - Type safe root shape by @HalidOdat in [#2940](https://github.com/boa-dev/boa/pull/2940) - Implement dynamic imports by @jedel1043 in [#2932](https://github.com/boa-dev/boa/pull/2932) - Implement pseudo-property `import.meta` by @jedel1043 in [#2956](https://github.com/boa-dev/boa/pull/2956) - Implement `with` and object environments by @raskad in [#2692](https://github.com/boa-dev/boa/pull/2692) - Add hooks to get the current time and timezone by @jedel1043 in [#2824](https://github.com/boa-dev/boa/pull/2824) - Make `JsSymbol` thread-safe by @jedel1043 in [#2539](https://github.com/boa-dev/boa/pull/2539) - Added a Boa runtime by @Razican in [#2743](https://github.com/boa-dev/boa/pull/2743) - Implement `TryFromJs` for `JsObject` wrappers by @Razican in [#2809](https://github.com/boa-dev/boa/pull/2809) - Allow passing owned `HostHooks` and `JobQueues` to `Context` by @jedel1043 in [#2811](https://github.com/boa-dev/boa/pull/2811) - Implement `String.prototype.toLocaleUpper/LowerCase` by @jedel1043 in [#2822](https://github.com/boa-dev/boa/pull/2822) - Implement constant folding optimization by @HalidOdat in [#2679](https://github.com/boa-dev/boa/pull/2679) - Implement `Hidden classes` by @HalidOdat in [#2723](https://github.com/boa-dev/boa/pull/2723) - show object kind, name and address when using dbg! by @jasonwilliams in [#2960](https://github.com/boa-dev/boa/pull/2960) - Add convenience methods to `ModuleLoader` by @jedel1043 in [#3007](https://github.com/boa-dev/boa/pull/3007) - Allow `JobQueue` to concurrently run jobs by @jedel1043 in [#3036](https://github.com/boa-dev/boa/pull/3036) - Make `IntegerIndexed::byte_offset` public by @CryZe in [#3017](https://github.com/boa-dev/boa/pull/3017) - Allow awaiting `JsPromise` from Rust code by @jedel1043 in [#3011](https://github.com/boa-dev/boa/pull/3011) - Cache `cargo-tarpaulin` binary by @jedel1043 in [#3071](https://github.com/boa-dev/boa/pull/3071) - add link to the main logo by @jasonwilliams in [#3082](https://github.com/boa-dev/boa/pull/3082) ### Bug Fixes - Add unicode terminator to line comment by @creampnx-x in [#2301](https://github.com/boa-dev/boa/pull/2301) - Fix function property order by @raskad in [#2305](https://github.com/boa-dev/boa/pull/2305) - Fix some Array spec deviations by @raskad in [#2306](https://github.com/boa-dev/boa/pull/2306) - Fix double conversion to primitive in `ToNumeric` by @raskad in [#2310](https://github.com/boa-dev/boa/pull/2310) - Fixing the output for the test diffs in PRs by @Razican in [#2320](https://github.com/boa-dev/boa/pull/2320) - Fix Regex literal parsing in MemberExpression by @tunz in [#2328](https://github.com/boa-dev/boa/pull/2328) - Fix error in `Proxy` set implementation by @raskad in [#2369](https://github.com/boa-dev/boa/pull/2369) - Allow LineTerminator before Semicolon in `continue` by @raskad in [#2371](https://github.com/boa-dev/boa/pull/2371) - Fix var collisions in strict eval calls by @jedel1043 in [#2382](https://github.com/boa-dev/boa/pull/2382) - Set `in` to `true` when parsing AssignmentExpression in ConditionalExpression by @raskad in [#2386](https://github.com/boa-dev/boa/pull/2386) - Skip prototype field definition for arrow function by @raskad in [#2388](https://github.com/boa-dev/boa/pull/2388) - Remove invalid optimization in addition by @raskad in [#2387](https://github.com/boa-dev/boa/pull/2387) - Fix order dependent execution in assignment. by @HalidOdat in [#2378](https://github.com/boa-dev/boa/pull/2378) - Add early error for `yield` in `GeneratorExpression` parameters by @raskad in [#2413](https://github.com/boa-dev/boa/pull/2413) - Handle `__proto__` fields in object literals by @raskad in [#2423](https://github.com/boa-dev/boa/pull/2423) - Fix built-ins/Array/prototype/toString/non-callable-join-string-tag.js test case by @akavi in [#2458](https://github.com/boa-dev/boa/pull/2458) - Fix `PartialEq` for `JsBigInt` and `f64` by @raskad in [#2461](https://github.com/boa-dev/boa/pull/2461) - Allow class expressions without identifier by @raskad in [#2464](https://github.com/boa-dev/boa/pull/2464) - Fix to weak_trace for `boa_tester` by @nekevss in [#2470](https://github.com/boa-dev/boa/pull/2470) - Fix unary operations on `this` by @veera-sivarajan in [#2507](https://github.com/boa-dev/boa/pull/2507) - Fix postfix operator line terminator parsing by @raskad in [#2520](https://github.com/boa-dev/boa/pull/2520) - Remove `Literal::Undefined` by @veera-sivarajan in [#2518](https://github.com/boa-dev/boa/pull/2518) - Add early errors for 'eval' or 'arguments' in parameters by @raskad in [#2515](https://github.com/boa-dev/boa/pull/2515) - Pass a receiver value in property getter opcodes by @raskad in [#2516](https://github.com/boa-dev/boa/pull/2516) - Add regex literal early errors by @raskad in [#2517](https://github.com/boa-dev/boa/pull/2517) - `Break` Opcode and `ByteCompiler` changes by @nekevss in [#2523](https://github.com/boa-dev/boa/pull/2523) - Refactor some class features by @raskad in [#2513](https://github.com/boa-dev/boa/pull/2513) - Recognize Directive Prologues correctly by @raskad in [#2521](https://github.com/boa-dev/boa/pull/2521) - Correctly parse consecutive semicolons by @raskad in [#2533](https://github.com/boa-dev/boa/pull/2533) - Fix some HoistableDeclaration parsing errors by @raskad in [#2532](https://github.com/boa-dev/boa/pull/2532) - Return the correct value from a statement list by @raskad in [#2554](https://github.com/boa-dev/boa/pull/2554) - Fix error for static class methods named `prototype` by @raskad in [#2552](https://github.com/boa-dev/boa/pull/2552) - Avoid creating `prototype` property on methods by @raskad in [#2553](https://github.com/boa-dev/boa/pull/2553) - Fix double property access on assignment ops by @raskad in [#2551](https://github.com/boa-dev/boa/pull/2551) - Add early errors for escaped identifiers by @raskad in [#2546](https://github.com/boa-dev/boa/pull/2546) - Fix failing collator tests by @jedel1043 in [#2575](https://github.com/boa-dev/boa/pull/2575) - fuzzer: bubble up NoInstructionsRemain error instead of trying to handle as exception by @Mrmaxmeier in [#2566](https://github.com/boa-dev/boa/pull/2566) - Try-catch-block control flow fix/refactor by @nekevss in [#2568](https://github.com/boa-dev/boa/pull/2568) - Fix doc tests and add CI check by @jedel1043 in [#2606](https://github.com/boa-dev/boa/pull/2606) - Fix string to number conversion for `infinity` by @raskad in [#2607](https://github.com/boa-dev/boa/pull/2607) - Fix exponent operator by @HalidOdat in [#2681](https://github.com/boa-dev/boa/pull/2681) - Update `README.md` cli options by @HalidOdat in [#2678](https://github.com/boa-dev/boa/pull/2678) - Fix incorrect `Number.MIN_VALUE` value by @HalidOdat in [#2682](https://github.com/boa-dev/boa/pull/2682) - Correctly run async tests by @jedel1043 in [#2683](https://github.com/boa-dev/boa/pull/2683) - Fix value to bigint conversion by @HalidOdat in [#2688](https://github.com/boa-dev/boa/pull/2688) - Fix Object constructor by @raskad in [#2694](https://github.com/boa-dev/boa/pull/2694) - Fix get function opcode traces by @HalidOdat in [#2708](https://github.com/boa-dev/boa/pull/2708) - Add early errors to dynamic function constructors by @raskad in [#2716](https://github.com/boa-dev/boa/pull/2716) - Add negative zero handling for `Map.delete` by @raskad in [#2726](https://github.com/boa-dev/boa/pull/2726) - Fix remaining `Set` tests by @raskad in [#2725](https://github.com/boa-dev/boa/pull/2725) - Fix update expressions getting values multiple times by @raskad in [#2733](https://github.com/boa-dev/boa/pull/2733) - Make if statements return their completion values by @raskad in [#2739](https://github.com/boa-dev/boa/pull/2739) - Fix super call execution order by @raskad in [#2724](https://github.com/boa-dev/boa/pull/2724) - Fix deserialization of `SpecEdition` by @jedel1043 in [#2762](https://github.com/boa-dev/boa/pull/2762) - Add `json-parse-with-source` feature to `boa_tester` by @HalidOdat in [#2778](https://github.com/boa-dev/boa/pull/2778) - Fix `Symbol.prototype[@@iterator]` by @HalidOdat in [#2800](https://github.com/boa-dev/boa/pull/2800) - Fix `String.prototype.replace()` order of `ToString` execution by @HalidOdat in [#2799](https://github.com/boa-dev/boa/pull/2799) - Fix `ThrowTypeError` intrinsic by @HalidOdat in [#2797](https://github.com/boa-dev/boa/pull/2797) - Fix `String.prototype.substr()` by @HalidOdat in [#2805](https://github.com/boa-dev/boa/pull/2805) - Fix destructive for-of loop assignments by @raskad in [#2803](https://github.com/boa-dev/boa/pull/2803) - Fix `TypedArray`s minus zero key by @HalidOdat in [#2808](https://github.com/boa-dev/boa/pull/2808) - Fix sync generator yield expressions by @raskad in [#2838](https://github.com/boa-dev/boa/pull/2838) - Fix async generators by @raskad in [#2853](https://github.com/boa-dev/boa/pull/2853) - Catch 'eval' and 'arguments' in setter method parameter by @raskad in [#2858](https://github.com/boa-dev/boa/pull/2858) - Fix `PropertyKey` index parse by @HalidOdat in [#2843](https://github.com/boa-dev/boa/pull/2843) - Fix `Date.prototype[Symbol.primitive]` incorrect attributes by @HalidOdat in [#2862](https://github.com/boa-dev/boa/pull/2862) - Allow `Date` object to store invalid `NativeDateTime` by @HalidOdat in [#2861](https://github.com/boa-dev/boa/pull/2861) - Fix panic when calling toString with radix by @HalidOdat in [#2863](https://github.com/boa-dev/boa/pull/2863) - Fix incorrect `LoopContinue` instruction in while-do loops by @HalidOdat in [#2866](https://github.com/boa-dev/boa/pull/2866) - Initialize `var` bindings in runtime environments with `undefined` by @raskad in [#2860](https://github.com/boa-dev/boa/pull/2860) - Bugfix/new.target is not understood by the parser as an expression #2793 by @projectnoa in [#2878](https://github.com/boa-dev/boa/pull/2878) - Fix `RegExp` constructor return value when pattern is a regexp by @HalidOdat in [#2880](https://github.com/boa-dev/boa/pull/2880) - `RegExp` constructor should call `IsRegExp()` by @HalidOdat in [#2881](https://github.com/boa-dev/boa/pull/2881) - Fix `for-of` expression parsing by @HalidOdat in [#2882](https://github.com/boa-dev/boa/pull/2882) - Disallow strict directives with escaped sequences by @jedel1043 in [#2892](https://github.com/boa-dev/boa/pull/2892) - Make `typeof` throw when accessing uninitialized variables by @raskad in [#2902](https://github.com/boa-dev/boa/pull/2902) - Fix wrong name of `Function.prototype[Symbol.hasInstance]` by @raskad in [#2905](https://github.com/boa-dev/boa/pull/2905) - Fix remaining object literal tests by @raskad in [#2906](https://github.com/boa-dev/boa/pull/2906) - Add SyntaxErrors in GlobalDeclarationInstantiation by @raskad in [#2908](https://github.com/boa-dev/boa/pull/2908) - Fix switch `default` execution by @HalidOdat in [#2907](https://github.com/boa-dev/boa/pull/2907) - Add loop and switch return values by @raskad in [#2828](https://github.com/boa-dev/boa/pull/2828) - Allow escaped `let` as expression by @HalidOdat in [#2916](https://github.com/boa-dev/boa/pull/2916) - Allow `let` name in for-in loop in non-strict mode by @HalidOdat in [#2915](https://github.com/boa-dev/boa/pull/2915) - Fix lexical environments in for loops by @raskad in [#2917](https://github.com/boa-dev/boa/pull/2917) - Fix `GetSubstitution` by @HalidOdat in [#2933](https://github.com/boa-dev/boa/pull/2933) - Allow escaped `async` as binding name by @jedel1043 in [#2936](https://github.com/boa-dev/boa/pull/2936) - Fix tagged template creation by @raskad in [#2925](https://github.com/boa-dev/boa/pull/2925) - Implement Private Runtime Environments by @raskad in [#2929](https://github.com/boa-dev/boa/pull/2929) - Fix remaining ES5 `built-ins/RegExp` tests by @HalidOdat in [#2957](https://github.com/boa-dev/boa/pull/2957) - Fix remaining static module bugs by @jedel1043 in [#2955](https://github.com/boa-dev/boa/pull/2955) - Deny Unicode Escapes in boolean and null expressions by @veera-sivarajan in [#2931](https://github.com/boa-dev/boa/pull/2931) - Fix `Date` for dynamic timezones by @jedel1043 in [#2877](https://github.com/boa-dev/boa/pull/2877) - Fix ES5 selector by @veera-sivaraja in [#2924](https://github.com/boa-dev/boa/pull/2924) - Labelled ByteCompiler Fix by @nekevss in [#2534](https://github.com/boa-dev/boa/pull/2534) - Fix verbose test display by @jedel1043 in [#2731](https://github.com/boa-dev/boa/pull/2731) - Fix WASM playground by @jedel1043 in [#2992](https://github.com/boa-dev/boa/pull/2992) - Correctly initialize functions inside modules by @jedel1043 in [#2993](https://github.com/boa-dev/boa/pull/2993) - Allow `true`, `false` and `null` in object patterns by @jedel1043 in [#2994](https://github.com/boa-dev/boa/pull/2994) - Fix panic in optional expressions with private identifiers by @raskad in [#2995](https://github.com/boa-dev/boa/pull/2995) - Fix prompt on windows by @ShaneEverittM in [#2986](https://github.com/boa-dev/boa/pull/2986) - Fix panic in constructor call by @raskad in [#3001](https://github.com/boa-dev/boa/pull/3001) - Unify async iterators and iterators compilation by @jedel1043 in [#2976](https://github.com/boa-dev/boa/pull/2976) - Correctly parse `yield import(..)` expressions by @jedel1043 in [#3006](https://github.com/boa-dev/boa/pull/3006) - Return the correct value during a labelled break by @raskad in [#2996](https://github.com/boa-dev/boa/pull/2996) - Fix panics on empty return values by @raskad in [#3018](https://github.com/boa-dev/boa/pull/3018) - Add early error for `await` in class static blocks by @raskad in [#3019](https://github.com/boa-dev/boa/pull/3019) - Fix class constructor return value by @raskad in [#3028](https://github.com/boa-dev/boa/pull/3028) - Fix super property access by @raskad in [#3026](https://github.com/boa-dev/boa/pull/3026) - Skip reversing arguments in SuperCallDerived by @dirkdev98 in [#3062](https://github.com/boa-dev/boa/pull/3062) - Mark header of rooted ephemerons when tracing by @jedel1043 in [#3049](https://github.com/boa-dev/boa/pull/3049) - Copy `ABOUT.md` file to all published crates by @jedel1043 in [#3074](https://github.com/boa-dev/boa/pull/3074) - Correctly handle finally..loop..break by @dirkdev98 in [#3073](https://github.com/boa-dev/boa/pull/3073) ### Internal Improvements - Direct conversion from `u8` to `Opcode` by @HalidOdat [#2951](https://github.com/boa-dev/boa/pull/2951) - Fix links in readme by @raskad in [#2304](https://github.com/boa-dev/boa/pull/2304) - Switch to workspace inherited properties by @jedel1043 in [#2297](https://github.com/boa-dev/boa/pull/2297) - Separate JsObjectType implementors to their own module by @CalliEve in [#2324](https://github.com/boa-dev/boa/pull/2324) - First prototype for new `JsString` using UTF-16 by @jedel1043 in [#1659](https://github.com/boa-dev/boa/pull/1659) - Split `Node` into `Statement`, `Expression` and `Declaration` by @jedel1043 in [#2319](https://github.com/boa-dev/boa/pull/2319) - Changes neccesary -> necessary by @nekevss in [#2370](https://github.com/boa-dev/boa/pull/2370) - Cleanup and speed-up CI by @RageKnify in [#2376](https://github.com/boa-dev/boa/pull/2376) - Reduce documentation size in blog by @jedel1043 in [#2383](https://github.com/boa-dev/boa/pull/2383) - Generate `Opcode` impl using macro by @jedel1043 in [#2391](https://github.com/boa-dev/boa/pull/2391) - Extract the ast to a crate by @jedel1043 in [#2402](https://github.com/boa-dev/boa/pull/2402) - Replace `contains` and friends with visitors by @jedel1043 in [#2403](https://github.com/boa-dev/boa/pull/2403) - Rewrite some patterns with let-else and ok_or_else by @jedel1043 in [#2404](https://github.com/boa-dev/boa/pull/2404) - Fix async tests result values by @jedel1043 in [#2406](https://github.com/boa-dev/boa/pull/2406) - Rewrite scope analysis operations using visitors by @jedel1043 in [#2408](https://github.com/boa-dev/boa/pull/2408) - Make `JsString` conform to miri tests by @jedel1043 in [#2412](https://github.com/boa-dev/boa/pull/2412) - Reduced boilerplate code in the parser by @Razican in [#2410](https://github.com/boa-dev/boa/pull/2410) - Extract the parser into a crate by @jedel1043 in [#2409](https://github.com/boa-dev/boa/pull/2409) - Switch tarpaulin to llvm engine by @RageKnify in [#2432](https://github.com/boa-dev/boa/pull/2432) - Cleanup `boa_tester` by @jedel1043 in [#2440](https://github.com/boa-dev/boa/pull/2440) - Restructure lint lists in `boa_ast` by @raskad in [#2433](https://github.com/boa-dev/boa/pull/2433) - Restructure lints in multiple crates by @raskad in [#2447](https://github.com/boa-dev/boa/pull/2447) - Restructure lint lists in `boa_engine` by @raskad in [#2455](https://github.com/boa-dev/boa/pull/2455) - Fix rust 1.66.0 lints by @raskad in [#2486](https://github.com/boa-dev/boa/pull/2486) - Divide byte compiler by @e-codes-stuff in [#2425](https://github.com/boa-dev/boa/pull/2425) - Cleanup inline annotations by @jedel1043 in [#2493](https://github.com/boa-dev/boa/pull/2493) - [profiler] Cache StringId by @tunz in [#2495](https://github.com/boa-dev/boa/pull/2495) - Improve identifier parsing by @jedel1043 in [#2581](https://github.com/boa-dev/boa/pull/2581) - Remove Syntax Errors from Bytecompiler by @raskad in [#2598](https://github.com/boa-dev/boa/pull/2598) - fix: RUSTSEC-2020-0071 in boa_engine by @hanabi1224 in [#2627](https://github.com/boa-dev/boa/pull/2627) - Migrate tests to new test API by @jedel1043 in [#2619](https://github.com/boa-dev/boa/pull/2619) - [regexp] new tests for unicode flag by @selfisekai in [#2656](https://github.com/boa-dev/boa/pull/2656) - Handle surrogates in `String.fromCodePoint` by @jedel1043 in [#2659](https://github.com/boa-dev/boa/pull/2659) - Bump Test262 and add new features by @jedel1043 in [#2729](https://github.com/boa-dev/boa/pull/2729) - Fix cross-realm construction bugs by @jedel1043 in [#2786](https://github.com/boa-dev/boa/pull/2786) - Lift `InternalObjectMethods` from `Object` by @jedel1043 in [#2790](https://github.com/boa-dev/boa/pull/2790) - Implement async functions using generators by @jedel1043 in [#2821](https://github.com/boa-dev/boa/pull/2821) - Improve strictness of `GeneratorState` by @jedel1043 in [#2837](https://github.com/boa-dev/boa/pull/2837) - Upgraded to ICU 1.2 by @Razican in [#2826](https://github.com/boa-dev/boa/pull/2826) - Fix setting properties inside `with` blocks by @jedel1043 in [#2847](https://github.com/boa-dev/boa/pull/2847) - Create a unique `PromiseCapability` on each async function call by @jedel1043 in [#2846](https://github.com/boa-dev/boa/pull/2846) - Refactor binding handling APIs by @jedel1043 in [#2870](https://github.com/boa-dev/boa/pull/2870) - Refactor guards into a `ContextCleanupGuard` abstraction by @jedel1043 in [#2890](https://github.com/boa-dev/boa/pull/2890) - Refactor binding declarations by @raskad in [#2887](https://github.com/boa-dev/boa/pull/2887) - Cleanup some bytecompiler code by @raskad in [#2918](https://github.com/boa-dev/boa/pull/2918) - Fix `use_self` lints by @raskad in [#2946](https://github.com/boa-dev/boa/pull/2946) - Remove unused lint allows by @raskad in [#2968](https://github.com/boa-dev/boa/pull/2968) - Decouple bytecompiler from CodeBlock by @HalidOdat in [#2669](https://github.com/boa-dev/boa/pull/2669) - Clarity changes for the VM by @nekevss in [#2531](https://github.com/boa-dev/boa/pull/2531) - Bump bitflags to 2.0.0 by @Razican in [#2666](https://github.com/boa-dev/boa/pull/2666) - Replace deprecated set-output command by @karol-jani in [#2500](https://github.com/boa-dev/boa/pull/2500) - Documentation Updates by @nekevss in [#2463](https://github.com/boa-dev/boa/pull/2463) - Fixed typo in the docs by @Razican in [#2450](https://github.com/boa-dev/boa/pull/2450) - update tasks.json by @jasonwilliams in [#2313](https://github.com/boa-dev/boa/pull/2313) - Updated the Code of Conduct by @Razican in [#2365](https://github.com/boa-dev/boa/pull/2365) - Bump serde_json from 1.0.85 to 1.0.86 by @jedel1043 in [#2341](https://github.com/boa-dev/boa/pull/2341) - Add test case for issue #2719 by @jedel1043 in [#2980](https://github.com/boa-dev/boa/pull/2980) - Remove unneded `num_bindings` in `Opcode`s and `CodeBlock` by @HalidOdat in [#2967](https://github.com/boa-dev/boa/pull/2967) - Added period to sentence by @nekevss in [#2939](https://github.com/boa-dev/boa/pull/2939) - Prune collected shared shapes by @HalidOdat in [#2941](https://github.com/boa-dev/boa/pull/2941) - Separate declarative environment kinds by @jedel1043 in [#2921](https://github.com/boa-dev/boa/pull/2921) - Shrink environment binding locators by @HalidOdat in [#2950](https://github.com/boa-dev/boa/pull/2950) - Extract "About Boa" section into a separate file by @jedel1043 in [#2938](https://github.com/boa-dev/boa/pull/2938) - Remove `arguments_binding` field from `CodeBlock` by @HalidOdat in [#2969](https://github.com/boa-dev/boa/pull/2969) - Remove redundant `param_count` field from `CallFrame` by @HalidOdat in [#2962](https://github.com/boa-dev/boa/pull/2962) - Direct length access on arrays by @HalidOdat in [#2796](https://github.com/boa-dev/boa/pull/2796) - Prevent allocation of field names by @HalidOdat in [#2901](https://github.com/boa-dev/boa/pull/2901) - Added unit tests for `boa_ast::Punctuator` by @Razican in [#2884](https://github.com/boa-dev/boa/pull/2884) - Added unit tests for `boa_ast::Keyword` by @Razican in [#2883](https://github.com/boa-dev/boa/pull/2883) - Make update operations reuse the last found binding locator by @jedel1043 in [#2876](https://github.com/boa-dev/boa/pull/2876) - Docs update for boa_runtime and console documentation by @nekevss in [#2891](https://github.com/boa-dev/boa/pull/2891) - Direct array element access on `ByValue` instructions by @HalidOdat in [#2827](https://github.com/boa-dev/boa/pull/2827) - Optimize `String.prototype.normalize` by @jedel1043 in [#2848](https://github.com/boa-dev/boa/pull/2848) - Fix more Annex B tests by @jedel1043 in [#2841](https://github.com/boa-dev/boa/pull/2841) - Enable github queues and remove bors.toml by @jedel1043 in [#2899](https://github.com/boa-dev/boa/pull/2899) - Shrink size of `IndexedProperties` by @HalidOdat in [#2757](https://github.com/boa-dev/boa/pull/2757) - Added an example usage to documentation by @Razican in [#2742](https://github.com/boa-dev/boa/pull/2742) - Don't construct prototype if not needed by @HalidOdat in [#2751](https://github.com/boa-dev/boa/pull/2751) - Implement `is_identifier_(start/part)` using `icu_properties` by @jedel1043 in [#2865](https://github.com/boa-dev/boa/pull/2865) - Add boa logo to remaining hosted docs by @nekevss in [#2740](https://github.com/boa-dev/boa/pull/2740) - Added a bunch more tests by @Razican in [#2885](https://github.com/boa-dev/boa/pull/2885) - Updated dependencies, removes `remove_dir_all`, which is vulnerable by @Razican in [#2685](https://github.com/boa-dev/boa/pull/2685) - Updated README by @Razican in [#2825](https://github.com/boa-dev/boa/pull/2825) - Remove panics on module compilation by @jedel1043 in [#2730](https://github.com/boa-dev/boa/pull/2730) - Update icu dependencies by @raskad in [#2574](https://github.com/boa-dev/boa/pull/2574) - Pin tarpaulin version to 0.22 by @jedel1043 in [#2562](https://github.com/boa-dev/boa/pull/2562) - Improve the design of ephemerons in our GC by @jedel1043 in [#2530](https://github.com/boa-dev/boa/pull/2530) - Pass locale data provider by ref instead of boxing by @jedel1043 in [#2508](https://github.com/boa-dev/boa/pull/2508) - Fast path for static property keys by @tunz in [#2604](https://github.com/boa-dev/boa/pull/2604) - Replace `criterion::black_box` with `std::hint::black_box` by @jedel1043 in [#2494](https://github.com/boa-dev/boa/pull/2494) - Shrink objects by using `ThinVec`s by @HalidOdat in [#2752](https://github.com/boa-dev/boa/pull/2752) - Redesign native functions and closures API by @jedel1043 in [#2499](https://github.com/boa-dev/boa/pull/2499) - Make the `wasmbind` feature of the `chrono` crate optional by @Razican in [#2810](https://github.com/boa-dev/boa/pull/2810) - Use opcode table rather than match by @tunz in [#2501](https://github.com/boa-dev/boa/pull/2501) - Align iterator loops to the spec by @jedel1043 in [#2686](https://github.com/boa-dev/boa/pull/2686) - Add AST node for parenthesized expressions by @raskad in [#2738](https://github.com/boa-dev/boa/pull/2738) - Optimize Get/SetPropertyByName by @tunz in [#2608](https://github.com/boa-dev/boa/pull/2608) - Keep Integer type for inc/dec of an integer by @tunz in [#2615](https://github.com/boa-dev/boa/pull/2615) - Implement `CompletionRecords` for the Vm by @nekevss in [#2618](https://github.com/boa-dev/boa/pull/2618) - Feature flag on builtins console import by @nekevss in [#2584](https://github.com/boa-dev/boa/pull/2584) - Fix documentation links by @Razican in [#2741](https://github.com/boa-dev/boa/pull/2741) - Updated syn to 2.0.3 by @Razican in [#2702](https://github.com/boa-dev/boa/pull/2702) - Cleanup intrinsics and move to realm by @jedel1043 in [#2555](https://github.com/boa-dev/boa/pull/2555) - Rename `check_parser` and `Identifier` by @jedel1043 in [#2576](https://github.com/boa-dev/boa/pull/2576) - Fix rust 1.67 lints by @raskad in [#2567](https://github.com/boa-dev/boa/pull/2567) - Avoid unneeded bounds checks in bytecode address patching by @HalidOdat in [#2680](https://github.com/boa-dev/boa/pull/2680) - Rust 1.68 clippy fixes by @nekevss in [#2646](https://github.com/boa-dev/boa/pull/2646) - Fix rust 1.70 lints by @raskad in [#2990](https://github.com/boa-dev/boa/pull/2990) - Simplify/Refactor exception handling and last statement value by @HalidOdat in [#3053](https://github.com/boa-dev/boa/pull/3053) ## [0.16.0 (2022-09-25)](https://github.com/boa-dev/boa/compare/v0.15...v0.16) ### Feature Enhancements - Implement getter and setter of `Object.prototype.__proto__` by @CYBAI in [#2110](https://github.com/boa-dev/boa/pull/2110) - Execution stack & promises by @Razican in [#2107](https://github.com/boa-dev/boa/pull/2107) - Add the `[[Done]]` field to iterators by @Razican in [#2125](https://github.com/boa-dev/boa/pull/2125) - Fix for in/of loop initializer environment by @raskad in [#2135](https://github.com/boa-dev/boa/pull/2135) - Implement `Promise.all` by @raskad in [#2140](https://github.com/boa-dev/boa/pull/2140) - Implement `Promise.any` by @raskad in [#2145](https://github.com/boa-dev/boa/pull/2145) - Implement `Promise.allSettled` by @raskad in [#2146](https://github.com/boa-dev/boa/pull/2146) - Implement `super` expressions by @raskad in [#2116](https://github.com/boa-dev/boa/pull/2116) - Implement `async function` and `await` by @raskad in [#2158](https://github.com/boa-dev/boa/pull/2158) - Implementation of `JsMap` Wrapper by @nekevss in [#2115](https://github.com/boa-dev/boa/pull/2115) - Safe wrapper for `JsSet` by @anuvratsingh in [#2162](https://github.com/boa-dev/boa/pull/2162) - Implement `JsArrayBuffer` by @HalidOdat in [#2170](https://github.com/boa-dev/boa/pull/2170) - Implement arrow function parsing based on `CoverParenthesizedExpressionAndArrowParameterList` by @raskad in [#2171](https://github.com/boa-dev/boa/pull/2171) - Implement Generator Function Constructor by @raskad in [#2174](https://github.com/boa-dev/boa/pull/2174) - Parse class private async generator methods by @raskad in [#2220](https://github.com/boa-dev/boa/pull/2220) - Implement Async Generators by @raskad in [#2200](https://github.com/boa-dev/boa/pull/2200) - Add field accessors to destructing assignment by @raskad in [#2213](https://github.com/boa-dev/boa/pull/2213) - Added a bit more integer operation consistency to ByteDataBlock creation by @Razican in [#2272](https://github.com/boa-dev/boa/pull/2272) - Implement Async-from-Sync Iterator Objects by @raskad in [#2234](https://github.com/boa-dev/boa/pull/2234) - Add URI encoding and decoding functions by @Razican in [#2267](https://github.com/boa-dev/boa/pull/2267) - Implement `for await...of` loops by @raskad in [#2286](https://github.com/boa-dev/boa/pull/2286) ### Bug Fixes - Fix `eval` attributes by @raskad in [#2130](https://github.com/boa-dev/boa/pull/2130) - Fix `this` in function calls by @raskad in [#2153](https://github.com/boa-dev/boa/pull/2153) - Fix remaining `Promise` bugs by @raskad in [#2156](https://github.com/boa-dev/boa/pull/2156) - Fix length/index in `32bit` architectures by @HalidOdat in [#2196](https://github.com/boa-dev/boa/pull/2196) - Fix `yield` expression to end on line terminator by @raskad in [#2232](https://github.com/boa-dev/boa/pull/2232) - Fix spread arguments in function calls by @raskad in [#2216](https://github.com/boa-dev/boa/pull/2216) - Fix `arguments` object iterator function by @raskad in [#2231](https://github.com/boa-dev/boa/pull/2231) - check history file exist if not create it by @udhaykumarbala in [#2245](https://github.com/boa-dev/boa/pull/2245) - Do not auto-insert semicolon in `VariableDeclarationList` by @tunz in [#2266](https://github.com/boa-dev/boa/pull/2266) - Fix property access of call expression by @tunz in [#2273](https://github.com/boa-dev/boa/pull/2273) - fix computed property methods can call super methods by @creampnx-x in [#2274](https://github.com/boa-dev/boa/pull/2274) - Fix regex literal `/[/]/` by @tunz in [#2277](https://github.com/boa-dev/boa/pull/2277) - Fixed assignment expression parsing by @Razican in [#2268](https://github.com/boa-dev/boa/pull/2268) - Fix labelled block statement by @creampnx-x in [#2285](https://github.com/boa-dev/boa/pull/2285) - Implement missing global object internal methods by @raskad in [#2287](https://github.com/boa-dev/boa/pull/2287) ### Internal Improvements - Fix spec links for some object operation methods by @CYBAI in [#2111](https://github.com/boa-dev/boa/pull/2111) - Only run benchmarks on PRs when a label is set by @raskad in [#2114](https://github.com/boa-dev/boa/pull/2114) - Refactor `construct` and `PromiseCapability` to preserve `JsObject` invariants by @jedel1043 in [#2136](https://github.com/boa-dev/boa/pull/2136) - Remove `string-interner` dependency and implement custom string `Interner` by @jedel1043 in [#2147](https://github.com/boa-dev/boa/pull/2147) - Fix clippy 1.62.0 lints by @raskad in [#2154](https://github.com/boa-dev/boa/pull/2154) - Store call frames in `Vec` instead of singly-linked list by @HalidOdat in [#2164](https://github.com/boa-dev/boa/pull/2164) - Dense/Packed JavaScript arrays by @HalidOdat in [#2167](https://github.com/boa-dev/boa/pull/2167) - Fix Rust 1.63 clippy lints by @raskad in [#2230](https://github.com/boa-dev/boa/pull/2230) - Removed some `unsafe_empty_trace!()` calls to improve performance by @Razican in [#2233](https://github.com/boa-dev/boa/pull/2233) - Add integer type to fast path of `to_property_key` by @tunz in [#2261](https://github.com/boa-dev/boa/pull/2261) **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.14...v0.15 ## [0.15.0 (2022-06-10)](https://github.com/boa-dev/boa/compare/v0.14...v0.15) ### Feature Enhancements - Deploy playground to custom destination dir by @jedel1043 in [#1943](https://github.com/boa-dev/boa/pull/1943) - add README for crates.io publish by @superhawk610 in [#1952](https://github.com/boa-dev/boa/pull/1952) - migrated to clap 3 by @manthanabc in [#1957](https://github.com/boa-dev/boa/pull/1957) - Implement unscopables for Array.prototype by @NorbertGarfield in [#1963](https://github.com/boa-dev/boa/pull/1963) - Retrieve feature-based results for Test262 runs by @NorbertGarfield in [#1980](https://github.com/boa-dev/boa/pull/1980) - Added better error handling for the Boa tester by @Razican in [#1984](https://github.com/boa-dev/boa/pull/1984) - Add From for JsValue by @lastmjs in [#1990](https://github.com/boa-dev/boa/pull/1990) - Implement Classes by @raskad in [#1976](https://github.com/boa-dev/boa/pull/1976) - Allow `PropertyName`s in `BindingProperty`in `ObjectBindingPattern` by @raskad in [#2022](https://github.com/boa-dev/boa/pull/2022) - Allow `Initializer` after `ArrayBindingPattern` in `FormalParameter` by @raskad in [#2002](https://github.com/boa-dev/boa/pull/2002) - Allow unicode escaped characters in identifiers that are keywords by @raskad in [#2021](https://github.com/boa-dev/boa/pull/2021) - Feature `JsTypedArray`s by @HalidOdat in [#2003](https://github.com/boa-dev/boa/pull/2003) - Allow creating object with true/false property names by @lupd in [#2028](https://github.com/boa-dev/boa/pull/2028) - Implement `get RegExp.prototype.hasIndices` by @HalidOdat in [#2031](https://github.com/boa-dev/boa/pull/2031) - Partial implementation for Intl.DateTimeFormat by @NorbertGarfield in [#2025](https://github.com/boa-dev/boa/pull/2025) - Allow `let` as variable declaration name by @raskad in [#2044](https://github.com/boa-dev/boa/pull/2044) - cargo workspaces fixes #2001 by @jasonwilliams in [#2026](https://github.com/boa-dev/boa/pull/2026) - Move redeclaration errors to parser by @raskad in [#2027](https://github.com/boa-dev/boa/pull/2027) - Feature `JsFunction` by @HalidOdat in [#2015](https://github.com/boa-dev/boa/pull/2015) - Improve `JsString` performance by @YXL76 in [#2042](https://github.com/boa-dev/boa/pull/2042) - Implement ResolveLocale helper by @NorbertGarfield in [#2036](https://github.com/boa-dev/boa/pull/2036) - Refactor `IdentifierReference` parsing by @raskad in [#2055](https://github.com/boa-dev/boa/pull/2055) - Implement the global `eval()` function by @raskad in [#2041](https://github.com/boa-dev/boa/pull/2041) - DateTimeFormat helpers by @NorbertGarfield in [#2064](https://github.com/boa-dev/boa/pull/2064) - Create `Date` standard constructor by @jedel1043 in [#2079](https://github.com/boa-dev/boa/pull/2079) - Implement `ProxyBuilder` by @jedel1043 in [#2076](https://github.com/boa-dev/boa/pull/2076) - Remove `strict` flag from `Context` by @raskad in [#2069](https://github.com/boa-dev/boa/pull/2069) - Integrate ICU4X into `Intl` module by @jedel1043 in [#2083](https://github.com/boa-dev/boa/pull/2083) - Implement `Function` constructor by @raskad in [#2090](https://github.com/boa-dev/boa/pull/2090) - Parse private generator methods in classes by @raskad in [#2092](https://github.com/boa-dev/boa/pull/2092) ### Bug Fixes - Fix link to the playground by @raskad in [#1947](https://github.com/boa-dev/boa/pull/1947) - convert inner datetime to local in `to_date_string` by @superhawk610 in [#1953](https://github.com/boa-dev/boa/pull/1953) - Fix panic on AST dump in JSON format by @kilotaras in [#1959](https://github.com/boa-dev/boa/pull/1959) - Fix panic in do while by @pdogr in [#1968](https://github.com/boa-dev/boa/pull/1968) - Support numbers with multiple leading zeroes by @lupd in [#1979](https://github.com/boa-dev/boa/pull/1979) - Fix length properties on array methods by @lupd in [#1983](https://github.com/boa-dev/boa/pull/1983) - Allow boolean/null as property identifier by dot operator assignment by @lupd in [#1985](https://github.com/boa-dev/boa/pull/1985) - fix(vm): off-by-one in code block stringification. by @tsutton in [#1999](https://github.com/boa-dev/boa/pull/1999) - Indicate bigint has constructor by @lupd in [#2008](https://github.com/boa-dev/boa/pull/2008) - Change `ArrayBuffer` `byteLength` to accessor property by @lupd in [#2010](https://github.com/boa-dev/boa/pull/2010) - Fix `ArrayBuffer.isView()` by @HalidOdat in [#2019](https://github.com/boa-dev/boa/pull/2019) - Fix casting negative number to usize in `Array.splice` by @lupd in [#2030](https://github.com/boa-dev/boa/pull/2030) - Fix `Symbol` and `BigInt` constructors by @HalidOdat in [#2032](https://github.com/boa-dev/boa/pull/2032) - Make `Array.prototype` an array object by @HalidOdat in [#2033](https://github.com/boa-dev/boa/pull/2033) - Fix early return in `for in loop` head by @raskad in [#2043](https://github.com/boa-dev/boa/pull/2043) ### Internal Improvements - docs: update README by structuring the topics by @ftonato in [#1958](https://github.com/boa-dev/boa/pull/1958) - Migrate to NPM and cleanup Playground by @jedel1043 in [#1951](https://github.com/boa-dev/boa/pull/1951) - Fix performance bottleneck in VM by @pdogr in [#1973](https://github.com/boa-dev/boa/pull/1973) - Remove `git2` and `hex` dependencies by @raskad in [#1992](https://github.com/boa-dev/boa/pull/1992) - Fix rust 1.60 clippy lints by @raskad in [#2014](https://github.com/boa-dev/boa/pull/2014) - Refactor `RegExp` constructor methods by @raskad in [#2049](https://github.com/boa-dev/boa/pull/2049) - Fixing build for changes in clippy for Rust 1.61 by @Razican in [#2082](https://github.com/boa-dev/boa/pull/2082) **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.14...v0.15 ## [0.14.0 (2022-03-15) - Virtual Machine](https://github.com/boa-dev/boa/compare/v0.13...v0.14) ### Feature Enhancements - Implement functions for vm by @HalidOdat in [#1433](https://github.com/boa-dev/boa/pull/1433) - Implement Object.getOwnPropertyNames and Object.getOwnPropertySymbols by @kevinputera in [#1606](https://github.com/boa-dev/boa/pull/1606) - Implement `Symbol.prototype.valueOf` by @hle0 in [#1618](https://github.com/boa-dev/boa/pull/1618) - Implement Array.prototype.at() by @nekevss in [#1613](https://github.com/boa-dev/boa/pull/1613) - Implement Array.from by @nrabulinski [#1831](https://github.com/boa-dev/boa/pull/1831) - Implement String.fromCharCode by @hle0 in [#1619](https://github.com/boa-dev/boa/pull/1619) - Implement `Typed Array` built-in by @Razican in [#1552](https://github.com/boa-dev/boa/pull/1552) - Implement arguments exotic objects by @jedel1043 in [#1522](https://github.com/boa-dev/boa/pull/1522) - Allow `BindingPattern`s as `CatchParameter` by @lowr in [#1628](https://github.com/boa-dev/boa/pull/1628) - Implement `Symbol.prototype[ @@toPrimitive ]` by @Nimpruda in [#1634](https://github.com/boa-dev/boa/pull/1634) - Implement Generator parsing by @raskad in [#1575](https://github.com/boa-dev/boa/pull/1575) - Implement Object.hasOwn and improve Object.prototype.hasOwnProperty by @kevinputera in [#1639](https://github.com/boa-dev/boa/pull/1639) - Hashbang lexer support by @nekevss in [#1631](https://github.com/boa-dev/boa/pull/1631) - Implement `delete` operator in the vm by @raskad in [#1649](https://github.com/boa-dev/boa/pull/1649) - Implement Object.fromEntries by @kevinputera in [#1660](https://github.com/boa-dev/boa/pull/1660) - Initial implementation for increment/decrement in VM by @abhishekc-sharma in [#1621](https://github.com/boa-dev/boa/pull/1621) - Implement `Proxy` object by @raskad in [#1664](https://github.com/boa-dev/boa/pull/1664) - Implement object literals for vm by @raskad in [#1668](https://github.com/boa-dev/boa/pull/1668) - Implement Array findLast and findLastIndex by @bsinky in [#1665](https://github.com/boa-dev/boa/pull/1665) - Implement `DataView` built-in object by @Nimpruda in [#1662](https://github.com/boa-dev/boa/pull/1662) - Clean-up contribution guidelines, dependencies, Test262, MSRV by @Razican in [#1683](https://github.com/boa-dev/boa/pull/1683) - Implement Async Generator Parsing by @nekevss in [#1669](https://github.com/boa-dev/boa/pull/1669) - Implement prototype of `Intl` built-in by @hle0 in [#1622](https://github.com/boa-dev/boa/pull/1622) - Add limited console.trace implementation by @osman-turan in [#1623](https://github.com/boa-dev/boa/pull/1623) - Allow `BindingPattern` in function parameters by @am-a-man in [#1666](https://github.com/boa-dev/boa/pull/1666) - Small test ux improvements by @orndorffgrant in [#1704](https://github.com/boa-dev/boa/pull/1704) - Implement missing vm operations by @raskad in [#1697](https://github.com/boa-dev/boa/pull/1697) - Added fallible allocation to data blocks by @Razican in [#1728](https://github.com/boa-dev/boa/pull/1728) - Document CodeBlock by @TheDoctor314 in [#1691](https://github.com/boa-dev/boa/pull/1691) - Generic `JsResult` in `context.throw_` methods by @HalidOdat in [#1734](https://github.com/boa-dev/boa/pull/1734) - Implement `String.raw( template, ...substitutions )` by @HalidOdat in [#1741](https://github.com/boa-dev/boa/pull/1741) - Updated test262 suite and dependencies by @Razican in [#1755](https://github.com/boa-dev/boa/pull/1755) - Lexer string interning by @Razican in [#1758](https://github.com/boa-dev/boa/pull/1758) - Adjust `compile` and `execute` to avoid clones by @Razican in [#1778](https://github.com/boa-dev/boa/pull/1778) - Interner support in the parser by @Razican in [#1765](https://github.com/boa-dev/boa/pull/1765) - Convert `Codeblock` variables to `Sym` by @raskad in [#1798](https://github.com/boa-dev/boa/pull/1798) - Using production builds for WebAssembly by @Razican in [#1825](https://github.com/boa-dev/boa/pull/1825) - Give the arrow function its proper name by @rumpl in [#1832](https://github.com/boa-dev/boa/pull/1832) - Unwrap removal by @Razican in [#1842](https://github.com/boa-dev/boa/pull/1842) - Feature `JsArray` by @HalidOdat in [#1746](https://github.com/boa-dev/boa/pull/1746) - Rename "Boa" to boa_engine, moved GC and profiler to their crates by @Razican in [#1844](https://github.com/boa-dev/boa/pull/1844) - Added conversions from and to serde_json's Value type by @Razican in [#1851](https://github.com/boa-dev/boa/pull/1851) - Toggleable `JsValue` internals displaying by @HalidOdat in [#1865](https://github.com/boa-dev/boa/pull/1865) - Implement generator execution by @raskad in [#1790](https://github.com/boa-dev/boa/pull/1790) - Feature arrays with empty elements by @HalidOdat in [#1870](https://github.com/boa-dev/boa/pull/1870) - Removed reference counted pointers from `JsValue` variants by @Razican in [#1866](https://github.com/boa-dev/boa/pull/1866) - Implement `Object.prototype.toLocaleString()` by @HalidOdat in [#1875](https://github.com/boa-dev/boa/pull/1875) - Implement `AggregateError` by @HalidOdat in [#1888](https://github.com/boa-dev/boa/pull/1888) - Implement destructing assignments for assignment expressions by @raskad in [#1895](https://github.com/boa-dev/boa/pull/1895) - Added boa examples by @elasmojs in [#1161](https://github.com/boa-dev/boa/pull/1161) ### Bug Fixes - Fix BigInt and Number comparison by @HalidOdat [#1887](https://github.com/boa-dev/boa/pull/1887) - Fix broken structure links in the documentation by @abhishekc-sharma in [#1612](https://github.com/boa-dev/boa/pull/1612) - Use function name from identifiers in assignment expressions by @raskad [#1908](https://github.com/boa-dev/boa/pull/1908) - Fix integer parsing by @nrabulinski in [#1614](https://github.com/boa-dev/boa/pull/1614) - Fix `Number.toExponential` and `Number.toFixed` by @nrabulinski in [#1620](https://github.com/boa-dev/boa/pull/1620) - Badge updates by @atouchet in [#1638](https://github.com/boa-dev/boa/pull/1638) - refactor: fix construct_error functions by @RageKnify in [#1703](https://github.com/boa-dev/boa/pull/1703) - Fix internal vm tests by @raskad in [#1718](https://github.com/boa-dev/boa/pull/1718) - Removed a bunch of warnings and clippy errors by @Razican in [#1754](https://github.com/boa-dev/boa/pull/1754) - Fix some broken links in the profiler documentation by @Razican in [#1762](https://github.com/boa-dev/boa/pull/1762) - Add proxy handling in `isArray` method by @raskad in [#1777](https://github.com/boa-dev/boa/pull/1777) - Copy/paste fix in Proxy error message by @icecream17 in [#1787](https://github.com/boa-dev/boa/pull/1787) - Fixed #1768 by @Razican in [#1820](https://github.com/boa-dev/boa/pull/1820) - Fix string.prototype methods and add static string methods by @jevancc in [#1123](https://github.com/boa-dev/boa/pull/1123) - Handle allocation errors by @y21 in [#1850](https://github.com/boa-dev/boa/pull/1850) - Fix wasm use outside browsers by @Razican in [#1846](https://github.com/boa-dev/boa/pull/1846) - Add assertion to check that a break label is identified at compile-time by @VTCAKAVSMoACE in [#1852](https://github.com/boa-dev/boa/pull/1852) - Correct reference error message by @aaronmunsters in [#1855](https://github.com/boa-dev/boa/pull/1855) - Fixing main branch workflows by @Razican in [#1858](https://github.com/boa-dev/boa/pull/1858) - Correct pop_on_return behaviour by @VTCAKAVSMoACE in [#1853](https://github.com/boa-dev/boa/pull/1853) - Fix equality between objects and `undefined` or `null` by @HalidOdat in [#1872](https://github.com/boa-dev/boa/pull/1872) - Removing the panic in favour of an error result by @Razican in [#1874](https://github.com/boa-dev/boa/pull/1874) - Make `Object.getOwnPropertyDescriptors` spec compliant by @HalidOdat in [#1876](https://github.com/boa-dev/boa/pull/1876) - Make `Error` and `%NativeError%` spec compliant by @HalidOdat in [#1879](https://github.com/boa-dev/boa/pull/1879) - Fix `Number.prototype.toString` when passing `undefined` as radix by @HalidOdat in [#1877](https://github.com/boa-dev/boa/pull/1877) - Cleanup vm stack on function return by @raskad in [#1880](https://github.com/boa-dev/boa/pull/1880) - `%NativeError%.[[prototype]]` should be `Error` constructor by @HalidOdat in [#1883](https://github.com/boa-dev/boa/pull/1883) - Make `StringToNumber` spec compliant by @HalidOdat in [#1881](https://github.com/boa-dev/boa/pull/1881) - Fix `PropertyKey` to `JsValue` conversion by @HalidOdat in [#1886](https://github.com/boa-dev/boa/pull/1886) - Make iterator spec complaint by @HalidOdat in [#1889](https://github.com/boa-dev/boa/pull/1889) - Implement `Number.parseInt` and `Number.parseFloat` by @HalidOdat in [#1894](https://github.com/boa-dev/boa/pull/1894) - Fix unreachable panics in compile_access by @VTCAKAVSMoACE in [#1861](https://github.com/boa-dev/boa/pull/1861) - Continue panic fixes by @VTCAKAVSMoACE in [#1896](https://github.com/boa-dev/boa/pull/1896) - Deny const declarations without initializer inside for loops by @jedel1043 in [#1903](https://github.com/boa-dev/boa/pull/1903) - Fix try/catch/finally related bugs and add tests by @jedel1043 in [#1901](https://github.com/boa-dev/boa/pull/1901) - Compile StatementList after parse passes on negative tests by @raskad in [#1906](https://github.com/boa-dev/boa/pull/1906) - Prevent breaks without loop or switch from causing panics by @VTCAKAVSMoACE in [#1860](https://github.com/boa-dev/boa/pull/1860) - Fix postfix increment and decrement return values by @raskad in [#1913](https://github.com/boa-dev/boa/pull/1913) ### Internal Improvements - Rewrite initialization of builtins to use the `BuiltIn` trait by @jedel1043 in [#1586](https://github.com/boa-dev/boa/pull/1586) - Unify object creation with `empty` and `from_proto_and_data` methods by @jedel1043 in [#1567](https://github.com/boa-dev/boa/pull/1567) - VM Tidy Up by @jasonwilliams in [#1610](https://github.com/boa-dev/boa/pull/1610) - Fix master refs to main by @jasonwilliams in [#1637](https://github.com/boa-dev/boa/pull/1637) - Refresh vm docs and fix bytecode trace output by @raskad [#1921](https://github.com/boa-dev/boa/pull/1921) - Change type of object prototypes to `Option` by @jedel1043 in [#1640](https://github.com/boa-dev/boa/pull/1640) - Refactor `Function` internal methods and implement `BoundFunction` objects by @jedel1043 in [#1583](https://github.com/boa-dev/boa/pull/1583) - change that verbosity comparison to > 2 by @praveenbakkal in [#1680](https://github.com/boa-dev/boa/pull/1680) - Respect rust 1.56 by @RageKnify in [#1681](https://github.com/boa-dev/boa/pull/1681) - Add bors to CI by @RageKnify in [#1684](https://github.com/boa-dev/boa/pull/1684) - Adding VM conformance output to PR checks by @Razican in [#1685](https://github.com/boa-dev/boa/pull/1685) - Start removing non-VM path by @jasonwilliams in [#1747](https://github.com/boa-dev/boa/pull/1747) - Using upstream benchmark action by @Razican in [#1753](https://github.com/boa-dev/boa/pull/1753) - Fix bors hanging by @RageKnify in [#1767](https://github.com/boa-dev/boa/pull/1767) - add more timers on object functions by @jasonwilliams in [#1775](https://github.com/boa-dev/boa/pull/1775) - Update the PR benchmarks action by @Razican in [#1774](https://github.com/boa-dev/boa/pull/1774) - General code clean-up and new lint addition by @Razican in [#1809](https://github.com/boa-dev/boa/pull/1809) - Reduced the size of AST nodes by @Razican in [#1821](https://github.com/boa-dev/boa/pull/1821) - Using the new formatting arguments from Rust 1.58 by @Razican in [#1834](https://github.com/boa-dev/boa/pull/1834) - Rework RegExp struct to include bitflags field by @aaronmunsters in [#1837](https://github.com/boa-dev/boa/pull/1837) - Ignore wastefull `RegExp` tests by @raskad in [#1840](https://github.com/boa-dev/boa/pull/1840) - Refactor the environment for runtime performance by @raskad in [#1829](https://github.com/boa-dev/boa/pull/1829) - Refactor mapped `Arguments` object by @raskad in [#1849](https://github.com/boa-dev/boa/pull/1849) - Fixed dependabot for submodule by @Razican in [#1856](https://github.com/boa-dev/boa/pull/1856) - Refactorings for Rust 1.59 by @RageKnify in [#1867](https://github.com/boa-dev/boa/pull/1867) - Removing internal deprecated functions by @HalidOdat in [#1854](https://github.com/boa-dev/boa/pull/1854) - Remove `toInteger` and document the `string` builtin by @jedel1043 in [#1884](https://github.com/boa-dev/boa/pull/1884) - Extract `Intrinsics` struct from `Context` and cleanup names by @jedel1043 in [#1890](https://github.com/boa-dev/boa/pull/1890) **Full Changelog**: https://github.com/boa-dev/boa/compare/v0.13...v0.14 ## [0.13.0 (2021-09-30) - Many new features and refactors](https://github.com/boa-dev/boa/compare/v0.12.0...v0.13.0) Feature Enhancements: - [FEATURE #1526](https://github.com/boa-dev/boa/pull/1526): Implement ComputedPropertyName for accessor properties in ObjectLiteral (@raskad) - [FEATURE #1365](https://github.com/boa-dev/boa/pull/1365): Implement splice method (@neeldug) - [FEATURE #1364](https://github.com/boa-dev/boa/pull/1364): Implement spread for objects (@FrancisMurillo) - [FEATURE #1525](https://github.com/boa-dev/boa/pull/1525): Implement Object.preventExtensions() and Object.isExtensible() (@HalidOdat) - [FEATURE #1508](https://github.com/boa-dev/boa/pull/1508): Implement Object.values() (@HalidOdat) - [FEATURE #1332](https://github.com/boa-dev/boa/pull/1332): Implement Array.prototype.sort (@jedel1043) - [FEATURE #1417](https://github.com/boa-dev/boa/pull/1471): Implement Object.keys and Object.entries (@skyne98) - [FEATURE #1406](https://github.com/boa-dev/boa/pull/1406): Implement destructuring assignments (@raskad) - [FEATURE #1469](https://github.com/boa-dev/boa/pull/1469): Implement String.prototype.replaceAll (@raskad) - [FEATURE #1442](https://github.com/boa-dev/boa/pull/1442): Implement closure functions (@HalidOdat) - [FEATURE #1390](https://github.com/boa-dev/boa/pull/1390): Implement RegExp named capture groups (@raskad) - [FEATURE #1424](https://github.com/boa-dev/boa/pull/1424): Implement Symbol.for and Symbol.keyFor (@HalidOdat) - [FEATURE #1375](https://github.com/boa-dev/boa/pull/1375): Implement `at` method for string (@neeldug) - [FEATURE #1369](https://github.com/boa-dev/boa/pull/1369): Implement normalize method (@neeldug) - [FEATURE #1334](https://github.com/boa-dev/boa/pull/1334): Implement Array.prototype.copyWithin (@jedel1043) - [FEATURE #1326](https://github.com/boa-dev/boa/pull/1326): Implement get RegExp[@@species] (@raskad) - [FEATURE #1314](https://github.com/boa-dev/boa/pull/1314): Implement RegExp.prototype [ @@search ] ( string ) (@raskad) - [FEATURE #1451](https://github.com/boa-dev/boa/pull/1451): Feature prelude module (@HalidOdat) - [FEATURE #1523](https://github.com/boa-dev/boa/pull/1523): Allow moving NativeObject variables into closures as external captures (@jedel1043) Bug Fixes: - [BUG #1521](https://github.com/boa-dev/boa/pull/1521): Added "js" feature for getrandom for WebAssembly builds (@Razican) - [BUG #1528](https://github.com/boa-dev/boa/pull/1528): Always return undefined from functions that do not return (@raskad) - [BUG #1518](https://github.com/boa-dev/boa/pull/1518): Moving a JsObject inside a closure caused a panic (@jedel1043) - [BUG #1502](https://github.com/boa-dev/boa/pull/1502): Adjust EnumerableOwnPropertyNames to use all String type property keys (@raskad) - [BUG #1415](https://github.com/boa-dev/boa/pull/1415): Fix panic on bigint size (@neeldug) - [BUG #1477](https://github.com/boa-dev/boa/pull/1477): Properly handle NaN in new Date() (@raskad) - [BUG #1449](https://github.com/boa-dev/boa/pull/1449): Make Array.prototype methods spec compliant (@HalidOdat) - [BUG #1353](https://github.com/boa-dev/boa/pull/1353): Make Array.prototype.concat spec compliant (@neeldug) - [BUG #1384](https://github.com/boa-dev/boa/pull/1384): bitwise not operation (spec improvements) (@neeldug) - [BUG #1374](https://github.com/boa-dev/boa/pull/1374): Match and regexp construct fixes (@neeldug) - [BUG #1366](https://github.com/boa-dev/boa/pull/1366): Use lock for map iteration (@joshwd36) - [BUG #1360](https://github.com/boa-dev/boa/pull/1360): Adjust a comment to be next to the correct module (@teymour-aldridge) - [BUG #1349](https://github.com/boa-dev/boa/pull/1349): Fixes Array.protoype.includes (@neeldug) - [BUG #1348](https://github.com/boa-dev/boa/pull/1348): Fixes unshift maximum size (@neeldug) - [BUG #1339](https://github.com/boa-dev/boa/pull/1339): Scripts should not be considered in a block (@macmv) - [BUG #1312](https://github.com/boa-dev/boa/pull/1312): Fix display for nodes (@macmv) - [BUG #1347](https://github.com/boa-dev/boa/pull/1347): Fix stringpad abstract operation (@neeldug) - [BUG #1584](https://github.com/boa-dev/boa/pull/1584): Refactor the Math builtin object (spec compliant) (@jedel1043) - [BUG #1535](https://github.com/boa-dev/boa/pull/1535): Refactor JSON.parse (@raskad) - [BUG #1572](https://github.com/boa-dev/boa/pull/1572): Refactor builtin Map intrinsics to follow more closely the spec (@jedel1043) - [BUG #1445](https://github.com/boa-dev/boa/pull/1445): improve map conformance without losing perf (@neeldug) - [BUG #1488](https://github.com/boa-dev/boa/pull/1488): Date refactor (@raskad) - [BUG #1463](https://github.com/boa-dev/boa/pull/1463): Return function execution result from constructor if the function returned (@raskad) - [BUG #1434](https://github.com/boa-dev/boa/pull/1434): Refactor regexp costructor (@raskad) - [BUG #1350](https://github.com/boa-dev/boa/pull/1350): Refactor / Implement RegExp functions (@RageKnify) (@raskad) - [BUG #1331](https://github.com/boa-dev/boa/pull/1331): Implement missing species getters (@raskad) Internal Improvements: - [INTERNAL #1569](https://github.com/boa-dev/boa/pull/1569): Refactor EnvironmentRecordTrait functions (@raskad) - [INTERNAL #1464](https://github.com/boa-dev/boa/pull/1464): Optimize integer negation (@HalidOdat) - [INTERNAL #1550](https://github.com/boa-dev/boa/pull/1550): Add strict mode flag to Context (@raskad) - [INTERNAL #1561](https://github.com/boa-dev/boa/pull/1561): Implement abstract operation GetPrototypeFromConstructor (@jedel1043) - [INTERNAL #1309](https://github.com/boa-dev/boa/pull/1309): Implement Display for function objects(@kvnvelasco) - [INTERNAL #1492](https://github.com/boa-dev/boa/pull/1492): Implement new get_or_undefined method for `JsValue` (@jedel1043) - [INTERNAL #1553](https://github.com/boa-dev/boa/pull/1553): Fix benchmark action in CI (@jedel1043) - [INTERNAL #1547](https://github.com/boa-dev/boa/pull/1547): Replace FxHashMap with IndexMap in object properties (@raskad) - [INTERNAL #1435](https://github.com/boa-dev/boa/pull/1435): Constant JsStrings (@HalidOdat) - [INTERNAL #1499](https://github.com/boa-dev/boa/pull/1499): Updated the Test262 submodule (@Razican) - [INTERNAL #1458](https://github.com/boa-dev/boa/pull/1458): Refactor the JS testing system (@bartlomieju) - [INTERNAL #1485](https://github.com/boa-dev/boa/pull/1485): Implement abstract operation CreateArrayFromList (@jedel1043) - [INTERNAL #1465](https://github.com/boa-dev/boa/pull/1465): Feature throw Error object (@HalidOdat) - [INTERNAL #1493](https://github.com/boa-dev/boa/pull/1493): Rename boa::Result to JsResult (@bartlomieju) - [INTERNAL #1457](https://github.com/boa-dev/boa/pull/1457): Rename Value to JsValue (@HalidOdat) - [INTERNAL #1460](https://github.com/boa-dev/boa/pull/1460): Change StringGetOwnProperty to produce the same strings that the lexer produces (@raskad) - [INTERNAL #1425](https://github.com/boa-dev/boa/pull/1425): Extract PropertyMap struct from Object (@jedel1043) - [INTERNAL #1432](https://github.com/boa-dev/boa/pull/1432): Proposal of new PropertyDescriptor design (@jedel1043) - [INTERNAL #1383](https://github.com/boa-dev/boa/pull/1383): clippy lints and cleanup of old todos (@neeldug) - [INTERNAL #1346](https://github.com/boa-dev/boa/pull/1346): Implement gh-page workflow on release (@FrancisMurillo) - [INTERNAL #1422](https://github.com/boa-dev/boa/pull/1422): Refactor internal methods and make some builtins spec compliant (@HalidOdat) - [INTERNAL #1419](https://github.com/boa-dev/boa/pull/1419): Fix DataDescriptor Value to possibly be empty (@raskad) - [INTERNAL #1357](https://github.com/boa-dev/boa/pull/1357): Add Example to Execute a Function of a Script File (@schrieveslaach) - [INTERNAL #1408](https://github.com/boa-dev/boa/pull/1408): Refactor JavaScript bigint rust type (@HalidOdat) - [INTERNAL #1380](https://github.com/boa-dev/boa/pull/1380): Custom JavaScript string rust type (@HalidOdat) - [INTERNAL #1382](https://github.com/boa-dev/boa/pull/1382): Refactor JavaScript symbol rust type (@HalidOdat) - [INTERNAL #1361](https://github.com/boa-dev/boa/pull/1361): Redesign bytecode virtual machine (@HalidOdat) - [INTERNAL #1381](https://github.com/boa-dev/boa/pull/1381): Fixed documentation warnings (@Razican) - [INTERNAL #1352](https://github.com/boa-dev/boa/pull/1352): Respect Rust 1.53 (@RageKnify) - [INTERNAL #1356](https://github.com/boa-dev/boa/pull/1356): Respect Rust fmt updates (@RageKnify) - [INTERNAL #1338](https://github.com/boa-dev/boa/pull/1338): Fix cargo check errors (@neeldug) - [INTERNAL #1329](https://github.com/boa-dev/boa/pull/1329): Allow Value.set_field to throw (@raskad) - [INTERNAL #1333](https://github.com/boa-dev/boa/pull/1333): adds condition to avoid triggers from dependabot (@neeldug) - [INTERNAL #1337](https://github.com/boa-dev/boa/pull/1337): Fix github actions (@neeldug) ## [0.12.0 (2021-06-07) - `Set`, accessors, `@@toStringTag` and no more panics](https://github.com/boa-dev/boa/compare/v0.11.0...v0.12.0) Feature Enhancements: - [FEATURE #1085](https://github.com/boa-dev/boa/pull/1085): Add primitive promotion for method calls on `GetField` (@RageKnify) - [FEATURE #1033](https://github.com/boa-dev/boa/pull/1033): Implement `Reflect` built-in object (@tofpie) - [FEATURE #1151](https://github.com/boa-dev/boa/pull/1151): Fully implement `EmptyStatement` (@SamuelQZQ) - [FEATURE #1158](https://github.com/boa-dev/boa/pull/1158): Include name in verbose results output of `boa-tester` (@0x7D2B) - [FEATURE #1225](https://github.com/boa-dev/boa/pull/1225): Implement `Math[ @@toStringTag ]` (@HalidOdat) - [FEATURE #1224](https://github.com/boa-dev/boa/pull/1224): Implement `JSON[ @@toStringTag ]` (@HalidOdat) - [FEATURE #1222](https://github.com/boa-dev/boa/pull/1222): Implement `Symbol.prototype.description` accessor (@HalidOdat) - [FEATURE #1221](https://github.com/boa-dev/boa/pull/1221): Implement `RegExp` flag accessors (@HalidOdat) - [FEATURE #1240](https://github.com/boa-dev/boa/pull/1240): Stop ignoring a bunch of tests (@Razican) - [FEATURE #1132](https://github.com/boa-dev/boa/pull/1132): Implement `Array.prototype.flat`/`flatMap` (@davimiku) - [FEATURE #1235](https://github.com/boa-dev/boa/pull/1235): Implement `Object.assign( target, ...sources )` (@HalidOdat) - [FEATURE #1243](https://github.com/boa-dev/boa/pull/1243): Cross realm symbols (@HalidOdat) - [FEATURE #1249](https://github.com/boa-dev/boa/pull/1249): Implement `Map.prototype[ @@toStringTag ]` (@wylie39) - [FEATURE #1111](https://github.com/boa-dev/boa/pull/1111): Implement `Set` builtin object (@RageKnify) - [FEATURE #1265](https://github.com/boa-dev/boa/pull/1265): Implement `BigInt.prototype[ @@toStringTag ]` (@n14littl) - [FEATURE #1102](https://github.com/boa-dev/boa/pull/1102): Support Unicode escape in identifier names (@jevancc) - [FEATURE #1273](https://github.com/boa-dev/boa/pull/1273): Add default parameter support (@0x7D2B) - [FEATURE #1292](https://github.com/boa-dev/boa/pull/1292): Implement `symbol.prototype[ @@ToStringTag ]` (@moadmmh) - [FEATURE #1291](https://github.com/boa-dev/boa/pull/1291): Support `GetOwnProperty` for `string` exotic object (@jarkonik) - [FEATURE #1296](https://github.com/boa-dev/boa/pull/1296): Added the `$262` object to the Test262 test runner (@Razican) - [FEATURE #1127](https://github.com/boa-dev/boa/pull/1127): Implement `Array.of` (@camc) Bug Fixes: - [BUG #1071](https://github.com/boa-dev/boa/pull/1071): Fix attribute configurable of the length property of arguments (@tofpie) - [BUG #1073](https://github.com/boa-dev/boa/pull/1073): Fixed spelling (@vishalsodani) - [BUG #1072](https://github.com/boa-dev/boa/pull/1072): Fix `get`/`set` as short method name in `object` (@tofpie) - [BUG #1077](https://github.com/boa-dev/boa/pull/1077): Fix panics from multiple borrows of `Map` (@joshwd36) - [BUG #1079](https://github.com/boa-dev/boa/pull/1079): Fix lexing escapes in string literal (@jevancc) - [BUG #1075](https://github.com/boa-dev/boa/pull/1075): Fix out-of-range panics of `Date` (@jevancc) - [BUG #1084](https://github.com/boa-dev/boa/pull/1084): Fix line terminator in string literal (@jevancc) - [BUG #1110](https://github.com/boa-dev/boa/pull/1110): Fix parsing floats panics and bugs (@jevancc) - [BUG #1202](https://github.com/boa-dev/boa/pull/1202): Fix a typo in `gc.rs` (@teymour-aldridge) - [BUG #1201](https://github.com/boa-dev/boa/pull/1201): Return optional value in `to_json` functions (@fermian) - [BUG #1223](https://github.com/boa-dev/boa/pull/1223): Update cli name in Readme (@sphinxc0re) - [BUG #1175](https://github.com/boa-dev/boa/pull/1175): Handle early errors for declarations in `StatementList` (@0x7D2B) - [BUG #1270](https://github.com/boa-dev/boa/pull/1270): Fix `Context::register_global_function()` (@HalidOdat) - [BUG #1135](https://github.com/boa-dev/boa/pull/1135): Fix of instructions.rs comment, to_precision impl and rfc changes (@NathanRoyer) - [BUG #1272](https://github.com/boa-dev/boa/pull/1272): Fix `Array.prototype.filter` (@tofpie & @Razican) - [BUG #1280](https://github.com/boa-dev/boa/pull/1280): Fix slice index panic in `add_rest_param` (@0x7D2B) - [BUG #1284](https://github.com/boa-dev/boa/pull/1284): Fix `GcObject` `to_json` mutable borrow panic (@0x7D2B) - [BUG #1283](https://github.com/boa-dev/boa/pull/1283): Fix panic in regex execution (@0x7D2B) - [BUG #1286](https://github.com/boa-dev/boa/pull/1286): Fix construct usage (@0x7D2B) - [BUG #1288](https://github.com/boa-dev/boa/pull/1288): Fixed `Math.hypot.length` bug (@moadmmh) - [BUG #1285](https://github.com/boa-dev/boa/pull/1285): Fix environment record panics (@0x7D2B) - [BUG #1302](https://github.com/boa-dev/boa/pull/1302): Fix VM branch (@jasonwilliams) Internal Improvements: - [INTERNAL #1067](https://github.com/boa-dev/boa/pull/1067): Change `Realm::global_object` field from `Value` to `GcObject` (@RageKnify) - [INTERNAL #1048](https://github.com/boa-dev/boa/pull/1048): VM Trace output fixes (@jasonwilliams) - [INTERNAL #1109](https://github.com/boa-dev/boa/pull/1109): Define all property methods of constructors (@RageKnify) - [INTERNAL #1126](https://github.com/boa-dev/boa/pull/1126): Remove unnecessary wraps for non built-in functions (@RageKnify) - [INTERNAL #1044](https://github.com/boa-dev/boa/pull/1044): Removed duplicated code in `vm.run` using macros (@stephanemagnenat) - [INTERNAL #1103](https://github.com/boa-dev/boa/pull/1103): Lazy evaluation for cooked template string (@jevancc) - [INTERNAL #1156](https://github.com/boa-dev/boa/pull/1156): Rework environment records (@0x7D2B) - [INTERNAL #1181](https://github.com/boa-dev/boa/pull/1181): Merge `Const`/`Let`/`Var` `DeclList` into `DeclarationList` (@0x7D2B) - [INTERNAL #1234](https://github.com/boa-dev/boa/pull/1234): Separate `Symbol` builtin (@HalidOdat) - [INTERNAL #1131](https://github.com/boa-dev/boa/pull/1131): Make environment methods take `&mut Context` (@HalidOdat) - [INTERNAL #1271](https://github.com/boa-dev/boa/pull/1271): Make `same_value` and `same_value_zero` static methods (@HalidOdat) - [INTERNAL #1276](https://github.com/boa-dev/boa/pull/1276): Cleanup (@Razican) - [INTERNAL #1279](https://github.com/boa-dev/boa/pull/1279): Add test comparison to Test262 result compare (@Razican) - [INTERNAL #1293](https://github.com/boa-dev/boa/pull/1293): Fix test262 comment formatting (@0x7D2B) - [INTERNAL #1294](https://github.com/boa-dev/boa/pull/1294): Don't consider panic fixes as "new failures" (@Razican) ## [0.11.0 (2021-01-14) - Faster Parsing & Better compliance](https://github.com/boa-dev/boa/compare/v0.10.0...v0.11.0) Feature Enhancements: - [FEATURE #836](https://github.com/boa-dev/boa/pull/836): Async/Await parse (@Lan2u) - [FEATURE #704](https://github.com/boa-dev/boa/pull/704): Implement for...of loops (@joshwd36) - [FEATURE #770](https://github.com/boa-dev/boa/pull/770): Support for symbols as property keys for `Object.defineProperty` (@georgeroman) - [FEATURE #717](https://github.com/boa-dev/boa/pull/717): Strict Mode Lex/Parse (@Lan2u) - [FEATURE #800](https://github.com/boa-dev/boa/pull/800): Implement `console` crate feature - Put `console` object behind a feature flag (@HalidOdat) - [FEATURE #804](https://github.com/boa-dev/boa/pull/804): Implement `EvalError` (@HalidOdat) - [FEATURE #805](https://github.com/boa-dev/boa/pull/805): Implement `Function.prototype.call` (@RageKnify) - [FEATURE #806](https://github.com/boa-dev/boa/pull/806): Implement `URIError` (@HalidOdat) - [FEATURE #811](https://github.com/boa-dev/boa/pull/811): Implement spread operator using iterator (@croraf) - [FEATURE #844](https://github.com/boa-dev/boa/pull/844): Allow UnaryExpression with prefix increment/decrement (@croraf) - [FEATURE #798](https://github.com/boa-dev/boa/pull/798): Implement Object.getOwnPropertyDescriptor() and Object.getOwnPropertyDescriptors() (@JohnDoneth) - [FEATURE #847](https://github.com/boa-dev/boa/pull/847): Implement Map.prototype.entries() (@croraf) - [FEATURE #859](https://github.com/boa-dev/boa/pull/859): Implement spec compliant Array constructor (@georgeroman) - [FEATURE #874](https://github.com/boa-dev/boa/pull/874): Implement Map.prototype.values and Map.prototype.keys (@croraf) - [FEATURE #877](https://github.com/boa-dev/boa/pull/877): Implement Function.prototype.apply (@georgeroman) - [FEATURE #908](https://github.com/boa-dev/boa/pull/908): Implementation of `instanceof` operator (@morrien) - [FEATURE #935](https://github.com/boa-dev/boa/pull/935): Implement String.prototype.codePointAt (@devinus) - [FEATURE #961](https://github.com/boa-dev/boa/pull/961): Implement the optional `space` parameter in `JSON.stringify` (@tofpie) - [FEATURE #962](https://github.com/boa-dev/boa/pull/962): Implement Number.prototype.toPrecision (@NathanRoyer) - [FEATURE #983](https://github.com/boa-dev/boa/pull/983): Implement Object.prototype.isPrototypeOf (@tofpie) - [FEATURE #995](https://github.com/boa-dev/boa/pull/995): Support Numeric separators (@tofpie) - [FEATURE #1013](https://github.com/boa-dev/boa/pull/1013): Implement nullish coalescing (?? and ??=) (@tofpie) - [FEATURE #987](https://github.com/boa-dev/boa/pull/987): Implement property accessors (@tofpie) - [FEATURE #1018](https://github.com/boa-dev/boa/pull/1018): Implement logical assignment operators (&&= and ||=) (@tofpie) - [FEATURE #1019](https://github.com/boa-dev/boa/pull/1019): Implement early errors for non-assignable nodes in assignment (@tofpie) - [FEATURE #1020](https://github.com/boa-dev/boa/pull/1020): Implement Symbol.toPrimitive (@tofpie) - [FEATURE #976](https://github.com/boa-dev/boa/pull/976): Implement for..in (@tofpie) - [FEATURE #1026](https://github.com/boa-dev/boa/pull/1026): Implement String.prototype.split (@jevancc) - [FEATURE #1047](https://github.com/boa-dev/boa/pull/1047): Added syntax highlighting for numbers, identifiers and template literals (@Razican) - [FEATURE #1003](https://github.com/boa-dev/boa/pull/1003): Improve Unicode support for identifier names (@jevancc) Bug Fixes: - [BUG #782](https://github.com/boa-dev/boa/pull/782): Throw TypeError if regexp is passed to startsWith, endsWith, includes (@pt2121) - [BUG #788](https://github.com/boa-dev/boa/pull/788): Fixing a duplicated attribute in test262 results (@Razican) - [BUG #790](https://github.com/boa-dev/boa/pull/790): Throw RangeError when BigInt division by zero occurs (@JohnDoneth) - [BUG #785](https://github.com/boa-dev/boa/pull/785): Fix zero argument panic in JSON.parse() (@JohnDoneth) - [BUG #749](https://github.com/boa-dev/boa/pull/749): Fix Error constructors to return rather than throw (@RageKnify) - [BUG #777](https://github.com/boa-dev/boa/pull/777): Fix cyclic JSON.stringify / primitive conversion stack overflows (@vgel) - [BUG #799](https://github.com/boa-dev/boa/pull/799): Fix lexer span panic with carriage return (@vgel) - [BUG #812](https://github.com/boa-dev/boa/pull/812): Fix 2 bugs that caused Test262 to fail (@RageKnify) - [BUG #826](https://github.com/boa-dev/boa/pull/826): Fix tokenizing Unicode escape sequence in string literal (@HalidOdat) - [BUG #825](https://github.com/boa-dev/boa/pull/825): calling "new" on a primitive value throw a type error (@dlemel8) - [BUG #853](https://github.com/boa-dev/boa/pull/853) Handle invalid Unicode code point in the string literals (@jevancc) - [BUG #870](https://github.com/boa-dev/boa/pull/870) Fix JSON stringification for fractional numbers (@georgeroman) - [BUG #807](https://github.com/boa-dev/boa/pull/807): Make boa::parse emit error on invalid input, not panic (@georgeroman) - [BUG #880](https://github.com/boa-dev/boa/pull/880): Support more number literals in BigInt's from string constructor (@georgeroman) - [BUG #885](https://github.com/boa-dev/boa/pull/885): Fix `BigInt.prototype.toString()` radix checks (@georgeroman) - [BUG #882](https://github.com/boa-dev/boa/pull/882): Fix (panic) remainder by zero (@georgeroman) - [BUG #884](https://github.com/boa-dev/boa/pull/884): Fix some panics related to BigInt operations (@georgeroman) - [BUG #888](https://github.com/boa-dev/boa/pull/888): Fix some panics in String.prototype properties (@georgeroman) - [BUG #902](https://github.com/boa-dev/boa/pull/902): Fix Accessors panics (@HalidOdat) - [BUG #959](https://github.com/boa-dev/boa/pull/959): Fix Unicode character escape sequence parsing (@tofpie) - [BUG #964](https://github.com/boa-dev/boa/pull/964): Fix single line comment lexing with CRLF line ending (@tofpie) - [BUG #919](https://github.com/boa-dev/boa/pull/919): Reduce the number of `Array`-related panics (@jakubfijalkowski) - [BUG #968](https://github.com/boa-dev/boa/pull/968): Fix unit tests that can be failed due to daylight saving time (@tofpie) - [BUG #972](https://github.com/boa-dev/boa/pull/972): Fix enumerable attribute on array length property (@tofpie) - [BUG #974](https://github.com/boa-dev/boa/pull/974): Fix enumerable attribute on string length property (@tofpie) - [BUG #981](https://github.com/boa-dev/boa/pull/981): Fix prototypes for Number, String and Boolean (@tofpie) - [BUG #999](https://github.com/boa-dev/boa/pull/999): Fix logical expressions evaluation (@tofpie) - [BUG #1001](https://github.com/boa-dev/boa/pull/1001): Fix comparison with infinity (@tofpie) - [BUG #1004](https://github.com/boa-dev/boa/pull/1004): Fix panics surrounding `Object.prototype.hasOwnProperty()` (@HalidOdat) - [BUG #1005](https://github.com/boa-dev/boa/pull/1005): Fix panics surrounding `Object.defineProperty()` (@HalidOdat) - [BUG #1021](https://github.com/boa-dev/boa/pull/1021): Fix spread in new and call expressions (@tofpie) - [BUG #1023](https://github.com/boa-dev/boa/pull/1023): Fix attributes on properties of functions and constructors (@tofpie) - [BUG #1017](https://github.com/boa-dev/boa/pull/1017): Don't panic when function parameters share names (@AnnikaCodes) - [BUG #1024](https://github.com/boa-dev/boa/pull/1024): Fix delete when the property is not configurable (@tofpie) - [BUG #1027](https://github.com/boa-dev/boa/pull/1027): Supress regress errors on invalid escapes for regex (@jasonwilliams - [BUG #1031](https://github.com/boa-dev/boa/pull/1031): Fixed some extra regex panics (@Razican) - [BUG #1049](https://github.com/boa-dev/boa/pull/1049): Support overriding the `arguments` variable (@AnnikaCodes) - [BUG #1050](https://github.com/boa-dev/boa/pull/1050): Remove panic on named capture groups (@Razican) - [BUG #1046](https://github.com/boa-dev/boa/pull/1046): Remove a few different panics (@Razican) - [BUG #1051](https://github.com/boa-dev/boa/pull/1051): Fix parsing of arrow functions with 1 argument (@Lan2u) - [BUG #1045](https://github.com/boa-dev/boa/pull/1045): Add newTarget to construct (@tofpie) - [BUG #659](https://github.com/boa-dev/boa/pull/659): Error handling in environment (@54k1) Internal Improvements: - [INTERNAL #735](https://github.com/boa-dev/boa/pull/735): Move exec implementations together with AST node structs (@georgeroman) - [INTERNAL #724](https://github.com/boa-dev/boa/pull/724): Ignore tests for code coverage count (@HalidOdat) - [INTERNAL #768](https://github.com/boa-dev/boa/pull/768) Update the benchmark Github action (@Razican) - [INTERNAL #722](https://github.com/boa-dev/boa/pull/722): `ConstructorBuilder`, `ObjectInitializer`, cache standard objects and fix global object attributes (@HalidOdat) - [INTERNAL #783](https://github.com/boa-dev/boa/pull/783): New test262 results format (This also reduces the payload size for the website) (@Razican) - [INTERNAL #787](https://github.com/boa-dev/boa/pull/787): Refactor ast/node/expression into ast/node/call and ast/node/new (@croraf) - [INTERNAL #802](https://github.com/boa-dev/boa/pull/802): Make `Function.prototype` a function (@HalidOdat) - [INTERNAL #746](https://github.com/boa-dev/boa/pull/746): Add Object.defineProperties and handle props argument in Object.create (@dvtkrlbs) - [INTERNAL #774](https://github.com/boa-dev/boa/pull/774): Switch from `regex` to `regress` for ECMA spec-compliant regex implementation (@neeldug) - [INTERNAL #794](https://github.com/boa-dev/boa/pull/794): Refactor `PropertyDescriptor` (Improved performance) (@HalidOdat) - [INTERNAL #824](https://github.com/boa-dev/boa/pull/824): [parser Expression] minor expression macro simplification (@croraf) - [INTERNAL #833](https://github.com/boa-dev/boa/pull/833): Using unstable sort for sorting keys on `to_json()` for GC objects (@Razican) - [INTERNAL #837](https://github.com/boa-dev/boa/pull/837): Set default-run to `boa` removing need for `--bin` (@RageKnify) - [INTERNAL #841](https://github.com/boa-dev/boa/pull/841): Minor refactor and rename in eval() method (@croraf) - [INTERNAL #840](https://github.com/boa-dev/boa/pull/840): fix(profiler): update profiler to match current measureme api (@neeldug) - [INTERNAL #838](https://github.com/boa-dev/boa/pull/838): style(boa): minor cleanup (@neeldug) - [INTERNAL #869](https://github.com/boa-dev/boa/pull/869): Updated cache in workflows (@Razican) - [INTERNAL #873](https://github.com/boa-dev/boa/pull/873) Removed cache from MacOS builds (@Razican) - [INTERNAL #835](https://github.com/boa-dev/boa/pull/835): Move `Object` internal object methods to `GcObject` (@HalidOdat) - [INTERNAL #886](https://github.com/boa-dev/boa/pull/886): Support running a specific test/suite in boa_tester (@georgeroman) - [INTERNAL #901](https://github.com/boa-dev/boa/pull/901): Added "unimplemented" syntax errors (@Razican) - [INTERNAL #911](https://github.com/boa-dev/boa/pull/911): Change Symbol hash to `u64` (@HalidOdat) - [INTERNAL #912](https://github.com/boa-dev/boa/pull/912): Feature `Context::register_global_property()` (@HalidOdat) - [INTERNAL #913](https://github.com/boa-dev/boa/pull/913): Added check to ignore semicolon in parser (@AngelOnFira) - [INTERNAL #915](https://github.com/boa-dev/boa/pull/915): Improve lexer by make cursor iterate over bytes (@jevancc) - [INTERNAL #952](https://github.com/boa-dev/boa/pull/952): Upgraded rustyline and test262 (@Razican) - [INTERNAL #960](https://github.com/boa-dev/boa/pull/960): Fix unresolved links in documentation (@tofpie) - [INTERNAL #979](https://github.com/boa-dev/boa/pull/979): Read file input in bytes instead of string (@tofpie) - [INTERNAL #1014](https://github.com/boa-dev/boa/pull/1014): StatementList: Rename `statements` to `items` (@AnnikaCodes) - [INTERNAL #860](https://github.com/boa-dev/boa/pull/860): Investigation into ByteCode Interpreter (@jasonwilliams) - [INTERNAL #1042](https://github.com/boa-dev/boa/pull/1042): Add receiver parameter to object internal methods (@tofpie) - [INTERNAL #1030](https://github.com/boa-dev/boa/pull/1030): VM: Implement variable declaration (var, const, and let) (@AnnikaCodes) - [INTERNAL #1010](https://github.com/boa-dev/boa/pull/1010): Modify environment binding behaviour of function (@54k1) ## [0.10.0 (2020-09-29) - New Lexer & Test 262 Harness](https://github.com/boa-dev/boa/compare/v0.9.0...v0.10.0) Feature Enhancements: - [FEATURE #524](https://github.com/boa-dev/boa/pull/525): Implement remaining `Math` methods (@mr-rodgers) - [FEATURE #562](https://github.com/boa-dev/boa/pull/562): Implement remaining `Number` methods (@joshwd36) - [FEATURE #536](https://github.com/boa-dev/boa/pull/536): Implement `SyntaxError` (@HalidOdat) - [FEATURE #543](https://github.com/boa-dev/boa/pull/543): Implements `Object.create` builtin method (@croraf) - [FEATURE #492](https://github.com/boa-dev/boa/pull/492): Switch to [rustyline](https://github.com/kkawakam/rustyline) for the CLI (@IovoslavIovchev & @Razican) - [FEATURE #595](https://github.com/boa-dev/boa/pull/595): Added syntax highlighting for strings in REPL (@HalidOdat) - [FEATURE #586](https://github.com/boa-dev/boa/pull/586): Better error formatting and cli color (@HalidOdat) - [FEATURE #590](https://github.com/boa-dev/boa/pull/590): Added keyword and operator colors and matching bracket validator to REPL (@HalidOdat) - [FEATURE #555](https://github.com/boa-dev/boa/pull/555): Implement Array.prototype.reduce (@benjaminflin) - [FEATURE #550](https://github.com/boa-dev/boa/pull/550): Initial implementation of Map() (@joshwd36 & @HalidOdat) - [FEATURE #579](https://github.com/boa-dev/boa/pull/579): Implement Array.prototype.reduceRight (@benjaminflin) - [FEATURE #585](https://github.com/boa-dev/boa/pull/587): Implement Well-Known Symbols (@joshwd36) - [FEATURE #589](https://github.com/boa-dev/boa/pull/589): Implement the comma operator (@KashParty) - [FEATURE #341](https://github.com/boa-dev/boa/pull/590): Ability to create multiline blocks in boa shell (@HalidOdat) - [FEATURE #252](https://github.com/boa-dev/boa/pull/596): Implement `Date` (@jcdickinson) - [FEATURE #711](https://github.com/boa-dev/boa/pull/711): Add support for >>>= (@arpit-saxena) - [FEATURE #549](https://github.com/boa-dev/boa/pull/549): Implement label statements (@jasonwilliams) - [FEATURE #373](https://github.com/boa-dev/boa/pull/373): Introduce PropertyKey for field acces (@RageKnify) - [FEATURE #627](https://github.com/boa-dev/boa/pull/627): Feature native class objects (`NativeObject` and `Class` traits) (@HalidOdat) - [FEATURE #694](https://github.com/boa-dev/boa/pull/694): Feature `gc` module (@HalidOdat) - [FEATURE #656](https://github.com/boa-dev/boa/pull/656): Feature `Context` (@HalidOdat) - [FEATURE #673](https://github.com/boa-dev/boa/pull/673): Add `#[track_caller]` to `GcObject` methods that can panic (@HalidOdat) - [FEATURE #661](https://github.com/boa-dev/boa/pull/661): Add documentation to `GcObject` methods (@HalidOdat) - [FEATURE #662](https://github.com/boa-dev/boa/pull/662): Implement `std::error::Error` for `GcObject` borrow errors (@HalidOdat) - [FEATURE #660](https://github.com/boa-dev/boa/pull/660): Make `GcObject::contruct` not take 'this' (@HalidOdat) - [FEATURE #654](https://github.com/boa-dev/boa/pull/654): Move `require_object_coercible` to `Value` (@HalidOdat) - [FEATURE #603](https://github.com/boa-dev/boa/pull/603): Index `PropertyKey`, `Object` iterators and symbol support (@HalidOdat) - [FEATURE #637](https://github.com/boa-dev/boa/pull/637): Feature `boa::Result` (@HalidOdat) - [FEATURE #625](https://github.com/boa-dev/boa/pull/625): Moved value operations from `Interpreter` to `Value` (@HalidOdat) - [FEATURE #638](https://github.com/boa-dev/boa/pull/638): Changed to `Value::to_*int32` => `Value::to_*32` (@HalidOdat) Bug Fixes: - [BUG #405](https://github.com/boa-dev/boa/issues/405): Fix json.stringify symbol handling (@n14little) - [BUG #520](https://github.com/boa-dev/boa/pull/520): Fix all `Value` operations and add unsigned shift right (@HalidOdat) - [BUG #529](https://github.com/boa-dev/boa/pull/529): Refactor exec/expression into exec/call and exec/new (@croraf) - [BUG #510](https://github.com/boa-dev/boa/issues/510): [[Call]] calling an undefined method does not throw (@joshwd36) - [BUG #493](https://github.com/boa-dev/boa/pull/493): Use correct exponential representation for rational values (@Tropid) - [BUG #572](https://github.com/boa-dev/boa/pull/572): Spec Compliant `Number.prototype.toString()`, better `Number` object formating and `-0` (@HalidOdat) - [BUG #599](https://github.com/boa-dev/boa/pull/599): Fixed `String.prototype.indexOf()` bug, when the search string is empty (@HalidOdat) - [BUG #615](https://github.com/boa-dev/boa/issues/615): Fix abstract relational comparison operators (@HalidOdat) - [BUG #608](https://github.com/boa-dev/boa/issues/608): `Debug::fmt` Causes Causes a Stack Overflow (@jcdickinson) - [BUG #532](https://github.com/boa-dev/boa/issues/532) [builtins - Object] Object.getPrototypeOf returning incorrectly (@54k1) - [BUG #533](https://github.com/boa-dev/boa/issues/533) [exec - function] function.prototype doesn't have own constructor property pointing to this function (@54k1) - [BUG #641](https://github.com/boa-dev/boa/issues/641) Test new_instance_should_point_to_prototype is not checked correctly (@54k1) - [BUG #644](https://github.com/boa-dev/boa/pull/645) `undefined` constants panic on execution (@jcdickinson) - [BUG #631](https://github.com/boa-dev/boa/pull/645): Unexpected result when applying typeof to undefined value (@jcdickinson) - [BUG #667](https://github.com/boa-dev/boa/pull/667): Fix panic when calling function that mutates itself (@dvtkrlbs) - [BUG #668](https://github.com/boa-dev/boa/pull/668): Fix clippy on Nightly (@dvtkrlbs) - [BUG #582](https://github.com/boa-dev/boa/pull/582): Make `String.prototype.repeat()` ECMAScript specification compliant (@HalidOdat) - [BUG #541](https://github.com/boa-dev/boa/pull/541): Made all `Math` methods spec compliant (@HalidOdat) - [BUG #597](https://github.com/boa-dev/boa/pull/597): Made `String.prototype.indexOf` spec compliant. (@HalidOdat) - [BUG #598](https://github.com/boa-dev/boa/pull/598): Made `String.prototype.lastIndexOf()` spec compliant (@HalidOdat) - [BUG #583](https://github.com/boa-dev/boa/pull/583): Fix string prototype `trim` methods (@HalidOdat) - [BUG #728](https://github.com/boa-dev/boa/pull/728): Fix bug when setting the length on String objects (@jasonwilliams) - [BUG #710](https://github.com/boa-dev/boa/pull/710): Fix panic when a self mutating function is constructing an object (@HalidOdat) - [BUG #699](https://github.com/boa-dev/boa/pull/699): Fix `Value::to_json` order of items in array (@sele9) - [BUG #610](https://github.com/boa-dev/boa/pull/610): Fix: `String.prototype.replace` substitutions (@RageKnify) - [BUG #645](https://github.com/boa-dev/boa/pull/645): Fix undefined constant expression evaluation (@jcdickinson) - [BUG #643](https://github.com/boa-dev/boa/pull/643): Change default return type from null to undefined (@54k1) - [BUG #642](https://github.com/boa-dev/boa/pull/642): Missing `constructor` field in ordinary functions (@54k1) - [BUG #604](https://github.com/boa-dev/boa/pull/604): Missing `__proto__` field in functions instances (@54k1) - [BUG #561](https://github.com/boa-dev/boa/pull/561): Throw a `TypeError` when a non-object is called (@joshwd36) - [BUG #748](https://github.com/boa-dev/boa/pull/748): Fix parse error throwing a `TypeError`, instead of `SyntaxError` (@iamsaquib8) - [BUG #737](https://github.com/boa-dev/boa/pull/737): Make `Object.toString()` spec compliant (@RageKnify) Internal Improvements: - [INTERNAL #567](https://github.com/boa-dev/boa/pull/567): Add ECMAScript test suite (test262) (@Razican) - [INTERNAL #559](https://github.com/boa-dev/boa/pull/559): New Lexer (@Lan2u @HalidOdat @Razican) - [INTERNAL #712](https://github.com/boa-dev/boa/pull/712): Refactor: `Value::to_object` to return `GcObject` (@RageKnify) - [INTERNAL #544](https://github.com/boa-dev/boa/pull/544): Removed `console`s dependency of `InternalState` (@HalidOdat) - [INTERNAL #556](https://github.com/boa-dev/boa/pull/556): Added benchmark for goal symbol switching (@Razican) - [INTERNAL #578](https://github.com/boa-dev/boa/pull/580): Extract `prototype` from internal slots (@HalidOdat) - [INTERNAL #553](https://github.com/boa-dev/boa/pull/553): Refactor Property Descriptor flags (@HalidOdat) - [INTERNAL #592](https://github.com/boa-dev/boa/pull/592): `RegExp` specialization (@HalidOdat) - [INTERNAL #626](https://github.com/boa-dev/boa/pull/626): Refactor `Function` (@HalidOdat @Razican) - [INTERNAL #564](https://github.com/boa-dev/boa/pull/581): Add benchmarks for "uglified" JS (@neeldug) - [INTERNAL #706](https://github.com/boa-dev/boa/pull/706): Cache well known symbols (@HalidOdat) - [INTERNAL #723](https://github.com/boa-dev/boa/pull/723): Add fast path for string concatenation (@RageKnify) - [INTERNAL #689](https://github.com/boa-dev/boa/pull/689): Move `object` module to root (@HalidOdat) - [INTERNAL #684](https://github.com/boa-dev/boa/pull/684): Move `property` module to root (@HalidOdat) - [INTERNAL #674](https://github.com/boa-dev/boa/pull/674): Move `value` module to root (@HalidOdat) - [INTERNAL #693](https://github.com/boa-dev/boa/pull/693): Rename `Object::prototype()` and `Object::set_prototype()` (@RageKnify) - [INTERNAL #665](https://github.com/boa-dev/boa/pull/665): `approx_eq!` macro for `expm1` tests. (@neeldung) - [INTERNAL #581](https://github.com/boa-dev/boa/pull/581): Added CLEAN_JS and MINI_JS benches (@neeldung) - [INTERNAL #640](https://github.com/boa-dev/boa/pull/640): Benchmark refactor (@neeldung) - [INTERNAL #635](https://github.com/boa-dev/boa/pull/635): Add missing ops to exec module (@jarredholman) - [INTERNAL #616](https://github.com/boa-dev/boa/pull/616): Remove `Value::as_num_to_power()` (@HalidOdat) - [INTERNAL #601](https://github.com/boa-dev/boa/pull/601): Removed internal_slots from object (@HalidOdat) - [INTERNAL #560](https://github.com/boa-dev/boa/pull/560): Added benchmarks for full program execution (@Razican) - [INTERNAL #547](https://github.com/boa-dev/boa/pull/547): Merged `create` into `init` for builtins (@HalidOdat) - [INTERNAL #538](https://github.com/boa-dev/boa/pull/538): Cleanup and added test for `String.prototype.concat` (@HalidOdat) - [INTERNAL #739](https://github.com/boa-dev/boa/pull/739): Add release action (@jasonwilliams) - [INTERNAL #744](https://github.com/boa-dev/boa/pull/744): Add MacOS check and test to CI (@neeldug) ## [0.9.0 (2020-07-03) - Move to Organisation, 78% faster execution time](https://github.com/boa-dev/boa/compare/v0.8.0...v0.9.0) Feature Enhancements: - [FEATURE #414](https://github.com/boa-dev/boa/issues/414): Implement `Number` object constants (@Lan2u) (@HalidOdat) - [FEATURE #345](https://github.com/boa-dev/boa/issues/345): Implement the optional `replacer` parameter in `JSON.stringify( value[, replacer [, space] ] )` (@n14little) - [FEATURE #480](https://github.com/boa-dev/boa/issues/480): Implement global `Infinity` property (@AnirudhKonduru) - [FEATURE #410](https://github.com/boa-dev/boa/pull/410): Add support for the reviver function to JSON.parse (@abhijeetbhagat) - [FEATURE #425](https://github.com/boa-dev/boa/pull/425): Specification compliant `ToString` (`to_string`) (@HalidOdat) - [FEATURE #442](https://github.com/boa-dev/boa/pull/442): Added `TypeError` implementation (@HalidOdat) - [FEATURE #450](https://github.com/boa-dev/boa/pull/450): Specification compliant `ToBigInt` (`to_bigint`) (@HalidOdat) - [FEATURE #455](https://github.com/boa-dev/boa/pull/455): TemplateLiteral Basic lexer implementation (@croraf) - [FEATURE #447](https://github.com/boa-dev/boa/issues/447): parseInt, parseFloat implementation (@Lan2u) - [FEATURE #468](https://github.com/boa-dev/boa/pull/468): Add BigInt.asIntN() and BigInt.asUintN() functions (@Tropid) - [FEATURE #428](https://github.com/boa-dev/boa/issues/428): [Feature Request] - Create benchmark for Array manipulation (@abhijeetbhagat) - [FEATURE #439](https://github.com/boa-dev/boa/issues/439): Implement break handling in switch statements (@Lan2u) - [FEATURE #301](https://github.com/boa-dev/boa/issues/301): Implementing the switch statement in the new parser (@Lan2u) - [FEATURE #120](https://github.com/boa-dev/boa/issues/120): Implement `globalThis` (@zanayr) - [FEATURE #513](https://github.com/boa-dev/boa/issues/513): Implement `Object.is()` method (@tylermorten) - [FEATURE #481](https://github.com/boa-dev/boa/issues/481): Implement global `undefined` property (@croraf) Bug Fixes: - [BUG #412](https://github.com/boa-dev/boa/pull/412): Fixed parsing if statement without else block preceded by a newline (@HalidOdat) - [BUG #409](https://github.com/boa-dev/boa/pull/409): Fix function object constructable/callable (@HalidOdat) - [BUG #403](https://github.com/boa-dev/boa/issues/403) `Value::to_json()` does not handle `undefined` correctly (@n14little) - [BUG #443](https://github.com/boa-dev/boa/issues/443): HasOwnProperty should call GetOwnProperty and not GetProperty (@n14little) - [BUG #210](https://github.com/boa-dev/boa/issues/210): builtinfun.length undefined (@Croraf) - [BUG #466](https://github.com/boa-dev/boa/issues/466): Change `ToPrimitive()` (`to_primitive()`) hint to be an enum, instead of string (@HalidOdat) - [BUG #421](https://github.com/boa-dev/boa/issues/421): `NaN` is lexed as a number, not as an identifier (@croraf) - [BUG #454](https://github.com/boa-dev/boa/issues/454): Function declaration returns the function, it should return `undefined` (@croraf) - [BUG #482](https://github.com/boa-dev/boa/issues/482): Field access should propagate the exception (`Err(_)`) (@neeldug) - [BUG #463](https://github.com/boa-dev/boa/issues/463): Use of undefined variable should throw an error (@croraf) - [BUG #502](https://github.com/boa-dev/boa/pull/502): Fixed global objects initialization order (@HalidOdat) - [BUG #509](https://github.com/boa-dev/boa/issues/509): JSON.stringify(undefined) panics (@n14little) - [BUG #514](https://github.com/boa-dev/boa/issues/514): Clean up `Math` Methods (@n14little) - [BUG #511](https://github.com/boa-dev/boa/issues/511): [Call] Usage of "this" in methods is not supported (@jasonwilliams) Internal Improvements - [INTERNAL #435](https://github.com/boa-dev/boa/issues/435): Optimize type comparisons (@Lan2u) - [INTERNAL #296](https://github.com/boa-dev/boa/issues/296): using measureme for profiling the interpreter (@jasonwilliams) - [INTERNAL #419](https://github.com/boa-dev/boa/pull/419): Object specialization (fast paths for many objects) (@HalidOdat) - [INTERNAL #392](https://github.com/boa-dev/boa/pull/392): Execution and Node modulization (@Razican) - [INTERNAL #465](https://github.com/boa-dev/boa/issues/465): Refactoring Value (decouple `Gc` from `Value`) (@HalidOdat) - [INTERNAL #416](https://github.com/boa-dev/boa/pull/416) & [INTERNAL #423](https://github.com/boa-dev/boa/commit/c8218dd91ef3181e048e7a2659a4fbf8d53c7174): Update links to boa-dev (@pedropaulosuzuki) - [INTERNAL #378](https://github.com/boa-dev/boa/issues/378): Code Coverage! (@Lan2u) - [INTERNAL #431](https://github.com/boa-dev/boa/pull/431): Updates to PR Benchmarks (@Razican) - [INTERNAL #427 #429 #430](https://github.com/boa-dev/boa/commit/64dbf13afd15f12f958daa87a3d236dc9af1a9aa): Added new benchmarks (@Razican) ## [0.8.0 (2020-05-23) - BigInt, Modularized Parser, Faster Hashing](https://github.com/boa-dev/boa/compare/v0.7.0...v0.8.0) `v0.8.0` brings more language implementations, such as do..while, function objects and also more recent EcmaScript additions, like BigInt. We have now moved the Web Assembly build into the `wasm` package, plus added a code of conduct for those contributing. The parser has been even more modularized in this release making it easier to add new parsing rules. Boa has migrated it's object implemention to FXHash which brings much improved results over the built-in Rust hashmaps (at the cost of less DOS Protection). Feature Enhancements: - [FEATURE #121](https://github.com/boa-dev/boa/issues/121): `BigInt` Implemented (@HalidOdat) - [FEATURE #293](https://github.com/boa-dev/boa/pull/293): Improved documentation of all modules (@HalidOdat) - [FEATURE #302](https://github.com/boa-dev/boa/issues/302): Implement do..while loop (@ptasz3k) - [FEATURE #318](https://github.com/boa-dev/boa/pull/318): Added continous integration for windows (@HalidOdat) - [FEATURE #290](https://github.com/boa-dev/boa/pull/290): Added more build profiles (@Razican) - [FEATURE #323](https://github.com/boa-dev/boa/pull/323): Aded more benchmarks (@Razican) - [FEATURE #326](https://github.com/boa-dev/boa/pull/326): Rename Boa CLI (@sphinxc0re) - [FEATURE #312](https://github.com/boa-dev/boa/pull/312): Added jemallocator for linux targets (@Razican) - [FEATURE #339](https://github.com/boa-dev/boa/pull/339): Improved Method parsing (@muskuloes) - [FEATURE #352](https://github.com/boa-dev/boa/pull/352): create boa-wasm package (@muskuloes) - [FEATURE #304](https://github.com/boa-dev/boa/pull/304): Modularized parser - [FEATURE #141](https://github.com/boa-dev/boa/issues/141): Implement function objects (@jasonwilliams) - [FEATURE #365](https://github.com/boa-dev/boa/issues/365): Implement for loop execution (@Razican) - [FEATURE #356](https://github.com/boa-dev/boa/issues/356): Use Fx Hash to speed up hash maps in the compiler (@Razican) - [FEATURE #321](https://github.com/boa-dev/boa/issues/321): Implement unary operator execution (@akryvomaz) - [FEATURE #379](https://github.com/boa-dev/boa/issues/379): Automatic auditing of Boa (@n14little) - [FEATURE #264](https://github.com/boa-dev/boa/issues/264): Implement `this` (@jasonwilliams) - [FEATURE #395](https://github.com/boa-dev/boa/pull/395): impl abstract-equality-comparison (@hello2dj) - [FEATURE #359](https://github.com/boa-dev/boa/issues/359): impl typeof (@RestitutorOrbis) - [FEATURE #390](https://github.com/boa-dev/boa/pull/390): Modularize try statement parsing (@abhijeetbhagat) Bug fixes: - [BUG #308](https://github.com/boa-dev/boa/issues/308): Assignment operator not working in tests (a = a +1) (@ptasz3k) - [BUG #322](https://github.com/boa-dev/boa/issues/322): Benchmarks are failing in master (@Razican) - [BUG #325](https://github.com/boa-dev/boa/pull/325): Put JSON functions on the object, not the prototype (@coolreader18) - [BUG #331](https://github.com/boa-dev/boa/issues/331): We only get `Const::Num`, never `Const::Int` (@HalidOdat) - [BUG #209](https://github.com/boa-dev/boa/issues/209): Calling `new Array` with 1 argument doesn't work properly (@HalidOdat) - [BUG #266](https://github.com/boa-dev/boa/issues/266): Panic assigning named function to variable (@Razican) - [BUG #397](https://github.com/boa-dev/boa/pull/397): fix `NaN` is lexed as identifier, not as a number (@attliaLin) - [BUG #362](https://github.com/boa-dev/boa/pull/362): Remove Monaco Editor Webpack Plugin and Manually Vendor Editor Workers (@subhankar-panda) - [BUG #406](https://github.com/boa-dev/boa/pull/406): Dependency Upgrade (@Razican) - [BUG #407](https://github.com/boa-dev/boa/pull/407): `String()` wasn't defaulting to empty string on call (@jasonwilliams) - [BUG #404](https://github.com/boa-dev/boa/pull/404): Fix for 0 length new String(@tylermorten) Code Of Conduct: - [COC #384](https://github.com/boa-dev/boa/pull/384): Code of conduct added (@Razican) Security: - [SEC #391](https://github.com/boa-dev/boa/pull/391): run security audit daily at midnight. (@n14little) ## [# 0.7.0 (2020-04-13) - New Parser is 67% faster](https://github.com/boa-dev/boa/compare/v0.6.0...v0.7.0) `v0.7.0` brings a REPL, Improved parser messages and a new parser! This is now the default behaviour of Boa, so running Boa without a file argument will bring you into a javascript shell. Tests have also been moved to their own files, we had a lot of tests in some modules so it was time to separate. ### New Parser Most of the work in this release has been on rewriting the parser. A big task taken on by [HalidOdat](https://github.com/HalidOdat), [Razican](https://github.com/Razican) and [myself](https://github.com/jasonwilliams). The majority of the old parser was 1 big function (called [`parse`](https://github.com/boa-dev/boa/blob/019033eff066e8c6ba9456139690eb214a0bf61d/boa/src/syntax/parser.rs#L353)) which had some pattern matching on each token coming in. The easy branches could generate expressions (which were basically AST Nodes), the more involved branches would recursively call into the same function, until eventually you had an expression generated. This only worked so far, eventually debugging parsing problems were difficult, also more bugs were being raised against the parser which couldn't be fixed. We decided to break the parser into more of a state-machine. The initial decision for this was inspired by [Fedor Indutny](https://github.com/indutny) who did a talk at (the last) JSConf EU about how he broke up the old node-parser to make it more maintanable. He goes into more detail here https://www.youtube.com/watch?v=x3k_5Mi66sY&feature=youtu.be&t=530 The new parser has functions to match the states of parsing in the spec. For example https://tc39.es/ecma262/#prod-VariableDeclaration has a matching function `read_variable_declaration`. This not only makes it better to maintain but easier for new contributors to get involed, as following the parsing logic of the spec is easier than before. Once finished some optimisations were added by [HalidOdat](https://github.com/HalidOdat) to use references to the tokens instead of cloning them each time we take them from the lexer. This works because the tokens live just as long as the parser operations do, so we don't need to copy the tokens. What this brings is a huge performance boost, the parser is 67% faster than before! ![Parser Improvement](./docs/img/parser-graph.png) Feature enhancements: - [FEATURE #281](https://github.com/boa-dev/boa/pull/281): Rebuild the parser (@jasonwilliams, @Razican, @HalidOdat) - [FEATURE #278](https://github.com/boa-dev/boa/pull/278): Added the ability to dump the token stream or ast in bin. (@HalidOdat) - [FEATURE #253](https://github.com/boa-dev/boa/pull/253): Implement Array.isArray (@cisen) - [FEATURE](https://github.com/boa-dev/boa/commit/edab5ca6cc10d13265f82fa4bc05d6b432a362fc) Switch to normal output instead of debugged output (stdout/stdout) (@jasonwilliams) - [FEATURE #258](https://github.com/boa-dev/boa/pull/258): Moved test modules to their own files (@Razican) - [FEATURE #267](https://github.com/boa-dev/boa/pull/267): Add print & REPL functionality to CLI (@JohnDoneth) - [FEATURE #268](https://github.com/boa-dev/boa/pull/268): Addition of forEach() (@jasonwilliams) (@xSke) - [FEATURE #262](https://github.com/boa-dev/boa/pull/262): Implement Array.prototype.filter (@Nickforall) - [FEATURE #261](https://github.com/boa-dev/boa/pull/261): Improved parser error messages (@Razican) - [FEATURE #277](https://github.com/boa-dev/boa/pull/277): Add a logo to the project (@HalidOdat) - [FEATURE #260](https://github.com/boa-dev/boa/pull/260): Add methods with f64 std equivelant to Math object (@Nickforall) Bug fixes: - [BUG #249](https://github.com/boa-dev/boa/pull/249): fix(parser): handle trailing comma in object literals (@gomesalexandre) - [BUG #244](https://github.com/boa-dev/boa/pull/244): Fixed more Lexer Panics (@adumbidiot) - [BUG #256](https://github.com/boa-dev/boa/pull/256): Fixed comments lexing (@Razican) - [BUG #251](https://github.com/boa-dev/boa/issues/251): Fixed empty returns (@Razican) - [BUG #272](https://github.com/boa-dev/boa/pull/272): Fix parsing of floats that start with a zero (@Nickforall) - [BUG #240](https://github.com/boa-dev/boa/issues/240): Fix parser panic - [BUG #273](https://github.com/boa-dev/boa/issues/273): new Class().method() has incorrect precedence Documentation Updates: - [DOC #297](https://github.com/boa-dev/boa/pull/297): Better user contributed documentation ## [0.6.0 (2020-02-14) - Migration to Workspace Architecture + lexer/parser improvements](https://github.com/boa-dev/boa/compare/v0.5.1...v0.6.0) The lexer has had several fixes in this release, including how it parses numbers, scientific notation should be improved. On top of that the lexer no longer panics on errors including Syntax Errors (thanks @adumbidiot), instead you get some output on where the error happened. ### Moving to a workspace architecture Boa offers both a CLI and a library, initially these were all in the same binary. The downside is those who want to embed boa as-is end up with all of the command-line dependencies. So the time has come to separate out the two, this is normal procedure, this should be analogous to ripgrep and the regex crate. Cargo has great support for workspaces, so this shouldn't be an issue. ### Benchmarks We now have [benchmarks which run against master](https://boajs.dev/boa/dev/bench/)! Thanks to Github Actions these will run automatically a commit is merged. Feature enhancements: - [FEATURE #218](https://github.com/boa-dev/boa/pull/218): Implement Array.prototype.toString (@cisen) - [FEATURE #216](https://github.com/boa-dev/boa/commit/85e9a3526105a600358bd53811e2b022987c6fc8): Keep accepting new array elements after spread. - [FEATURE #220](https://github.com/boa-dev/boa/pull/220): Documentation updates. (@croraf) - [FEATURE #226](https://github.com/boa-dev/boa/pull/226): add parser benchmark for expressions. (@jasonwilliams) - [FEATURE #217](https://github.com/boa-dev/boa/pull/217): String.prototype.replace() implemented - [FEATURE #247](https://github.com/boa-dev/boa/pull/247): Moved to a workspace architecture (@Razican) Bug fixes: - [BUG #222](https://github.com/boa-dev/boa/pull/222): Fixed clippy errors (@IovoslavIovchev) - [BUG #228](https://github.com/boa-dev/boa/pull/228): [lexer: single-line-comment] Fix bug when single line comment is last line of file (@croraf) - [BUG #229](https://github.com/boa-dev/boa/pull/229): Replace error throwing with panic in "Lexer::next()" (@croraf) - [BUG #232/BUG #238](https://github.com/boa-dev/boa/pull/232): Clippy checking has been scaled right back to just Perf and Style (@jasonwilliams) - [BUG #227](https://github.com/boa-dev/boa/pull/227): Array.prototype.toString should be called by ES value (@cisen) - [BUG #242](https://github.com/boa-dev/boa/pull/242): Fixed some panics in the lexer (@adumbidiot) - [BUG #235](https://github.com/boa-dev/boa/pull/235): Fixed arithmetic operations with no space (@gomesalexandre) - [BUG #245](https://github.com/boa-dev/boa/pull/245): Fixed parsing of floats with scientific notation (@adumbidiot) ## [# 0.5.1 (2019-12-02) - Rest / Spread (almost)](https://github.com/boa-dev/boa/compare/v0.5.0...v0.5.1) Feature enhancements: - [FEATURE #151](https://github.com/boa-dev/boa/issues/151): Implement the Rest/Spread operator (functions and arrays). - [FEATURE #193](https://github.com/boa-dev/boa/issues/193): Implement macro for setting builtin functions - [FEATURE #211](https://github.com/boa-dev/boa/pull/211): Better Display support for all Objects (pretty printing) ## [# 0.5.0 (2019-11-06) - Hacktoberfest Release](https://github.com/boa-dev/boa/compare/v0.4.0...v0.5.1) Feature enhancements: - [FEATURE #119](https://github.com/boa-dev/boa/issues/119): Introduce realm struct to hold realm context and global object. - [FEATURE #89](https://github.com/boa-dev/boa/issues/89): Implement exponentiation operator. Thanks @arbroween - [FEATURE #47](https://github.com/boa-dev/boa/issues/47): Add tests for comments in source code. Thanks @Emanon42 - [FEATURE #137](https://github.com/boa-dev/boa/issues/137): Use Monaco theme for the demo page - [FEATURE #114](https://github.com/boa-dev/boa/issues/114): String.match(regExp) is implemented (@muskuloes) - [FEATURE #115](https://github.com/boa-dev/boa/issues/115): String.matchAll(regExp) is implemented (@bojan88) - [FEATURE #163](https://github.com/boa-dev/boa/issues/163): Implement Array.prototype.every() (@letmutx) - [FEATURE #165](https://github.com/boa-dev/boa/issues/165): Implement Array.prototype.find() (@letmutx) - [FEATURE #166](https://github.com/boa-dev/boa/issues/166): Implement Array.prototype.findIndex() (@felipe-fg) - [FEATURE #39](https://github.com/boa-dev/boa/issues/39): Implement block scoped variable declarations (@barskern) - [FEATURE #161](https://github.com/boa-dev/boa/pull/161): Enable obj[key] = value syntax. - [FEATURE #179](https://github.com/boa-dev/boa/issues/179): Implement the Tilde operator (@letmutx) - [FEATURE #189](https://github.com/boa-dev/boa/pull/189): Implement Array.prototype.includes (incl tests) (@simonbrahan) - [FEATURE #180](https://github.com/boa-dev/boa/pull/180): Implement Array.prototype.slice (@muskuloes @letmutx) - [FEATURE #152](https://github.com/boa-dev/boa/issues/152): Short Function syntax (no arguments) - [FEATURE #164](https://github.com/boa-dev/boa/issues/164): Implement Array.prototype.fill() (@bojan88) - Array tests: Tests implemented for shift, unshift and reverse, pop and push (@muskuloes) - Demo page has been improved, new font plus change on input. Thanks @WofWca - [FEATURE #182](https://github.com/boa-dev/boa/pull/182): Implement some Number prototype methods (incl tests) (@pop) - [FEATURE #34](https://github.com/boa-dev/boa/issues/34): Number object and Constructore are implemented (including methods) (@pop) - [FEATURE #194](https://github.com/boa-dev/boa/pull/194): Array.prototype.map (@IovoslavIovchev) - [FEATURE #90](https://github.com/boa-dev/boa/issues/90): Symbol Implementation (@jasonwilliams) Bug fixes: - [BUG #113](https://github.com/boa-dev/boa/issues/113): Unassigned variables have default of undefined (@pop) - [BUG #61](https://github.com/boa-dev/boa/issues/61): Clippy warnings/errors fixed (@korpen) - [BUG #147](https://github.com/boa-dev/boa/pull/147): Updated object global - [BUG #154](https://github.com/boa-dev/boa/issues/154): Correctly handle all whitespaces within the lexer - Tidy up Globals being added to Global Object. Thanks @DomParfitt ## 0.4.0 (2019-09-25) v0.4.0 brings quite a big release. The biggest feature to land is the support of regular expressions. Functions now have the arguments object supported and we have a [`debugging`](docs/debugging.md) section in the docs. Feature enhancements: - [FEATURE #6](https://github.com/boa-dev/boa/issues/6): Support for regex literals. (Big thanks @999eagle) - [FEATURE #13](https://github.com/boa-dev/boa/issues/13): toLowerCase, toUpperCase, substring, substr and valueOf implemented (thanks @arbroween) - Support for `arguments` object within functions - `StringData` instead of `PrimitieData` to match spec - Native function signatures changed, operations added to match spec - Primitives can now be boxed/unboxed when methods are ran on them - Spelling edits (thanks @someguynamedmatt) - Ability to set global values before interpreter starts (thanks @999eagle) - Assign operators implemented (thanks @oll3) - Bug fixes: - [BUG #57](https://github.com/boa-dev/boa/issues/57): Fixed issue with stackoverflow by implementing early returns. - Allow to re-assign value to an existing binding. (Thanks @oll3) ## 0.3.0 (2019-07-26) - UnexpectedKeyword(Else) bug fixed https://github.com/boa-dev/boa/issues/38 - Contributing guide added - Ability to specify file - Thanks @callumquick - Travis fixes - Parser Tests - Thanks @Razican - Migrate to dyn traits - Thanks @Atul9 - Added implementations for Array.prototype: concat(), push(), pop() and join() - Thanks @callumquick - Some clippy Issues fixed - Thanks @Razican - Objects have been refactored to use structs which are more closely aligned with the specification - Benchmarks have been added - String and Array specific console.log formats - Thanks @callumquick - isPropertyKey implementation added - Thanks @KrisChambers - Unit Tests for Array and Strings - Thanks @GalAster - typo fix - Thanks @palerdot - dist cleanup, thanks @zgotsch ## 0.2.1 (2019-06-30) Some String prototype methods are implemented. Thanks to @lennartbuit we have trim/trimStart/trimEnd added to the string prototype Feature enhancements: - [String.prototype.concat ( ...args )](https://tc39.es/ecma262/#sec-string.prototype.slice) - [String.prototype.endsWith ( searchString [ , endPosition ] )](https://tc39.es/ecma262/#sec-string.prototype.endswith) - [String.prototype.includes ( searchString [ , position ] )](https://tc39.es/ecma262/#sec-string.prototype.includes) - [String.prototype.indexOf ( searchString [ , position ] )](https://tc39.es/ecma262/#sec-string.prototype.indexof) - [String.prototype.lastIndexOf ( searchString [ , position ] )](https://tc39.es/ecma262/#sec-string.prototype.lastindexof) - [String.prototype.repeat ( count )](https://tc39.es/ecma262/#sec-string.prototype.repeat) - [String.prototype.slice ( start, end )](https://tc39.es/ecma262/#sec-string.prototype.slice) - [String.prototype.startsWith ( searchString [ , position ] )](https://tc39.es/ecma262/#sec-string.prototype.startswith) Bug fixes: - Plenty ## 0.2.0 (2019-06-10) Working state reached - Tests on the lexer, conforms with puncturators and keywords from TC39 specification - wasm-bindgen added with working demo in Web Assembly - snapshot of boa in a working state for the first time ================================================ FILE: CODEOWNERS ================================================ # Fallback to all maintainers for common directories. * @boa-dev/maintainers # Owned components core/engine/src/builtins/intl @jedel1043 @nekevss core/engine/src/builtins/temporal @jasonwilliams @jedel1043 @nekevss ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # 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 [Matrix](https://matrix.to/#/#boa:matrix.org) by contacting any of the admins in the space. 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 ================================================ FILE: CONTRIBUTING.md ================================================ # 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 . ### 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) ## 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 ``` 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 ``` To save test results for later comparison, use the `-o` flag to specify an output directory: ```shell cargo run --release --bin boa_tester -- run -o ./test-results ``` ### Comparing Test Results You can compare two test suite runs to see what changed: ```shell cargo run --release --bin boa_tester -- compare ``` Both arguments can be either result files (e.g., `latest.json`) or directories containing test results. When directories are provided, the tester automatically uses the `latest.json` file from each directory. For example: ```shell # Compare using directories cargo run --release --bin boa_tester -- compare ./test-results-main ./test-results-feature # Compare using explicit files cargo run --release --bin boa_tester -- compare ./test-results-main/latest.json ./test-results-feature/latest.json ``` ## Documentation To build the development documentation, run: ```shell cargo doc --all-features --document-private-items --workspace ``` This will also document all the dependencies on the workspace, which could be heavier in size. To only generate documentation for the workspace members, just add the `--no-deps` flag: ```shell cargo doc --all-features --document-private-items --workspace --no-deps ``` ## Reading and Understanding the ECMAScript Specification Many contributions to Boa involve implementing parts of the [ECMAScript Language Specification](https://tc39.es/ecma262/), which defines how JavaScript behaves. At first, the spec can seem intimidating, but it quickly becomes easier to follow once you get familiar with its structure and notation. The specification is written in a pseudo-language designed to describe behavior without being tied to any particular programming language. It introduces some important concepts: - **Abstract operations** – general algorithms (i.e. [`IsCallable`](https://tc39.es/ecma262/#sec-iscallable)), which usually map to Rust functions or methods. - **Internal slots** – hidden object fields like [`[[Prototype]]`](https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots) that correspond to private struct or enum fields in Rust, not accessible to JavaScript. - **Completion records** – describe how values or exceptions are returned ([link](https://tc39.es/ecma262/#sec-completion-record-specification-type)), and typically map to `JsResult` types in Rust. - **Symbols `?` and `!`** – `? Foo(...)` propagates exceptions mapped to propagate `?` operator in rust, while `! Foo(...)` are infallible operations and are usually mapped to [`Result::expect()`](https://doc.rust-lang.org/std/result/enum.Result.html#method.expect) call. For an in-depth introduction to these concepts and more, check out [V8’s “Understanding the ECMAScript spec” series](https://v8.dev/blog/tags/understanding-ecmascript), starting with [Part 1](https://v8.dev/blog/understanding-ecmascript-part-1). When implementing the spec in Boa, try to map your code to the corresponding spec steps whenever possible, and indicate in comments which steps are implemented. This makes the code easier to understand, ensures it aligns with the specification, and helps reviewers and future contributors follow the logic. If a spec step does not map directly because of Rust limitations or performance reasons, just add a note in the code explaining the difference. Being clear about these cases helps others understand your implementation while still following the spec as closely as possible. For examples of how to implement the specification, check out the built-in implementations in Boa [here](https://github.com/boa-dev/boa/tree/main/core/engine/src/builtins). If anything in the specification is confusing, don’t hesitate to ask in the [Boa Matrix](https://matrix.to/#/#boa:matrix.org) channel. ## Learning Resources For contributors looking to learn JavaScript and how it works, check out the [Mozilla Developer Guided Tours](https://www.youtube.com/playlist?list=PLo3w8EB99pqJVPhmYbYdInBvAGarDavh-). ## Communication We have a Matrix space, feel free to ask questions here: [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 ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ # CORE "core/*", # FFI "ffi/*", # TESTS "tests/*", # TOOLS "tools/*", # OTHERS "examples", "cli", # UTILS "utils/*", # BENCHES "benches", ] exclude = [ "tests/fuzz", # Does weird things on Windows tests "tests/src", # Just a hack to have fuzz inside tests "tests/wpt", # Should not run WPT by default. ] [workspace.package] edition = "2024" version = "1.0.0-dev" rust-version = "1.91.0" authors = ["boa-dev"] repository = "https://github.com/boa-dev/boa" license = "Unlicense OR MIT" description = "Boa is a Javascript lexer, parser and compiler written in Rust. Currently, it has support for some of the language." [workspace.dependencies] # Repo Crates boa_ast = { version = "~1.0.0-dev", path = "core/ast" } boa_engine = { version = "~1.0.0-dev", path = "core/engine", default-features = false } boa_gc = { version = "~1.0.0-dev", path = "core/gc" } boa_icu_provider = { version = "~1.0.0-dev", path = "core/icu_provider" } boa_interner = { version = "~1.0.0-dev", path = "core/interner" } boa_macros = { version = "~1.0.0-dev", path = "core/macros" } boa_parser = { version = "~1.0.0-dev", path = "core/parser" } boa_runtime = { version = "~1.0.0-dev", path = "core/runtime" } boa_string = { version = "~1.0.0-dev", path = "core/string" } # Utility Repo Crates tag_ptr = { version = "~0.1.0", path = "utils/tag_ptr" } small_btree = { version = "~0.1.0", path = "utils/small_btree" } # Shared deps arbitrary = "1" base64 = "0.22.1" bitflags = "2.11.0" clap = "4.5.59" colored = "3.1.1" cow-utils = "0.1.3" fast-float2 = "0.2.3" hashbrown = "0.16.1" http = { version = "1.4.0" } iana-time-zone = "0.1.65" indexmap = { version = "2.13.0", default-features = false } indoc = "2.0.7" itoa = "1.0.18" jemallocator = "0.5.4" mimalloc-safe = "0.1.56" lz4_flex = "0.13.0" num-bigint = "0.4.6" num-traits = "0.2.19" once_cell = { version = "1.21.3", default-features = false } oneshot = "0.2.1" phf = { version = "0.13.1", default-features = false } pollster = "0.4.0" regex = "1.12.3" regress = { version = "0.11.0", features = ["utf16"] } reqwest = { version = "0.13.2", default-features = false, features = ["rustls-no-provider"] } rustls = { version = "0.23.37", default-features = false, features = ["ring"] } rustc-hash = { version = "2.1.1", default-features = false } serde_json = "1.0.149" serde = "1.0.219" static_assertions = "1.1.0" textwrap = "0.16.2" thin-vec = "0.2.14" time = { version = "0.3.47", default-features = false, features = [ "local-offset", "large-dates", "parsing", "formatting", "macros", ] } log = "0.4.29" simple_logger = "5.1.0" cargo_metadata = "0.23.1" trybuild = "1.0.116" rayon = "1.10.0" toml = "1.0.7" color-eyre = "0.6.3" comfy-table = "7.2.2" serde_repr = "0.1.20" bus = "2.4.1" wasm-bindgen = { version = "0.2.97", default-features = false } getrandom = { version = "0.4.2", default-features = false } console_error_panic_hook = "0.1.7" wasm-bindgen-test = "0.3.64" smol = "2.0.2" rustyline = { version = "17.0.2", default-features = false } dhat = "0.3.3" quote = "1.0.45" syn = { version = "2.0.116", default-features = false } proc-macro2 = "1.0" synstructure = "0.13" measureme = "12.0.3" paste = "1.0" rand = "0.10.0" num-integer = "0.1.46" ryu-js = "1.0.2" tap = "1.0.1" thiserror = { version = "2.0.18", default-features = false } dashmap = "6.1.0" num_enum = "0.7.6" itertools = { version = "0.14.0", default-features = false } portable-atomic = "1.13.1" bytemuck = { version = "1.25.0", default-features = false } arrayvec = "0.7.6" intrusive-collections = "0.10.0" cfg-if = "1.0.4" either = "1.15.0" sys-locale = "0.3.2" timezone_provider = { version = "0.2.0" } temporal_rs = { version = "0.2.0", default-features = false, features = ["float64_representable_durations"] } web-time = "1.1.0" criterion = "0.8.1" float-cmp = "0.10.0" futures-lite = "2.6.1" test-case = "3.3.1" rstest = "0.26.1" url = "2.5.8" tokio = { version = "1.50.0", default-features = false } futures-concurrency = "7.7.1" dynify = "0.1.2" futures-channel = "0.3.32" aligned-vec = "0.6.4" temp-env = "0.3.6" strum = { version = "0.28", features = ["derive"] } husky-rs = "0.3.2" async-channel = "2.5.0" # ICU4X icu_provider = { version = "~2.1.1", default-features = false } icu_locale = { version = "~2.1.1", default-features = false } icu_locale_core = { version = "~2.1.1", default-features = false } icu_datetime = { version = "~2.1.1", default-features = false } icu_time = { version = "~2.1.1", default-features = false } icu_calendar = { version = "~2.1.1", default-features = false } icu_collator = { version = "~2.1.1", default-features = false } icu_plurals = { version = "~2.1.1", default-features = false } icu_list = { version = "~2.1.1", default-features = false } icu_casemap = { version = "~2.1.1", default-features = false } icu_segmenter = { version = "~2.1.2", default-features = false } icu_provider_export = { version = "~2.1.1", default-features = false } icu_provider_source = { version = "~2.1.2", default-features = false } icu_provider_adapters = { version = "~2.1.1", default-features = false } icu_provider_blob = { version = "~2.1.1", default-features = false } icu_properties = { version = "~2.1.2", default-features = true } icu_normalizer = { version = "~2.1.1", default-features = false } icu_decimal = { version = "~2.1.1", default-features = false } writeable = "~0.6.2" tinystr = "~0.8.2" yoke = "~0.8.1" zerofrom = "~0.1.6" fixed_decimal = "~0.7.1" [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 # Strips debug information and symbols from the binary, reducing its size strip = "symbols" [profile.release-dbg] inherits = "release" debug = true strip = "none" # 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 [workspace.lints.rust] # rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html warnings = "warn" future_incompatible = { level = "warn", priority = -1 } let_underscore = { level = "warn", priority = -1 } nonstandard_style = { level = "warn", priority = -1 } rust_2018_compatibility = { level = "warn", priority = -1 } rust_2018_idioms = { level = "warn", priority = -1 } rust_2021_compatibility = { level = "warn", priority = -1 } unused = { level = "warn", priority = -1 } # rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html missing_docs = "warn" macro_use_extern_crate = "warn" meta_variable_misuse = "warn" missing_abi = "warn" missing_copy_implementations = "warn" missing_debug_implementations = "warn" non_ascii_idents = "warn" noop_method_call = "warn" single_use_lifetimes = "warn" trivial_casts = "warn" trivial_numeric_casts = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_crate_dependencies = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" unused_qualifications = "warn" variant_size_differences = "warn" [workspace.lints.rustdoc] # rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html broken_intra_doc_links = "warn" private_intra_doc_links = "warn" missing_crate_level_docs = "warn" private_doc_tests = "warn" invalid_codeblock_attributes = "warn" invalid_rust_codeblocks = "warn" bare_urls = "warn" [workspace.lints.clippy] # clippy allowed by default dbg_macro = "warn" print_stdout = "warn" print_stderr = "warn" # clippy categories https://doc.rust-lang.org/clippy/ all = { level = "warn", priority = -1 } correctness = { level = "warn", priority = -1 } suspicious = { level = "warn", priority = -1 } style = { level = "warn", priority = -1 } complexity = { level = "warn", priority = -1 } perf = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } ================================================ FILE: LICENSE-MIT ================================================ 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. ================================================ FILE: LICENSE-UNLICENSE ================================================ 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 ================================================ FILE: Makefile.toml ================================================ extend = [ { path = "./make/ci.toml"} ] [config] default_to_workspace = false [tasks.run-ci] description = "Run Boa CI locally" run_task.name = [ "ci-fmt-check", "ci-lint-all-features", "ci-lint-no-features" ] [tasks.format] install_crate = "rustfmt" command = "cargo" args = ["fmt", "--all", "--", "--emit=files"] [tasks.test] command = "cargo" args = ["test", "${@}"] [tasks.test262] command = "cargo" args = ["run", "@@remove-empty(RELEASE_ARG)", "--bin", "boa_tester", "--", "run", "${@}"] [tasks.bench-js] condition = { env_set = [ "BOA_DATA_ROOT" ] } command = "cargo" args = ["run", "--bin", "boa", "--release", "--", "@@remove-empty(BOA_DATA_ROOT)bench/bench-v8/combined.js"] [tasks.insta-test] install_crate = "cargo-insta" command = "cargo" args = ["insta", "test", "-p", "insta-bytecode", "--review"] [tasks.insta-review] install_crate = "cargo-insta" command = "cargo" args = ["insta", "review", "--manifest-path", "./tests/insta-bytecode/Cargo.toml"] [env.development] RELEASE_ARG="" [env.profiling] RELEASE_ARG="--profile=release-dbg" [env.production] RELEASE_ARG="--release" ================================================ FILE: PR_MESSAGE_EXPECT_EXPRESSION.md ================================================ # fix(parser): replace expect_expression panic with try_into_expression error handling ## Summary Replaces all uses of `expect_expression()` with `try_into_expression()` in the parser, and removes the panicking `expect_expression()` method. When the parser encounters arrow-function parameter list syntax in a context that requires an expression (e.g. conditional operator `a ? b : c`, call expression `foo()`, optional chaining `a?.b`), it now returns a recoverable parse error instead of panicking. ## Motivation The `FormalParameterListOrExpression` type represents the grammar ambiguity between `(a, b)` as a comma expression vs `(a, b)` as arrow-function parameters. In contexts like the conditional operator (`x ? y : z`), call expressions (`f()`), and optional chaining (`a?.b`), the left-hand side must be an expression—arrow params are invalid. Previously, call sites used `expect_expression()`, which panics with "Unexpected arrow-function arguments" when the parser produced a `FormalParameterList` instead of an `Expression`. This can occur with edge-case input such as `(a) ? 1 : 2` when `(a)` is parsed as a single arrow param. For Boa as an embedded engine or in tooling, panics are unacceptable; the host expects a parse error. The `try_into_expression()` method already existed and returns `Err(Error::General { ... })` with a helpful message ("invalid arrow-function arguments (parentheses around the arrow-function may help)"). Replacing `expect_expression()` with `try_into_expression()?` propagates that error instead of panicking, aligning with Boa's stability goals. ## Changes | Category | Description | | ------------ | ----------------------------------------------------------------------------------------------------- | | **Replaced** | `lhs.expect_expression()` with `lhs.try_into_expression()?` in conditional expression parser | | **Replaced** | `member.expect_expression()` with `member.try_into_expression()?` in left-hand side (call expression) | | **Replaced** | `lhs.expect_expression()` with `lhs.try_into_expression()?` in left-hand side (optional expression) | | **Removed** | `expect_expression()` method from `FormalParameterListOrExpression` to prevent future misuse | ## Technical Details - **Files modified:** `core/parser/src/parser/expression/assignment/conditional.rs`, `core/parser/src/parser/expression/left_hand_side/mod.rs`, `core/parser/src/parser/expression/fpl_or_exp.rs` - **Lines changed:** ~15 lines across 3 files - **Behavioral impact:** Invalid input that previously caused a panic now produces `Err(Error::General { message: "invalid arrow-function arguments (parentheses around the arrow-function may help)", position })`. Valid input is unchanged. ## Testing - [x] `cargo test -p boa_parser` — all 296 tests pass - [x] `cargo test -p boa_engine --lib` — all 921 tests pass - [x] `cargo clippy` — no warnings ================================================ FILE: README.md ================================================ # Boa

Boa logo

Boa is an experimental JavaScript lexer, parser and interpreter written in Rust 🦀, it has support for **more** than 90% of the latest ECMAScript specification. We continuously improve the conformance to keep up with the ever-evolving standard. [![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) [![Matrix](https://img.shields.io/matrix/boa:matrix.org?logo=matrix)](https://matrix.to/#/#boa:matrix.org) [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) Try out the engine now at the live Wasm playground [here](https://boajs.dev/playground)! Prefer a CLI? Feel free to try out `boa_cli`! ## 📦 Crates Boa currently publishes and actively maintains the following crates: - **`boa_ast`** - Boa's ECMAScript Abstract Syntax Tree - **`boa_cli`** - Boa's CLI && REPL implementation - **`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_icu_provider`** - Boa's ICU4X data provider - **`boa_runtime`** - Boa's WebAPI features - **`boa_string`** - Boa's ECMAScript string implementation. - **`tag_ptr`** - Utility library that enables a pointer to be associated with a tag of type `usize`. > [!NOTE] > > The `Boa` and `boa_unicode` crates are deprecated. ## 🚀 Example To start using Boa simply add the `boa_engine` crate to your `Cargo.toml`: ```toml [dependencies] boa_engine = "0.21.0" ``` Then in `main.rs`, copy the below: ```rust use boa_engine::{Context, Source, JsResult}; fn main() -> JsResult<()> { let js_code = r#" let two = 1 + 1; let definitely_not_four = two + "2"; definitely_not_four "#; // Instantiate the execution context let mut context = Context::default(); // Parse the source code let result = context.eval(Source::from_bytes(js_code))?; println!("{}", result.display()); Ok(()) } ``` Now, all that's left to do is `cargo run`. Congrats! You've executed your first JavaScript code using Boa! ## 🔎 Documentation For more information on Boa's API, feel free to check out our documentation. [**API Documentation**](https://docs.rs/boa_engine/latest/boa_engine/) ## 🏅 Conformance To know more details about Boa's conformance surrounding the _ECMAScript_ specification, you can check out our _ECMAScript Test262_ test suite results [here](https://boajs.dev/conformance). ## 🪚 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 > [!IMPORTANT] > > This only applies to `wasm32-unknown-unknown` target, > `WASI` and `Emscripten` target variants are handled automatically. - Enable the `js` feature flag. - Set `RUSTFLAGS='--cfg getrandom_backend="wasm_js"'` The `rustflags` can also be set by adding a `.cargo/config.toml` file in the project root directory: ```toml [target.wasm32-unknown-unknown] rustflags = '--cfg getrandom_backend="wasm_js"' ``` For more information see: [`getrandom` WebAssembly Support][getrandom-webassembly-support] [getrandom-webassembly-support]: https://docs.rs/getrandom/latest/getrandom/index.html#webassembly-support ## ⚙️ Usage - Clone this repo. - Run with `cargo run -- test.js` in the project root directory where `test.js` is a path to an existing JS file with any valid JS 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 ```txt Usage: boa [OPTIONS] [FILE]... Arguments: [FILE]... The JavaScript file(s) to be evaluated Options: --strict Run in strict mode -a, --dump-ast [] Dump the AST to stdout with the given format [possible values: debug, json, json-pretty] -t, --trace Dump the AST to stdout with the given format --vi Use vi mode in the REPL -O, --optimize --optimizer-statistics --flowgraph [] Generate instruction flowgraph. Default is Graphviz [possible values: graphviz, mermaid] --flowgraph-direction Specifies the direction of the flowgraph. Default is top-top-bottom [possible values: top-to-bottom, bottom-to-top, left-to-right, right-to-left] --debug-object Inject debugging object `$boa` -m, --module Treats the input files as modules -r, --root Root path from where the module resolver will try to load the modules [default: .] -h, --help Print help (see more with '--help') -V, --version Print version ``` ## 🧭 Roadmap See [Milestones](https://github.com/boa-dev/boa/milestones). ## 📊 Benchmarks The current benchmarks are taken from v8's benchmark that you can find [here][boa-benchmarks]. You can also view the results of nightly benchmark runs comparing Boa with other JavaScript engines [here](https://boajs.dev/benchmarks). If you wish to run the benchmarks locally, then run Boa in release using the `combined.js` script which contains all the sub-benchmarks in the `bench-v8` directory. ```bash cargo run --release -p boa_cli -- bench-v8/combined.js ``` > [!TIP] > > If you'd like to run only a subset of the benchmarks, you can modify the `Makefile` located in the [`bench-v8` directory][boa-benchmarks]. > Comment out the benchmarks you don't want to include, then run `make`. After that, you can run Boa using the same command as above. [boa-benchmarks]: https://github.com/boa-dev/data/tree/benchmarks/bench ## 🧠 Profiling See [Profiling](./docs/profiling.md). ## 📆 Changelog See [CHANGELOG.md](./CHANGELOG.md). ## 💬 Communication Feel free to contact us on [Matrix](https://matrix.to/#/#boa:matrix.org) if you have any questions. Contributor discussions take place on the same Matrix Space if you're interested in contributing. We also have a [Discord](https://discord.gg/tUFFk9Y) for any questions or issues. ## ⚖️ License This project is licensed under the [Unlicense](./LICENSE-UNLICENSE) or [MIT](./LICENSE-MIT) licenses, at your option. ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions Until we reach the 1.0 version, we only support the latest 0.x release and the current `main` branch. You can find the latest release of Boa in the [GitHub releases][gh_releases] page or on [crates.io][crate]. [gh_releases]: https://github.com/boa-dev/boa/releases [crate]: https://crates.io/crates/boa_engine/versions ## Reporting a Vulnerability If you find any potential vulnerability, join our [Matrix](https://matrix.to/#/#boa:matrix.org) space and contact anyone who is an admin. Explain how to trigger the vulnerability, where can it be found and any recommendation you might have to fix it. ================================================ FILE: benches/Cargo.toml ================================================ [package] name = "boa_benches" version = "0.1.0" edition = "2024" publish = false [dependencies] boa_engine = { workspace = true, features = ["intl_bundled"] } boa_runtime.workspace = true [dev-dependencies] criterion.workspace = true walkdir = "2" [target.x86_64-unknown-linux-gnu.dev-dependencies] jemallocator.workspace = true [[bench]] name = "scripts" harness = false ================================================ FILE: benches/benches/scripts.rs ================================================ #![allow(unused_crate_dependencies, missing_docs)] use boa_engine::{ Context, JsValue, Source, js_string, optimizer::OptimizerOptions, script::Script, }; use criterion::{Criterion, criterion_group, criterion_main}; use std::{path::Path, time::Duration}; #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] #[global_allocator] static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; fn bench_scripts(c: &mut Criterion) { let scripts_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("scripts"); let scripts: Vec<_> = walkdir::WalkDir::new(&scripts_dir) .into_iter() .filter_map(|e| e.ok()) .filter(|e| { let path = e.path(); path.extension().is_some_and(|ext| ext == "js") && path .file_name() .is_some_and(|base| !base.display().to_string().starts_with("_")) }) .collect(); for entry in scripts { let path = entry.path(); let code = std::fs::read_to_string(path).unwrap(); // Create a nice benchmark name from the relative path let rel_path = path.strip_prefix(&scripts_dir).unwrap().with_extension(""); let name = rel_path.display().to_string(); let mut group = c.benchmark_group(&name); // Use reduced sample size for slow benchmarks (e.g., v8-benches) if rel_path.starts_with("v8-benches") { group.sample_size(10); group.measurement_time(Duration::from_secs(5)); } let context = &mut Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); // Register runtime for console.log support boa_runtime::register( boa_runtime::extensions::ConsoleExtension(boa_runtime::NullLogger), None, context, ) .expect("Runtime registration failed"); // Parse and compile once, outside the benchmark loop let script = Script::parse(Source::from_bytes(&code), None, context).unwrap(); script.codeblock(context).unwrap(); // Evaluate once to define the main function script.evaluate(context).unwrap(); // Get the main function let function = context .global_object() .get(js_string!("main"), context) .unwrap_or_else(|_| panic!("No main function defined in script: {}", path.display())) .as_callable() .unwrap_or_else(|| panic!("'main' is not a function in script: {}", path.display())) .clone(); group.bench_function("Execution", |b| { b.iter(|| function.call(&JsValue::undefined(), &[], context)); }); group.finish(); } } criterion_group!(benches, bench_scripts); criterion_main!(benches); ================================================ FILE: benches/scripts/basic/call-loop.js ================================================ function f() {} function main() { for (let i = 0; i < 100_000; i++) { f(); } } ================================================ FILE: benches/scripts/basic/closure.js ================================================ function outer() { let x = 1; function middle() { let y = 2; function inner() { return x + y; } return inner; } return middle; } let f = outer()(); function main() { for (let n = 0; n < 10_000_000; n++) { f(); } } ================================================ FILE: benches/scripts/basic/nested-loop.js ================================================ const n = 1_000; function main() { for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) {} } } ================================================ FILE: benches/scripts/closures/create.js ================================================ // Measures closure creation overhead: allocation of function objects // and captured variable environments inside a hot loop. const kIterationCount = 100_000; function main() { let sum = 0; for (let i = 0; i < kIterationCount; i++) { const x = i; const y = i * 2; const closure = (z) => x + y + z; sum += closure(i + 1); } return sum; } ================================================ FILE: benches/scripts/closures/invoke.js ================================================ // Measures closure invocation overhead: closures are created once, // then called repeatedly to isolate dispatch and variable lookup cost. const kIterationCount = 100_000; function makeCounter(start) { let count = start; return { increment: (n) => { count += n; return count; }, decrement: (n) => { count -= n; return count; }, value: () => count, }; } const counterA = makeCounter(0); const counterB = makeCounter(1000); const counterC = makeCounter(-500); function main() { let sum = 0; for (let i = 0; i < kIterationCount; i++) { sum += counterA.increment(1); sum += counterB.decrement(1); sum += counterC.value(); } return sum; } ================================================ FILE: benches/scripts/intl/collator-compare.js ================================================ const kIterationCount = 1000; const collator = new Intl.Collator("en"); function main() { for (let i = 0; i < kIterationCount; i++) { collator.compare("apple", "banana"); } } ================================================ FILE: benches/scripts/intl/collator-construction.js ================================================ const kIterationCount = 100; const locales = ["en", "de", "ja", "ar", "fr", "es", "zh"]; function main() { for (let i = 0; i < kIterationCount; i++) { for (const locale of locales) { new Intl.Collator(locale); } } } ================================================ FILE: benches/scripts/intl/datetimeformat-construction.js ================================================ const kIterationCount = 100; const locales = ["en", "de", "ja", "ar", "fr", "es", "zh"]; function main() { for (let i = 0; i < kIterationCount; i++) { for (const locale of locales) { new Intl.DateTimeFormat(locale); } } } ================================================ FILE: benches/scripts/intl/datetimeformat-format.js ================================================ const kIterationCount = 1000; const dtf = new Intl.DateTimeFormat("en"); const date = new Date(2024, 0, 1); function main() { for (let i = 0; i < kIterationCount; i++) { dtf.format(date); } } ================================================ FILE: benches/scripts/intl/datetimeformat-with-options.js ================================================ const kIterationCount = 100; function main() { for (let i = 0; i < kIterationCount; i++) { new Intl.DateTimeFormat("en", { year: "numeric", month: "long", day: "numeric", }); new Intl.DateTimeFormat("en", { hour: "2-digit", minute: "2-digit", second: "2-digit", }); } } ================================================ FILE: benches/scripts/intl/datetimeformat_resolved_options.js ================================================ const kIterationCount = 1000; const fmt = new Intl.DateTimeFormat("en-US", { dateStyle: "full", timeStyle: "short", timeZone: "UTC", }); function main() { for (let i = 0; i < kIterationCount; i++) { fmt.resolvedOptions(); } } ================================================ FILE: benches/scripts/intl/listformat-construction.js ================================================ const kIterationCount = 100; const locales = ["en", "de", "ja", "ar", "fr", "es", "zh"]; function main() { for (let i = 0; i < kIterationCount; i++) { for (const locale of locales) { new Intl.ListFormat(locale); } } } ================================================ FILE: benches/scripts/intl/listformat-format.js ================================================ const kIterationCount = 1000; const lf = new Intl.ListFormat("en"); const list = ["apple", "banana", "cherry"]; function main() { for (let i = 0; i < kIterationCount; i++) { lf.format(list); } } ================================================ FILE: benches/scripts/intl/numberformat-construction.js ================================================ const kIterationCount = 100; const locales = ["en", "de", "ja", "ar", "fr", "es", "zh"]; function main() { for (let i = 0; i < kIterationCount; i++) { for (const locale of locales) { new Intl.NumberFormat(locale); } } } ================================================ FILE: benches/scripts/intl/numberformat-different-options.js ================================================ const kIterationCount = 100; function main() { for (let i = 0; i < kIterationCount; i++) { new Intl.NumberFormat("en", { style: "currency", currency: "USD" }); new Intl.NumberFormat("en", { style: "percent" }); new Intl.NumberFormat("en", { notation: "scientific" }); } } ================================================ FILE: benches/scripts/intl/pluralrules-construction.js ================================================ const kIterationCount = 100; const locales = ["en", "de", "ja", "ar", "fr", "es", "zh"]; function main() { for (let i = 0; i < kIterationCount; i++) { for (const locale of locales) { new Intl.PluralRules(locale); } } } ================================================ FILE: benches/scripts/intl/pluralrules-select.js ================================================ const kIterationCount = 1000; const pr = new Intl.PluralRules("en"); function main() { for (let i = 0; i < kIterationCount; i++) { pr.select(i); } } ================================================ FILE: benches/scripts/intl/segmenter-construction.js ================================================ const kIterationCount = 100; const locales = ["en", "de", "ja", "ar", "fr", "es", "zh"]; function main() { for (let i = 0; i < kIterationCount; i++) { for (const locale of locales) { new Intl.Segmenter(locale); } } } ================================================ FILE: benches/scripts/intl/segmenter-segment.js ================================================ const kIterationCount = 100; const segmenter = new Intl.Segmenter("en"); const text = "The quick brown fox jumps over the lazy dog."; function main() { for (let i = 0; i < kIterationCount; i++) { [...segmenter.segment(text)]; } } ================================================ FILE: benches/scripts/properties/access.js ================================================ // Measures property access performance across varying object shapes: // monomorphic (single shape), polymorphic (few shapes), and // megamorphic (many shapes) lookup patterns. const kIterationCount = 50_000; // Monomorphic: always the same object shape. function monoAccess(obj) { return obj.x + obj.y + obj.z; } // Polymorphic: receives objects with a few different shapes. function polyAccess(obj) { return obj.value + obj.id; } // Test data: objects with distinct shapes. const mono = { x: 1, y: 2, z: 3 }; const polyA = { value: 10, id: 1, a: "extra" }; const polyB = { id: 2, b: true, value: 20 }; const polyC = { c: null, d: 4, value: 30, id: 3 }; const polyD = { value: 40, id: 4, e: [1, 2] }; // Megamorphic: 20 objects, each with a unique shape. const megaObjects = []; for (let i = 0; i < 20; i++) { const obj = { value: i * 100, id: i }; for (let j = 0; j < i; j++) { obj["prop" + j] = j; } megaObjects.push(obj); } function main() { let sum = 0; // Monomorphic access (single shape, hot path). for (let i = 0; i < kIterationCount; i++) { sum += monoAccess(mono); } // Polymorphic access (4 shapes rotating). const polyObjs = [polyA, polyB, polyC, polyD]; for (let i = 0; i < kIterationCount; i++) { sum += polyAccess(polyObjs[i % 4]); } // Megamorphic access (20 unique shapes). for (let i = 0; i < kIterationCount; i++) { sum += polyAccess(megaObjects[i % 20]); } return sum; } ================================================ FILE: benches/scripts/prototypes/chain.js ================================================ // Measures property resolution through prototype chains of varying // depth (1, 5, and 10 levels) to quantify chain-walking overhead. const kIterationCount = 100_000; // Build a prototype chain of the given depth with a base property. function buildChain(depth) { let proto = { baseValue: 42, type: "root" }; for (let i = 0; i < depth; i++) { const child = Object.create(proto); child["level" + i] = i; child["name" + i] = "node_" + i; proto = child; } return proto; } const shallow = buildChain(1); const medium = buildChain(5); const deep = buildChain(10); function main() { let sum = 0; // Shallow chain: 1 prototype hop to reach baseValue. for (let i = 0; i < kIterationCount; i++) { sum += shallow.baseValue; } // Medium chain: 5 prototype hops. for (let i = 0; i < kIterationCount; i++) { sum += medium.baseValue; } // Deep chain: 10 prototype hops. for (let i = 0; i < kIterationCount; i++) { sum += deep.baseValue; } return sum; } ================================================ FILE: benches/scripts/strings/concat.js ================================================ // Measures repeated string concatenation via the += operator, // stressing string allocation and garbage collection throughput. const kIterationCount = 10_000; const fragments = [ "hello", " ", "world", "! ", "foo", "bar", "baz", " ", "qux", "\n", ]; function main() { let result = ""; for (let i = 0; i < kIterationCount; i++) { result += fragments[i % fragments.length]; } return result.length; } ================================================ FILE: benches/scripts/strings/replace.js ================================================ // Measures String.prototype.replace performance: substring search // and per-replacement allocation cost on short strings. const kIterationCount = 10_000; const templates = [ "the quick brown fox jumps over the lazy dog", "a fast red fox leaps across the slow cat", "one small step for man one giant leap for mankind", ]; const replacements = [ ["fox", "cat"], ["quick", "slow"], ["the", "a"], ]; function main() { let result = ""; for (let i = 0; i < kIterationCount; i++) { let s = templates[i % templates.length]; const pair = replacements[i % replacements.length]; s = s.replace(pair[0], pair[1]); result = s; } return result.length; } ================================================ FILE: benches/scripts/strings/slice.js ================================================ // This script should take a few seconds to run. const kIterationCount = 10_000; const base = "abcdefghijklmnopqrstuvwxyz".repeat(10_000_000); function main() { for (let i = 0; i < kIterationCount; i++) { base.slice(i * 100, i * 100 + 20000); } } ================================================ FILE: benches/scripts/strings/split.js ================================================ // This script should take a few seconds to run. const kIterationCount = 10_000; const base = "abcdefghijklmnopqrstuvwxyz".repeat(1_000); function main() { const k = base.split("a").length; console.log(k); } ================================================ FILE: benches/scripts/v8-benches/README.md ================================================ # V8 Benchmarks These benchmarks were copied from https://chromium.googlesource.com/v8/v8/+/52ab610bd13/benchmarks/ ================================================ FILE: benches/scripts/v8-benches/crypto.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; /* * Copyright (c) 2003-2005 Tom Wu * All Rights Reserved. * * 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" AND WITHOUT WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. * * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * In addition, the following condition applies: * * All redistributions must retain an intact copy of this copyright notice * and disclaimer. */ // Basic JavaScript BN library - subset useful for RSA encryption. // Bits per digit var dbits; var BI_DB; var BI_DM; var BI_DV; var BI_FP; var BI_FV; var BI_F1; var BI_F2; // JavaScript engine analysis var canary = 0xdeadbeefcafe; var j_lm = ((canary & 0xffffff) == 0xefcafe); // (public) Constructor function BigInteger(a, b, c) { this.array = new Array(); if (a != null) if ("number" == typeof a) this.fromNumber(a, b, c); else if (b == null && "string" != typeof a) this.fromString(a, 256); else this.fromString(a, b); } // return new, unset BigInteger function nbi() { return new BigInteger(null); } // am: Compute w_j += (x*this_i), propagate carries, // c is initial carry, returns final carry. // c < 3*dvalue, x < 2*dvalue, this_i < dvalue // We need to select the fastest one that works in this environment. // am1: use a single mult and divide to get the high bits, // max digit bits should be 26 because // max internal value = 2*dvalue^2-2*dvalue (< 2^53) function am1(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; while (--n >= 0) { var v = x * this_array[i++] + w_array[j] + c; c = Math.floor(v / 0x4000000); w_array[j++] = v & 0x3ffffff; } return c; } // am2 avoids a big mult-and-extract completely. // Max digit bits should be <= 30 because we do bitwise ops // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) function am2(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; var xl = x & 0x7fff, xh = x >> 15; while (--n >= 0) { var l = this_array[i] & 0x7fff; var h = this_array[i++] >> 15; var m = xh * l + h * xl; l = xl * l + ((m & 0x7fff) << 15) + w_array[j] + (c & 0x3fffffff); c = (l >>> 30) + (m >>> 15) + xh * h + (c >>> 30); w_array[j++] = l & 0x3fffffff; } return c; } // Alternately, set max digit bits to 28 since some // browsers slow down when dealing with 32-bit numbers. function am3(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; var xl = x & 0x3fff, xh = x >> 14; while (--n >= 0) { var l = this_array[i] & 0x3fff; var h = this_array[i++] >> 14; var m = xh * l + h * xl; l = xl * l + ((m & 0x3fff) << 14) + w_array[j] + c; c = (l >> 28) + (m >> 14) + xh * h; w_array[j++] = l & 0xfffffff; } return c; } // This is tailored to VMs with 2-bit tagging. It makes sure // that all the computations stay within the 29 bits available. function am4(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; var xl = x & 0x1fff, xh = x >> 13; while (--n >= 0) { var l = this_array[i] & 0x1fff; var h = this_array[i++] >> 13; var m = xh * l + h * xl; l = xl * l + ((m & 0x1fff) << 13) + w_array[j] + c; c = (l >> 26) + (m >> 13) + xh * h; w_array[j++] = l & 0x3ffffff; } return c; } // am3/28 is best for SM, Rhino, but am4/26 is best for v8. // Kestrel (Opera 9.5) gets its best result with am4/26. // IE7 does 9% better with am3/28 than with am4/26. // Firefox (SM) gets 10% faster with am3/28 than with am4/26. var setupEngine = function (fn, bits) { BigInteger.prototype.am = fn; dbits = bits; BI_DB = dbits; BI_DM = ((1 << dbits) - 1); BI_DV = (1 << dbits); BI_FP = 52; BI_FV = Math.pow(2, BI_FP); BI_F1 = BI_FP - dbits; BI_F2 = 2 * dbits - BI_FP; } // Digit conversions var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; var BI_RC = new Array(); var rr, vv; rr = "0".charCodeAt(0); for (vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; rr = "a".charCodeAt(0); for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; rr = "A".charCodeAt(0); for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; function int2char(n) { return BI_RM.charAt(n); } function intAt(s, i) { var c = BI_RC[s.charCodeAt(i)]; return (c == null) ? -1 : c; } // (protected) copy this to r function bnpCopyTo(r) { var this_array = this.array; var r_array = r.array; for (var i = this.t - 1; i >= 0; --i) r_array[i] = this_array[i]; r.t = this.t; r.s = this.s; } // (protected) set from integer value x, -DV <= x < DV function bnpFromInt(x) { var this_array = this.array; this.t = 1; this.s = (x < 0) ? -1 : 0; if (x > 0) this_array[0] = x; else if (x < -1) this_array[0] = x + DV; else this.t = 0; } // return bigint initialized to value function nbv(i) { var r = nbi(); r.fromInt(i); return r; } // (protected) set from string and radix function bnpFromString(s, b) { var this_array = this.array; var k; if (b == 16) k = 4; else if (b == 8) k = 3; else if (b == 256) k = 8; // byte array else if (b == 2) k = 1; else if (b == 32) k = 5; else if (b == 4) k = 2; else { this.fromRadix(s, b); return; } this.t = 0; this.s = 0; var i = s.length, mi = false, sh = 0; while (--i >= 0) { var x = (k == 8) ? s[i] & 0xff : intAt(s, i); if (x < 0) { if (s.charAt(i) == "-") mi = true; continue; } mi = false; if (sh == 0) this_array[this.t++] = x; else if (sh + k > BI_DB) { this_array[this.t - 1] |= (x & ((1 << (BI_DB - sh)) - 1)) << sh; this_array[this.t++] = (x >> (BI_DB - sh)); } else this_array[this.t - 1] |= x << sh; sh += k; if (sh >= BI_DB) sh -= BI_DB; } if (k == 8 && (s[0] & 0x80) != 0) { this.s = -1; if (sh > 0) this_array[this.t - 1] |= ((1 << (BI_DB - sh)) - 1) << sh; } this.clamp(); if (mi) BigInteger.ZERO.subTo(this, this); } // (protected) clamp off excess high words function bnpClamp() { var this_array = this.array; var c = this.s & BI_DM; while (this.t > 0 && this_array[this.t - 1] == c) --this.t; } // (public) return string representation in given radix function bnToString(b) { var this_array = this.array; if (this.s < 0) return "-" + this.negate().toString(b); var k; if (b == 16) k = 4; else if (b == 8) k = 3; else if (b == 2) k = 1; else if (b == 32) k = 5; else if (b == 4) k = 2; else return this.toRadix(b); var km = (1 << k) - 1, d, m = false, r = "", i = this.t; var p = BI_DB - (i * BI_DB) % k; if (i-- > 0) { if (p < BI_DB && (d = this_array[i] >> p) > 0) { m = true; r = int2char(d); } while (i >= 0) { if (p < k) { d = (this_array[i] & ((1 << p) - 1)) << (k - p); d |= this_array[--i] >> (p += BI_DB - k); } else { d = (this_array[i] >> (p -= k)) & km; if (p <= 0) { p += BI_DB; --i; } } if (d > 0) m = true; if (m) r += int2char(d); } } return m ? r : "0"; } // (public) -this function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this, r); return r; } // (public) |this| function bnAbs() { return (this.s < 0) ? this.negate() : this; } // (public) return + if this > a, - if this < a, 0 if equal function bnCompareTo(a) { var this_array = this.array; var a_array = a.array; var r = this.s - a.s; if (r != 0) return r; var i = this.t; r = i - a.t; if (r != 0) return r; while (--i >= 0) if ((r = this_array[i] - a_array[i]) != 0) return r; return 0; } // returns bit length of the integer x function nbits(x) { var r = 1, t; if ((t = x >>> 16) != 0) { x = t; r += 16; } if ((t = x >> 8) != 0) { x = t; r += 8; } if ((t = x >> 4) != 0) { x = t; r += 4; } if ((t = x >> 2) != 0) { x = t; r += 2; } if ((t = x >> 1) != 0) { x = t; r += 1; } return r; } // (public) return the number of bits in "this" function bnBitLength() { var this_array = this.array; if (this.t <= 0) return 0; return BI_DB * (this.t - 1) + nbits(this_array[this.t - 1] ^ (this.s & BI_DM)); } // (protected) r = this << n*DB function bnpDLShiftTo(n, r) { var this_array = this.array; var r_array = r.array; var i; for (i = this.t - 1; i >= 0; --i) r_array[i + n] = this_array[i]; for (i = n - 1; i >= 0; --i) r_array[i] = 0; r.t = this.t + n; r.s = this.s; } // (protected) r = this >> n*DB function bnpDRShiftTo(n, r) { var this_array = this.array; var r_array = r.array; for (var i = n; i < this.t; ++i) r_array[i - n] = this_array[i]; r.t = Math.max(this.t - n, 0); r.s = this.s; } // (protected) r = this << n function bnpLShiftTo(n, r) { var this_array = this.array; var r_array = r.array; var bs = n % BI_DB; var cbs = BI_DB - bs; var bm = (1 << cbs) - 1; var ds = Math.floor(n / BI_DB), c = (this.s << bs) & BI_DM, i; for (i = this.t - 1; i >= 0; --i) { r_array[i + ds + 1] = (this_array[i] >> cbs) | c; c = (this_array[i] & bm) << bs; } for (i = ds - 1; i >= 0; --i) r_array[i] = 0; r_array[ds] = c; r.t = this.t + ds + 1; r.s = this.s; r.clamp(); } // (protected) r = this >> n function bnpRShiftTo(n, r) { var this_array = this.array; var r_array = r.array; r.s = this.s; var ds = Math.floor(n / BI_DB); if (ds >= this.t) { r.t = 0; return; } var bs = n % BI_DB; var cbs = BI_DB - bs; var bm = (1 << bs) - 1; r_array[0] = this_array[ds] >> bs; for (var i = ds + 1; i < this.t; ++i) { r_array[i - ds - 1] |= (this_array[i] & bm) << cbs; r_array[i - ds] = this_array[i] >> bs; } if (bs > 0) r_array[this.t - ds - 1] |= (this.s & bm) << cbs; r.t = this.t - ds; r.clamp(); } // (protected) r = this - a function bnpSubTo(a, r) { var this_array = this.array; var r_array = r.array; var a_array = a.array; var i = 0, c = 0, m = Math.min(a.t, this.t); while (i < m) { c += this_array[i] - a_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } if (a.t < this.t) { c -= a.s; while (i < this.t) { c += this_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c += this.s; } else { c += this.s; while (i < a.t) { c -= a_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c -= a.s; } r.s = (c < 0) ? -1 : 0; if (c < -1) r_array[i++] = BI_DV + c; else if (c > 0) r_array[i++] = c; r.t = i; r.clamp(); } // (protected) r = this * a, r != this,a (HAC 14.12) // "this" should be the larger one if appropriate. function bnpMultiplyTo(a, r) { var this_array = this.array; var r_array = r.array; var x = this.abs(), y = a.abs(); var y_array = y.array; var i = x.t; r.t = i + y.t; while (--i >= 0) r_array[i] = 0; for (i = 0; i < y.t; ++i) r_array[i + x.t] = x.am(0, y_array[i], r, i, 0, x.t); r.s = 0; r.clamp(); if (this.s != a.s) BigInteger.ZERO.subTo(r, r); } // (protected) r = this^2, r != this (HAC 14.16) function bnpSquareTo(r) { var x = this.abs(); var x_array = x.array; var r_array = r.array; var i = r.t = 2 * x.t; while (--i >= 0) r_array[i] = 0; for (i = 0; i < x.t - 1; ++i) { var c = x.am(i, x_array[i], r, 2 * i, 0, 1); if ((r_array[i + x.t] += x.am(i + 1, 2 * x_array[i], r, 2 * i + 1, c, x.t - i - 1)) >= BI_DV) { r_array[i + x.t] -= BI_DV; r_array[i + x.t + 1] = 1; } } if (r.t > 0) r_array[r.t - 1] += x.am(i, x_array[i], r, 2 * i, 0, 1); r.s = 0; r.clamp(); } // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. function bnpDivRemTo(m, q, r) { var pm = m.abs(); if (pm.t <= 0) return; var pt = this.abs(); if (pt.t < pm.t) { if (q != null) q.fromInt(0); if (r != null) this.copyTo(r); return; } if (r == null) r = nbi(); var y = nbi(), ts = this.s, ms = m.s; var pm_array = pm.array; var nsh = BI_DB - nbits(pm_array[pm.t - 1]); // normalize modulus if (nsh > 0) { pm.lShiftTo(nsh, y); pt.lShiftTo(nsh, r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; var y_array = y.array; var y0 = y_array[ys - 1]; if (y0 == 0) return; var yt = y0 * (1 << BI_F1) + ((ys > 1) ? y_array[ys - 2] >> BI_F2 : 0); var d1 = BI_FV / yt, d2 = (1 << BI_F1) / yt, e = 1 << BI_F2; var i = r.t, j = i - ys, t = (q == null) ? nbi() : q; y.dlShiftTo(j, t); var r_array = r.array; if (r.compareTo(t) >= 0) { r_array[r.t++] = 1; r.subTo(t, r); } BigInteger.ONE.dlShiftTo(ys, t); t.subTo(y, y); // "negative" y so we can replace sub with am later while (y.t < ys) y_array[y.t++] = 0; while (--j >= 0) { // Estimate quotient digit var qd = (r_array[--i] == y0) ? BI_DM : Math.floor(r_array[i] * d1 + (r_array[i - 1] + e) * d2); if ((r_array[i] += y.am(0, qd, r, j, 0, ys)) < qd) { // Try it out y.dlShiftTo(j, t); r.subTo(t, r); while (r_array[i] < --qd) r.subTo(t, r); } } if (q != null) { r.drShiftTo(ys, q); if (ts != ms) BigInteger.ZERO.subTo(q, q); } r.t = ys; r.clamp(); if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder if (ts < 0) BigInteger.ZERO.subTo(r, r); } // (public) this mod a function bnMod(a) { var r = nbi(); this.abs().divRemTo(a, null, r); if (this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r, r); return r; } // Modular reduction using "classic" algorithm function Classic(m) { this.m = m; } function cConvert(x) { if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); else return x; } function cRevert(x) { return x; } function cReduce(x) { x.divRemTo(this.m, null, x); } function cMulTo(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } function cSqrTo(x, r) { x.squareTo(r); this.reduce(r); } Classic.prototype.convert = cConvert; Classic.prototype.revert = cRevert; Classic.prototype.reduce = cReduce; Classic.prototype.mulTo = cMulTo; Classic.prototype.sqrTo = cSqrTo; // (protected) return "-1/this % 2^DB"; useful for Mont. reduction // justification: // xy == 1 (mod m) // xy = 1+km // xy(2-xy) = (1+km)(1-km) // x[y(2-xy)] = 1-k^2m^2 // x[y(2-xy)] == 1 (mod m^2) // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. // JS multiply "overflows" differently from C/C++, so care is needed here. function bnpInvDigit() { var this_array = this.array; if (this.t < 1) return 0; var x = this_array[0]; if ((x & 1) == 0) return 0; var y = x & 3; // y == 1/x mod 2^2 y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 // last step - calculate inverse mod DV directly; // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints y = (y * (2 - x * y % BI_DV)) % BI_DV; // y == 1/x mod 2^dbits // we really want the negative inverse, and -DV < y < DV return (y > 0) ? BI_DV - y : -y; } // Montgomery reduction function Montgomery(m) { this.m = m; this.mp = m.invDigit(); this.mpl = this.mp & 0x7fff; this.mph = this.mp >> 15; this.um = (1 << (BI_DB - 15)) - 1; this.mt2 = 2 * m.t; } // xR mod m function montConvert(x) { var r = nbi(); x.abs().dlShiftTo(this.m.t, r); r.divRemTo(this.m, null, r); if (x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r, r); return r; } // x/R mod m function montRevert(x) { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } // x = x/R mod m (HAC 14.32) function montReduce(x) { var x_array = x.array; while (x.t <= this.mt2) // pad x so am has enough room later x_array[x.t++] = 0; for (var i = 0; i < this.m.t; ++i) { // faster way of calculating u0 = x[i]*mp mod DV var j = x_array[i] & 0x7fff; var u0 = (j * this.mpl + (((j * this.mph + (x_array[i] >> 15) * this.mpl) & this.um) << 15)) & BI_DM; // use am to combine the multiply-shift-add into one call j = i + this.m.t; x_array[j] += this.m.am(0, u0, x, i, 0, this.m.t); // propagate carry while (x_array[j] >= BI_DV) { x_array[j] -= BI_DV; x_array[++j]++; } } x.clamp(); x.drShiftTo(this.m.t, x); if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); } // r = "x^2/R mod m"; x != r function montSqrTo(x, r) { x.squareTo(r); this.reduce(r); } // r = "xy/R mod m"; x,y != r function montMulTo(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } Montgomery.prototype.convert = montConvert; Montgomery.prototype.revert = montRevert; Montgomery.prototype.reduce = montReduce; Montgomery.prototype.mulTo = montMulTo; Montgomery.prototype.sqrTo = montSqrTo; // (protected) true iff this is even function bnpIsEven() { var this_array = this.array; return ((this.t > 0) ? (this_array[0] & 1) : this.s) == 0; } // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) function bnpExp(e, z) { if (e > 0xffffffff || e < 1) return BigInteger.ONE; var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e) - 1; g.copyTo(r); while (--i >= 0) { z.sqrTo(r, r2); if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); else { var t = r; r = r2; r2 = t; } } return z.revert(r); } // (public) this^e % m, 0 <= e < 2^32 function bnModPowInt(e, m) { var z; if (e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); return this.exp(e, z); } // protected BigInteger.prototype.copyTo = bnpCopyTo; BigInteger.prototype.fromInt = bnpFromInt; BigInteger.prototype.fromString = bnpFromString; BigInteger.prototype.clamp = bnpClamp; BigInteger.prototype.dlShiftTo = bnpDLShiftTo; BigInteger.prototype.drShiftTo = bnpDRShiftTo; BigInteger.prototype.lShiftTo = bnpLShiftTo; BigInteger.prototype.rShiftTo = bnpRShiftTo; BigInteger.prototype.subTo = bnpSubTo; BigInteger.prototype.multiplyTo = bnpMultiplyTo; BigInteger.prototype.squareTo = bnpSquareTo; BigInteger.prototype.divRemTo = bnpDivRemTo; BigInteger.prototype.invDigit = bnpInvDigit; BigInteger.prototype.isEven = bnpIsEven; BigInteger.prototype.exp = bnpExp; // public BigInteger.prototype.toString = bnToString; BigInteger.prototype.negate = bnNegate; BigInteger.prototype.abs = bnAbs; BigInteger.prototype.compareTo = bnCompareTo; BigInteger.prototype.bitLength = bnBitLength; BigInteger.prototype.mod = bnMod; BigInteger.prototype.modPowInt = bnModPowInt; // "constants" BigInteger.ZERO = nbv(0); BigInteger.ONE = nbv(1); // Copyright (c) 2005 Tom Wu // All Rights Reserved. // See "LICENSE" for details. // Extended JavaScript BN functions, required for RSA private ops. // (public) function bnClone() { var r = nbi(); this.copyTo(r); return r; } // (public) return value as integer function bnIntValue() { var this_array = this.array; if (this.s < 0) { if (this.t == 1) return this_array[0] - BI_DV; else if (this.t == 0) return -1; } else if (this.t == 1) return this_array[0]; else if (this.t == 0) return 0; // assumes 16 < DB < 32 return ((this_array[1] & ((1 << (32 - BI_DB)) - 1)) << BI_DB) | this_array[0]; } // (public) return value as byte function bnByteValue() { var this_array = this.array; return (this.t == 0) ? this.s : (this_array[0] << 24) >> 24; } // (public) return value as short (assumes DB>=16) function bnShortValue() { var this_array = this.array; return (this.t == 0) ? this.s : (this_array[0] << 16) >> 16; } // (protected) return x s.t. r^x < DV function bnpChunkSize(r) { return Math.floor(Math.LN2 * BI_DB / Math.log(r)); } // (public) 0 if this == 0, 1 if this > 0 function bnSigNum() { var this_array = this.array; if (this.s < 0) return -1; else if (this.t <= 0 || (this.t == 1 && this_array[0] <= 0)) return 0; else return 1; } // (protected) convert to radix string function bnpToRadix(b) { if (b == null) b = 10; if (this.signum() == 0 || b < 2 || b > 36) return "0"; var cs = this.chunkSize(b); var a = Math.pow(b, cs); var d = nbv(a), y = nbi(), z = nbi(), r = ""; this.divRemTo(d, y, z); while (y.signum() > 0) { r = (a + z.intValue()).toString(b).substr(1) + r; y.divRemTo(d, y, z); } return z.intValue().toString(b) + r; } // (protected) convert from radix string function bnpFromRadix(s, b) { this.fromInt(0); if (b == null) b = 10; var cs = this.chunkSize(b); var d = Math.pow(b, cs), mi = false, j = 0, w = 0; for (var i = 0; i < s.length; ++i) { var x = intAt(s, i); if (x < 0) { if (s.charAt(i) == "-" && this.signum() == 0) mi = true; continue; } w = b * w + x; if (++j >= cs) { this.dMultiply(d); this.dAddOffset(w, 0); j = 0; w = 0; } } if (j > 0) { this.dMultiply(Math.pow(b, j)); this.dAddOffset(w, 0); } if (mi) BigInteger.ZERO.subTo(this, this); } // (protected) alternate constructor function bnpFromNumber(a, b, c) { if ("number" == typeof b) { // new BigInteger(int,int,RNG) if (a < 2) this.fromInt(1); else { this.fromNumber(a, c); if (!this.testBit(a - 1)) // force MSB set this.bitwiseTo(BigInteger.ONE.shiftLeft(a - 1), op_or, this); if (this.isEven()) this.dAddOffset(1, 0); // force odd while (!this.isProbablePrime(b)) { this.dAddOffset(2, 0); if (this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a - 1), this); } } } else { // new BigInteger(int,RNG) var x = new Array(), t = a & 7; x.length = (a >> 3) + 1; b.nextBytes(x); if (t > 0) x[0] &= ((1 << t) - 1); else x[0] = 0; this.fromString(x, 256); } } // (public) convert to bigendian byte array function bnToByteArray() { var this_array = this.array; var i = this.t, r = new Array(); r[0] = this.s; var p = BI_DB - (i * BI_DB) % 8, d, k = 0; if (i-- > 0) { if (p < BI_DB && (d = this_array[i] >> p) != (this.s & BI_DM) >> p) r[k++] = d | (this.s << (BI_DB - p)); while (i >= 0) { if (p < 8) { d = (this_array[i] & ((1 << p) - 1)) << (8 - p); d |= this_array[--i] >> (p += BI_DB - 8); } else { d = (this_array[i] >> (p -= 8)) & 0xff; if (p <= 0) { p += BI_DB; --i; } } if ((d & 0x80) != 0) d |= -256; if (k == 0 && (this.s & 0x80) != (d & 0x80)) ++k; if (k > 0 || d != this.s) r[k++] = d; } } return r; } function bnEquals(a) { return (this.compareTo(a) == 0); } function bnMin(a) { return (this.compareTo(a) < 0) ? this : a; } function bnMax(a) { return (this.compareTo(a) > 0) ? this : a; } // (protected) r = this op a (bitwise) function bnpBitwiseTo(a, op, r) { var this_array = this.array; var a_array = a.array; var r_array = r.array; var i, f, m = Math.min(a.t, this.t); for (i = 0; i < m; ++i) r_array[i] = op(this_array[i], a_array[i]); if (a.t < this.t) { f = a.s & BI_DM; for (i = m; i < this.t; ++i) r_array[i] = op(this_array[i], f); r.t = this.t; } else { f = this.s & BI_DM; for (i = m; i < a.t; ++i) r_array[i] = op(f, a_array[i]); r.t = a.t; } r.s = op(this.s, a.s); r.clamp(); } // (public) this & a function op_and(x, y) { return x & y; } function bnAnd(a) { var r = nbi(); this.bitwiseTo(a, op_and, r); return r; } // (public) this | a function op_or(x, y) { return x | y; } function bnOr(a) { var r = nbi(); this.bitwiseTo(a, op_or, r); return r; } // (public) this ^ a function op_xor(x, y) { return x ^ y; } function bnXor(a) { var r = nbi(); this.bitwiseTo(a, op_xor, r); return r; } // (public) this & ~a function op_andnot(x, y) { return x & ~y; } function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a, op_andnot, r); return r; } // (public) ~this function bnNot() { var this_array = this.array; var r = nbi(); var r_array = r.array; for (var i = 0; i < this.t; ++i) r_array[i] = BI_DM & ~this_array[i]; r.t = this.t; r.s = ~this.s; return r; } // (public) this << n function bnShiftLeft(n) { var r = nbi(); if (n < 0) this.rShiftTo(-n, r); else this.lShiftTo(n, r); return r; } // (public) this >> n function bnShiftRight(n) { var r = nbi(); if (n < 0) this.lShiftTo(-n, r); else this.rShiftTo(n, r); return r; } // return index of lowest 1-bit in x, x < 2^31 function lbit(x) { if (x == 0) return -1; var r = 0; if ((x & 0xffff) == 0) { x >>= 16; r += 16; } if ((x & 0xff) == 0) { x >>= 8; r += 8; } if ((x & 0xf) == 0) { x >>= 4; r += 4; } if ((x & 3) == 0) { x >>= 2; r += 2; } if ((x & 1) == 0) ++r; return r; } // (public) returns index of lowest 1-bit (or -1 if none) function bnGetLowestSetBit() { var this_array = this.array; for (var i = 0; i < this.t; ++i) if (this_array[i] != 0) return i * BI_DB + lbit(this_array[i]); if (this.s < 0) return this.t * BI_DB; return -1; } // return number of 1 bits in x function cbit(x) { var r = 0; while (x != 0) { x &= x - 1; ++r; } return r; } // (public) return number of set bits function bnBitCount() { var r = 0, x = this.s & BI_DM; for (var i = 0; i < this.t; ++i) r += cbit(this_array[i] ^ x); return r; } // (public) true iff nth bit is set function bnTestBit(n) { var this_array = this.array; var j = Math.floor(n / BI_DB); if (j >= this.t) return (this.s != 0); return ((this_array[j] & (1 << (n % BI_DB))) != 0); } // (protected) this op (1<>= BI_DB; } if (a.t < this.t) { c += a.s; while (i < this.t) { c += this_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c += this.s; } else { c += this.s; while (i < a.t) { c += a_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c += a.s; } r.s = (c < 0) ? -1 : 0; if (c > 0) r_array[i++] = c; else if (c < -1) r_array[i++] = BI_DV + c; r.t = i; r.clamp(); } // (public) this + a function bnAdd(a) { var r = nbi(); this.addTo(a, r); return r; } // (public) this - a function bnSubtract(a) { var r = nbi(); this.subTo(a, r); return r; } // (public) this * a function bnMultiply(a) { var r = nbi(); this.multiplyTo(a, r); return r; } // (public) this / a function bnDivide(a) { var r = nbi(); this.divRemTo(a, r, null); return r; } // (public) this % a function bnRemainder(a) { var r = nbi(); this.divRemTo(a, null, r); return r; } // (public) [this/a,this%a] function bnDivideAndRemainder(a) { var q = nbi(), r = nbi(); this.divRemTo(a, q, r); return new Array(q, r); } // (protected) this *= n, this >= 0, 1 < n < DV function bnpDMultiply(n) { var this_array = this.array; this_array[this.t] = this.am(0, n - 1, this, 0, 0, this.t); ++this.t; this.clamp(); } // (protected) this += n << w words, this >= 0 function bnpDAddOffset(n, w) { var this_array = this.array; while (this.t <= w) this_array[this.t++] = 0; this_array[w] += n; while (this_array[w] >= BI_DV) { this_array[w] -= BI_DV; if (++w >= this.t) this_array[this.t++] = 0; ++this_array[w]; } } // A "null" reducer function NullExp() { } function nNop(x) { return x; } function nMulTo(x, y, r) { x.multiplyTo(y, r); } function nSqrTo(x, r) { x.squareTo(r); } NullExp.prototype.convert = nNop; NullExp.prototype.revert = nNop; NullExp.prototype.mulTo = nMulTo; NullExp.prototype.sqrTo = nSqrTo; // (public) this^e function bnPow(e) { return this.exp(e, new NullExp()); } // (protected) r = lower n words of "this * a", a.t <= n // "this" should be the larger one if appropriate. function bnpMultiplyLowerTo(a, n, r) { var r_array = r.array; var a_array = a.array; var i = Math.min(this.t + a.t, n); r.s = 0; // assumes a,this >= 0 r.t = i; while (i > 0) r_array[--i] = 0; var j; for (j = r.t - this.t; i < j; ++i) r_array[i + this.t] = this.am(0, a_array[i], r, i, 0, this.t); for (j = Math.min(a.t, n); i < j; ++i) this.am(0, a_array[i], r, i, 0, n - i); r.clamp(); } // (protected) r = "this * a" without lower n words, n > 0 // "this" should be the larger one if appropriate. function bnpMultiplyUpperTo(a, n, r) { var r_array = r.array; var a_array = a.array; --n; var i = r.t = this.t + a.t - n; r.s = 0; // assumes a,this >= 0 while (--i >= 0) r_array[i] = 0; for (i = Math.max(n - this.t, 0); i < a.t; ++i) r_array[this.t + i - n] = this.am(n - i, a_array[i], r, 0, 0, this.t + i - n); r.clamp(); r.drShiftTo(1, r); } // Barrett modular reduction function Barrett(m) { // setup Barrett this.r2 = nbi(); this.q3 = nbi(); BigInteger.ONE.dlShiftTo(2 * m.t, this.r2); this.mu = this.r2.divide(m); this.m = m; } function barrettConvert(x) { if (x.s < 0 || x.t > 2 * this.m.t) return x.mod(this.m); else if (x.compareTo(this.m) < 0) return x; else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } } function barrettRevert(x) { return x; } // x = x mod m (HAC 14.42) function barrettReduce(x) { x.drShiftTo(this.m.t - 1, this.r2); if (x.t > this.m.t + 1) { x.t = this.m.t + 1; x.clamp(); } this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3); this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); while (x.compareTo(this.r2) < 0) x.dAddOffset(1, this.m.t + 1); x.subTo(this.r2, x); while (x.compareTo(this.m) >= 0) x.subTo(this.m, x); } // r = x^2 mod m; x != r function barrettSqrTo(x, r) { x.squareTo(r); this.reduce(r); } // r = x*y mod m; x,y != r function barrettMulTo(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } Barrett.prototype.convert = barrettConvert; Barrett.prototype.revert = barrettRevert; Barrett.prototype.reduce = barrettReduce; Barrett.prototype.mulTo = barrettMulTo; Barrett.prototype.sqrTo = barrettSqrTo; // (public) this^e % m (HAC 14.85) function bnModPow(e, m) { var e_array = e.array; var i = e.bitLength(), k, r = nbv(1), z; if (i <= 0) return r; else if (i < 18) k = 1; else if (i < 48) k = 3; else if (i < 144) k = 4; else if (i < 768) k = 5; else k = 6; if (i < 8) z = new Classic(m); else if (m.isEven()) z = new Barrett(m); else z = new Montgomery(m); // precomputation var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; g[1] = z.convert(this); if (k > 1) { var g2 = nbi(); z.sqrTo(g[1], g2); while (n <= km) { g[n] = nbi(); z.mulTo(g2, g[n - 2], g[n]); n += 2; } } var j = e.t - 1, w, is1 = true, r2 = nbi(), t; i = nbits(e_array[j]) - 1; while (j >= 0) { if (i >= k1) w = (e_array[j] >> (i - k1)) & km; else { w = (e_array[j] & ((1 << (i + 1)) - 1)) << (k1 - i); if (j > 0) w |= e_array[j - 1] >> (BI_DB + i - k1); } n = k; while ((w & 1) == 0) { w >>= 1; --n; } if ((i -= n) < 0) { i += BI_DB; --j; } if (is1) { // ret == 1, don't bother squaring or multiplying it g[w].copyTo(r); is1 = false; } else { while (n > 1) { z.sqrTo(r, r2); z.sqrTo(r2, r); n -= 2; } if (n > 0) z.sqrTo(r, r2); else { t = r; r = r2; r2 = t; } z.mulTo(r2, g[w], r); } while (j >= 0 && (e_array[j] & (1 << i)) == 0) { z.sqrTo(r, r2); t = r; r = r2; r2 = t; if (--i < 0) { i = BI_DB - 1; --j; } } } return z.revert(r); } // (public) gcd(this,a) (HAC 14.54) function bnGCD(a) { var x = (this.s < 0) ? this.negate() : this.clone(); var y = (a.s < 0) ? a.negate() : a.clone(); if (x.compareTo(y) < 0) { var t = x; x = y; y = t; } var i = x.getLowestSetBit(), g = y.getLowestSetBit(); if (g < 0) return x; if (i < g) g = i; if (g > 0) { x.rShiftTo(g, x); y.rShiftTo(g, y); } while (x.signum() > 0) { if ((i = x.getLowestSetBit()) > 0) x.rShiftTo(i, x); if ((i = y.getLowestSetBit()) > 0) y.rShiftTo(i, y); if (x.compareTo(y) >= 0) { x.subTo(y, x); x.rShiftTo(1, x); } else { y.subTo(x, y); y.rShiftTo(1, y); } } if (g > 0) y.lShiftTo(g, y); return y; } // (protected) this % n, n < 2^26 function bnpModInt(n) { var this_array = this.array; if (n <= 0) return 0; var d = BI_DV % n, r = (this.s < 0) ? n - 1 : 0; if (this.t > 0) if (d == 0) r = this_array[0] % n; else for (var i = this.t - 1; i >= 0; --i) r = (d * r + this_array[i]) % n; return r; } // (public) 1/this % m (HAC 14.61) function bnModInverse(m) { var ac = m.isEven(); if ((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; var u = m.clone(), v = this.clone(); var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); while (u.signum() != 0) { while (u.isEven()) { u.rShiftTo(1, u); if (ac) { if (!a.isEven() || !b.isEven()) { a.addTo(this, a); b.subTo(m, b); } a.rShiftTo(1, a); } else if (!b.isEven()) b.subTo(m, b); b.rShiftTo(1, b); } while (v.isEven()) { v.rShiftTo(1, v); if (ac) { if (!c.isEven() || !d.isEven()) { c.addTo(this, c); d.subTo(m, d); } c.rShiftTo(1, c); } else if (!d.isEven()) d.subTo(m, d); d.rShiftTo(1, d); } if (u.compareTo(v) >= 0) { u.subTo(v, u); if (ac) a.subTo(c, a); b.subTo(d, b); } else { v.subTo(u, v); if (ac) c.subTo(a, c); d.subTo(b, d); } } if (v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; if (d.compareTo(m) >= 0) return d.subtract(m); if (d.signum() < 0) d.addTo(m, d); else return d; if (d.signum() < 0) return d.add(m); else return d; } var lowprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509]; var lplim = (1 << 26) / lowprimes[lowprimes.length - 1]; // (public) test primality with certainty >= 1-.5^t function bnIsProbablePrime(t) { var i, x = this.abs(); var x_array = x.array; if (x.t == 1 && x_array[0] <= lowprimes[lowprimes.length - 1]) { for (i = 0; i < lowprimes.length; ++i) if (x_array[0] == lowprimes[i]) return true; return false; } if (x.isEven()) return false; i = 1; while (i < lowprimes.length) { var m = lowprimes[i], j = i + 1; while (j < lowprimes.length && m < lplim) m *= lowprimes[j++]; m = x.modInt(m); while (i < j) if (m % lowprimes[i++] == 0) return false; } return x.millerRabin(t); } // (protected) true if probably prime (HAC 4.24, Miller-Rabin) function bnpMillerRabin(t) { var n1 = this.subtract(BigInteger.ONE); var k = n1.getLowestSetBit(); if (k <= 0) return false; var r = n1.shiftRight(k); t = (t + 1) >> 1; if (t > lowprimes.length) t = lowprimes.length; var a = nbi(); for (var i = 0; i < t; ++i) { a.fromInt(lowprimes[i]); var y = a.modPow(r, this); if (y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { var j = 1; while (j++ < k && y.compareTo(n1) != 0) { y = y.modPowInt(2, this); if (y.compareTo(BigInteger.ONE) == 0) return false; } if (y.compareTo(n1) != 0) return false; } } return true; } // protected BigInteger.prototype.chunkSize = bnpChunkSize; BigInteger.prototype.toRadix = bnpToRadix; BigInteger.prototype.fromRadix = bnpFromRadix; BigInteger.prototype.fromNumber = bnpFromNumber; BigInteger.prototype.bitwiseTo = bnpBitwiseTo; BigInteger.prototype.changeBit = bnpChangeBit; BigInteger.prototype.addTo = bnpAddTo; BigInteger.prototype.dMultiply = bnpDMultiply; BigInteger.prototype.dAddOffset = bnpDAddOffset; BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; BigInteger.prototype.modInt = bnpModInt; BigInteger.prototype.millerRabin = bnpMillerRabin; // public BigInteger.prototype.clone = bnClone; BigInteger.prototype.intValue = bnIntValue; BigInteger.prototype.byteValue = bnByteValue; BigInteger.prototype.shortValue = bnShortValue; BigInteger.prototype.signum = bnSigNum; BigInteger.prototype.toByteArray = bnToByteArray; BigInteger.prototype.equals = bnEquals; BigInteger.prototype.min = bnMin; BigInteger.prototype.max = bnMax; BigInteger.prototype.and = bnAnd; BigInteger.prototype.or = bnOr; BigInteger.prototype.xor = bnXor; BigInteger.prototype.andNot = bnAndNot; BigInteger.prototype.not = bnNot; BigInteger.prototype.shiftLeft = bnShiftLeft; BigInteger.prototype.shiftRight = bnShiftRight; BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; BigInteger.prototype.bitCount = bnBitCount; BigInteger.prototype.testBit = bnTestBit; BigInteger.prototype.setBit = bnSetBit; BigInteger.prototype.clearBit = bnClearBit; BigInteger.prototype.flipBit = bnFlipBit; BigInteger.prototype.add = bnAdd; BigInteger.prototype.subtract = bnSubtract; BigInteger.prototype.multiply = bnMultiply; BigInteger.prototype.divide = bnDivide; BigInteger.prototype.remainder = bnRemainder; BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; BigInteger.prototype.modPow = bnModPow; BigInteger.prototype.modInverse = bnModInverse; BigInteger.prototype.pow = bnPow; BigInteger.prototype.gcd = bnGCD; BigInteger.prototype.isProbablePrime = bnIsProbablePrime; // BigInteger interfaces not implemented in jsbn: // BigInteger(int signum, byte[] magnitude) // double doubleValue() // float floatValue() // int hashCode() // long longValue() // static BigInteger valueOf(long val) // prng4.js - uses Arcfour as a PRNG function Arcfour() { this.i = 0; this.j = 0; this.S = new Array(); } // Initialize arcfour context from key, an array of ints, each from [0..255] function ARC4init(key) { var i, j, t; for (i = 0; i < 256; ++i) this.S[i] = i; j = 0; for (i = 0; i < 256; ++i) { j = (j + this.S[i] + key[i % key.length]) & 255; t = this.S[i]; this.S[i] = this.S[j]; this.S[j] = t; } this.i = 0; this.j = 0; } function ARC4next() { var t; this.i = (this.i + 1) & 255; this.j = (this.j + this.S[this.i]) & 255; t = this.S[this.i]; this.S[this.i] = this.S[this.j]; this.S[this.j] = t; return this.S[(t + this.S[this.i]) & 255]; } Arcfour.prototype.init = ARC4init; Arcfour.prototype.next = ARC4next; // Plug in your RNG constructor here function prng_newstate() { return new Arcfour(); } // Pool size must be a multiple of 4 and greater than 32. // An array of bytes the size of the pool will be passed to init() var rng_psize = 256; // Random number generator - requires a PRNG backend, e.g. prng4.js // For best results, put code like // // in your main HTML document. var rng_state; var rng_pool; var rng_pptr; // Mix in a 32-bit integer into the pool function rng_seed_int(x) { rng_pool[rng_pptr++] ^= x & 255; rng_pool[rng_pptr++] ^= (x >> 8) & 255; rng_pool[rng_pptr++] ^= (x >> 16) & 255; rng_pool[rng_pptr++] ^= (x >> 24) & 255; if (rng_pptr >= rng_psize) rng_pptr -= rng_psize; } // Mix in the current time (w/milliseconds) into the pool function rng_seed_time() { // Use pre-computed date to avoid making the benchmark // results dependent on the current date. rng_seed_int(1122926989487); } // Initialize the pool with junk if needed. if (rng_pool == null) { rng_pool = new Array(); rng_pptr = 0; var t; while (rng_pptr < rng_psize) { // extract some randomness from Math.random() t = Math.floor(65536 * Math.random()); rng_pool[rng_pptr++] = t >>> 8; rng_pool[rng_pptr++] = t & 255; } rng_pptr = 0; rng_seed_time(); //rng_seed_int(window.screenX); //rng_seed_int(window.screenY); } function rng_get_byte() { if (rng_state == null) { rng_seed_time(); rng_state = prng_newstate(); rng_state.init(rng_pool); for (rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) rng_pool[rng_pptr] = 0; rng_pptr = 0; //rng_pool = null; } // TODO: allow reseeding after first request return rng_state.next(); } function rng_get_bytes(ba) { var i; for (i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); } function SecureRandom() { } SecureRandom.prototype.nextBytes = rng_get_bytes; // Depends on jsbn.js and rng.js // convert a (hex) string to a bignum object function parseBigInt(str, r) { return new BigInteger(str, r); } function linebrk(s, n) { var ret = ""; var i = 0; while (i + n < s.length) { ret += s.substring(i, i + n) + "\n"; i += n; } return ret + s.substring(i, s.length); } function byte2Hex(b) { if (b < 0x10) return "0" + b.toString(16); else return b.toString(16); } // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint function pkcs1pad2(s, n) { if (n < s.length + 11) { alert("Message too long for RSA"); return null; } var ba = new Array(); var i = s.length - 1; while (i >= 0 && n > 0) ba[--n] = s.charCodeAt(i--); ba[--n] = 0; var rng = new SecureRandom(); var x = new Array(); while (n > 2) { // random non-zero pad x[0] = 0; while (x[0] == 0) rng.nextBytes(x); ba[--n] = x[0]; } ba[--n] = 2; ba[--n] = 0; return new BigInteger(ba); } // "empty" RSA key constructor function RSAKey() { this.n = null; this.e = 0; this.d = null; this.p = null; this.q = null; this.dmp1 = null; this.dmq1 = null; this.coeff = null; } // Set the public key fields N and e from hex strings function RSASetPublic(N, E) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); } else alert("Invalid RSA public key"); } // Perform raw public operation on "x": return x^e (mod n) function RSADoPublic(x) { return x.modPowInt(this.e, this.n); } // Return the PKCS#1 RSA encryption of "text" as an even-length hex string function RSAEncrypt(text) { var m = pkcs1pad2(text, (this.n.bitLength() + 7) >> 3); if (m == null) return null; var c = this.doPublic(m); if (c == null) return null; var h = c.toString(16); if ((h.length & 1) == 0) return h; else return "0" + h; } // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string //function RSAEncryptB64(text) { // var h = this.encrypt(text); // if(h) return hex2b64(h); else return null; //} // protected RSAKey.prototype.doPublic = RSADoPublic; // public RSAKey.prototype.setPublic = RSASetPublic; RSAKey.prototype.encrypt = RSAEncrypt; //RSAKey.prototype.encrypt_b64 = RSAEncryptB64; // Depends on rsa.js and jsbn2.js // Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext function pkcs1unpad2(d, n) { var b = d.toByteArray(); var i = 0; while (i < b.length && b[i] == 0) ++i; if (b.length - i != n - 1 || b[i] != 2) return null; ++i; while (b[i] != 0) if (++i >= b.length) return null; var ret = ""; while (++i < b.length) ret += String.fromCharCode(b[i]); return ret; } // Set the private key fields N, e, and d from hex strings function RSASetPrivate(N, E, D) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = parseBigInt(D, 16); } else alert("Invalid RSA private key"); } // Set the private key fields N, e, d and CRT params from hex strings function RSASetPrivateEx(N, E, D, P, Q, DP, DQ, C) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = parseBigInt(D, 16); this.p = parseBigInt(P, 16); this.q = parseBigInt(Q, 16); this.dmp1 = parseBigInt(DP, 16); this.dmq1 = parseBigInt(DQ, 16); this.coeff = parseBigInt(C, 16); } else alert("Invalid RSA private key"); } // Generate a new random private key B bits long, using public expt E function RSAGenerate(B, E) { var rng = new SecureRandom(); var qs = B >> 1; this.e = parseInt(E, 16); var ee = new BigInteger(E, 16); for (; ;) { for (; ;) { this.p = new BigInteger(B - qs, 1, rng); if (this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; } for (; ;) { this.q = new BigInteger(qs, 1, rng); if (this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; } if (this.p.compareTo(this.q) <= 0) { var t = this.p; this.p = this.q; this.q = t; } var p1 = this.p.subtract(BigInteger.ONE); var q1 = this.q.subtract(BigInteger.ONE); var phi = p1.multiply(q1); if (phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { this.n = this.p.multiply(this.q); this.d = ee.modInverse(phi); this.dmp1 = this.d.mod(p1); this.dmq1 = this.d.mod(q1); this.coeff = this.q.modInverse(this.p); break; } } } // Perform raw private operation on "x": return x^d (mod n) function RSADoPrivate(x) { if (this.p == null || this.q == null) return x.modPow(this.d, this.n); // TODO: re-calculate any missing CRT params var xp = x.mod(this.p).modPow(this.dmp1, this.p); var xq = x.mod(this.q).modPow(this.dmq1, this.q); while (xp.compareTo(xq) < 0) xp = xp.add(this.p); return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); } // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is an even-length hex string and the output is a plain string. function RSADecrypt(ctext) { var c = parseBigInt(ctext, 16); var m = this.doPrivate(c); if (m == null) return null; return pkcs1unpad2(m, (this.n.bitLength() + 7) >> 3); } // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is a Base64-encoded string and the output is a plain string. //function RSAB64Decrypt(ctext) { // var h = b64tohex(ctext); // if(h) return this.decrypt(h); else return null; //} // protected RSAKey.prototype.doPrivate = RSADoPrivate; // public RSAKey.prototype.setPrivate = RSASetPrivate; RSAKey.prototype.setPrivateEx = RSASetPrivateEx; RSAKey.prototype.generate = RSAGenerate; RSAKey.prototype.decrypt = RSADecrypt; //RSAKey.prototype.b64_decrypt = RSAB64Decrypt; var nValue = "a5261939975948bb7a58dffe5ff54e65f0498f9175f5a09288810b8975871e99af3b5dd94057b0fc07535f5f97444504fa35169d461d0d30cf0192e307727c065168c788771c561a9400fb49175e9e6aa4e23fe11af69e9412dd23b0cb6684c4c2429bce139e848ab26d0829073351f4acd36074eafd036a5eb83359d2a698d3"; var eValue = "10001"; var dValue = "8e9912f6d3645894e8d38cb58c0db81ff516cf4c7e5a14c7f1eddb1459d2cded4d8d293fc97aee6aefb861859c8b6a3d1dfe710463e1f9ddc72048c09751971c4a580aa51eb523357a3cc48d31cfad1d4a165066ed92d4748fb6571211da5cb14bc11b6e2df7c1a559e6d5ac1cd5c94703a22891464fba23d0d965086277a161"; var pValue = "d090ce58a92c75233a6486cb0a9209bf3583b64f540c76f5294bb97d285eed33aec220bde14b2417951178ac152ceab6da7090905b478195498b352048f15e7d"; var qValue = "cab575dc652bb66df15a0359609d51d1db184750c00c6698b90ef3465c99655103edbf0d54c56aec0ce3c4d22592338092a126a0cc49f65a4a30d222b411e58f"; var dmp1Value = "1a24bca8e273df2f0e47c199bbf678604e7df7215480c77c8db39f49b000ce2cf7500038acfff5433b7d582a01f1826e6f4d42e1c57f5e1fef7b12aabc59fd25"; var dmq1Value = "3d06982efbbe47339e1f6d36b1216b8a741d410b0c662f54f7118b27b9a4ec9d914337eb39841d8666f3034408cf94f5b62f11c402fc994fe15a05493150d9fd"; var coeffValue = "3a3e731acd8960b7ff9eb81a7ff93bd1cfa74cbd56987db58b4594fb09c09084db1734c8143f98b602b981aaa9243ca28deb69b5b280ee8dcee0fd2625e53250"; setupEngine(am3, 28); var TEXT = "The quick brown fox jumped over the extremely lazy frog! " + "Now is the time for all good men to come to the party."; var encrypted; function encrypt() { var RSA = new RSAKey(); RSA.setPublic(nValue, eValue); RSA.setPrivateEx(nValue, eValue, dValue, pValue, qValue, dmp1Value, dmq1Value, coeffValue); encrypted = RSA.encrypt(TEXT); } function decrypt() { var RSA = new RSAKey(); RSA.setPublic(nValue, eValue); RSA.setPrivateEx(nValue, eValue, dValue, pValue, qValue, dmp1Value, dmq1Value, coeffValue); var decrypted = RSA.decrypt(encrypted); if (decrypted != TEXT) { throw new Error("Crypto operation failed"); } } // The code has been adapted for use as a benchmark by Google. var Crypto = new BenchmarkSuite('Crypto', 266181, [ new Benchmark("Encrypt", encrypt), new Benchmark("Decrypt", decrypt) ]); /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/scripts/v8-benches/deltablue.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; // Copyright 2008 the V8 project authors. All rights reserved. // Copyright 1996 John Maloney and Mario Wolczko. // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // This implementation of the DeltaBlue benchmark is derived // from the Smalltalk implementation by John Maloney and Mario // Wolczko. Some parts have been translated directly, whereas // others have been modified more aggresively to make it feel // more like a JavaScript program. /** * A JavaScript implementation of the DeltaBlue constraint-solving * algorithm, as described in: * * "The DeltaBlue Algorithm: An Incremental Constraint Hierarchy Solver" * Bjorn N. Freeman-Benson and John Maloney * January 1990 Communications of the ACM, * also available as University of Washington TR 89-08-06. * * Beware: this benchmark is written in a grotesque style where * the constraint model is built by side-effects from constructors. * I've kept it this way to avoid deviating too much from the original * implementation. */ /* --- O b j e c t M o d e l --- */ Object.prototype.inheritsFrom = function (shuper) { function Inheriter() { } Inheriter.prototype = shuper.prototype; this.prototype = new Inheriter(); this.superConstructor = shuper; }; function OrderedCollection() { this.elms = new Array(); } OrderedCollection.prototype.add = function (elm) { this.elms.push(elm); }; OrderedCollection.prototype.at = function (index) { return this.elms[index]; }; OrderedCollection.prototype.size = function () { return this.elms.length; }; OrderedCollection.prototype.removeFirst = function () { return this.elms.pop(); }; OrderedCollection.prototype.remove = function (elm) { var index = 0, skipped = 0; for (var i = 0; i < this.elms.length; i++) { var value = this.elms[i]; if (value != elm) { this.elms[index] = value; index++; } else { skipped++; } } for (var i = 0; i < skipped; i++) this.elms.pop(); }; /* --- * * S t r e n g t h * --- */ /** * Strengths are used to measure the relative importance of constraints. * New strengths may be inserted in the strength hierarchy without * disrupting current constraints. Strengths cannot be created outside * this class, so pointer comparison can be used for value comparison. */ function Strength(strengthValue, name) { this.strengthValue = strengthValue; this.name = name; } Strength.stronger = function (s1, s2) { return s1.strengthValue < s2.strengthValue; }; Strength.weaker = function (s1, s2) { return s1.strengthValue > s2.strengthValue; }; Strength.weakestOf = function (s1, s2) { return this.weaker(s1, s2) ? s1 : s2; }; Strength.strongest = function (s1, s2) { return this.stronger(s1, s2) ? s1 : s2; }; Strength.prototype.nextWeaker = function () { switch (this.strengthValue) { case 0: return Strength.STRONG_PREFERRED; case 1: return Strength.PREFERRED; case 2: return Strength.STRONG_DEFAULT; case 3: return Strength.NORMAL; case 4: return Strength.WEAK_DEFAULT; case 5: return Strength.WEAKEST; } }; // Strength constants. Strength.REQUIRED = new Strength(0, "required"); Strength.STRONG_PREFERRED = new Strength(1, "strongPreferred"); Strength.PREFERRED = new Strength(2, "preferred"); Strength.STRONG_DEFAULT = new Strength(3, "strongDefault"); Strength.NORMAL = new Strength(4, "normal"); Strength.WEAK_DEFAULT = new Strength(5, "weakDefault"); Strength.WEAKEST = new Strength(6, "weakest"); /* --- * * C o n s t r a i n t * --- */ /** * An abstract class representing a system-maintainable relationship * (or "constraint") between a set of variables. A constraint supplies * a strength instance variable; concrete subclasses provide a means * of storing the constrained variables and other information required * to represent a constraint. */ function Constraint(strength) { this.strength = strength; } /** * Activate this constraint and attempt to satisfy it. */ Constraint.prototype.addConstraint = function () { this.addToGraph(); planner.incrementalAdd(this); }; /** * Attempt to find a way to enforce this constraint. If successful, * record the solution, perhaps modifying the current dataflow * graph. Answer the constraint that this constraint overrides, if * there is one, or nil, if there isn't. * Assume: I am not already satisfied. */ Constraint.prototype.satisfy = function (mark) { this.chooseMethod(mark); if (!this.isSatisfied()) { if (this.strength == Strength.REQUIRED) alert("Could not satisfy a required constraint!"); return null; } this.markInputs(mark); var out = this.output(); var overridden = out.determinedBy; if (overridden != null) overridden.markUnsatisfied(); out.determinedBy = this; if (!planner.addPropagate(this, mark)) alert("Cycle encountered"); out.mark = mark; return overridden; }; Constraint.prototype.destroyConstraint = function () { if (this.isSatisfied()) planner.incrementalRemove(this); else this.removeFromGraph(); }; /** * Normal constraints are not input constraints. An input constraint * is one that depends on external state, such as the mouse, the * keybord, a clock, or some arbitraty piece of imperative code. */ Constraint.prototype.isInput = function () { return false; }; /* --- * * U n a r y C o n s t r a i n t * --- */ /** * Abstract superclass for constraints having a single possible output * variable. */ function UnaryConstraint(v, strength) { UnaryConstraint.superConstructor.call(this, strength); this.myOutput = v; this.satisfied = false; this.addConstraint(); } UnaryConstraint.inheritsFrom(Constraint); /** * Adds this constraint to the constraint graph */ UnaryConstraint.prototype.addToGraph = function () { this.myOutput.addConstraint(this); this.satisfied = false; }; /** * Decides if this constraint can be satisfied and records that * decision. */ UnaryConstraint.prototype.chooseMethod = function (mark) { this.satisfied = (this.myOutput.mark != mark) && Strength.stronger(this.strength, this.myOutput.walkStrength); }; /** * Returns true if this constraint is satisfied in the current solution. */ UnaryConstraint.prototype.isSatisfied = function () { return this.satisfied; }; UnaryConstraint.prototype.markInputs = function (mark) { // has no inputs }; /** * Returns the current output variable. */ UnaryConstraint.prototype.output = function () { return this.myOutput; }; /** * Calculate the walkabout strength, the stay flag, and, if it is * 'stay', the value for the current output of this constraint. Assume * this constraint is satisfied. */ UnaryConstraint.prototype.recalculate = function () { this.myOutput.walkStrength = this.strength; this.myOutput.stay = !this.isInput(); if (this.myOutput.stay) this.execute(); // Stay optimization }; /** * Records that this constraint is unsatisfied */ UnaryConstraint.prototype.markUnsatisfied = function () { this.satisfied = false; }; UnaryConstraint.prototype.inputsKnown = function () { return true; }; UnaryConstraint.prototype.removeFromGraph = function () { if (this.myOutput != null) this.myOutput.removeConstraint(this); this.satisfied = false; }; /* --- * * S t a y C o n s t r a i n t * --- */ /** * Variables that should, with some level of preference, stay the same. * Planners may exploit the fact that instances, if satisfied, will not * change their output during plan execution. This is called "stay * optimization". */ function StayConstraint(v, str) { StayConstraint.superConstructor.call(this, v, str); } StayConstraint.inheritsFrom(UnaryConstraint); StayConstraint.prototype.execute = function () { // Stay constraints do nothing }; /* --- * * E d i t C o n s t r a i n t * --- */ /** * A unary input constraint used to mark a variable that the client * wishes to change. */ function EditConstraint(v, str) { EditConstraint.superConstructor.call(this, v, str); } EditConstraint.inheritsFrom(UnaryConstraint); /** * Edits indicate that a variable is to be changed by imperative code. */ EditConstraint.prototype.isInput = function () { return true; }; EditConstraint.prototype.execute = function () { // Edit constraints do nothing }; /* --- * * B i n a r y C o n s t r a i n t * --- */ var Direction = new Object(); Direction.NONE = 0; Direction.FORWARD = 1; Direction.BACKWARD = -1; /** * Abstract superclass for constraints having two possible output * variables. */ function BinaryConstraint(var1, var2, strength) { BinaryConstraint.superConstructor.call(this, strength); this.v1 = var1; this.v2 = var2; this.direction = Direction.NONE; this.addConstraint(); } BinaryConstraint.inheritsFrom(Constraint); /** * Decides if this constraint can be satisfied and which way it * should flow based on the relative strength of the variables related, * and record that decision. */ BinaryConstraint.prototype.chooseMethod = function (mark) { if (this.v1.mark == mark) { this.direction = (this.v2.mark != mark && Strength.stronger(this.strength, this.v2.walkStrength)) ? Direction.FORWARD : Direction.NONE; } if (this.v2.mark == mark) { this.direction = (this.v1.mark != mark && Strength.stronger(this.strength, this.v1.walkStrength)) ? Direction.BACKWARD : Direction.NONE; } if (Strength.weaker(this.v1.walkStrength, this.v2.walkStrength)) { this.direction = Strength.stronger(this.strength, this.v1.walkStrength) ? Direction.BACKWARD : Direction.NONE; } else { this.direction = Strength.stronger(this.strength, this.v2.walkStrength) ? Direction.FORWARD : Direction.BACKWARD } }; /** * Add this constraint to the constraint graph */ BinaryConstraint.prototype.addToGraph = function () { this.v1.addConstraint(this); this.v2.addConstraint(this); this.direction = Direction.NONE; }; /** * Answer true if this constraint is satisfied in the current solution. */ BinaryConstraint.prototype.isSatisfied = function () { return this.direction != Direction.NONE; }; /** * Mark the input variable with the given mark. */ BinaryConstraint.prototype.markInputs = function (mark) { this.input().mark = mark; }; /** * Returns the current input variable */ BinaryConstraint.prototype.input = function () { return (this.direction == Direction.FORWARD) ? this.v1 : this.v2; }; /** * Returns the current output variable */ BinaryConstraint.prototype.output = function () { return (this.direction == Direction.FORWARD) ? this.v2 : this.v1; }; /** * Calculate the walkabout strength, the stay flag, and, if it is * 'stay', the value for the current output of this * constraint. Assume this constraint is satisfied. */ BinaryConstraint.prototype.recalculate = function () { var ihn = this.input(), out = this.output(); out.walkStrength = Strength.weakestOf(this.strength, ihn.walkStrength); out.stay = ihn.stay; if (out.stay) this.execute(); }; /** * Record the fact that this constraint is unsatisfied. */ BinaryConstraint.prototype.markUnsatisfied = function () { this.direction = Direction.NONE; }; BinaryConstraint.prototype.inputsKnown = function (mark) { var i = this.input(); return i.mark == mark || i.stay || i.determinedBy == null; }; BinaryConstraint.prototype.removeFromGraph = function () { if (this.v1 != null) this.v1.removeConstraint(this); if (this.v2 != null) this.v2.removeConstraint(this); this.direction = Direction.NONE; }; /* --- * * S c a l e C o n s t r a i n t * --- */ /** * Relates two variables by the linear scaling relationship: "v2 = * (v1 * scale) + offset". Either v1 or v2 may be changed to maintain * this relationship but the scale factor and offset are considered * read-only. */ function ScaleConstraint(src, scale, offset, dest, strength) { this.direction = Direction.NONE; this.scale = scale; this.offset = offset; ScaleConstraint.superConstructor.call(this, src, dest, strength); } ScaleConstraint.inheritsFrom(BinaryConstraint); /** * Adds this constraint to the constraint graph. */ ScaleConstraint.prototype.addToGraph = function () { ScaleConstraint.superConstructor.prototype.addToGraph.call(this); this.scale.addConstraint(this); this.offset.addConstraint(this); }; ScaleConstraint.prototype.removeFromGraph = function () { ScaleConstraint.superConstructor.prototype.removeFromGraph.call(this); if (this.scale != null) this.scale.removeConstraint(this); if (this.offset != null) this.offset.removeConstraint(this); }; ScaleConstraint.prototype.markInputs = function (mark) { ScaleConstraint.superConstructor.prototype.markInputs.call(this, mark); this.scale.mark = this.offset.mark = mark; }; /** * Enforce this constraint. Assume that it is satisfied. */ ScaleConstraint.prototype.execute = function () { if (this.direction == Direction.FORWARD) { this.v2.value = this.v1.value * this.scale.value + this.offset.value; } else { this.v1.value = (this.v2.value - this.offset.value) / this.scale.value; } } /** * Calculate the walkabout strength, the stay flag, and, if it is * 'stay', the value for the current output of this constraint. Assume * this constraint is satisfied. */ ScaleConstraint.prototype.recalculate = function () { var ihn = this.input(), out = this.output(); out.walkStrength = Strength.weakestOf(this.strength, ihn.walkStrength); out.stay = ihn.stay && this.scale.stay && this.offset.stay; if (out.stay) this.execute(); } /* --- * * E q u a l i t y C o n s t r a i n t * --- */ /** * Constrains two variables to have the same value. */ function EqualityConstraint(var1, var2, strength) { EqualityConstraint.superConstructor.call(this, var1, var2, strength); } EqualityConstraint.inheritsFrom(BinaryConstraint); /** * Enforce this constraint. Assume that it is satisfied. */ EqualityConstraint.prototype.execute = function () { this.output().value = this.input().value; } /* --- * * V a r i a b l e * --- */ /** * A constrained variable. In addition to its value, it maintain the * structure of the constraint graph, the current dataflow graph, and * various parameters of interest to the DeltaBlue incremental * constraint solver. **/ function Variable(name, initialValue) { this.value = initialValue || 0; this.constraints = new OrderedCollection(); this.determinedBy = null; this.mark = 0; this.walkStrength = Strength.WEAKEST; this.stay = true; this.name = name; } /** * Add the given constraint to the set of all constraints that refer * this variable. */ Variable.prototype.addConstraint = function (c) { this.constraints.add(c); } /** * Removes all traces of c from this variable. */ Variable.prototype.removeConstraint = function (c) { this.constraints.remove(c); if (this.determinedBy == c) this.determinedBy = null; } /* --- * * P l a n n e r * --- */ /** * The DeltaBlue planner */ function Planner() { this.currentMark = 0; } /** * Attempt to satisfy the given constraint and, if successful, * incrementally update the dataflow graph. Details: If satifying * the constraint is successful, it may override a weaker constraint * on its output. The algorithm attempts to resatisfy that * constraint using some other method. This process is repeated * until either a) it reaches a variable that was not previously * determined by any constraint or b) it reaches a constraint that * is too weak to be satisfied using any of its methods. The * variables of constraints that have been processed are marked with * a unique mark value so that we know where we've been. This allows * the algorithm to avoid getting into an infinite loop even if the * constraint graph has an inadvertent cycle. */ Planner.prototype.incrementalAdd = function (c) { var mark = this.newMark(); var overridden = c.satisfy(mark); while (overridden != null) overridden = overridden.satisfy(mark); } /** * Entry point for retracting a constraint. Remove the given * constraint and incrementally update the dataflow graph. * Details: Retracting the given constraint may allow some currently * unsatisfiable downstream constraint to be satisfied. We therefore collect * a list of unsatisfied downstream constraints and attempt to * satisfy each one in turn. This list is traversed by constraint * strength, strongest first, as a heuristic for avoiding * unnecessarily adding and then overriding weak constraints. * Assume: c is satisfied. */ Planner.prototype.incrementalRemove = function (c) { var out = c.output(); c.markUnsatisfied(); c.removeFromGraph(); var unsatisfied = this.removePropagateFrom(out); var strength = Strength.REQUIRED; do { for (var i = 0; i < unsatisfied.size(); i++) { var u = unsatisfied.at(i); if (u.strength == strength) this.incrementalAdd(u); } strength = strength.nextWeaker(); } while (strength != Strength.WEAKEST); } /** * Select a previously unused mark value. */ Planner.prototype.newMark = function () { return ++this.currentMark; } /** * Extract a plan for resatisfaction starting from the given source * constraints, usually a set of input constraints. This method * assumes that stay optimization is desired; the plan will contain * only constraints whose output variables are not stay. Constraints * that do no computation, such as stay and edit constraints, are * not included in the plan. * Details: The outputs of a constraint are marked when it is added * to the plan under construction. A constraint may be appended to * the plan when all its input variables are known. A variable is * known if either a) the variable is marked (indicating that has * been computed by a constraint appearing earlier in the plan), b) * the variable is 'stay' (i.e. it is a constant at plan execution * time), or c) the variable is not determined by any * constraint. The last provision is for past states of history * variables, which are not stay but which are also not computed by * any constraint. * Assume: sources are all satisfied. */ Planner.prototype.makePlan = function (sources) { var mark = this.newMark(); var plan = new Plan(); var todo = sources; while (todo.size() > 0) { var c = todo.removeFirst(); if (c.output().mark != mark && c.inputsKnown(mark)) { plan.addConstraint(c); c.output().mark = mark; this.addConstraintsConsumingTo(c.output(), todo); } } return plan; } /** * Extract a plan for resatisfying starting from the output of the * given constraints, usually a set of input constraints. */ Planner.prototype.extractPlanFromConstraints = function (constraints) { var sources = new OrderedCollection(); for (var i = 0; i < constraints.size(); i++) { var c = constraints.at(i); if (c.isInput() && c.isSatisfied()) // not in plan already and eligible for inclusion sources.add(c); } return this.makePlan(sources); } /** * Recompute the walkabout strengths and stay flags of all variables * downstream of the given constraint and recompute the actual * values of all variables whose stay flag is true. If a cycle is * detected, remove the given constraint and answer * false. Otherwise, answer true. * Details: Cycles are detected when a marked variable is * encountered downstream of the given constraint. The sender is * assumed to have marked the inputs of the given constraint with * the given mark. Thus, encountering a marked node downstream of * the output constraint means that there is a path from the * constraint's output to one of its inputs. */ Planner.prototype.addPropagate = function (c, mark) { var todo = new OrderedCollection(); todo.add(c); while (todo.size() > 0) { var d = todo.removeFirst(); if (d.output().mark == mark) { this.incrementalRemove(c); return false; } d.recalculate(); this.addConstraintsConsumingTo(d.output(), todo); } return true; } /** * Update the walkabout strengths and stay flags of all variables * downstream of the given constraint. Answer a collection of * unsatisfied constraints sorted in order of decreasing strength. */ Planner.prototype.removePropagateFrom = function (out) { out.determinedBy = null; out.walkStrength = Strength.WEAKEST; out.stay = true; var unsatisfied = new OrderedCollection(); var todo = new OrderedCollection(); todo.add(out); while (todo.size() > 0) { var v = todo.removeFirst(); for (var i = 0; i < v.constraints.size(); i++) { var c = v.constraints.at(i); if (!c.isSatisfied()) unsatisfied.add(c); } var determining = v.determinedBy; for (var i = 0; i < v.constraints.size(); i++) { var next = v.constraints.at(i); if (next != determining && next.isSatisfied()) { next.recalculate(); todo.add(next.output()); } } } return unsatisfied; } Planner.prototype.addConstraintsConsumingTo = function (v, coll) { var determining = v.determinedBy; var cc = v.constraints; for (var i = 0; i < cc.size(); i++) { var c = cc.at(i); if (c != determining && c.isSatisfied()) coll.add(c); } } /* --- * * P l a n * --- */ /** * A Plan is an ordered list of constraints to be executed in sequence * to resatisfy all currently satisfiable constraints in the face of * one or more changing inputs. */ function Plan() { this.v = new OrderedCollection(); } Plan.prototype.addConstraint = function (c) { this.v.add(c); } Plan.prototype.size = function () { return this.v.size(); } Plan.prototype.constraintAt = function (index) { return this.v.at(index); } Plan.prototype.execute = function () { for (var i = 0; i < this.size(); i++) { var c = this.constraintAt(i); c.execute(); } } /* --- * * M a i n * --- */ /** * This is the standard DeltaBlue benchmark. A long chain of equality * constraints is constructed with a stay constraint on one end. An * edit constraint is then added to the opposite end and the time is * measured for adding and removing this constraint, and extracting * and executing a constraint satisfaction plan. There are two cases. * In case 1, the added constraint is stronger than the stay * constraint and values must propagate down the entire length of the * chain. In case 2, the added constraint is weaker than the stay * constraint so it cannot be accomodated. The cost in this case is, * of course, very low. Typical situations lie somewhere between these * two extremes. */ function chainTest(n) { planner = new Planner(); var prev = null, first = null, last = null; // Build chain of n equality constraints for (var i = 0; i <= n; i++) { var name = "v" + i; var v = new Variable(name); if (prev != null) new EqualityConstraint(prev, v, Strength.REQUIRED); if (i == 0) first = v; if (i == n) last = v; prev = v; } new StayConstraint(last, Strength.STRONG_DEFAULT); var edit = new EditConstraint(first, Strength.PREFERRED); var edits = new OrderedCollection(); edits.add(edit); var plan = planner.extractPlanFromConstraints(edits); for (var i = 0; i < 100; i++) { first.value = i; plan.execute(); if (last.value != i) alert("Chain test failed."); } } /** * This test constructs a two sets of variables related to each * other by a simple linear transformation (scale and offset). The * time is measured to change a variable on either side of the * mapping and to change the scale and offset factors. */ function projectionTest(n) { planner = new Planner(); var scale = new Variable("scale", 10); var offset = new Variable("offset", 1000); var src = null, dst = null; var dests = new OrderedCollection(); for (var i = 0; i < n; i++) { src = new Variable("src" + i, i); dst = new Variable("dst" + i, i); dests.add(dst); new StayConstraint(src, Strength.NORMAL); new ScaleConstraint(src, scale, offset, dst, Strength.REQUIRED); } change(src, 17); if (dst.value != 1170) alert("Projection 1 failed"); change(dst, 1050); if (src.value != 5) alert("Projection 2 failed"); change(scale, 5); for (var i = 0; i < n - 1; i++) { if (dests.at(i).value != i * 5 + 1000) alert("Projection 3 failed"); } change(offset, 2000); for (var i = 0; i < n - 1; i++) { if (dests.at(i).value != i * 5 + 2000) alert("Projection 4 failed"); } } function change(v, newValue) { var edit = new EditConstraint(v, Strength.PREFERRED); var edits = new OrderedCollection(); edits.add(edit); var plan = planner.extractPlanFromConstraints(edits); for (var i = 0; i < 10; i++) { v.value = newValue; plan.execute(); } edit.destroyConstraint(); } // Global variable holding the current planner. var planner = null; function deltaBlue() { chainTest(100); projectionTest(100); } var DeltaBlue = new BenchmarkSuite('DeltaBlue', 66118, [ new Benchmark('DeltaBlue', deltaBlue) ]); /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/scripts/v8-benches/earley-boyer.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; // This file is automatically generated by scheme2js, except for the // benchmark harness code at the beginning and end of the file. /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /* * To use write/prints/... the default-output port has to be set first. * Simply setting SC_DEFAULT_OUT and SC_ERROR_OUT to the desired values * should do the trick. * In the following example the std-out and error-port are redirected to * a DIV. function initRuntime() { function escapeHTML(s) { var tmp = s; tmp = tmp.replace(/&/g, "&"); tmp = tmp.replace(//g, ">"); tmp = tmp.replace(/ /g, " "); tmp = tmp.replace(/\n/g, "
"); tmp = tmp.replace(/\t/g, "    "); return tmp; } document.write("
"); SC_DEFAULT_OUT = new sc_GenericOutputPort( function(s) { var stdout = document.getElementById('stdout'); stdout.innerHTML = stdout.innerHTML + escapeHTML(s); }); SC_ERROR_OUT = SC_DEFAULT_OUT; } */ function sc_print_debug() { sc_print.apply(null, arguments); } /*** META ((export *js*)) */ var sc_JS_GLOBALS = this; var __sc_LINE = -1; var __sc_FILE = ""; /*** META ((export #t)) */ function sc_alert() { var len = arguments.length; var s = ""; var i; for (i = 0; i < len; i++) { s += sc_toDisplayString(arguments[i]); } return alert(s); } /*** META ((export #t)) */ function sc_typeof(x) { return typeof x; } /*** META ((export #t)) */ function sc_error() { var a = [sc_jsstring2symbol("*error*")]; for (var i = 0; i < arguments.length; i++) { a[i + 1] = arguments[i]; } throw a; } /*** META ((export #t) (peephole (prefix "throw "))) */ function sc_raise(obj) { throw obj; } /*** META ((export with-handler-lambda)) */ function sc_withHandlerLambda(handler, body) { try { return body(); } catch (e) { if (!e._internalException) return handler(e); else throw e; } } var sc_properties = new Object(); /*** META ((export #t)) */ function sc_putpropBang(sym, key, val) { var ht = sc_properties[sym]; if (!ht) { ht = new Object(); sc_properties[sym] = ht; } ht[key] = val; } /*** META ((export #t)) */ function sc_getprop(sym, key) { var ht = sc_properties[sym]; if (ht) { if (key in ht) return ht[key]; else return false; } else return false; } /*** META ((export #t)) */ function sc_rempropBang(sym, key) { var ht = sc_properties[sym]; if (ht) delete ht[key]; } /*** META ((export #t)) */ function sc_any2String(o) { return jsstring2string(sc_toDisplayString(o)); } /*** META ((export #t) (peephole (infix 2 2 "===")) (type bool)) */ function sc_isEqv(o1, o2) { return (o1 === o2); } /*** META ((export #t) (peephole (infix 2 2 "===")) (type bool)) */ function sc_isEq(o1, o2) { return (o1 === o2); } /*** META ((export #t) (type bool)) */ function sc_isNumber(n) { return (typeof n === "number"); } /*** META ((export #t) (type bool)) */ function sc_isComplex(n) { return sc_isNumber(n); } /*** META ((export #t) (type bool)) */ function sc_isReal(n) { return sc_isNumber(n); } /*** META ((export #t) (type bool)) */ function sc_isRational(n) { return sc_isReal(n); } /*** META ((export #t) (type bool)) */ function sc_isInteger(n) { return (parseInt(n) === n); } /*** META ((export #t) (type bool) (peephole (postfix ", false"))) */ // we don't have exact numbers... function sc_isExact(n) { return false; } /*** META ((export #t) (peephole (postfix ", true")) (type bool)) */ function sc_isInexact(n) { return true; } /*** META ((export = =fx =fl) (type bool) (peephole (infix 2 2 "==="))) */ function sc_equal(x) { for (var i = 1; i < arguments.length; i++) if (x !== arguments[i]) return false; return true; } /*** META ((export < = arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export > >fx >fl) (type bool) (peephole (infix 2 2 ">"))) */ function sc_greater(x, y) { for (var i = 1; i < arguments.length; i++) { if (x <= arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export <= <=fx <=fl) (type bool) (peephole (infix 2 2 "<="))) */ function sc_lessEqual(x, y) { for (var i = 1; i < arguments.length; i++) { if (x > arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export >= >=fl >=fx) (type bool) (peephole (infix 2 2 ">="))) */ function sc_greaterEqual(x, y) { for (var i = 1; i < arguments.length; i++) { if (x < arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export #t) (type bool) (peephole (postfix "=== 0"))) */ function sc_isZero(x) { return (x === 0); } /*** META ((export #t) (type bool) (peephole (postfix "> 0"))) */ function sc_isPositive(x) { return (x > 0); } /*** META ((export #t) (type bool) (peephole (postfix "< 0"))) */ function sc_isNegative(x) { return (x < 0); } /*** META ((export #t) (type bool) (peephole (postfix "%2===1"))) */ function sc_isOdd(x) { return (x % 2 === 1); } /*** META ((export #t) (type bool) (peephole (postfix "%2===0"))) */ function sc_isEven(x) { return (x % 2 === 0); } /*** META ((export #t)) */ var sc_max = Math.max; /*** META ((export #t)) */ var sc_min = Math.min; /*** META ((export + +fx +fl) (peephole (infix 0 #f "+" "0"))) */ function sc_plus() { var sum = 0; for (var i = 0; i < arguments.length; i++) sum += arguments[i]; return sum; } /*** META ((export * *fx *fl) (peephole (infix 0 #f "*" "1"))) */ function sc_multi() { var product = 1; for (var i = 0; i < arguments.length; i++) product *= arguments[i]; return product; } /*** META ((export - -fx -fl) (peephole (minus))) */ function sc_minus(x) { if (arguments.length === 1) return -x; else { var res = x; for (var i = 1; i < arguments.length; i++) res -= arguments[i]; return res; } } /*** META ((export / /fl) (peephole (div))) */ function sc_div(x) { if (arguments.length === 1) return 1 / x; else { var res = x; for (var i = 1; i < arguments.length; i++) res /= arguments[i]; return res; } } /*** META ((export #t)) */ var sc_abs = Math.abs; /*** META ((export quotient /fx) (peephole (hole 2 "parseInt(" x "/" y ")"))) */ function sc_quotient(x, y) { return parseInt(x / y); } /*** META ((export #t) (peephole (infix 2 2 "%"))) */ function sc_remainder(x, y) { return x % y; } /*** META ((export #t) (peephole (modulo))) */ function sc_modulo(x, y) { var remainder = x % y; // if they don't have the same sign if ((remainder * y) < 0) return remainder + y; else return remainder; } function sc_euclid_gcd(a, b) { var temp; if (a === 0) return b; if (b === 0) return a; if (a < 0) { a = -a; } if (b < 0) { b = -b; } if (b > a) { temp = a; a = b; b = temp; } while (true) { a %= b; if (a === 0) { return b; } b %= a; if (b === 0) { return a; } } return b; } /*** META ((export #t)) */ function sc_gcd() { var gcd = 0; for (var i = 0; i < arguments.length; i++) gcd = sc_euclid_gcd(gcd, arguments[i]); return gcd; } /*** META ((export #t)) */ function sc_lcm() { var lcm = 1; for (var i = 0; i < arguments.length; i++) { var f = Math.round(arguments[i] / sc_euclid_gcd(arguments[i], lcm)); lcm *= Math.abs(f); } return lcm; } // LIMITATION: numerator and denominator don't make sense in floating point world. //var SC_MAX_DECIMALS = 1000000 // // function sc_numerator(x) { // var rounded = Math.round(x * SC_MAX_DECIMALS); // return Math.round(rounded / sc_euclid_gcd(rounded, SC_MAX_DECIMALS)); // } // function sc_denominator(x) { // var rounded = Math.round(x * SC_MAX_DECIMALS); // return Math.round(SC_MAX_DECIMALS / sc_euclid_gcd(rounded, SC_MAX_DECIMALS)); // } /*** META ((export #t)) */ var sc_floor = Math.floor; /*** META ((export #t)) */ var sc_ceiling = Math.ceil; /*** META ((export #t)) */ var sc_truncate = parseInt; /*** META ((export #t)) */ var sc_round = Math.round; // LIMITATION: sc_rationalize doesn't make sense in a floating point world. /*** META ((export #t)) */ var sc_exp = Math.exp; /*** META ((export #t)) */ var sc_log = Math.log; /*** META ((export #t)) */ var sc_sin = Math.sin; /*** META ((export #t)) */ var sc_cos = Math.cos; /*** META ((export #t)) */ var sc_tan = Math.tan; /*** META ((export #t)) */ var sc_asin = Math.asin; /*** META ((export #t)) */ var sc_acos = Math.acos; /*** META ((export #t)) */ var sc_atan = Math.atan; /*** META ((export #t)) */ var sc_sqrt = Math.sqrt; /*** META ((export #t)) */ var sc_expt = Math.pow; // LIMITATION: we don't have complex numbers. // LIMITATION: the following functions are hence not implemented. // LIMITATION: make-rectangular, make-polar, real-part, imag-part, magnitude, angle // LIMITATION: 2 argument atan /*** META ((export #t) (peephole (id))) */ function sc_exact2inexact(x) { return x; } /*** META ((export #t) (peephole (id))) */ function sc_inexact2exact(x) { return x; } function sc_number2jsstring(x, radix) { if (radix) return x.toString(radix); else return x.toString(); } function sc_jsstring2number(s, radix) { if (s === "") return false; if (radix) { var t = parseInt(s, radix); if (!t && t !== 0) return false; // verify that each char is in range. (parseInt ignores leading // white and trailing chars) var allowedChars = "01234567890abcdefghijklmnopqrstuvwxyz".substring(0, radix + 1); if ((new RegExp("^[" + allowedChars + "]*$", "i")).test(s)) return t; else return false; } else { var t = +s; // does not ignore trailing chars. if (!t && t !== 0) return false; // simply verify that first char is not whitespace. var c = s.charAt(0); // if +c is 0, but the char is not "0", then we have a whitespace. if (+c === 0 && c !== "0") return false; return t; } } /*** META ((export #t) (type bool) (peephole (not))) */ function sc_not(b) { return b === false; } /*** META ((export #t) (type bool)) */ function sc_isBoolean(b) { return (b === true) || (b === false); } function sc_Pair(car, cdr) { this.car = car; this.cdr = cdr; } sc_Pair.prototype.toString = function () { return sc_toDisplayString(this); }; sc_Pair.prototype.sc_toWriteOrDisplayString = function (writeOrDisplay) { var current = this; var res = "("; while (true) { res += writeOrDisplay(current.car); if (sc_isPair(current.cdr)) { res += " "; current = current.cdr; } else if (current.cdr !== null) { res += " . " + writeOrDisplay(current.cdr); break; } else // current.cdr == null break; } res += ")"; return res; }; sc_Pair.prototype.sc_toDisplayString = function () { return this.sc_toWriteOrDisplayString(sc_toDisplayString); }; sc_Pair.prototype.sc_toWriteString = function () { return this.sc_toWriteOrDisplayString(sc_toWriteString); }; // sc_Pair.prototype.sc_toWriteCircleString in IO.js /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_Pair"))) */ function sc_isPair(p) { return (p instanceof sc_Pair); } function sc_isPairEqual(p1, p2, comp) { return (comp(p1.car, p2.car) && comp(p1.cdr, p2.cdr)); } /*** META ((export #t) (peephole (hole 2 "new sc_Pair(" car ", " cdr ")"))) */ function sc_cons(car, cdr) { return new sc_Pair(car, cdr); } /*** META ((export cons*)) */ function sc_consStar() { var res = arguments[arguments.length - 1]; for (var i = arguments.length - 2; i >= 0; i--) res = new sc_Pair(arguments[i], res); return res; } /*** META ((export #t) (peephole (postfix ".car"))) */ function sc_car(p) { return p.car; } /*** META ((export #t) (peephole (postfix ".cdr"))) */ function sc_cdr(p) { return p.cdr; } /*** META ((export #t) (peephole (hole 2 p ".car = " val))) */ function sc_setCarBang(p, val) { p.car = val; } /*** META ((export #t) (peephole (hole 2 p ".cdr = " val))) */ function sc_setCdrBang(p, val) { p.cdr = val; } /*** META ((export #t) (peephole (postfix ".car.car"))) */ function sc_caar(p) { return p.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.car"))) */ function sc_cadr(p) { return p.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.cdr"))) */ function sc_cdar(p) { return p.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr"))) */ function sc_cddr(p) { return p.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".car.car.car"))) */ function sc_caaar(p) { return p.car.car.car; } /*** META ((export #t) (peephole (postfix ".car.cdr.car"))) */ function sc_cadar(p) { return p.car.cdr.car; } /*** META ((export #t) (peephole (postfix ".cdr.car.car"))) */ function sc_caadr(p) { return p.cdr.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.car"))) */ function sc_caddr(p) { return p.cdr.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.car.cdr"))) */ function sc_cdaar(p) { return p.car.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.car.cdr"))) */ function sc_cdadr(p) { return p.cdr.car.cdr; } /*** META ((export #t) (peephole (postfix ".car.cdr.cdr"))) */ function sc_cddar(p) { return p.car.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.cdr"))) */ function sc_cdddr(p) { return p.cdr.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".car.car.car.car"))) */ function sc_caaaar(p) { return p.car.car.car.car; } /*** META ((export #t) (peephole (postfix ".car.cdr.car.car"))) */ function sc_caadar(p) { return p.car.cdr.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.car.car.car"))) */ function sc_caaadr(p) { return p.cdr.car.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.car.car"))) */ function sc_caaddr(p) { return p.cdr.cdr.car.car; } /*** META ((export #t) (peephole (postfix ".car.car.car.cdr"))) */ function sc_cdaaar(p) { return p.car.car.car.cdr; } /*** META ((export #t) (peephole (postfix ".car.cdr.car.cdr"))) */ function sc_cdadar(p) { return p.car.cdr.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.car.car.cdr"))) */ function sc_cdaadr(p) { return p.cdr.car.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.car.cdr"))) */ function sc_cdaddr(p) { return p.cdr.cdr.car.cdr; } /*** META ((export #t) (peephole (postfix ".car.car.cdr.car"))) */ function sc_cadaar(p) { return p.car.car.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.cdr.cdr.car"))) */ function sc_caddar(p) { return p.car.cdr.cdr.car; } /*** META ((export #t) (peephole (postfix ".cdr.car.cdr.car"))) */ function sc_cadadr(p) { return p.cdr.car.cdr.car; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.cdr.car"))) */ function sc_cadddr(p) { return p.cdr.cdr.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.car.cdr.cdr"))) */ function sc_cddaar(p) { return p.car.car.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".car.cdr.cdr.cdr"))) */ function sc_cdddar(p) { return p.car.cdr.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.car.cdr.cdr"))) */ function sc_cddadr(p) { return p.cdr.car.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.cdr.cdr"))) */ function sc_cddddr(p) { return p.cdr.cdr.cdr.cdr; } /*** META ((export #t)) */ function sc_lastPair(l) { if (!sc_isPair(l)) sc_error("sc_lastPair: pair expected"); var res = l; var cdr = l.cdr; while (sc_isPair(cdr)) { res = cdr; cdr = res.cdr; } return res; } /*** META ((export #t) (type bool) (peephole (postfix " === null"))) */ function sc_isNull(o) { return (o === null); } /*** META ((export #t) (type bool)) */ function sc_isList(o) { var rabbit; var turtle; var rabbit = o; var turtle = o; while (true) { if (rabbit === null || (rabbit instanceof sc_Pair && rabbit.cdr === null)) return true; // end of list else if ((rabbit instanceof sc_Pair) && (rabbit.cdr instanceof sc_Pair)) { rabbit = rabbit.cdr.cdr; turtle = turtle.cdr; if (rabbit === turtle) return false; // cycle } else return false; // not pair } } /*** META ((export #t)) */ function sc_list() { var res = null; var a = arguments; for (var i = a.length - 1; i >= 0; i--) res = new sc_Pair(a[i], res); return res; } /*** META ((export #t)) */ function sc_iota(num, init) { var res = null; if (!init) init = 0; for (var i = num - 1; i >= 0; i--) res = new sc_Pair(i + init, res); return res; } /*** META ((export #t)) */ function sc_makeList(nbEls, fill) { var res = null; for (var i = 0; i < nbEls; i++) res = new sc_Pair(fill, res); return res; } /*** META ((export #t)) */ function sc_length(l) { var res = 0; while (l !== null) { res++; l = l.cdr; } return res; } /*** META ((export #t)) */ function sc_remq(o, l) { var dummy = {cdr: null}; var tail = dummy; while (l !== null) { if (l.car !== o) { tail.cdr = sc_cons(l.car, null); tail = tail.cdr; } l = l.cdr; } return dummy.cdr; } /*** META ((export #t)) */ function sc_remqBang(o, l) { var dummy = {cdr: null}; var tail = dummy; var needsAssig = true; while (l !== null) { if (l.car === o) { needsAssig = true; } else { if (needsAssig) { tail.cdr = l; needsAssig = false; } tail = l; } l = l.cdr; } tail.cdr = null; return dummy.cdr; } /*** META ((export #t)) */ function sc_delete(o, l) { var dummy = {cdr: null}; var tail = dummy; while (l !== null) { if (!sc_isEqual(l.car, o)) { tail.cdr = sc_cons(l.car, null); tail = tail.cdr; } l = l.cdr; } return dummy.cdr; } /*** META ((export #t)) */ function sc_deleteBang(o, l) { var dummy = {cdr: null}; var tail = dummy; var needsAssig = true; while (l !== null) { if (sc_isEqual(l.car, o)) { needsAssig = true; } else { if (needsAssig) { tail.cdr = l; needsAssig = false; } tail = l; } l = l.cdr; } tail.cdr = null; return dummy.cdr; } function sc_reverseAppendBang(l1, l2) { var res = l2; while (l1 !== null) { var tmp = res; res = l1; l1 = l1.cdr; res.cdr = tmp; } return res; } function sc_dualAppend(l1, l2) { if (l1 === null) return l2; if (l2 === null) return l1; var rev = sc_reverse(l1); return sc_reverseAppendBang(rev, l2); } /*** META ((export #t)) */ function sc_append() { if (arguments.length === 0) return null; var res = arguments[arguments.length - 1]; for (var i = arguments.length - 2; i >= 0; i--) res = sc_dualAppend(arguments[i], res); return res; } function sc_dualAppendBang(l1, l2) { if (l1 === null) return l2; if (l2 === null) return l1; var tmp = l1; while (tmp.cdr !== null) tmp = tmp.cdr; tmp.cdr = l2; return l1; } /*** META ((export #t)) */ function sc_appendBang() { var res = null; for (var i = 0; i < arguments.length; i++) res = sc_dualAppendBang(res, arguments[i]); return res; } /*** META ((export #t)) */ function sc_reverse(l1) { var res = null; while (l1 !== null) { res = sc_cons(l1.car, res); l1 = l1.cdr; } return res; } /*** META ((export #t)) */ function sc_reverseBang(l) { return sc_reverseAppendBang(l, null); } /*** META ((export #t)) */ function sc_listTail(l, k) { var res = l; for (var i = 0; i < k; i++) { res = res.cdr; } return res; } /*** META ((export #t)) */ function sc_listRef(l, k) { return sc_listTail(l, k).car; } /* // unoptimized generic versions function sc_memX(o, l, comp) { while (l != null) { if (comp(l.car, o)) return l; l = l.cdr; } return false; } function sc_memq(o, l) { return sc_memX(o, l, sc_isEq); } function sc_memv(o, l) { return sc_memX(o, l, sc_isEqv); } function sc_member(o, l) { return sc_memX(o, l, sc_isEqual); } */ /* optimized versions */ /*** META ((export #t)) */ function sc_memq(o, l) { while (l !== null) { if (l.car === o) return l; l = l.cdr; } return false; } /*** META ((export #t)) */ function sc_memv(o, l) { while (l !== null) { if (l.car === o) return l; l = l.cdr; } return false; } /*** META ((export #t)) */ function sc_member(o, l) { while (l !== null) { if (sc_isEqual(l.car, o)) return l; l = l.cdr; } return false; } /* // generic unoptimized versions function sc_assX(o, al, comp) { while (al != null) { if (comp(al.car.car, o)) return al.car; al = al.cdr; } return false; } function sc_assq(o, al) { return sc_assX(o, al, sc_isEq); } function sc_assv(o, al) { return sc_assX(o, al, sc_isEqv); } function sc_assoc(o, al) { return sc_assX(o, al, sc_isEqual); } */ // optimized versions /*** META ((export #t)) */ function sc_assq(o, al) { while (al !== null) { if (al.car.car === o) return al.car; al = al.cdr; } return false; } /*** META ((export #t)) */ function sc_assv(o, al) { while (al !== null) { if (al.car.car === o) return al.car; al = al.cdr; } return false; } /*** META ((export #t)) */ function sc_assoc(o, al) { while (al !== null) { if (sc_isEqual(al.car.car, o)) return al.car; al = al.cdr; } return false; } /* can be used for mutable strings and characters */ function sc_isCharStringEqual(cs1, cs2) { return cs1.val === cs2.val; } function sc_isCharStringLess(cs1, cs2) { return cs1.val < cs2.val; } function sc_isCharStringGreater(cs1, cs2) { return cs1.val > cs2.val; } function sc_isCharStringLessEqual(cs1, cs2) { return cs1.val <= cs2.val; } function sc_isCharStringGreaterEqual(cs1, cs2) { return cs1.val >= cs2.val; } function sc_isCharStringCIEqual(cs1, cs2) { return cs1.val.toLowerCase() === cs2.val.toLowerCase(); } function sc_isCharStringCILess(cs1, cs2) { return cs1.val.toLowerCase() < cs2.val.toLowerCase(); } function sc_isCharStringCIGreater(cs1, cs2) { return cs1.val.toLowerCase() > cs2.val.toLowerCase(); } function sc_isCharStringCILessEqual(cs1, cs2) { return cs1.val.toLowerCase() <= cs2.val.toLowerCase(); } function sc_isCharStringCIGreaterEqual(cs1, cs2) { return cs1.val.toLowerCase() >= cs2.val.toLowerCase(); } function sc_Char(c) { var cached = sc_Char.lazy[c]; if (cached) return cached; this.val = c; sc_Char.lazy[c] = this; // add return, so FF does not complain. return undefined; } sc_Char.lazy = new Object(); // thanks to Eric sc_Char.char2readable = { "\u0000": "#\\null", "\u0007": "#\\bell", "\b": "#\\backspace", "\t": "#\\tab", "\n": "#\\newline", "\f": "#\\page", "\r": "#\\return", "\u001b": "#\\esc", " ": "#\\space", "\u007f": "#\\delete", /* poeticless names */ "\u0001": "#\\soh", "\u0002": "#\\stx", "\u0003": "#\\etx", "\u0004": "#\\eot", "\u0005": "#\\enq", "\u0006": "#\\ack", "\u000b": "#\\vt", "\u000e": "#\\so", "\u000f": "#\\si", "\u0010": "#\\dle", "\u0011": "#\\dc1", "\u0012": "#\\dc2", "\u0013": "#\\dc3", "\u0014": "#\\dc4", "\u0015": "#\\nak", "\u0016": "#\\syn", "\u0017": "#\\etb", "\u0018": "#\\can", "\u0019": "#\\em", "\u001a": "#\\sub", "\u001c": "#\\fs", "\u001d": "#\\gs", "\u001e": "#\\rs", "\u001f": "#\\us", }; sc_Char.readable2char = { "null": "\u0000", "bell": "\u0007", "backspace": "\b", "tab": "\t", "newline": "\n", "page": "\f", "return": "\r", "escape": "\u001b", "space": " ", "delete": "\u0000", "soh": "\u0001", "stx": "\u0002", "etx": "\u0003", "eot": "\u0004", "enq": "\u0005", "ack": "\u0006", "bel": "\u0007", "bs": "\b", "ht": "\t", "nl": "\n", "vt": "\u000b", "np": "\f", "cr": "\r", "so": "\u000e", "si": "\u000f", "dle": "\u0010", "dc1": "\u0011", "dc2": "\u0012", "dc3": "\u0013", "dc4": "\u0014", "nak": "\u0015", "syn": "\u0016", "etb": "\u0017", "can": "\u0018", "em": "\u0019", "sub": "\u001a", "esc": "\u001b", "fs": "\u001c", "gs": "\u001d", "rs": "\u001e", "us": "\u001f", "sp": " ", "del": "\u007f", }; sc_Char.prototype.toString = function () { return this.val; }; // sc_toDisplayString == toString sc_Char.prototype.sc_toWriteString = function () { var entry = sc_Char.char2readable[this.val]; if (entry) return entry; else return "#\\" + this.val; }; /*** META ((export #t) (type bool) (peephole (postfix "instanceof sc_Char"))) */ function sc_isChar(c) { return (c instanceof sc_Char); } /*** META ((export char=?) (type bool) (peephole (hole 2 c1 ".val === " c2 ".val"))) */ var sc_isCharEqual = sc_isCharStringEqual; /*** META ((export char?) (type bool) (peephole (hole 2 c1 ".val > " c2 ".val"))) */ var sc_isCharGreater = sc_isCharStringGreater; /*** META ((export char<=?) (type bool) (peephole (hole 2 c1 ".val <= " c2 ".val"))) */ var sc_isCharLessEqual = sc_isCharStringLessEqual; /*** META ((export char>=?) (type bool) (peephole (hole 2 c1 ".val >= " c2 ".val"))) */ var sc_isCharGreaterEqual = sc_isCharStringGreaterEqual; /*** META ((export char-ci=?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() === " c2 ".val.toLowerCase()"))) */ var sc_isCharCIEqual = sc_isCharStringCIEqual; /*** META ((export char-ci?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() > " c2 ".val.toLowerCase()"))) */ var sc_isCharCIGreater = sc_isCharStringCIGreater; /*** META ((export char-ci<=?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() <= " c2 ".val.toLowerCase()"))) */ var sc_isCharCILessEqual = sc_isCharStringCILessEqual; /*** META ((export char-ci>=?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() >= " c2 ".val.toLowerCase()"))) */ var sc_isCharCIGreaterEqual = sc_isCharStringCIGreaterEqual; var SC_NUMBER_CLASS = "0123456789"; var SC_WHITESPACE_CLASS = ' \r\n\t\f'; var SC_LOWER_CLASS = 'abcdefghijklmnopqrstuvwxyz'; var SC_UPPER_CLASS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; function sc_isCharOfClass(c, cl) { return (cl.indexOf(c) != -1); } /*** META ((export #t) (type bool)) */ function sc_isCharAlphabetic(c) { return sc_isCharOfClass(c.val, SC_LOWER_CLASS) || sc_isCharOfClass(c.val, SC_UPPER_CLASS); } /*** META ((export #t) (type bool) (peephole (hole 1 "SC_NUMBER_CLASS.indexOf(" c ".val) != -1"))) */ function sc_isCharNumeric(c) { return sc_isCharOfClass(c.val, SC_NUMBER_CLASS); } /*** META ((export #t) (type bool)) */ function sc_isCharWhitespace(c) { var tmp = c.val; return tmp === " " || tmp === "\r" || tmp === "\n" || tmp === "\t" || tmp === "\f"; } /*** META ((export #t) (type bool) (peephole (hole 1 "SC_UPPER_CLASS.indexOf(" c ".val) != -1"))) */ function sc_isCharUpperCase(c) { return sc_isCharOfClass(c.val, SC_UPPER_CLASS); } /*** META ((export #t) (type bool) (peephole (hole 1 "SC_LOWER_CLASS.indexOf(" c ".val) != -1"))) */ function sc_isCharLowerCase(c) { return sc_isCharOfClass(c.val, SC_LOWER_CLASS); } /*** META ((export #t) (peephole (postfix ".val.charCodeAt(0)"))) */ function sc_char2integer(c) { return c.val.charCodeAt(0); } /*** META ((export #t) (peephole (hole 1 "new sc_Char(String.fromCharCode(" n "))"))) */ function sc_integer2char(n) { return new sc_Char(String.fromCharCode(n)); } /*** META ((export #t) (peephole (hole 1 "new sc_Char(" c ".val.toUpperCase())"))) */ function sc_charUpcase(c) { return new sc_Char(c.val.toUpperCase()); } /*** META ((export #t) (peephole (hole 1 "new sc_Char(" c ".val.toLowerCase())"))) */ function sc_charDowncase(c) { return new sc_Char(c.val.toLowerCase()); } function sc_makeJSStringOfLength(k, c) { var fill; if (c === undefined) fill = " "; else fill = c; var res = ""; var len = 1; // every round doubles the size of fill. while (k >= len) { if (k & len) res = res.concat(fill); fill = fill.concat(fill); len *= 2; } return res; } function sc_makejsString(k, c) { var fill; if (c) fill = c.val; else fill = " "; return sc_makeJSStringOfLength(k, fill); } function sc_jsstring2list(s) { var res = null; for (var i = s.length - 1; i >= 0; i--) res = sc_cons(new sc_Char(s.charAt(i)), res); return res; } function sc_list2jsstring(l) { var a = new Array(); while (l !== null) { a.push(l.car.val); l = l.cdr; } return "".concat.apply("", a); } var sc_Vector = Array; sc_Vector.prototype.sc_toWriteOrDisplayString = function (writeOrDisplay) { if (this.length === 0) return "#()"; var res = "#(" + writeOrDisplay(this[0]); for (var i = 1; i < this.length; i++) res += " " + writeOrDisplay(this[i]); res += ")"; return res; }; sc_Vector.prototype.sc_toDisplayString = function () { return this.sc_toWriteOrDisplayString(sc_toDisplayString); }; sc_Vector.prototype.sc_toWriteString = function () { return this.sc_toWriteOrDisplayString(sc_toWriteString); }; /*** META ((export vector? array?) (type bool) (peephole (postfix " instanceof sc_Vector"))) */ function sc_isVector(v) { return (v instanceof sc_Vector); } // only applies to vectors function sc_isVectorEqual(v1, v2, comp) { if (v1.length !== v2.length) return false; for (var i = 0; i < v1.length; i++) if (!comp(v1[i], v2[i])) return false; return true; } /*** META ((export make-vector make-array)) */ function sc_makeVector(size, fill) { var a = new sc_Vector(size); if (fill !== undefined) sc_vectorFillBang(a, fill); return a; } /*** META ((export vector array) (peephole (vector))) */ function sc_vector() { var a = new sc_Vector(); for (var i = 0; i < arguments.length; i++) a.push(arguments[i]); return a; } /*** META ((export vector-length array-length) (peephole (postfix ".length"))) */ function sc_vectorLength(v) { return v.length; } /*** META ((export vector-ref array-ref) (peephole (hole 2 v "[" pos "]"))) */ function sc_vectorRef(v, pos) { return v[pos]; } /*** META ((export vector-set! array-set!) (peephole (hole 3 v "[" pos "] = " val))) */ function sc_vectorSetBang(v, pos, val) { v[pos] = val; } /*** META ((export vector->list array->list)) */ function sc_vector2list(a) { var res = null; for (var i = a.length - 1; i >= 0; i--) res = sc_cons(a[i], res); return res; } /*** META ((export list->vector list->array)) */ function sc_list2vector(l) { var a = new sc_Vector(); while (l !== null) { a.push(l.car); l = l.cdr; } return a; } /*** META ((export vector-fill! array-fill!)) */ function sc_vectorFillBang(a, fill) { for (var i = 0; i < a.length; i++) a[i] = fill; } /*** META ((export #t)) */ function sc_copyVector(a, len) { if (len <= a.length) return a.slice(0, len); else { var tmp = a.concat(); tmp.length = len; return tmp; } } /*** META ((export #t) (peephole (hole 3 a ".slice(" start "," end ")"))) */ function sc_vectorCopy(a, start, end) { return a.slice(start, end); } /*** META ((export #t)) */ function sc_vectorCopyBang(target, tstart, source, sstart, send) { if (!sstart) sstart = 0; if (!send) send = source.length; // if target == source we don't want to overwrite not yet copied elements. if (tstart <= sstart) { for (var i = tstart, j = sstart; j < send; i++, j++) { target[i] = source[j]; } } else { var diff = send - sstart; for (var i = tstart + diff - 1, j = send - 1; j >= sstart; i--, j--) { target[i] = source[j]; } } return target; } /*** META ((export #t) (type bool) (peephole (hole 1 "typeof " o " === 'function'"))) */ function sc_isProcedure(o) { return (typeof o === "function"); } /*** META ((export #t)) */ function sc_apply(proc) { var args = new Array(); // first part of arguments are not in list-form. for (var i = 1; i < arguments.length - 1; i++) args.push(arguments[i]); var l = arguments[arguments.length - 1]; while (l !== null) { args.push(l.car); l = l.cdr; } return proc.apply(null, args); } /*** META ((export #t)) */ function sc_map(proc, l1) { if (l1 === undefined) return null; // else var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); var revres = null; while (l1 !== null) { for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } revres = sc_cons(proc.apply(null, applyArgs), revres); } return sc_reverseAppendBang(revres, null); } /*** META ((export #t)) */ function sc_mapBang(proc, l1) { if (l1 === undefined) return null; // else var l1_orig = l1; var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); while (l1 !== null) { var tmp = l1; for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } tmp.car = proc.apply(null, applyArgs); } return l1_orig; } /*** META ((export #t)) */ function sc_forEach(proc, l1) { if (l1 === undefined) return undefined; // else var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); while (l1 !== null) { for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } proc.apply(null, applyArgs); } // add return so FF does not complain. return undefined; } /*** META ((export #t)) */ function sc_filter(proc, l1) { var dummy = {cdr: null}; var tail = dummy; while (l1 !== null) { if (proc(l1.car) !== false) { tail.cdr = sc_cons(l1.car, null); tail = tail.cdr; } l1 = l1.cdr; } return dummy.cdr; } /*** META ((export #t)) */ function sc_filterBang(proc, l1) { var head = sc_cons("dummy", l1); var it = head; var next = l1; while (next !== null) { if (proc(next.car) !== false) { it.cdr = next it = next; } next = next.cdr; } it.cdr = null; return head.cdr; } function sc_filterMap1(proc, l1) { var revres = null; while (l1 !== null) { var tmp = proc(l1.car) if (tmp !== false) revres = sc_cons(tmp, revres); l1 = l1.cdr; } return sc_reverseAppendBang(revres, null); } function sc_filterMap2(proc, l1, l2) { var revres = null; while (l1 !== null) { var tmp = proc(l1.car, l2.car); if (tmp !== false) revres = sc_cons(tmp, revres); l1 = l1.cdr; l2 = l2.cdr } return sc_reverseAppendBang(revres, null); } /*** META ((export #t)) */ function sc_filterMap(proc, l1, l2, l3) { if (l2 === undefined) return sc_filterMap1(proc, l1); else if (l3 === undefined) return sc_filterMap2(proc, l1, l2); // else var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); var revres = null; while (l1 !== null) { for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } var tmp = proc.apply(null, applyArgs); if (tmp !== false) revres = sc_cons(tmp, revres); } return sc_reverseAppendBang(revres, null); } /*** META ((export #t)) */ function sc_any(proc, l) { var revres = null; while (l !== null) { var tmp = proc(l.car); if (tmp !== false) return tmp; l = l.cdr; } return false; } /*** META ((export any?) (peephole (hole 2 "sc_any(" proc "," l ") !== false"))) */ function sc_anyPred(proc, l) { return sc_any(proc, l) !== false; } /*** META ((export #t)) */ function sc_every(proc, l) { var revres = null; var tmp = true; while (l !== null) { tmp = proc(l.car); if (tmp === false) return false; l = l.cdr; } return tmp; } /*** META ((export every?) (peephole (hole 2 "sc_every(" proc "," l ") !== false"))) */ function sc_everyPred(proc, l) { var tmp = sc_every(proc, l); if (tmp !== false) return true; return false; } /*** META ((export #t) (peephole (postfix "()"))) */ function sc_force(o) { return o(); } /*** META ((export #t)) */ function sc_makePromise(proc) { var isResultReady = false; var result = undefined; return function () { if (!isResultReady) { var tmp = proc(); if (!isResultReady) { isResultReady = true; result = tmp; } } return result; }; } function sc_Values(values) { this.values = values; } /*** META ((export #t) (peephole (values))) */ function sc_values() { if (arguments.length === 1) return arguments[0]; else return new sc_Values(arguments); } /*** META ((export #t)) */ function sc_callWithValues(producer, consumer) { var produced = producer(); if (produced instanceof sc_Values) return consumer.apply(null, produced.values); else return consumer(produced); } /*** META ((export #t)) */ function sc_dynamicWind(before, thunk, after) { before(); try { var res = thunk(); return res; } finally { after(); } } // TODO: eval/scheme-report-environment/null-environment/interaction-environment // LIMITATION: 'load' doesn't exist without files. // LIMITATION: transcript-on/transcript-off doesn't exist without files. function sc_Struct(name) { this.name = name; } sc_Struct.prototype.sc_toDisplayString = function () { return "#"; }; sc_Struct.prototype.sc_toWriteString = sc_Struct.prototype.sc_toDisplayString; /*** META ((export #t) (peephole (hole 1 "new sc_Struct(" name ")"))) */ function sc_makeStruct(name) { return new sc_Struct(name); } /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_Struct"))) */ function sc_isStruct(o) { return (o instanceof sc_Struct); } /*** META ((export #t) (type bool) (peephole (hole 2 "(" 1 " instanceof sc_Struct) && ( " 1 ".name === " 0 ")"))) */ function sc_isStructNamed(name, s) { return ((s instanceof sc_Struct) && (s.name === name)); } /*** META ((export struct-field) (peephole (hole 3 0 "[" 2 "]"))) */ function sc_getStructField(s, name, field) { return s[field]; } /*** META ((export struct-field-set!) (peephole (hole 4 0 "[" 2 "] = " 3))) */ function sc_setStructFieldBang(s, name, field, val) { s[field] = val; } /*** META ((export #t) (peephole (prefix "~"))) */ function sc_bitNot(x) { return ~x; } /*** META ((export #t) (peephole (infix 2 2 "&"))) */ function sc_bitAnd(x, y) { return x & y; } /*** META ((export #t) (peephole (infix 2 2 "|"))) */ function sc_bitOr(x, y) { return x | y; } /*** META ((export #t) (peephole (infix 2 2 "^"))) */ function sc_bitXor(x, y) { return x ^ y; } /*** META ((export #t) (peephole (infix 2 2 "<<"))) */ function sc_bitLsh(x, y) { return x << y; } /*** META ((export #t) (peephole (infix 2 2 ">>"))) */ function sc_bitRsh(x, y) { return x >> y; } /*** META ((export #t) (peephole (infix 2 2 ">>>"))) */ function sc_bitUrsh(x, y) { return x >>> y; } /*** META ((export js-field js-property) (peephole (hole 2 o "[" field "]"))) */ function sc_jsField(o, field) { return o[field]; } /*** META ((export js-field-set! js-property-set!) (peephole (hole 3 o "[" field "] = " val))) */ function sc_setJsFieldBang(o, field, val) { return o[field] = val; } /*** META ((export js-field-delete! js-property-delete!) (peephole (hole 2 "delete" o "[" field "]"))) */ function sc_deleteJsFieldBang(o, field) { delete o[field]; } /*** META ((export #t) (peephole (jsCall))) */ function sc_jsCall(o, fun) { var args = new Array(); for (var i = 2; i < arguments.length; i++) args[i - 2] = arguments[i]; return fun.apply(o, args); } /*** META ((export #t) (peephole (jsMethodCall))) */ function sc_jsMethodCall(o, field) { var args = new Array(); for (var i = 2; i < arguments.length; i++) args[i - 2] = arguments[i]; return o[field].apply(o, args); } /*** META ((export new js-new) (peephole (jsNew))) */ function sc_jsNew(c) { var evalStr = "new c("; evalStr += arguments.length > 1 ? "arguments[1]" : ""; for (var i = 2; i < arguments.length; i++) evalStr += ", arguments[" + i + "]"; evalStr += ")"; return eval(evalStr); } // ======================== RegExp ==================== /*** META ((export #t)) */ function sc_pregexp(re) { return new RegExp(sc_string2jsstring(re)); } /*** META ((export #t)) */ function sc_pregexpMatch(re, s) { var reg = (re instanceof RegExp) ? re : sc_pregexp(re); var tmp = reg.exec(sc_string2jsstring(s)); if (tmp == null) return false; var res = null; for (var i = tmp.length - 1; i >= 0; i--) { if (tmp[i] !== null) { res = sc_cons(sc_jsstring2string(tmp[i]), res); } else { res = sc_cons(false, res); } } return res; } /*** META ((export #t)) */ function sc_pregexpReplace(re, s1, s2) { var reg; var jss1 = sc_string2jsstring(s1); var jss2 = sc_string2jsstring(s2); if (re instanceof RegExp) { if (re.global) reg = re; else reg = new RegExp(re.source); } else { reg = new RegExp(sc_string2jsstring(re)); } return jss1.replace(reg, jss2); } /*** META ((export pregexp-replace*)) */ function sc_pregexpReplaceAll(re, s1, s2) { var reg; var jss1 = sc_string2jsstring(s1); var jss2 = sc_string2jsstring(s2); if (re instanceof RegExp) { if (re.global) reg = re; else reg = new RegExp(re.source, "g"); } else { reg = new RegExp(sc_string2jsstring(re), "g"); } return jss1.replace(reg, jss2); } /*** META ((export #t)) */ function sc_pregexpSplit(re, s) { var reg = ((re instanceof RegExp) ? re : new RegExp(sc_string2jsstring(re))); var jss = sc_string2jsstring(s); var tmp = jss.split(reg); if (tmp == null) return false; return sc_vector2list(tmp); } /* =========================================================================== */ /* Other library stuff */ /* =========================================================================== */ /*** META ((export #t) (peephole (hole 1 "Math.floor(Math.random()*" 'n ")"))) */ function sc_random(n) { return Math.floor(Math.random() * n); } /*** META ((export current-date) (peephole (hole 0 "new Date()"))) */ function sc_currentDate() { return new Date(); } function sc_Hashtable() { } sc_Hashtable.prototype.toString = function () { return "#{%hashtable}"; }; // sc_toWriteString == sc_toDisplayString == toString function sc_HashtableElement(key, val) { this.key = key; this.val = val; } /*** META ((export #t) (peephole (hole 0 "new sc_Hashtable()"))) */ function sc_makeHashtable() { return new sc_Hashtable(); } /*** META ((export #t)) */ function sc_hashtablePutBang(ht, key, val) { var hash = sc_hash(key); ht[hash] = new sc_HashtableElement(key, val); } /*** META ((export #t)) */ function sc_hashtableGet(ht, key) { var hash = sc_hash(key); if (hash in ht) return ht[hash].val; else return false; } /*** META ((export #t)) */ function sc_hashtableForEach(ht, f) { for (var v in ht) { if (ht[v] instanceof sc_HashtableElement) f(ht[v].key, ht[v].val); } } /*** META ((export hashtable-contains?) (peephole (hole 2 "sc_hash(" 1 ") in " 0))) */ function sc_hashtableContains(ht, key) { var hash = sc_hash(key); if (hash in ht) return true; else return false; } var SC_HASH_COUNTER = 0; function sc_hash(o) { if (o === null) return "null"; else if (o === undefined) return "undefined"; else if (o === true) return "true"; else if (o === false) return "false"; else if (typeof o === "number") return "num-" + o; else if (typeof o === "string") return "jsstr-" + o; else if (o.sc_getHash) return o.sc_getHash(); else return sc_counterHash.call(o); } function sc_counterHash() { if (!this.sc_hash) { this.sc_hash = "hash-" + SC_HASH_COUNTER; SC_HASH_COUNTER++; } return this.sc_hash; } function sc_Trampoline(args, maxTailCalls) { this['__trampoline return__'] = true; this.args = args; this.MAX_TAIL_CALLs = maxTailCalls; } // TODO: call/cc stuff sc_Trampoline.prototype.restart = function () { var o = this; while (true) { // set both globals. SC_TAIL_OBJECT.calls = o.MAX_TAIL_CALLs - 1; var fun = o.args.callee; var res = fun.apply(SC_TAIL_OBJECT, o.args); if (res instanceof sc_Trampoline) o = res; else return res; } } /*** META ((export bind-exit-lambda)) */ function sc_bindExitLambda(proc) { var escape_obj = new sc_BindExitException(); var escape = function (res) { escape_obj.res = res; throw escape_obj; }; try { return proc(escape); } catch (e) { if (e === escape_obj) { return e.res; } throw e; } } function sc_BindExitException() { this._internalException = true; } var SC_SCM2JS_GLOBALS = new Object(); // default tail-call depth. // normally the program should set it again. but just in case... var SC_TAIL_OBJECT = new Object(); SC_SCM2JS_GLOBALS.TAIL_OBJECT = SC_TAIL_OBJECT; // ======================== I/O ======================= /*------------------------------------------------------------------*/ function sc_EOF() { } var SC_EOF_OBJECT = new sc_EOF(); function sc_Port() { } /* --------------- Input ports -------------------------------------*/ function sc_InputPort() { } sc_InputPort.prototype = new sc_Port(); sc_InputPort.prototype.peekChar = function () { if (!("peeked" in this)) this.peeked = this.getNextChar(); return this.peeked; } sc_InputPort.prototype.readChar = function () { var tmp = this.peekChar(); delete this.peeked; return tmp; } sc_InputPort.prototype.isCharReady = function () { return true; } sc_InputPort.prototype.close = function () { // do nothing } /* .............. String port ..........................*/ function sc_ErrorInputPort() { } sc_ErrorInputPort.prototype = new sc_InputPort(); sc_ErrorInputPort.prototype.getNextChar = function () { throw "can't read from error-port."; }; sc_ErrorInputPort.prototype.isCharReady = function () { return false; }; /* .............. String port ..........................*/ function sc_StringInputPort(jsStr) { // we are going to do some charAts on the str. // instead of recreating all the time a String-object, we // create one in the beginning. (not sure, if this is really an optim) this.str = new String(jsStr); this.pos = 0; } sc_StringInputPort.prototype = new sc_InputPort(); sc_StringInputPort.prototype.getNextChar = function () { if (this.pos >= this.str.length) return SC_EOF_OBJECT; return this.str.charAt(this.pos++); }; /* ------------- Read and other lib-funs -------------------------------*/ function sc_Token(type, val, pos) { this.type = type; this.val = val; this.pos = pos; } sc_Token.EOF = 0/*EOF*/; sc_Token.OPEN_PAR = 1/*OPEN_PAR*/; sc_Token.CLOSE_PAR = 2/*CLOSE_PAR*/; sc_Token.OPEN_BRACE = 3/*OPEN_BRACE*/; sc_Token.CLOSE_BRACE = 4/*CLOSE_BRACE*/; sc_Token.OPEN_BRACKET = 5/*OPEN_BRACKET*/; sc_Token.CLOSE_BRACKET = 6/*CLOSE_BRACKET*/; sc_Token.WHITESPACE = 7/*WHITESPACE*/; sc_Token.QUOTE = 8/*QUOTE*/; sc_Token.ID = 9/*ID*/; sc_Token.DOT = 10/*DOT*/; sc_Token.STRING = 11/*STRING*/; sc_Token.NUMBER = 12/*NUMBER*/; sc_Token.ERROR = 13/*ERROR*/; sc_Token.VECTOR_BEGIN = 14/*VECTOR_BEGIN*/; sc_Token.TRUE = 15/*TRUE*/; sc_Token.FALSE = 16/*FALSE*/; sc_Token.UNSPECIFIED = 17/*UNSPECIFIED*/; sc_Token.REFERENCE = 18/*REFERENCE*/; sc_Token.STORE = 19/*STORE*/; sc_Token.CHAR = 20/*CHAR*/; var SC_ID_CLASS = SC_LOWER_CLASS + SC_UPPER_CLASS + "!$%*+-./:<=>?@^_~"; function sc_Tokenizer(port) { this.port = port; } sc_Tokenizer.prototype.peekToken = function () { if (this.peeked) return this.peeked; var newToken = this.nextToken(); this.peeked = newToken; return newToken; }; sc_Tokenizer.prototype.readToken = function () { var tmp = this.peekToken(); delete this.peeked; return tmp; }; sc_Tokenizer.prototype.nextToken = function () { var port = this.port; function isNumberChar(c) { return (c >= "0" && c <= "9"); } function isIdOrNumberChar(c) { return SC_ID_CLASS.indexOf(c) != -1 || // ID-char (c >= "0" && c <= "9"); } function isWhitespace(c) { return c === " " || c === "\r" || c === "\n" || c === "\t" || c === "\f"; } function isWhitespaceOrEOF(c) { return isWhitespace(c) || c === SC_EOF_OBJECT; } function readString() { res = ""; while (true) { var c = port.readChar(); switch (c) { case '"': return new sc_Token(11/*STRING*/, res); case "\\": var tmp = port.readChar(); switch (tmp) { case '0': res += "\0"; break; case 'a': res += "\a"; break; case 'b': res += "\b"; break; case 'f': res += "\f"; break; case 'n': res += "\n"; break; case 'r': res += "\r"; break; case 't': res += "\t"; break; case 'v': res += "\v"; break; case '"': res += '"'; break; case '\\': res += '\\'; break; case 'x': /* hexa-number */ var nb = 0; while (true) { var hexC = port.peekChar(); if (hexC >= '0' && hexC <= '9') { port.readChar(); nb = nb * 16 + hexC.charCodeAt(0) - '0'.charCodeAt(0); } else if (hexC >= 'a' && hexC <= 'f') { port.readChar(); nb = nb * 16 + hexC.charCodeAt(0) - 'a'.charCodeAt(0); } else if (hexC >= 'A' && hexC <= 'F') { port.readChar(); nb = nb * 16 + hexC.charCodeAt(0) - 'A'.charCodeAt(0); } else { // next char isn't part of hex. res += String.fromCharCode(nb); break; } } break; default: if (tmp === SC_EOF_OBJECT) { return new sc_Token(13/*ERROR*/, "unclosed string-literal" + res); } res += tmp; } break; default: if (c === SC_EOF_OBJECT) { return new sc_Token(13/*ERROR*/, "unclosed string-literal" + res); } res += c; } } } function readIdOrNumber(firstChar) { var res = firstChar; while (isIdOrNumberChar(port.peekChar())) res += port.readChar(); if (isNaN(res)) return new sc_Token(9/*ID*/, res); else return new sc_Token(12/*NUMBER*/, res - 0); } function skipWhitespaceAndComments() { var done = false; while (!done) { done = true; while (isWhitespace(port.peekChar())) port.readChar(); if (port.peekChar() === ';') { port.readChar(); done = false; while (true) { curChar = port.readChar(); if (curChar === SC_EOF_OBJECT || curChar === '\n') break; } } } } function readDot() { if (isWhitespace(port.peekChar())) return new sc_Token(10/*DOT*/); else return readIdOrNumber("."); } function readSharp() { var c = port.readChar(); if (isWhitespace(c)) return new sc_Token(13/*ERROR*/, "bad #-pattern0."); // reference if (isNumberChar(c)) { var nb = c - 0; while (isNumberChar(port.peekChar())) nb = nb * 10 + (port.readChar() - 0); switch (port.readChar()) { case '#': return new sc_Token(18/*REFERENCE*/, nb); case '=': return new sc_Token(19/*STORE*/, nb); default: return new sc_Token(13/*ERROR*/, "bad #-pattern1." + nb); } } if (c === "(") return new sc_Token(14/*VECTOR_BEGIN*/); if (c === "\\") { // character var tmp = "" while (!isWhitespaceOrEOF(port.peekChar())) tmp += port.readChar(); switch (tmp.length) { case 0: // it's escaping a whitespace char: if (sc_isEOFObject(port.peekChar)) return new sc_Token(13/*ERROR*/, "bad #-pattern2."); else return new sc_Token(20/*CHAR*/, port.readChar()); case 1: return new sc_Token(20/*CHAR*/, tmp); default: var entry = sc_Char.readable2char[tmp.toLowerCase()]; if (entry) return new sc_Token(20/*CHAR*/, entry); else return new sc_Token(13/*ERROR*/, "unknown character description: #\\" + tmp); } } // some constants (#t, #f, #unspecified) var res; var needing; switch (c) { case 't': res = new sc_Token(15/*TRUE*/, true); needing = ""; break; case 'f': res = new sc_Token(16/*FALSE*/, false); needing = ""; break; case 'u': res = new sc_Token(17/*UNSPECIFIED*/, undefined); needing = "nspecified"; break; default: return new sc_Token(13/*ERROR*/, "bad #-pattern3: " + c); } while (true) { c = port.peekChar(); if ((isWhitespaceOrEOF(c) || c === ')') && needing == "") return res; else if (isWhitespace(c) || needing == "") return new sc_Token(13/*ERROR*/, "bad #-pattern4 " + c + " " + needing); else if (needing.charAt(0) == c) { port.readChar(); // consume needing = needing.slice(1); } else return new sc_Token(13/*ERROR*/, "bad #-pattern5"); } } skipWhitespaceAndComments(); var curChar = port.readChar(); if (curChar === SC_EOF_OBJECT) return new sc_Token(0/*EOF*/, curChar); switch (curChar) { case " ": case "\n": case "\t": return readWhitespace(); case "(": return new sc_Token(1/*OPEN_PAR*/); case ")": return new sc_Token(2/*CLOSE_PAR*/); case "{": return new sc_Token(3/*OPEN_BRACE*/); case "}": return new sc_Token(4/*CLOSE_BRACE*/); case "[": return new sc_Token(5/*OPEN_BRACKET*/); case "]": return new sc_Token(6/*CLOSE_BRACKET*/); case "'": return new sc_Token(8/*QUOTE*/); case "#": return readSharp(); case ".": return readDot(); case '"': return readString(); default: if (isIdOrNumberChar(curChar)) return readIdOrNumber(curChar); throw "unexpected character: " + curChar; } }; function sc_Reader(tokenizer) { this.tokenizer = tokenizer; this.backref = new Array(); } sc_Reader.prototype.read = function () { function readList(listBeginType) { function matchesPeer(open, close) { return open === 1/*OPEN_PAR*/ && close === 2/*CLOSE_PAR*/ || open === 3/*OPEN_BRACE*/ && close === 4/*CLOSE_BRACE*/ || open === 5/*OPEN_BRACKET*/ && close === 6/*CLOSE_BRACKET*/; } var res = null; while (true) { var token = tokenizer.peekToken(); switch (token.type) { case 2/*CLOSE_PAR*/ : case 4/*CLOSE_BRACE*/ : case 6/*CLOSE_BRACKET*/ : if (matchesPeer(listBeginType, token.type)) { tokenizer.readToken(); // consume token return sc_reverseBang(res); } else throw "closing par doesn't match: " + listBeginType + " " + listEndType; case 0/*EOF*/ : throw "unexpected end of file"; case 10/*DOT*/ : tokenizer.readToken(); // consume token var cdr = this.read(); var par = tokenizer.readToken(); if (!matchesPeer(listBeginType, par.type)) throw "closing par doesn't match: " + listBeginType + " " + par.type; else return sc_reverseAppendBang(res, cdr); default: res = sc_cons(this.read(), res); } } } function readQuote() { return sc_cons("quote", sc_cons(this.read(), null)); } function readVector() { // opening-parenthesis is already consumed var a = new Array(); while (true) { var token = tokenizer.peekToken(); switch (token.type) { case 2/*CLOSE_PAR*/ : tokenizer.readToken(); return a; default: a.push(this.read()); } } } function storeRefence(nb) { var tmp = this.read(); this.backref[nb] = tmp; return tmp; } function readReference(nb) { if (nb in this.backref) return this.backref[nb]; else throw "bad reference: " + nb; } var tokenizer = this.tokenizer; var token = tokenizer.readToken(); // handle error if (token.type === 13/*ERROR*/) throw token.val; switch (token.type) { case 1/*OPEN_PAR*/ : case 3/*OPEN_BRACE*/ : case 5/*OPEN_BRACKET*/ : return readList.call(this, token.type); case 8/*QUOTE*/ : return readQuote.call(this); case 11/*STRING*/ : return sc_jsstring2string(token.val); case 20/*CHAR*/ : return new sc_Char(token.val); case 14/*VECTOR_BEGIN*/ : return readVector.call(this); case 18/*REFERENCE*/ : return readReference.call(this, token.val); case 19/*STORE*/ : return storeRefence.call(this, token.val); case 9/*ID*/ : return sc_jsstring2symbol(token.val); case 0/*EOF*/ : case 12/*NUMBER*/ : case 15/*TRUE*/ : case 16/*FALSE*/ : case 17/*UNSPECIFIED*/ : return token.val; default: throw "unexpected token " + token.type + " " + token.val; } }; /*** META ((export #t)) */ function sc_read(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... var reader = new sc_Reader(new sc_Tokenizer(port)); return reader.read(); } /*** META ((export #t)) */ function sc_readChar(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... var t = port.readChar(); return t === SC_EOF_OBJECT ? t : new sc_Char(t); } /*** META ((export #t)) */ function sc_peekChar(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... var t = port.peekChar(); return t === SC_EOF_OBJECT ? t : new sc_Char(t); } /*** META ((export #t) (type bool)) */ function sc_isCharReady(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... return port.isCharReady(); } /*** META ((export #t) (peephole (postfix ".close()"))) */ function sc_closeInputPort(p) { return p.close(); } /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_InputPort"))) */ function sc_isInputPort(o) { return (o instanceof sc_InputPort); } /*** META ((export eof-object?) (type bool) (peephole (postfix " === SC_EOF_OBJECT"))) */ function sc_isEOFObject(o) { return o === SC_EOF_OBJECT; } /*** META ((export #t) (peephole (hole 0 "SC_DEFAULT_IN"))) */ function sc_currentInputPort() { return SC_DEFAULT_IN; } /* ------------ file operations are not supported -----------*/ /*** META ((export #t)) */ function sc_callWithInputFile(s, proc) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_callWithOutputFile(s, proc) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_withInputFromFile(s, thunk) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_withOutputToFile(s, thunk) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_openInputFile(s) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_openOutputFile(s) { throw "can't open " + s; } /* ----------------------------------------------------------------------------*/ /*** META ((export #t)) */ function sc_basename(p) { var i = p.lastIndexOf('/'); if (i >= 0) return p.substring(i + 1, p.length); else return ''; } /*** META ((export #t)) */ function sc_dirname(p) { var i = p.lastIndexOf('/'); if (i >= 0) return p.substring(0, i); else return ''; } /* ----------------------------------------------------------------------------*/ /*** META ((export #t)) */ function sc_withInputFromPort(p, thunk) { try { var tmp = SC_DEFAULT_IN; // THREAD: shared var. SC_DEFAULT_IN = p; return thunk(); } finally { SC_DEFAULT_IN = tmp; } } /*** META ((export #t)) */ function sc_withInputFromString(s, thunk) { return sc_withInputFromPort(new sc_StringInputPort(sc_string2jsstring(s)), thunk); } /*** META ((export #t)) */ function sc_withOutputToPort(p, thunk) { try { var tmp = SC_DEFAULT_OUT; // THREAD: shared var. SC_DEFAULT_OUT = p; return thunk(); } finally { SC_DEFAULT_OUT = tmp; } } /*** META ((export #t)) */ function sc_withOutputToString(thunk) { var p = new sc_StringOutputPort(); sc_withOutputToPort(p, thunk); return p.close(); } /*** META ((export #t)) */ function sc_withOutputToProcedure(proc, thunk) { var t = function (s) { proc(sc_jsstring2string(s)); }; return sc_withOutputToPort(new sc_GenericOutputPort(t), thunk); } /*** META ((export #t) (peephole (hole 0 "new sc_StringOutputPort()"))) */ function sc_openOutputString() { return new sc_StringOutputPort(); } /*** META ((export #t)) */ function sc_openInputString(str) { return new sc_StringInputPort(sc_string2jsstring(str)); } /* ----------------------------------------------------------------------------*/ function sc_OutputPort() { } sc_OutputPort.prototype = new sc_Port(); sc_OutputPort.prototype.appendJSString = function (obj) { /* do nothing */ } sc_OutputPort.prototype.close = function () { /* do nothing */ } function sc_StringOutputPort() { this.res = ""; } sc_StringOutputPort.prototype = new sc_OutputPort(); sc_StringOutputPort.prototype.appendJSString = function (s) { this.res += s; } sc_StringOutputPort.prototype.close = function () { return sc_jsstring2string(this.res); } /*** META ((export #t)) */ function sc_getOutputString(sp) { return sc_jsstring2string(sp.res); } function sc_ErrorOutputPort() { } sc_ErrorOutputPort.prototype = new sc_OutputPort(); sc_ErrorOutputPort.prototype.appendJSString = function (s) { throw "don't write on ErrorPort!"; } sc_ErrorOutputPort.prototype.close = function () { /* do nothing */ } function sc_GenericOutputPort(appendJSString, close) { this.appendJSString = appendJSString; if (close) this.close = close; } sc_GenericOutputPort.prototype = new sc_OutputPort(); /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_OutputPort"))) */ function sc_isOutputPort(o) { return (o instanceof sc_OutputPort); } /*** META ((export #t) (peephole (postfix ".close()"))) */ function sc_closeOutputPort(p) { return p.close(); } /* ------------------ write ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_write(o, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(sc_toWriteString(o)); } function sc_toWriteString(o) { if (o === null) return "()"; else if (o === true) return "#t"; else if (o === false) return "#f"; else if (o === undefined) return "#unspecified"; else if (typeof o === 'function') return "#"; else if (o.sc_toWriteString) return o.sc_toWriteString(); else return o.toString(); } function sc_escapeWriteString(s) { var res = ""; var j = 0; for (i = 0; i < s.length; i++) { switch (s.charAt(i)) { case "\0": res += s.substring(j, i) + "\\0"; j = i + 1; break; case "\b": res += s.substring(j, i) + "\\b"; j = i + 1; break; case "\f": res += s.substring(j, i) + "\\f"; j = i + 1; break; case "\n": res += s.substring(j, i) + "\\n"; j = i + 1; break; case "\r": res += s.substring(j, i) + "\\r"; j = i + 1; break; case "\t": res += s.substring(j, i) + "\\t"; j = i + 1; break; case "\v": res += s.substring(j, i) + "\\v"; j = i + 1; break; case '"': res += s.substring(j, i) + '\\"'; j = i + 1; break; case "\\": res += s.substring(j, i) + "\\\\"; j = i + 1; break; default: var c = s.charAt(i); if ("\a" !== "a" && c == "\a") { res += s.substring(j, i) + "\\a"; j = i + 1; continue; } if ("\v" !== "v" && c == "\v") { res += s.substring(j, i) + "\\v"; j = i + 1; continue; } //if (s.charAt(i) < ' ' || s.charCodeAt(i) > 127) { // CARE: Manuel is this OK with HOP? if (s.charAt(i) < ' ') { /* non printable character and special chars */ res += s.substring(j, i) + "\\x" + s.charCodeAt(i).toString(16); j = i + 1; } // else just let i increase... } } res += s.substring(j, i); return res; } /* ------------------ display ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_display(o, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(sc_toDisplayString(o)); } function sc_toDisplayString(o) { if (o === null) return "()"; else if (o === true) return "#t"; else if (o === false) return "#f"; else if (o === undefined) return "#unspecified"; else if (typeof o === 'function') return "#"; else if (o.sc_toDisplayString) return o.sc_toDisplayString(); else return o.toString(); } /* ------------------ newline ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_newline(p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString("\n"); } /* ------------------ write-char ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_writeChar(c, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(c.val); } /* ------------------ write-circle ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_writeCircle(o, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(sc_toWriteCircleString(o)); } function sc_toWriteCircleString(o) { var symb = sc_gensym("writeCircle"); var nbPointer = new Object(); nbPointer.nb = 0; sc_prepWriteCircle(o, symb, nbPointer); return sc_genToWriteCircleString(o, symb); } function sc_prepWriteCircle(o, symb, nbPointer) { // TODO sc_Struct if (o instanceof sc_Pair || o instanceof sc_Vector) { if (o[symb] !== undefined) { // not the first visit. o[symb]++; // unless there is already a number, assign one. if (!o[symb + "nb"]) o[symb + "nb"] = nbPointer.nb++; return; } o[symb] = 0; if (o instanceof sc_Pair) { sc_prepWriteCircle(o.car, symb, nbPointer); sc_prepWriteCircle(o.cdr, symb, nbPointer); } else { for (var i = 0; i < o.length; i++) sc_prepWriteCircle(o[i], symb, nbPointer); } } } function sc_genToWriteCircleString(o, symb) { if (!(o instanceof sc_Pair || o instanceof sc_Vector)) return sc_toWriteString(o); return o.sc_toWriteCircleString(symb); } sc_Pair.prototype.sc_toWriteCircleString = function (symb, inList) { if (this[symb + "use"]) { // use-flag is set. Just use it. var nb = this[symb + "nb"]; if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } if (inList) return '. #' + nb + '#'; else return '#' + nb + '#'; } if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } var res = ""; if (this[symb] !== undefined) { // implies > 0 this[symb + "use"] = true; if (inList) res += '. #' + this[symb + "nb"] + '='; else res += '#' + this[symb + "nb"] + '='; inList = false; } if (!inList) res += "("; // print car res += sc_genToWriteCircleString(this.car, symb); if (sc_isPair(this.cdr)) { res += " " + this.cdr.sc_toWriteCircleString(symb, true); } else if (this.cdr !== null) { res += " . " + sc_genToWriteCircleString(this.cdr, symb); } if (!inList) res += ")"; return res; }; sc_Vector.prototype.sc_toWriteCircleString = function (symb) { if (this[symb + "use"]) { // use-flag is set. Just use it. var nb = this[symb + "nb"]; if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } return '#' + nb + '#'; } if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } var res = ""; if (this[symb] !== undefined) { // implies > 0 this[symb + "use"] = true; res += '#' + this[symb + "nb"] + '='; } res += "#("; for (var i = 0; i < this.length; i++) { res += sc_genToWriteCircleString(this[i], symb); if (i < this.length - 1) res += " "; } res += ")"; return res; }; /* ------------------ print ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_print(s) { if (arguments.length === 1) { sc_display(s); sc_newline(); } else { for (var i = 0; i < arguments.length; i++) sc_display(arguments[i]); sc_newline(); } } /* ------------------ format ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_format(s, args) { var len = s.length; var p = new sc_StringOutputPort(); var i = 0, j = 1; while (i < len) { var i2 = s.indexOf("~", i); if (i2 == -1) { p.appendJSString(s.substring(i, len)); return p.close(); } else { if (i2 > i) { if (i2 == (len - 1)) { p.appendJSString(s.substring(i, len)); return p.close(); } else { p.appendJSString(s.substring(i, i2)); i = i2; } } switch (s.charCodeAt(i2 + 1)) { case 65: case 97: // a sc_display(arguments[j], p); i += 2; j++; break; case 83: case 115: // s sc_write(arguments[j], p); i += 2; j++; break; case 86: case 118: // v sc_display(arguments[j], p); p.appendJSString("\n"); i += 2; j++; break; case 67: case 99: // c p.appendJSString(String.fromCharCode(arguments[j])); i += 2; j++; break; case 88: case 120: // x p.appendJSString(arguments[j].toString(6)); i += 2; j++; break; case 79: case 111: // o p.appendJSString(arguments[j].toString(8)); i += 2; j++; break; case 66: case 98: // b p.appendJSString(arguments[j].toString(2)); i += 2; j++; break; case 37: case 110: // %, n p.appendJSString("\n"); i += 2; break; case 114: // r p.appendJSString("\r"); i += 2; break; case 126: // ~ p.appendJSString("~"); i += 2; break; default: sc_error("format: illegal ~" + String.fromCharCode(s.charCodeAt(i2 + 1)) + " sequence"); return ""; } } } return p.close(); } /* ------------------ global ports ---------------------------------------------------*/ var SC_DEFAULT_IN = new sc_ErrorInputPort(); var SC_DEFAULT_OUT = new sc_ErrorOutputPort(); var SC_ERROR_OUT = new sc_ErrorOutputPort(); var sc_SYMBOL_PREFIX = "\u1E9C"; var sc_KEYWORD_PREFIX = "\u1E9D"; /*** META ((export #t) (peephole (id))) */ function sc_jsstring2string(s) { return s; } /*** META ((export #t) (peephole (prefix "'\\u1E9C' +"))) */ function sc_jsstring2symbol(s) { return sc_SYMBOL_PREFIX + s; } /*** META ((export #t) (peephole (id))) */ function sc_string2jsstring(s) { return s; } /*** META ((export #t) (peephole (symbol2jsstring_immutable))) */ function sc_symbol2jsstring(s) { return s.slice(1); } /*** META ((export #t) (peephole (postfix ".slice(1)"))) */ function sc_keyword2jsstring(k) { return k.slice(1); } /*** META ((export #t) (peephole (prefix "'\\u1E9D' +"))) */ function sc_jsstring2keyword(s) { return sc_KEYWORD_PREFIX + s; } /*** META ((export #t) (type bool)) */ function sc_isKeyword(s) { return (typeof s === "string") && (s.charAt(0) === sc_KEYWORD_PREFIX); } /*** META ((export #t)) */ var sc_gensym = function () { var counter = 1000; return function (sym) { counter++; if (!sym) sym = sc_SYMBOL_PREFIX; return sym + "s" + counter + "~" + "^sC-GeNsYm "; }; }(); /*** META ((export #t) (type bool)) */ function sc_isEqual(o1, o2) { return ((o1 === o2) || (sc_isPair(o1) && sc_isPair(o2) && sc_isPairEqual(o1, o2, sc_isEqual)) || (sc_isVector(o1) && sc_isVector(o2) && sc_isVectorEqual(o1, o2, sc_isEqual))); } /*** META ((export number->symbol integer->symbol)) */ function sc_number2symbol(x, radix) { return sc_SYMBOL_PREFIX + sc_number2jsstring(x, radix); } /*** META ((export number->string integer->string)) */ var sc_number2string = sc_number2jsstring; /*** META ((export #t)) */ function sc_symbol2number(s, radix) { return sc_jsstring2number(s.slice(1), radix); } /*** META ((export #t)) */ var sc_string2number = sc_jsstring2number; /*** META ((export #t) (peephole (prefix "+" s))) ;; peephole will only apply if no radix is given. */ function sc_string2integer(s, radix) { if (!radix) return +s; return parseInt(s, radix); } /*** META ((export #t) (peephole (prefix "+"))) */ function sc_string2real(s) { return +s; } /*** META ((export #t) (type bool)) */ function sc_isSymbol(s) { return (typeof s === "string") && (s.charAt(0) === sc_SYMBOL_PREFIX); } /*** META ((export #t) (peephole (symbol2string_immutable))) */ function sc_symbol2string(s) { return s.slice(1); } /*** META ((export #t) (peephole (prefix "'\\u1E9C' +"))) */ function sc_string2symbol(s) { return sc_SYMBOL_PREFIX + s; } /*** META ((export symbol-append) (peephole (symbolAppend_immutable))) */ function sc_symbolAppend() { var res = sc_SYMBOL_PREFIX; for (var i = 0; i < arguments.length; i++) res += arguments[i].slice(1); return res; } /*** META ((export #t) (peephole (postfix ".val"))) */ function sc_char2string(c) { return c.val; } /*** META ((export #t) (peephole (hole 1 "'\\u1E9C' + " c ".val"))) */ function sc_char2symbol(c) { return sc_SYMBOL_PREFIX + c.val; } /*** META ((export #t) (type bool)) */ function sc_isString(s) { return (typeof s === "string") && (s.charAt(0) !== sc_SYMBOL_PREFIX); } /*** META ((export #t)) */ var sc_makeString = sc_makejsString; /*** META ((export #t)) */ function sc_string() { for (var i = 0; i < arguments.length; i++) arguments[i] = arguments[i].val; return "".concat.apply("", arguments); } /*** META ((export #t) (peephole (postfix ".length"))) */ function sc_stringLength(s) { return s.length; } /*** META ((export #t)) */ function sc_stringRef(s, k) { return new sc_Char(s.charAt(k)); } /* there's no stringSet in the immutable version function sc_stringSet(s, k, c) */ /*** META ((export string=?) (type bool) (peephole (hole 2 str1 " === " str2))) */ function sc_isStringEqual(s1, s2) { return s1 === s2; } /*** META ((export string?) (type bool) (peephole (hole 2 str1 " > " str2))) */ function sc_isStringGreater(s1, s2) { return s1 > s2; } /*** META ((export string<=?) (type bool) (peephole (hole 2 str1 " <= " str2))) */ function sc_isStringLessEqual(s1, s2) { return s1 <= s2; } /*** META ((export string>=?) (type bool) (peephole (hole 2 str1 " >= " str2))) */ function sc_isStringGreaterEqual(s1, s2) { return s1 >= s2; } /*** META ((export string-ci=?) (type bool) (peephole (hole 2 str1 ".toLowerCase() === " str2 ".toLowerCase()"))) */ function sc_isStringCIEqual(s1, s2) { return s1.toLowerCase() === s2.toLowerCase(); } /*** META ((export string-ci?) (type bool) (peephole (hole 2 str1 ".toLowerCase() > " str2 ".toLowerCase()"))) */ function sc_isStringCIGreater(s1, s2) { return s1.toLowerCase() > s2.toLowerCase(); } /*** META ((export string-ci<=?) (type bool) (peephole (hole 2 str1 ".toLowerCase() <= " str2 ".toLowerCase()"))) */ function sc_isStringCILessEqual(s1, s2) { return s1.toLowerCase() <= s2.toLowerCase(); } /*** META ((export string-ci>=?) (type bool) (peephole (hole 2 str1 ".toLowerCase() >= " str2 ".toLowerCase()"))) */ function sc_isStringCIGreaterEqual(s1, s2) { return s1.toLowerCase() >= s2.toLowerCase(); } /*** META ((export #t) (peephole (hole 3 s ".substring(" start ", " end ")"))) */ function sc_substring(s, start, end) { return s.substring(start, end); } /*** META ((export #t)) */ function sc_isSubstring_at(s1, s2, i) { return s2 == s1.substring(i, i + s2.length); } /*** META ((export #t) (peephole (infix 0 #f "+" "''"))) */ function sc_stringAppend() { return "".concat.apply("", arguments); } /*** META ((export #t)) */ var sc_string2list = sc_jsstring2list; /*** META ((export #t)) */ var sc_list2string = sc_list2jsstring; /*** META ((export #t) (peephole (id))) */ function sc_stringCopy(s) { return s; } /* there's no string-fill in the immutable version function sc_stringFill(s, c) */ /*** META ((export #t) (peephole (postfix ".slice(1)"))) */ function sc_keyword2string(o) { return o.slice(1); } /*** META ((export #t) (peephole (prefix "'\\u1E9D' +"))) */ function sc_string2keyword(o) { return sc_KEYWORD_PREFIX + o; } String.prototype.sc_toDisplayString = function () { if (this.charAt(0) === sc_SYMBOL_PREFIX) // TODO: care for symbols with spaces (escape-chars symbols). return this.slice(1); else if (this.charAt(0) === sc_KEYWORD_PREFIX) return ":" + this.slice(1); else return this.toString(); }; String.prototype.sc_toWriteString = function () { if (this.charAt(0) === sc_SYMBOL_PREFIX) // TODO: care for symbols with spaces (escape-chars symbols). return this.slice(1); else if (this.charAt(0) === sc_KEYWORD_PREFIX) return ":" + this.slice(1); else return '"' + sc_escapeWriteString(this) + '"'; }; /* Exported Variables */ var BgL_testzd2boyerzd2; var BgL_nboyerzd2benchmarkzd2; var BgL_setupzd2boyerzd2; /* End Exports */ var translate_term_nboyer; var translate_args_nboyer; var untranslate_term_nboyer; var BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer; var BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer; var translate_alist_nboyer; var apply_subst_nboyer; var apply_subst_lst_nboyer; var tautologyp_nboyer; var if_constructor_nboyer; var rewrite_count_nboyer; var rewrite_nboyer; var rewrite_args_nboyer; var unify_subst_nboyer; var one_way_unify1_nboyer; var false_term_nboyer; var true_term_nboyer; var trans_of_implies1_nboyer; var is_term_equal_nboyer; var is_term_member_nboyer; var const_nboyer; var sc_const_3_nboyer; var sc_const_4_nboyer; { (sc_const_4_nboyer = (new sc_Pair("\u1E9Cimplies", (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cz", (new sc_Pair("\u1E9Cu", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cu", (new sc_Pair("\u1E9Cw", null)))))), null)))))), null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cw", null)))))), null))))))); (sc_const_3_nboyer = sc_list((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ccompile", (new sc_Pair("\u1E9Cform", null)))), (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair((new sc_Pair("\u1E9Ccodegen", (new sc_Pair((new sc_Pair("\u1E9Coptimize", (new sc_Pair("\u1E9Cform", null)))), (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ceqp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cy", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cgreaterp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clesseqp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cgreatereqp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cboolean", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Ct", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cf", null)), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ciff", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ceven1", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Ct", null)), (new sc_Pair((new sc_Pair("\u1E9Codd", (new sc_Pair((new sc_Pair("\u1E9Csub1", (new sc_Pair("\u1E9Cx", null)))), null)))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ccountps-", (new sc_Pair("\u1E9Cl", (new sc_Pair("\u1E9Cpred", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ccountps-loop", (new sc_Pair("\u1E9Cl", (new sc_Pair("\u1E9Cpred", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cfact-", (new sc_Pair("\u1E9Ci", null)))), (new sc_Pair((new sc_Pair("\u1E9Cfact-loop", (new sc_Pair("\u1E9Ci", (new sc_Pair((1), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Creverse-", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Creverse-loop", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdivides", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cassume-true", (new sc_Pair("\u1E9Cvar", (new sc_Pair("\u1E9Calist", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cvar", (new sc_Pair((new sc_Pair("\u1E9Ct", null)), null)))))), (new sc_Pair("\u1E9Calist", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cassume-false", (new sc_Pair("\u1E9Cvar", (new sc_Pair("\u1E9Calist", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cvar", (new sc_Pair((new sc_Pair("\u1E9Cf", null)), null)))))), (new sc_Pair("\u1E9Calist", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctautology-checker", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Ctautologyp", (new sc_Pair((new sc_Pair("\u1E9Cnormalize", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cfalsify", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cfalsify1", (new sc_Pair((new sc_Pair("\u1E9Cnormalize", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cprime", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))), null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cprime1", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Csub1", (new sc_Pair("\u1E9Cx", null)))), null)))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair("\u1E9Cp", (new sc_Pair("\u1E9Cq", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cp", (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cq", (new sc_Pair((new sc_Pair("\u1E9Ct", null)), (new sc_Pair((new sc_Pair("\u1E9Cf", null)), null)))))))), (new sc_Pair((new sc_Pair("\u1E9Cf", null)), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair("\u1E9Cp", (new sc_Pair("\u1E9Cq", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cp", (new sc_Pair((new sc_Pair("\u1E9Ct", null)), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cq", (new sc_Pair((new sc_Pair("\u1E9Ct", null)), (new sc_Pair((new sc_Pair("\u1E9Cf", null)), null)))))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair("\u1E9Cp", null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cp", (new sc_Pair((new sc_Pair("\u1E9Cf", null)), (new sc_Pair((new sc_Pair("\u1E9Ct", null)), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cimplies", (new sc_Pair("\u1E9Cp", (new sc_Pair("\u1E9Cq", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cp", (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cq", (new sc_Pair((new sc_Pair("\u1E9Ct", null)), (new sc_Pair((new sc_Pair("\u1E9Cf", null)), null)))))))), (new sc_Pair((new sc_Pair("\u1E9Ct", null)), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", (new sc_Pair("\u1E9Cc", null)))))))), (new sc_Pair("\u1E9Cd", (new sc_Pair("\u1E9Ce", null)))))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Ca", (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cb", (new sc_Pair("\u1E9Cd", (new sc_Pair("\u1E9Ce", null)))))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair("\u1E9Cc", (new sc_Pair("\u1E9Cd", (new sc_Pair("\u1E9Ce", null)))))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cx", null)))), null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Ca", null)))), (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cb", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cc", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cb", null)))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cc", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cy", null)))), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair((new sc_Pair("\u1E9Cplus-tree", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair((new sc_Pair("\u1E9Cplus-tree", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair((new sc_Pair("\u1E9Cplus-tree", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair("\u1E9Ca", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair((new sc_Pair("\u1E9Cplus-tree", (new sc_Pair((new sc_Pair("\u1E9Cplus-fringe", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Ca", null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair("\u1E9Cb", null)))), (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair("\u1E9Ca", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cz", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cy", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cexec", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cpds", (new sc_Pair("\u1E9Cenvrn", null)))))))), (new sc_Pair((new sc_Pair("\u1E9Cexec", (new sc_Pair("\u1E9Cy", (new sc_Pair((new sc_Pair("\u1E9Cexec", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cpds", (new sc_Pair("\u1E9Cenvrn", null)))))))), (new sc_Pair("\u1E9Cenvrn", null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cmc-flatten", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair((new sc_Pair("\u1E9Cflatten", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cb", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair("\u1E9Cy", null)))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair("\u1E9Cx", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Ca", (new sc_Pair((new sc_Pair("\u1E9Cintersect", (new sc_Pair("\u1E9Cb", (new sc_Pair("\u1E9Cc", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cc", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cnth", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), (new sc_Pair("\u1E9Ci", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cexp", (new sc_Pair("\u1E9Ci", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cj", (new sc_Pair("\u1E9Ck", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair((new sc_Pair("\u1E9Cexp", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cj", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cexp", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Ck", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cexp", (new sc_Pair("\u1E9Ci", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cj", (new sc_Pair("\u1E9Ck", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cexp", (new sc_Pair((new sc_Pair("\u1E9Cexp", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cj", null)))))), (new sc_Pair("\u1E9Ck", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Creverse-loop", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Creverse-loop", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair("\u1E9Cx", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ccount-list", (new sc_Pair("\u1E9Cz", (new sc_Pair((new sc_Pair("\u1E9Csort-lp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Ccount-list", (new sc_Pair("\u1E9Cz", (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ccount-list", (new sc_Pair("\u1E9Cz", (new sc_Pair("\u1E9Cy", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cc", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cb", (new sc_Pair("\u1E9Cc", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cy", (new sc_Pair((new sc_Pair("\u1E9Cquotient", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cx", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cpower-eval", (new sc_Pair((new sc_Pair("\u1E9Cbig-plus1", (new sc_Pair("\u1E9Cl", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cbase", null)))))))), (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Cpower-eval", (new sc_Pair("\u1E9Cl", (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair("\u1E9Ci", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cpower-eval", (new sc_Pair((new sc_Pair("\u1E9Cbig-plus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cbase", null)))))))))), (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ci", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Cpower-eval", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cpower-eval", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cbase", null)))))), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair("\u1E9Cy", (new sc_Pair((1), null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cy", null)))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair((new sc_Pair("\u1E9Cquotient", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cj", null)))))), (new sc_Pair("\u1E9Ci", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Ci", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cj", null)))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cj", (new sc_Pair((1), null)))))), null)))), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cy", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cpower-eval", (new sc_Pair((new sc_Pair("\u1E9Cpower-rep", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Ci", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cpower-eval", (new sc_Pair((new sc_Pair("\u1E9Cbig-plus", (new sc_Pair((new sc_Pair("\u1E9Cpower-rep", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cpower-rep", (new sc_Pair("\u1E9Cj", (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), (new sc_Pair("\u1E9Cbase", null)))))))))), (new sc_Pair("\u1E9Cbase", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cj", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cgcd", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cgcd", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cnth", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair("\u1E9Ci", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair((new sc_Pair("\u1E9Cnth", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Ci", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnth", (new sc_Pair("\u1E9Cb", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Ci", (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair("\u1E9Ca", null)))), null)))))), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cy", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cy", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cc", (new sc_Pair("\u1E9Cw", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cc", (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cw", (new sc_Pair("\u1E9Cx", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cb", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cc", null)))))), null)))))), (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cb", (new sc_Pair("\u1E9Cc", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))), (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair("\u1E9Cy", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cz", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cy", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cx", null)))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cgcd", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cz", (new sc_Pair((new sc_Pair("\u1E9Cgcd", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cvalue", (new sc_Pair((new sc_Pair("\u1E9Cnormalize", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cvalue", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Ca", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cflatten", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cy", (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnlistp", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clistp", (new sc_Pair((new sc_Pair("\u1E9Cgopher", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Clistp", (new sc_Pair("\u1E9Cx", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Csamefringe", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cflatten", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cflatten", (new sc_Pair("\u1E9Cy", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cgreatest-factor", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cy", (new sc_Pair((1), null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cgreatest-factor", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((1), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((1), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair((new sc_Pair("\u1E9Cgreatest-factor", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cy", (new sc_Pair((1), null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cx", null)))), null)))), null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctimes-list", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair((new sc_Pair("\u1E9Ctimes-list", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Ctimes-list", (new sc_Pair("\u1E9Cy", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cprime-list", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cprime-list", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cprime-list", (new sc_Pair("\u1E9Cy", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cz", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cw", (new sc_Pair("\u1E9Cz", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cz", null)))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cz", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cw", (new sc_Pair((1), null)))))), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cgreatereqp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cor", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cand", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cy", (new sc_Pair((1), null)))))), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((1), null)))))), (new sc_Pair(sc_list("\u1E9Cand", (new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Ca", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), null)))), (new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair("\u1E9Cb", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), null)))), (new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Ca", null)))), (new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cb", null)))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Csub1", (new sc_Pair("\u1E9Ca", null)))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Csub1", (new sc_Pair("\u1E9Cb", null)))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair((new sc_Pair("\u1E9Cdelete", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cl", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair("\u1E9Cl", null)))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cl", null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Csort2", (new sc_Pair((new sc_Pair("\u1E9Cdelete", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cl", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cdelete", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Csort2", (new sc_Pair("\u1E9Cl", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdsort", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Csort2", (new sc_Pair("\u1E9Cx", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cx1", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cx2", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cx3", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cx4", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cx5", (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair("\u1E9Cx6", (new sc_Pair("\u1E9Cx7", null)))))), null)))))), null)))))), null)))))), null)))))), null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((6), (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair("\u1E9Cx7", null)))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((2), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cx", null)))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cquotient", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((2), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cquotient", (new sc_Pair("\u1E9Cy", (new sc_Pair((2), null)))))), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Csigma", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), (new sc_Pair("\u1E9Ci", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cquotient", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Ci", (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair("\u1E9Ci", null)))), null)))))), (new sc_Pair((2), null)))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair("\u1E9Cy", null)))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair("\u1E9Cx", null)))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cz", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cz", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cz", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnot", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cz", null)))), null)))))), null)))))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair((new sc_Pair("\u1E9Cplus-tree", (new sc_Pair((new sc_Pair("\u1E9Cdelete", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))), (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair((new sc_Pair("\u1E9Cplus-tree", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Ca", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmeaning", (new sc_Pair((new sc_Pair("\u1E9Cplus-tree", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair("\u1E9Ca", null)))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cadd1", (new sc_Pair("\u1E9Cy", null)))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Cnumberp", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cx", null)))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cnth", (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), (new sc_Pair("\u1E9Ci", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Ci", null)))), (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clast", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Clistp", (new sc_Pair("\u1E9Cb", null)))), (new sc_Pair((new sc_Pair("\u1E9Clast", (new sc_Pair("\u1E9Cb", null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Clistp", (new sc_Pair("\u1E9Ca", null)))), (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair((new sc_Pair("\u1E9Ccar", (new sc_Pair((new sc_Pair("\u1E9Clast", (new sc_Pair("\u1E9Ca", null)))), null)))), (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair("\u1E9Cb", null)))))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Clessp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ct", null)), (new sc_Pair("\u1E9Cz", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cf", null)), (new sc_Pair("\u1E9Cz", null)))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cassignment", (new sc_Pair("\u1E9Cx", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Cassignedp", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cassignment", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Ca", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cassignment", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cb", null)))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Ccar", (new sc_Pair((new sc_Pair("\u1E9Cgopher", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Clistp", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Ccar", (new sc_Pair((new sc_Pair("\u1E9Cflatten", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cflatten", (new sc_Pair((new sc_Pair("\u1E9Ccdr", (new sc_Pair((new sc_Pair("\u1E9Cgopher", (new sc_Pair("\u1E9Cx", null)))), null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Clistp", (new sc_Pair("\u1E9Cx", null)))), (new sc_Pair((new sc_Pair("\u1E9Ccdr", (new sc_Pair((new sc_Pair("\u1E9Cflatten", (new sc_Pair("\u1E9Cx", null)))), null)))), (new sc_Pair((new sc_Pair("\u1E9Ccons", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cquotient", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cx", null)))))), (new sc_Pair("\u1E9Cy", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Czerop", (new sc_Pair("\u1E9Cy", null)))), (new sc_Pair((new sc_Pair("\u1E9Czero", null)), (new sc_Pair((new sc_Pair("\u1E9Cfix", (new sc_Pair("\u1E9Cx", null)))), null)))))))), null)))))), (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cget", (new sc_Pair("\u1E9Cj", (new sc_Pair((new sc_Pair("\u1E9Cset", (new sc_Pair("\u1E9Ci", (new sc_Pair("\u1E9Cval", (new sc_Pair("\u1E9Cmem", null)))))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cif", (new sc_Pair((new sc_Pair("\u1E9Ceqp", (new sc_Pair("\u1E9Cj", (new sc_Pair("\u1E9Ci", null)))))), (new sc_Pair("\u1E9Cval", (new sc_Pair((new sc_Pair("\u1E9Cget", (new sc_Pair("\u1E9Cj", (new sc_Pair("\u1E9Cmem", null)))))), null)))))))), null)))))))); (const_nboyer = (new sc_Pair((new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cf", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cc", (new sc_Pair((new sc_Pair("\u1E9Czero", null)), null)))))), null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cy", (new sc_Pair("\u1E9Cf", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair((new sc_Pair("\u1E9Ctimes", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Cc", (new sc_Pair("\u1E9Cd", null)))))), null)))))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cz", (new sc_Pair("\u1E9Cf", (new sc_Pair((new sc_Pair("\u1E9Creverse", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair((new sc_Pair("\u1E9Cappend", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cnil", null)), null)))))), null)))), null)))))), (new sc_Pair((new sc_Pair("\u1E9Cu", (new sc_Pair("\u1E9Cequal", (new sc_Pair((new sc_Pair("\u1E9Cplus", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cdifference", (new sc_Pair("\u1E9Cx", (new sc_Pair("\u1E9Cy", null)))))), null)))))))), (new sc_Pair((new sc_Pair("\u1E9Cw", (new sc_Pair("\u1E9Clessp", (new sc_Pair((new sc_Pair("\u1E9Cremainder", (new sc_Pair("\u1E9Ca", (new sc_Pair("\u1E9Cb", null)))))), (new sc_Pair((new sc_Pair("\u1E9Cmember", (new sc_Pair("\u1E9Ca", (new sc_Pair((new sc_Pair("\u1E9Clength", (new sc_Pair("\u1E9Cb", null)))), null)))))), null)))))))), null))))))))))); BgL_nboyerzd2benchmarkzd2 = function () { var args = null; for (var sc_tmp = arguments.length - 1; sc_tmp >= 0; sc_tmp--) { args = sc_cons(arguments[sc_tmp], args); } var n; return ((n = ((args === null) ? (0) : (args.car))), (BgL_setupzd2boyerzd2()), (BgL_runzd2benchmarkzd2(("nboyer" + (sc_number2string(n))), (1), function () { return (BgL_testzd2boyerzd2(n)); }, function (rewrites) { if ((sc_isNumber(rewrites))) switch (n) { case (0): return (rewrites === (95024)); break; case (1): return (rewrites === (591777)); break; case (2): return (rewrites === (1813975)); break; case (3): return (rewrites === (5375678)); break; case (4): return (rewrites === (16445406)); break; case (5): return (rewrites === (51507739)); break; default: return true; break; } else return false; }))); }; BgL_setupzd2boyerzd2 = function () { return true; }; BgL_testzd2boyerzd2 = function () { return true; }; translate_term_nboyer = function (term) { var lst; return (!(term instanceof sc_Pair) ? term : (new sc_Pair((BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer((term.car))), ((lst = (term.cdr)), ((lst === null) ? null : (new sc_Pair((translate_term_nboyer((lst.car))), (translate_args_nboyer((lst.cdr)))))))))); }; translate_args_nboyer = function (lst) { var sc_lst_5; var term; return ((lst === null) ? null : (new sc_Pair(((term = (lst.car)), (!(term instanceof sc_Pair) ? term : (new sc_Pair((BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer((term.car))), (translate_args_nboyer((term.cdr))))))), ((sc_lst_5 = (lst.cdr)), ((sc_lst_5 === null) ? null : (new sc_Pair((translate_term_nboyer((sc_lst_5.car))), (translate_args_nboyer((sc_lst_5.cdr)))))))))); }; untranslate_term_nboyer = function (term) { var optrOpnd; var tail1131; var L1127; var falseHead1130; var symbol_record; if (!(term instanceof sc_Pair)) return term; else { (falseHead1130 = (new sc_Pair(null, null))); (L1127 = (term.cdr)); (tail1131 = falseHead1130); while (!(L1127 === null)) { { (tail1131.cdr = (new sc_Pair((untranslate_term_nboyer((L1127.car))), null))); (tail1131 = (tail1131.cdr)); (L1127 = (L1127.cdr)); } } (optrOpnd = (falseHead1130.cdr)); return (new sc_Pair(((symbol_record = (term.car)), (symbol_record[(0)])), optrOpnd)); } }; BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer = function (sym) { var r; var x; return ((x = (sc_assq(sym, BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer))), ((x !== false) ? (x.cdr) : ((r = [sym, null]), (BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer = (new sc_Pair((new sc_Pair(sym, r)), BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer))), r))); }; (BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer = null); translate_alist_nboyer = function (alist) { var sc_alist_6; var term; return ((alist === null) ? null : (new sc_Pair((new sc_Pair((alist.car.car), ((term = (alist.car.cdr)), (!(term instanceof sc_Pair) ? term : (new sc_Pair((BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer((term.car))), (translate_args_nboyer((term.cdr))))))))), ((sc_alist_6 = (alist.cdr)), ((sc_alist_6 === null) ? null : (new sc_Pair((new sc_Pair((sc_alist_6.car.car), (translate_term_nboyer((sc_alist_6.car.cdr))))), (translate_alist_nboyer((sc_alist_6.cdr)))))))))); }; apply_subst_nboyer = function (alist, term) { var lst; var temp_temp; return (!(term instanceof sc_Pair) ? ((temp_temp = (sc_assq(term, alist))), ((temp_temp !== false) ? (temp_temp.cdr) : term)) : (new sc_Pair((term.car), ((lst = (term.cdr)), ((lst === null) ? null : (new sc_Pair((apply_subst_nboyer(alist, (lst.car))), (apply_subst_lst_nboyer(alist, (lst.cdr)))))))))); }; apply_subst_lst_nboyer = function (alist, lst) { var sc_lst_7; return ((lst === null) ? null : (new sc_Pair((apply_subst_nboyer(alist, (lst.car))), ((sc_lst_7 = (lst.cdr)), ((sc_lst_7 === null) ? null : (new sc_Pair((apply_subst_nboyer(alist, (sc_lst_7.car))), (apply_subst_lst_nboyer(alist, (sc_lst_7.cdr)))))))))); }; tautologyp_nboyer = function (sc_x_11, true_lst, false_lst) { var tmp1125; var x; var tmp1126; var sc_x_8; var sc_tmp1125_9; var sc_tmp1126_10; var sc_x_11; var true_lst; var false_lst; while (true) { if ((((sc_tmp1126_10 = (is_term_equal_nboyer(sc_x_11, true_term_nboyer))), ((sc_tmp1126_10 !== false) ? sc_tmp1126_10 : (is_term_member_nboyer(sc_x_11, true_lst)))) !== false)) return true; else if ((((sc_tmp1125_9 = (is_term_equal_nboyer(sc_x_11, false_term_nboyer))), ((sc_tmp1125_9 !== false) ? sc_tmp1125_9 : (is_term_member_nboyer(sc_x_11, false_lst)))) !== false)) return false; else if (!(sc_x_11 instanceof sc_Pair)) return false; else if (((sc_x_11.car) === if_constructor_nboyer)) if ((((sc_x_8 = (sc_x_11.cdr.car)), (tmp1126 = (is_term_equal_nboyer(sc_x_8, true_term_nboyer))), ((tmp1126 !== false) ? tmp1126 : (is_term_member_nboyer(sc_x_8, true_lst)))) !== false)) (sc_x_11 = (sc_x_11.cdr.cdr.car)); else if ((((x = (sc_x_11.cdr.car)), (tmp1125 = (is_term_equal_nboyer(x, false_term_nboyer))), ((tmp1125 !== false) ? tmp1125 : (is_term_member_nboyer(x, false_lst)))) !== false)) (sc_x_11 = (sc_x_11.cdr.cdr.cdr.car)); else if (((tautologyp_nboyer((sc_x_11.cdr.cdr.car), (new sc_Pair((sc_x_11.cdr.car), true_lst)), false_lst)) !== false)) { (false_lst = (new sc_Pair((sc_x_11.cdr.car), false_lst))); (sc_x_11 = (sc_x_11.cdr.cdr.cdr.car)); } else return false; else return false; } }; (if_constructor_nboyer = "\u1E9C*"); (rewrite_count_nboyer = (0)); rewrite_nboyer = function (term) { var term2; var sc_term_12; var lst; var symbol_record; var sc_lst_13; { (++rewrite_count_nboyer); if (!(term instanceof sc_Pair)) return term; else { (sc_term_12 = (new sc_Pair((term.car), ((sc_lst_13 = (term.cdr)), ((sc_lst_13 === null) ? null : (new sc_Pair((rewrite_nboyer((sc_lst_13.car))), (rewrite_args_nboyer((sc_lst_13.cdr)))))))))); (lst = ((symbol_record = (term.car)), (symbol_record[(1)]))); while (true) { if ((lst === null)) return sc_term_12; else if ((((term2 = ((lst.car).cdr.car)), (unify_subst_nboyer = null), (one_way_unify1_nboyer(sc_term_12, term2))) !== false)) return (rewrite_nboyer((apply_subst_nboyer(unify_subst_nboyer, ((lst.car).cdr.cdr.car))))); else (lst = (lst.cdr)); } } } }; rewrite_args_nboyer = function (lst) { var sc_lst_14; return ((lst === null) ? null : (new sc_Pair((rewrite_nboyer((lst.car))), ((sc_lst_14 = (lst.cdr)), ((sc_lst_14 === null) ? null : (new sc_Pair((rewrite_nboyer((sc_lst_14.car))), (rewrite_args_nboyer((sc_lst_14.cdr)))))))))); }; (unify_subst_nboyer = "\u1E9C*"); one_way_unify1_nboyer = function (term1, term2) { var lst1; var lst2; var temp_temp; if (!(term2 instanceof sc_Pair)) { (temp_temp = (sc_assq(term2, unify_subst_nboyer))); if ((temp_temp !== false)) return (is_term_equal_nboyer(term1, (temp_temp.cdr))); else if ((sc_isNumber(term2))) return (sc_isEqual(term1, term2)); else { (unify_subst_nboyer = (new sc_Pair((new sc_Pair(term2, term1)), unify_subst_nboyer))); return true; } } else if (!(term1 instanceof sc_Pair)) return false; else if (((term1.car) === (term2.car))) { (lst1 = (term1.cdr)); (lst2 = (term2.cdr)); while (true) { if ((lst1 === null)) return (lst2 === null); else if ((lst2 === null)) return false; else if (((one_way_unify1_nboyer((lst1.car), (lst2.car))) !== false)) { (lst1 = (lst1.cdr)); (lst2 = (lst2.cdr)); } else return false; } } else return false; }; (false_term_nboyer = "\u1E9C*"); (true_term_nboyer = "\u1E9C*"); trans_of_implies1_nboyer = function (n) { var sc_n_15; return ((sc_isEqual(n, (1))) ? (sc_list("\u1E9Cimplies", (0), (1))) : (sc_list("\u1E9Cand", (sc_list("\u1E9Cimplies", (n - (1)), n)), ((sc_n_15 = (n - (1))), ((sc_isEqual(sc_n_15, (1))) ? (sc_list("\u1E9Cimplies", (0), (1))) : (sc_list("\u1E9Cand", (sc_list("\u1E9Cimplies", (sc_n_15 - (1)), sc_n_15)), (trans_of_implies1_nboyer((sc_n_15 - (1))))))))))); }; is_term_equal_nboyer = function (x, y) { var lst1; var lst2; var r2; var r1; if ((x instanceof sc_Pair)) if ((y instanceof sc_Pair)) if ((((r1 = (x.car)), (r2 = (y.car)), (r1 === r2)) !== false)) { (lst1 = (x.cdr)); (lst2 = (y.cdr)); while (true) { if ((lst1 === null)) return (lst2 === null); else if ((lst2 === null)) return false; else if (((is_term_equal_nboyer((lst1.car), (lst2.car))) !== false)) { (lst1 = (lst1.cdr)); (lst2 = (lst2.cdr)); } else return false; } } else return false; else return false; else return (sc_isEqual(x, y)); }; is_term_member_nboyer = function (x, lst) { var x; var lst; while (true) { if ((lst === null)) return false; else if (((is_term_equal_nboyer(x, (lst.car))) !== false)) return true; else (lst = (lst.cdr)); } }; BgL_setupzd2boyerzd2 = function () { var symbol_record; var value; var BgL_sc_symbolzd2record_16zd2; var sym; var sc_sym_17; var term; var lst; var sc_term_18; var sc_term_19; { (BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer = null); (if_constructor_nboyer = (BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer("\u1E9Cif"))); (false_term_nboyer = ((sc_term_19 = (new sc_Pair("\u1E9Cf", null))), (!(sc_term_19 instanceof sc_Pair) ? sc_term_19 : (new sc_Pair((BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer((sc_term_19.car))), (translate_args_nboyer((sc_term_19.cdr)))))))); (true_term_nboyer = ((sc_term_18 = (new sc_Pair("\u1E9Ct", null))), (!(sc_term_18 instanceof sc_Pair) ? sc_term_18 : (new sc_Pair((BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer((sc_term_18.car))), (translate_args_nboyer((sc_term_18.cdr)))))))); (lst = sc_const_3_nboyer); while (!(lst === null)) { { (term = (lst.car)); if (((term instanceof sc_Pair) && (((term.car) === "\u1E9Cequal") && ((term.cdr.car) instanceof sc_Pair)))) { (sc_sym_17 = ((term.cdr.car).car)); (value = (new sc_Pair((!(term instanceof sc_Pair) ? term : (new sc_Pair((BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer((term.car))), (translate_args_nboyer((term.cdr)))))), ((sym = ((term.cdr.car).car)), (BgL_sc_symbolzd2record_16zd2 = (BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(sym))), (BgL_sc_symbolzd2record_16zd2[(1)]))))); (symbol_record = (BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(sc_sym_17))); (symbol_record[(1)] = value); } else (sc_error("ADD-LEMMA did not like term: ", term)); (lst = (lst.cdr)); } } return true; } }; BgL_testzd2boyerzd2 = function (n) { var optrOpnd; var term; var sc_n_20; var answer; var sc_term_21; var sc_term_22; { (rewrite_count_nboyer = (0)); (term = sc_const_4_nboyer); (sc_n_20 = n); while (!(sc_n_20 === 0)) { { (term = (sc_list("\u1E9Cor", term, (new sc_Pair("\u1E9Cf", null))))); (--sc_n_20); } } (sc_term_22 = term); if (!(sc_term_22 instanceof sc_Pair)) (optrOpnd = sc_term_22); else (optrOpnd = (new sc_Pair((BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer((sc_term_22.car))), (translate_args_nboyer((sc_term_22.cdr)))))); (sc_term_21 = (apply_subst_nboyer(((const_nboyer === null) ? null : (new sc_Pair((new sc_Pair((const_nboyer.car.car), (translate_term_nboyer((const_nboyer.car.cdr))))), (translate_alist_nboyer((const_nboyer.cdr)))))), optrOpnd))); (answer = (tautologyp_nboyer((rewrite_nboyer(sc_term_21)), null, null))); (sc_write(rewrite_count_nboyer)); (sc_display(" rewrites")); (sc_newline()); if ((answer !== false)) return rewrite_count_nboyer; else return false; } }; } /* Exported Variables */ var BgL_parsezd2ze3nbzd2treesze3; var BgL_earleyzd2benchmarkzd2; var BgL_parsezd2ze3parsedzf3zc2; var test; var BgL_parsezd2ze3treesz31; var BgL_makezd2parserzd2; /* End Exports */ var const_earley; { (const_earley = (new sc_Pair((new sc_Pair("\u1E9Cs", (new sc_Pair((new sc_Pair("\u1E9Ca", null)), (new sc_Pair((new sc_Pair("\u1E9Cs", (new sc_Pair("\u1E9Cs", null)))), null)))))), null))); BgL_makezd2parserzd2 = function (grammar, lexer) { var i; var parser_descr; var def_loop; var nb_nts; var names; var steps; var predictors; var enders; var starters; var nts; var sc_names_1; var sc_steps_2; var sc_predictors_3; var sc_enders_4; var sc_starters_5; var nb_confs; var BgL_sc_defzd2loop_6zd2; var BgL_sc_nbzd2nts_7zd2; var sc_nts_8; var BgL_sc_defzd2loop_9zd2; var ind; { ind = function (nt, sc_nts_10) { var i; { (i = ((sc_nts_10.length) - (1))); while (true) { if ((i >= (0))) if ((sc_isEqual((sc_nts_10[i]), nt))) return i; else (--i); else return false; } } }; (sc_nts_8 = ((BgL_sc_defzd2loop_9zd2 = function (defs, sc_nts_11) { var rule_loop; var head; var def; return ((defs instanceof sc_Pair) ? ((def = (defs.car)), (head = (def.car)), (rule_loop = function (rules, sc_nts_12) { var nt; var l; var sc_nts_13; var rule; if ((rules instanceof sc_Pair)) { (rule = (rules.car)); (l = rule); (sc_nts_13 = sc_nts_12); while ((l instanceof sc_Pair)) { { (nt = (l.car)); (l = (l.cdr)); (sc_nts_13 = (((sc_member(nt, sc_nts_13)) !== false) ? sc_nts_13 : (new sc_Pair(nt, sc_nts_13)))); } } return (rule_loop((rules.cdr), sc_nts_13)); } else return (BgL_sc_defzd2loop_9zd2((defs.cdr), sc_nts_12)); }), (rule_loop((def.cdr), (((sc_member(head, sc_nts_11)) !== false) ? sc_nts_11 : (new sc_Pair(head, sc_nts_11)))))) : (sc_list2vector((sc_reverse(sc_nts_11))))); }), (BgL_sc_defzd2loop_9zd2(grammar, null)))); (BgL_sc_nbzd2nts_7zd2 = (sc_nts_8.length)); (nb_confs = (((BgL_sc_defzd2loop_6zd2 = function (defs, BgL_sc_nbzd2confs_14zd2) { var rule_loop; var def; return ((defs instanceof sc_Pair) ? ((def = (defs.car)), (rule_loop = function (rules, BgL_sc_nbzd2confs_15zd2) { var l; var BgL_sc_nbzd2confs_16zd2; var rule; if ((rules instanceof sc_Pair)) { (rule = (rules.car)); (l = rule); (BgL_sc_nbzd2confs_16zd2 = BgL_sc_nbzd2confs_15zd2); while ((l instanceof sc_Pair)) { { (l = (l.cdr)); (++BgL_sc_nbzd2confs_16zd2); } } return (rule_loop((rules.cdr), (BgL_sc_nbzd2confs_16zd2 + (1)))); } else return (BgL_sc_defzd2loop_6zd2((defs.cdr), BgL_sc_nbzd2confs_15zd2)); }), (rule_loop((def.cdr), BgL_sc_nbzd2confs_14zd2))) : BgL_sc_nbzd2confs_14zd2); }), (BgL_sc_defzd2loop_6zd2(grammar, (0)))) + BgL_sc_nbzd2nts_7zd2)); (sc_starters_5 = (sc_makeVector(BgL_sc_nbzd2nts_7zd2, null))); (sc_enders_4 = (sc_makeVector(BgL_sc_nbzd2nts_7zd2, null))); (sc_predictors_3 = (sc_makeVector(BgL_sc_nbzd2nts_7zd2, null))); (sc_steps_2 = (sc_makeVector(nb_confs, false))); (sc_names_1 = (sc_makeVector(nb_confs, false))); (nts = sc_nts_8); (starters = sc_starters_5); (enders = sc_enders_4); (predictors = sc_predictors_3); (steps = sc_steps_2); (names = sc_names_1); (nb_nts = (sc_nts_8.length)); (i = (nb_nts - (1))); while ((i >= (0))) { { (sc_steps_2[i] = (i - nb_nts)); (sc_names_1[i] = (sc_list((sc_nts_8[i]), (0)))); (sc_enders_4[i] = (sc_list(i))); (--i); } } def_loop = function (defs, conf) { var rule_loop; var head; var def; return ((defs instanceof sc_Pair) ? ((def = (defs.car)), (head = (def.car)), (rule_loop = function (rules, conf, rule_num) { var i; var sc_i_17; var nt; var l; var sc_conf_18; var sc_i_19; var rule; if ((rules instanceof sc_Pair)) { (rule = (rules.car)); (names[conf] = (sc_list(head, rule_num))); (sc_i_19 = (ind(head, nts))); (starters[sc_i_19] = (new sc_Pair(conf, (starters[sc_i_19])))); (l = rule); (sc_conf_18 = conf); while ((l instanceof sc_Pair)) { { (nt = (l.car)); (steps[sc_conf_18] = (ind(nt, nts))); (sc_i_17 = (ind(nt, nts))); (predictors[sc_i_17] = (new sc_Pair(sc_conf_18, (predictors[sc_i_17])))); (l = (l.cdr)); (++sc_conf_18); } } (steps[sc_conf_18] = ((ind(head, nts)) - nb_nts)); (i = (ind(head, nts))); (enders[i] = (new sc_Pair(sc_conf_18, (enders[i])))); return (rule_loop((rules.cdr), (sc_conf_18 + (1)), (rule_num + (1)))); } else return (def_loop((defs.cdr), conf)); }), (rule_loop((def.cdr), conf, (1)))) : undefined); }; (def_loop(grammar, (sc_nts_8.length))); (parser_descr = [lexer, sc_nts_8, sc_starters_5, sc_enders_4, sc_predictors_3, sc_steps_2, sc_names_1]); return function (input) { var optrOpnd; var sc_optrOpnd_20; var sc_optrOpnd_21; var sc_optrOpnd_22; var loop1; var BgL_sc_stateza2_23za2; var toks; var BgL_sc_nbzd2nts_24zd2; var sc_steps_25; var sc_enders_26; var state_num; var BgL_sc_statesza2_27za2; var states; var i; var conf; var l; var tok_nts; var sc_i_28; var sc_i_29; var l1; var l2; var tok; var tail1129; var L1125; var goal_enders; var BgL_sc_statesza2_30za2; var BgL_sc_nbzd2nts_31zd2; var BgL_sc_nbzd2confs_32zd2; var nb_toks; var goal_starters; var sc_states_33; var BgL_sc_nbzd2confs_34zd2; var BgL_sc_nbzd2toks_35zd2; var sc_toks_36; var falseHead1128; var sc_names_37; var sc_steps_38; var sc_predictors_39; var sc_enders_40; var sc_starters_41; var sc_nts_42; var lexer; var sc_ind_43; var make_states; var BgL_sc_confzd2setzd2getza2_44za2; var conf_set_merge_new_bang; var conf_set_adjoin; var BgL_sc_confzd2setzd2adjoinza2_45za2; var BgL_sc_confzd2setzd2adjoinza2za2_46z00; var conf_set_union; var forw; var is_parsed; var deriv_trees; var BgL_sc_derivzd2treesza2_47z70; var nb_deriv_trees; var BgL_sc_nbzd2derivzd2treesza2_48za2; { sc_ind_43 = function (nt, sc_nts_49) { var i; { (i = ((sc_nts_49.length) - (1))); while (true) { if ((i >= (0))) if ((sc_isEqual((sc_nts_49[i]), nt))) return i; else (--i); else return false; } } }; make_states = function (BgL_sc_nbzd2toks_50zd2, BgL_sc_nbzd2confs_51zd2) { var v; var i; var sc_states_52; { (sc_states_52 = (sc_makeVector((BgL_sc_nbzd2toks_50zd2 + (1)), false))); (i = BgL_sc_nbzd2toks_50zd2); while ((i >= (0))) { { (v = (sc_makeVector((BgL_sc_nbzd2confs_51zd2 + (1)), false))); (v[(0)] = (-1)); (sc_states_52[i] = v); (--i); } } return sc_states_52; } }; BgL_sc_confzd2setzd2getza2_44za2 = function (state, BgL_sc_statezd2num_53zd2, sc_conf_54) { var conf_set; var BgL_sc_confzd2set_55zd2; return ((BgL_sc_confzd2set_55zd2 = (state[(sc_conf_54 + (1))])), ((BgL_sc_confzd2set_55zd2 !== false) ? BgL_sc_confzd2set_55zd2 : ((conf_set = (sc_makeVector((BgL_sc_statezd2num_53zd2 + (6)), false))), (conf_set[(1)] = (-3)), (conf_set[(2)] = (-1)), (conf_set[(3)] = (-1)), (conf_set[(4)] = (-1)), (state[(sc_conf_54 + (1))] = conf_set), conf_set))); }; conf_set_merge_new_bang = function (conf_set) { return ((conf_set[((conf_set[(1)]) + (5))] = (conf_set[(4)])), (conf_set[(1)] = (conf_set[(3)])), (conf_set[(3)] = (-1)), (conf_set[(4)] = (-1))); }; conf_set_adjoin = function (state, conf_set, sc_conf_56, i) { var tail; return ((tail = (conf_set[(3)])), (conf_set[(i + (5))] = (-1)), (conf_set[(tail + (5))] = i), (conf_set[(3)] = i), ((tail < (0)) ? ((conf_set[(0)] = (state[(0)])), (state[(0)] = sc_conf_56)) : undefined)); }; BgL_sc_confzd2setzd2adjoinza2_45za2 = function (sc_states_57, BgL_sc_statezd2num_58zd2, l, i) { var conf_set; var sc_conf_59; var l1; var state; { (state = (sc_states_57[BgL_sc_statezd2num_58zd2])); (l1 = l); while ((l1 instanceof sc_Pair)) { { (sc_conf_59 = (l1.car)); (conf_set = (BgL_sc_confzd2setzd2getza2_44za2(state, BgL_sc_statezd2num_58zd2, sc_conf_59))); if (((conf_set[(i + (5))]) === false)) { (conf_set_adjoin(state, conf_set, sc_conf_59, i)); (l1 = (l1.cdr)); } else (l1 = (l1.cdr)); } } return undefined; } }; BgL_sc_confzd2setzd2adjoinza2za2_46z00 = function (sc_states_60, BgL_sc_statesza2_61za2, BgL_sc_statezd2num_62zd2, sc_conf_63, i) { var BgL_sc_confzd2setza2_64z70; var BgL_sc_stateza2_65za2; var conf_set; var state; return ((state = (sc_states_60[BgL_sc_statezd2num_62zd2])), ((((conf_set = (state[(sc_conf_63 + (1))])), ((conf_set !== false) ? (conf_set[(i + (5))]) : false)) !== false) ? ((BgL_sc_stateza2_65za2 = (BgL_sc_statesza2_61za2[BgL_sc_statezd2num_62zd2])), (BgL_sc_confzd2setza2_64z70 = (BgL_sc_confzd2setzd2getza2_44za2(BgL_sc_stateza2_65za2, BgL_sc_statezd2num_62zd2, sc_conf_63))), (((BgL_sc_confzd2setza2_64z70[(i + (5))]) === false) ? (conf_set_adjoin(BgL_sc_stateza2_65za2, BgL_sc_confzd2setza2_64z70, sc_conf_63, i)) : undefined), true) : false)); }; conf_set_union = function (state, conf_set, sc_conf_66, other_set) { var i; { (i = (other_set[(2)])); while ((i >= (0))) { if (((conf_set[(i + (5))]) === false)) { (conf_set_adjoin(state, conf_set, sc_conf_66, i)); (i = (other_set[(i + (5))])); } else (i = (other_set[(i + (5))])); } return undefined; } }; forw = function (sc_states_67, BgL_sc_statezd2num_68zd2, sc_starters_69, sc_enders_70, sc_predictors_71, sc_steps_72, sc_nts_73) { var next_set; var next; var conf_set; var ender; var l; var starter_set; var starter; var sc_l_74; var sc_loop1_75; var head; var BgL_sc_confzd2set_76zd2; var BgL_sc_statezd2num_77zd2; var state; var sc_states_78; var preds; var BgL_sc_confzd2set_79zd2; var step; var sc_conf_80; var BgL_sc_nbzd2nts_81zd2; var sc_state_82; { (sc_state_82 = (sc_states_67[BgL_sc_statezd2num_68zd2])); (BgL_sc_nbzd2nts_81zd2 = (sc_nts_73.length)); while (true) { { (sc_conf_80 = (sc_state_82[(0)])); if ((sc_conf_80 >= (0))) { (step = (sc_steps_72[sc_conf_80])); (BgL_sc_confzd2set_79zd2 = (sc_state_82[(sc_conf_80 + (1))])); (head = (BgL_sc_confzd2set_79zd2[(4)])); (sc_state_82[(0)] = (BgL_sc_confzd2set_79zd2[(0)])); (conf_set_merge_new_bang(BgL_sc_confzd2set_79zd2)); if ((step >= (0))) { (sc_l_74 = (sc_starters_69[step])); while ((sc_l_74 instanceof sc_Pair)) { { (starter = (sc_l_74.car)); (starter_set = (BgL_sc_confzd2setzd2getza2_44za2(sc_state_82, BgL_sc_statezd2num_68zd2, starter))); if (((starter_set[(BgL_sc_statezd2num_68zd2 + (5))]) === false)) { (conf_set_adjoin(sc_state_82, starter_set, starter, BgL_sc_statezd2num_68zd2)); (sc_l_74 = (sc_l_74.cdr)); } else (sc_l_74 = (sc_l_74.cdr)); } } (l = (sc_enders_70[step])); while ((l instanceof sc_Pair)) { { (ender = (l.car)); if ((((conf_set = (sc_state_82[(ender + (1))])), ((conf_set !== false) ? (conf_set[(BgL_sc_statezd2num_68zd2 + (5))]) : false)) !== false)) { (next = (sc_conf_80 + (1))); (next_set = (BgL_sc_confzd2setzd2getza2_44za2(sc_state_82, BgL_sc_statezd2num_68zd2, next))); (conf_set_union(sc_state_82, next_set, next, BgL_sc_confzd2set_79zd2)); (l = (l.cdr)); } else (l = (l.cdr)); } } } else { (preds = (sc_predictors_71[(step + BgL_sc_nbzd2nts_81zd2)])); (sc_states_78 = sc_states_67); (state = sc_state_82); (BgL_sc_statezd2num_77zd2 = BgL_sc_statezd2num_68zd2); (BgL_sc_confzd2set_76zd2 = BgL_sc_confzd2set_79zd2); sc_loop1_75 = function (l) { var sc_state_83; var BgL_sc_nextzd2set_84zd2; var sc_next_85; var pred_set; var i; var pred; if ((l instanceof sc_Pair)) { (pred = (l.car)); (i = head); while ((i >= (0))) { { (pred_set = ((sc_state_83 = (sc_states_78[i])), (sc_state_83[(pred + (1))]))); if ((pred_set !== false)) { (sc_next_85 = (pred + (1))); (BgL_sc_nextzd2set_84zd2 = (BgL_sc_confzd2setzd2getza2_44za2(state, BgL_sc_statezd2num_77zd2, sc_next_85))); (conf_set_union(state, BgL_sc_nextzd2set_84zd2, sc_next_85, pred_set)); } (i = (BgL_sc_confzd2set_76zd2[(i + (5))])); } } return (sc_loop1_75((l.cdr))); } else return undefined; }; (sc_loop1_75(preds)); } } else return undefined; } } } }; is_parsed = function (nt, i, j, sc_nts_86, sc_enders_87, sc_states_88) { var conf_set; var state; var sc_conf_89; var l; var BgL_sc_ntza2_90za2; { (BgL_sc_ntza2_90za2 = (sc_ind_43(nt, sc_nts_86))); if ((BgL_sc_ntza2_90za2 !== false)) { (sc_nts_86.length); (l = (sc_enders_87[BgL_sc_ntza2_90za2])); while (true) { if ((l instanceof sc_Pair)) { (sc_conf_89 = (l.car)); if ((((state = (sc_states_88[j])), (conf_set = (state[(sc_conf_89 + (1))])), ((conf_set !== false) ? (conf_set[(i + (5))]) : false)) !== false)) return true; else (l = (l.cdr)); } else return false; } } else return false; } }; deriv_trees = function (sc_conf_91, i, j, sc_enders_92, sc_steps_93, sc_names_94, sc_toks_95, sc_states_96, BgL_sc_nbzd2nts_97zd2) { var sc_loop1_98; var prev; var name; return ((name = (sc_names_94[sc_conf_91])), ((name !== false) ? ((sc_conf_91 < BgL_sc_nbzd2nts_97zd2) ? (sc_list((sc_list(name, ((sc_toks_95[i]).car))))) : (sc_list((sc_list(name))))) : ((prev = (sc_conf_91 - (1))), (sc_loop1_98 = function (l1, l2) { var loop2; var ender_set; var state; var ender; var l1; var l2; while (true) { if ((l1 instanceof sc_Pair)) { (ender = (l1.car)); (ender_set = ((state = (sc_states_96[j])), (state[(ender + (1))]))); if ((ender_set !== false)) { loop2 = function (k, l2) { var loop3; var ender_trees; var prev_trees; var conf_set; var sc_state_99; var k; var l2; while (true) { if ((k >= (0))) if (((k >= i) && (((sc_state_99 = (sc_states_96[k])), (conf_set = (sc_state_99[(prev + (1))])), ((conf_set !== false) ? (conf_set[(i + (5))]) : false)) !== false))) { (prev_trees = (deriv_trees(prev, i, k, sc_enders_92, sc_steps_93, sc_names_94, sc_toks_95, sc_states_96, BgL_sc_nbzd2nts_97zd2))); (ender_trees = (deriv_trees(ender, k, j, sc_enders_92, sc_steps_93, sc_names_94, sc_toks_95, sc_states_96, BgL_sc_nbzd2nts_97zd2))); loop3 = function (l3, l2) { var l4; var sc_l2_100; var ender_tree; if ((l3 instanceof sc_Pair)) { (ender_tree = (sc_list((l3.car)))); (l4 = prev_trees); (sc_l2_100 = l2); while ((l4 instanceof sc_Pair)) { { (sc_l2_100 = (new sc_Pair((sc_append((l4.car), ender_tree)), sc_l2_100))); (l4 = (l4.cdr)); } } return (loop3((l3.cdr), sc_l2_100)); } else return (loop2((ender_set[(k + (5))]), l2)); }; return (loop3(ender_trees, l2)); } else (k = (ender_set[(k + (5))])); else return (sc_loop1_98((l1.cdr), l2)); } }; return (loop2((ender_set[(2)]), l2)); } else (l1 = (l1.cdr)); } else return l2; } }), (sc_loop1_98((sc_enders_92[(sc_steps_93[prev])]), null))))); }; BgL_sc_derivzd2treesza2_47z70 = function (nt, i, j, sc_nts_101, sc_enders_102, sc_steps_103, sc_names_104, sc_toks_105, sc_states_106) { var conf_set; var state; var sc_conf_107; var l; var trees; var BgL_sc_nbzd2nts_108zd2; var BgL_sc_ntza2_109za2; { (BgL_sc_ntza2_109za2 = (sc_ind_43(nt, sc_nts_101))); if ((BgL_sc_ntza2_109za2 !== false)) { (BgL_sc_nbzd2nts_108zd2 = (sc_nts_101.length)); (l = (sc_enders_102[BgL_sc_ntza2_109za2])); (trees = null); while ((l instanceof sc_Pair)) { { (sc_conf_107 = (l.car)); if ((((state = (sc_states_106[j])), (conf_set = (state[(sc_conf_107 + (1))])), ((conf_set !== false) ? (conf_set[(i + (5))]) : false)) !== false)) { (l = (l.cdr)); (trees = (sc_append((deriv_trees(sc_conf_107, i, j, sc_enders_102, sc_steps_103, sc_names_104, sc_toks_105, sc_states_106, BgL_sc_nbzd2nts_108zd2)), trees))); } else (l = (l.cdr)); } } return trees; } else return false; } }; nb_deriv_trees = function (sc_conf_110, i, j, sc_enders_111, sc_steps_112, sc_toks_113, sc_states_114, BgL_sc_nbzd2nts_115zd2) { var sc_loop1_116; var tmp1124; var prev; return ((prev = (sc_conf_110 - (1))), ((((tmp1124 = (sc_conf_110 < BgL_sc_nbzd2nts_115zd2)), ((tmp1124 !== false) ? tmp1124 : ((sc_steps_112[prev]) < (0)))) !== false) ? (1) : ((sc_loop1_116 = function (l, sc_n_118) { var nb_ender_trees; var nb_prev_trees; var conf_set; var state; var k; var n; var ender_set; var sc_state_117; var ender; var l; var sc_n_118; while (true) { if ((l instanceof sc_Pair)) { (ender = (l.car)); (ender_set = ((sc_state_117 = (sc_states_114[j])), (sc_state_117[(ender + (1))]))); if ((ender_set !== false)) { (k = (ender_set[(2)])); (n = sc_n_118); while ((k >= (0))) { if (((k >= i) && (((state = (sc_states_114[k])), (conf_set = (state[(prev + (1))])), ((conf_set !== false) ? (conf_set[(i + (5))]) : false)) !== false))) { (nb_prev_trees = (nb_deriv_trees(prev, i, k, sc_enders_111, sc_steps_112, sc_toks_113, sc_states_114, BgL_sc_nbzd2nts_115zd2))); (nb_ender_trees = (nb_deriv_trees(ender, k, j, sc_enders_111, sc_steps_112, sc_toks_113, sc_states_114, BgL_sc_nbzd2nts_115zd2))); (k = (ender_set[(k + (5))])); (n += (nb_prev_trees * nb_ender_trees)); } else (k = (ender_set[(k + (5))])); } return (sc_loop1_116((l.cdr), n)); } else (l = (l.cdr)); } else return sc_n_118; } }), (sc_loop1_116((sc_enders_111[(sc_steps_112[prev])]), (0)))))); }; BgL_sc_nbzd2derivzd2treesza2_48za2 = function (nt, i, j, sc_nts_119, sc_enders_120, sc_steps_121, sc_toks_122, sc_states_123) { var conf_set; var state; var sc_conf_124; var l; var nb_trees; var BgL_sc_nbzd2nts_125zd2; var BgL_sc_ntza2_126za2; { (BgL_sc_ntza2_126za2 = (sc_ind_43(nt, sc_nts_119))); if ((BgL_sc_ntza2_126za2 !== false)) { (BgL_sc_nbzd2nts_125zd2 = (sc_nts_119.length)); (l = (sc_enders_120[BgL_sc_ntza2_126za2])); (nb_trees = (0)); while ((l instanceof sc_Pair)) { { (sc_conf_124 = (l.car)); if ((((state = (sc_states_123[j])), (conf_set = (state[(sc_conf_124 + (1))])), ((conf_set !== false) ? (conf_set[(i + (5))]) : false)) !== false)) { (l = (l.cdr)); (nb_trees = ((nb_deriv_trees(sc_conf_124, i, j, sc_enders_120, sc_steps_121, sc_toks_122, sc_states_123, BgL_sc_nbzd2nts_125zd2)) + nb_trees)); } else (l = (l.cdr)); } } return nb_trees; } else return false; } }; (lexer = (parser_descr[(0)])); (sc_nts_42 = (parser_descr[(1)])); (sc_starters_41 = (parser_descr[(2)])); (sc_enders_40 = (parser_descr[(3)])); (sc_predictors_39 = (parser_descr[(4)])); (sc_steps_38 = (parser_descr[(5)])); (sc_names_37 = (parser_descr[(6)])); (falseHead1128 = (new sc_Pair(null, null))); (L1125 = (lexer(input))); (tail1129 = falseHead1128); while (!(L1125 === null)) { { (tok = (L1125.car)); (l1 = (tok.cdr)); (l2 = null); while ((l1 instanceof sc_Pair)) { { (sc_i_29 = (sc_ind_43((l1.car), sc_nts_42))); if ((sc_i_29 !== false)) { (l1 = (l1.cdr)); (l2 = (new sc_Pair(sc_i_29, l2))); } else (l1 = (l1.cdr)); } } (sc_optrOpnd_22 = (new sc_Pair((tok.car), (sc_reverse(l2))))); (sc_optrOpnd_21 = (new sc_Pair(sc_optrOpnd_22, null))); (tail1129.cdr = sc_optrOpnd_21); (tail1129 = (tail1129.cdr)); (L1125 = (L1125.cdr)); } } (sc_optrOpnd_20 = (falseHead1128.cdr)); (sc_toks_36 = (sc_list2vector(sc_optrOpnd_20))); (BgL_sc_nbzd2toks_35zd2 = (sc_toks_36.length)); (BgL_sc_nbzd2confs_34zd2 = (sc_steps_38.length)); (sc_states_33 = (make_states(BgL_sc_nbzd2toks_35zd2, BgL_sc_nbzd2confs_34zd2))); (goal_starters = (sc_starters_41[(0)])); (BgL_sc_confzd2setzd2adjoinza2_45za2(sc_states_33, (0), goal_starters, (0))); (forw(sc_states_33, (0), sc_starters_41, sc_enders_40, sc_predictors_39, sc_steps_38, sc_nts_42)); (sc_i_28 = (0)); while ((sc_i_28 < BgL_sc_nbzd2toks_35zd2)) { { (tok_nts = ((sc_toks_36[sc_i_28]).cdr)); (BgL_sc_confzd2setzd2adjoinza2_45za2(sc_states_33, (sc_i_28 + (1)), tok_nts, sc_i_28)); (forw(sc_states_33, (sc_i_28 + (1)), sc_starters_41, sc_enders_40, sc_predictors_39, sc_steps_38, sc_nts_42)); (++sc_i_28); } } (nb_toks = (sc_toks_36.length)); (BgL_sc_nbzd2confs_32zd2 = (sc_steps_38.length)); (BgL_sc_nbzd2nts_31zd2 = (sc_nts_42.length)); (BgL_sc_statesza2_30za2 = (make_states(nb_toks, BgL_sc_nbzd2confs_32zd2))); (goal_enders = (sc_enders_40[(0)])); (l = goal_enders); while ((l instanceof sc_Pair)) { { (conf = (l.car)); (BgL_sc_confzd2setzd2adjoinza2za2_46z00(sc_states_33, BgL_sc_statesza2_30za2, nb_toks, conf, (0))); (l = (l.cdr)); } } (i = nb_toks); while ((i >= (0))) { { (states = sc_states_33); (BgL_sc_statesza2_27za2 = BgL_sc_statesza2_30za2); (state_num = i); (sc_enders_26 = sc_enders_40); (sc_steps_25 = sc_steps_38); (BgL_sc_nbzd2nts_24zd2 = BgL_sc_nbzd2nts_31zd2); (toks = sc_toks_36); (BgL_sc_stateza2_23za2 = (BgL_sc_statesza2_30za2[i])); loop1 = function () { var sc_loop1_127; var prev; var BgL_sc_statesza2_128za2; var sc_states_129; var j; var i; var sc_i_130; var head; var conf_set; var sc_conf_131; { (sc_conf_131 = (BgL_sc_stateza2_23za2[(0)])); if ((sc_conf_131 >= (0))) { (conf_set = (BgL_sc_stateza2_23za2[(sc_conf_131 + (1))])); (head = (conf_set[(4)])); (BgL_sc_stateza2_23za2[(0)] = (conf_set[(0)])); (conf_set_merge_new_bang(conf_set)); (sc_i_130 = head); while ((sc_i_130 >= (0))) { { (i = sc_i_130); (j = state_num); (sc_states_129 = states); (BgL_sc_statesza2_128za2 = BgL_sc_statesza2_27za2); (prev = (sc_conf_131 - (1))); if (((sc_conf_131 >= BgL_sc_nbzd2nts_24zd2) && ((sc_steps_25[prev]) >= (0)))) { sc_loop1_127 = function (l) { var k; var ender_set; var state; var ender; var l; while (true) { if ((l instanceof sc_Pair)) { (ender = (l.car)); (ender_set = ((state = (sc_states_129[j])), (state[(ender + (1))]))); if ((ender_set !== false)) { (k = (ender_set[(2)])); while ((k >= (0))) { { if ((k >= i)) if (((BgL_sc_confzd2setzd2adjoinza2za2_46z00(sc_states_129, BgL_sc_statesza2_128za2, k, prev, i)) !== false)) (BgL_sc_confzd2setzd2adjoinza2za2_46z00(sc_states_129, BgL_sc_statesza2_128za2, j, ender, k)); (k = (ender_set[(k + (5))])); } } return (sc_loop1_127((l.cdr))); } else (l = (l.cdr)); } else return undefined; } }; (sc_loop1_127((sc_enders_26[(sc_steps_25[prev])]))); } (sc_i_130 = (conf_set[(sc_i_130 + (5))])); } } return (loop1()); } else return undefined; } }; (loop1()); (--i); } } (optrOpnd = BgL_sc_statesza2_30za2); return [sc_nts_42, sc_starters_41, sc_enders_40, sc_predictors_39, sc_steps_38, sc_names_37, sc_toks_36, optrOpnd, is_parsed, BgL_sc_derivzd2treesza2_47z70, BgL_sc_nbzd2derivzd2treesza2_48za2]; } }; } }; BgL_parsezd2ze3parsedzf3zc2 = function (parse, nt, i, j) { var is_parsed; var states; var enders; var nts; return ((nts = (parse[(0)])), (enders = (parse[(2)])), (states = (parse[(7)])), (is_parsed = (parse[(8)])), (is_parsed(nt, i, j, nts, enders, states))); }; BgL_parsezd2ze3treesz31 = function (parse, nt, i, j) { var BgL_sc_derivzd2treesza2_132z70; var states; var toks; var names; var steps; var enders; var nts; return ((nts = (parse[(0)])), (enders = (parse[(2)])), (steps = (parse[(4)])), (names = (parse[(5)])), (toks = (parse[(6)])), (states = (parse[(7)])), (BgL_sc_derivzd2treesza2_132z70 = (parse[(9)])), (BgL_sc_derivzd2treesza2_132z70(nt, i, j, nts, enders, steps, names, toks, states))); }; BgL_parsezd2ze3nbzd2treesze3 = function (parse, nt, i, j) { var BgL_sc_nbzd2derivzd2treesza2_133za2; var states; var toks; var steps; var enders; var nts; return ((nts = (parse[(0)])), (enders = (parse[(2)])), (steps = (parse[(4)])), (toks = (parse[(6)])), (states = (parse[(7)])), (BgL_sc_nbzd2derivzd2treesza2_133za2 = (parse[(10)])), (BgL_sc_nbzd2derivzd2treesza2_133za2(nt, i, j, nts, enders, steps, toks, states))); }; test = function (k) { var x; var p; return ((p = (BgL_makezd2parserzd2(const_earley, function (l) { var sc_x_134; var tail1134; var L1130; var falseHead1133; { (falseHead1133 = (new sc_Pair(null, null))); (tail1134 = falseHead1133); (L1130 = l); while (!(L1130 === null)) { { (tail1134.cdr = (new sc_Pair(((sc_x_134 = (L1130.car)), (sc_list(sc_x_134, sc_x_134))), null))); (tail1134 = (tail1134.cdr)); (L1130 = (L1130.cdr)); } } return (falseHead1133.cdr); } }))), (x = (p((sc_vector2list((sc_makeVector(k, "\u1E9Ca"))))))), (sc_length((BgL_parsezd2ze3treesz31(x, "\u1E9Cs", (0), k))))); }; BgL_earleyzd2benchmarkzd2 = function () { var args = null; for (var sc_tmp = arguments.length - 1; sc_tmp >= 0; sc_tmp--) { args = sc_cons(arguments[sc_tmp], args); } var k; return ((k = ((args === null) ? (7) : (args.car))), (BgL_runzd2benchmarkzd2("earley", (1), function () { return (test(k)); }, function (result) { return ((sc_display(result)), (sc_newline()), result == 132); }))); }; } /************* END OF GENERATED CODE *************/ // Invoke this function to run a benchmark. // The first argument is a string identifying the benchmark. // The second argument is the number of times to run the benchmark. // The third argument is a function that runs the benchmark. // The fourth argument is a unary function that warns if the result // returned by the benchmark is incorrect. // // Example: // RunBenchmark("new Array()", // 1, // function () { new Array(1000000); } // function (v) { // return (v instanceof Array) && (v.length == 1000000); // }); SC_DEFAULT_OUT = new sc_GenericOutputPort(function (s) { }); SC_ERROR_OUT = SC_DEFAULT_OUT; function RunBenchmark(name, count, run, warn) { for (var n = 0; n < count; ++n) { var result = run(); if (!warn(result)) { throw new Error("Earley or Boyer did incorrect number of rewrites"); } } } var BgL_runzd2benchmarkzd2 = RunBenchmark; var EarleyBoyer = new BenchmarkSuite('EarleyBoyer', 666463, [ new Benchmark("Earley", function () { BgL_earleyzd2benchmarkzd2(); }), new Benchmark("Boyer", function () { BgL_nboyerzd2benchmarkzd2(); }) ]); /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/scripts/v8-benches/navier-stokes.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; /** * Copyright 2012 the V8 project authors. All rights reserved. * Copyright 2009 Oliver Hunt * * 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. */ var solver = null; function runNavierStokes() { solver.update(); } function setupNavierStokes() { solver = new FluidField(null); solver.setResolution(128, 128); solver.setIterations(20); solver.setDisplayFunction(function () { }); solver.setUICallback(prepareFrame); solver.reset(); } function tearDownNavierStokes() { solver = null; } function addPoints(field) { var n = 64; for (var i = 1; i <= n; i++) { field.setVelocity(i, i, n, n); field.setDensity(i, i, 5); field.setVelocity(i, n - i, -n, -n); field.setDensity(i, n - i, 20); field.setVelocity(128 - i, n + i, -n, -n); field.setDensity(128 - i, n + i, 30); } } var framesTillAddingPoints = 0; var framesBetweenAddingPoints = 5; function prepareFrame(field) { if (framesTillAddingPoints == 0) { addPoints(field); framesTillAddingPoints = framesBetweenAddingPoints; framesBetweenAddingPoints++; } else { framesTillAddingPoints--; } } // Code from Oliver Hunt (http://nerget.com/fluidSim/pressure.js) starts here. function FluidField(canvas) { function addFields(x, s, dt) { for (var i = 0; i < size; i++) x[i] += dt * s[i]; } function set_bnd(b, x) { if (b === 1) { for (var i = 1; i <= width; i++) { x[i] = x[i + rowSize]; x[i + (height + 1) * rowSize] = x[i + height * rowSize]; } for (var j = 1; i <= height; i++) { x[j * rowSize] = -x[1 + j * rowSize]; x[(width + 1) + j * rowSize] = -x[width + j * rowSize]; } } else if (b === 2) { for (var i = 1; i <= width; i++) { x[i] = -x[i + rowSize]; x[i + (height + 1) * rowSize] = -x[i + height * rowSize]; } for (var j = 1; j <= height; j++) { x[j * rowSize] = x[1 + j * rowSize]; x[(width + 1) + j * rowSize] = x[width + j * rowSize]; } } else { for (var i = 1; i <= width; i++) { x[i] = x[i + rowSize]; x[i + (height + 1) * rowSize] = x[i + height * rowSize]; } for (var j = 1; j <= height; j++) { x[j * rowSize] = x[1 + j * rowSize]; x[(width + 1) + j * rowSize] = x[width + j * rowSize]; } } var maxEdge = (height + 1) * rowSize; x[0] = 0.5 * (x[1] + x[rowSize]); x[maxEdge] = 0.5 * (x[1 + maxEdge] + x[height * rowSize]); x[(width + 1)] = 0.5 * (x[width] + x[(width + 1) + rowSize]); x[(width + 1) + maxEdge] = 0.5 * (x[width + maxEdge] + x[(width + 1) + height * rowSize]); } function lin_solve(b, x, x0, a, c) { if (a === 0 && c === 1) { for (var j = 1; j <= height; j++) { var currentRow = j * rowSize; ++currentRow; for (var i = 0; i < width; i++) { x[currentRow] = x0[currentRow]; ++currentRow; } } set_bnd(b, x); } else { var invC = 1 / c; for (var k = 0; k < iterations; k++) { for (var j = 1; j <= height; j++) { var lastRow = (j - 1) * rowSize; var currentRow = j * rowSize; var nextRow = (j + 1) * rowSize; var lastX = x[currentRow]; ++currentRow; for (var i = 1; i <= width; i++) lastX = x[currentRow] = (x0[currentRow] + a * (lastX + x[++currentRow] + x[++lastRow] + x[++nextRow])) * invC; } set_bnd(b, x); } } } function diffuse(b, x, x0, dt) { var a = 0; lin_solve(b, x, x0, a, 1 + 4 * a); } function lin_solve2(x, x0, y, y0, a, c) { if (a === 0 && c === 1) { for (var j = 1; j <= height; j++) { var currentRow = j * rowSize; ++currentRow; for (var i = 0; i < width; i++) { x[currentRow] = x0[currentRow]; y[currentRow] = y0[currentRow]; ++currentRow; } } set_bnd(1, x); set_bnd(2, y); } else { var invC = 1 / c; for (var k = 0; k < iterations; k++) { for (var j = 1; j <= height; j++) { var lastRow = (j - 1) * rowSize; var currentRow = j * rowSize; var nextRow = (j + 1) * rowSize; var lastX = x[currentRow]; var lastY = y[currentRow]; ++currentRow; for (var i = 1; i <= width; i++) { lastX = x[currentRow] = (x0[currentRow] + a * (lastX + x[currentRow] + x[lastRow] + x[nextRow])) * invC; lastY = y[currentRow] = (y0[currentRow] + a * (lastY + y[++currentRow] + y[++lastRow] + y[++nextRow])) * invC; } } set_bnd(1, x); set_bnd(2, y); } } } function diffuse2(x, x0, y, y0, dt) { var a = 0; lin_solve2(x, x0, y, y0, a, 1 + 4 * a); } function advect(b, d, d0, u, v, dt) { var Wdt0 = dt * width; var Hdt0 = dt * height; var Wp5 = width + 0.5; var Hp5 = height + 0.5; for (var j = 1; j <= height; j++) { var pos = j * rowSize; for (var i = 1; i <= width; i++) { var x = i - Wdt0 * u[++pos]; var y = j - Hdt0 * v[pos]; if (x < 0.5) x = 0.5; else if (x > Wp5) x = Wp5; var i0 = x | 0; var i1 = i0 + 1; if (y < 0.5) y = 0.5; else if (y > Hp5) y = Hp5; var j0 = y | 0; var j1 = j0 + 1; var s1 = x - i0; var s0 = 1 - s1; var t1 = y - j0; var t0 = 1 - t1; var row1 = j0 * rowSize; var row2 = j1 * rowSize; d[pos] = s0 * (t0 * d0[i0 + row1] + t1 * d0[i0 + row2]) + s1 * (t0 * d0[i1 + row1] + t1 * d0[i1 + row2]); } } set_bnd(b, d); } function project(u, v, p, div) { var h = -0.5 / Math.sqrt(width * height); for (var j = 1; j <= height; j++) { var row = j * rowSize; var previousRow = (j - 1) * rowSize; var prevValue = row - 1; var currentRow = row; var nextValue = row + 1; var nextRow = (j + 1) * rowSize; for (var i = 1; i <= width; i++) { div[++currentRow] = h * (u[++nextValue] - u[++prevValue] + v[++nextRow] - v[++previousRow]); p[currentRow] = 0; } } set_bnd(0, div); set_bnd(0, p); lin_solve(0, p, div, 1, 4); var wScale = 0.5 * width; var hScale = 0.5 * height; for (var j = 1; j <= height; j++) { var prevPos = j * rowSize - 1; var currentPos = j * rowSize; var nextPos = j * rowSize + 1; var prevRow = (j - 1) * rowSize; var currentRow = j * rowSize; var nextRow = (j + 1) * rowSize; for (var i = 1; i <= width; i++) { u[++currentPos] -= wScale * (p[++nextPos] - p[++prevPos]); v[currentPos] -= hScale * (p[++nextRow] - p[++prevRow]); } } set_bnd(1, u); set_bnd(2, v); } function dens_step(x, x0, u, v, dt) { addFields(x, x0, dt); diffuse(0, x0, x, dt); advect(0, x, x0, u, v, dt); } function vel_step(u, v, u0, v0, dt) { addFields(u, u0, dt); addFields(v, v0, dt); var temp = u0; u0 = u; u = temp; var temp = v0; v0 = v; v = temp; diffuse2(u, u0, v, v0, dt); project(u, v, u0, v0); var temp = u0; u0 = u; u = temp; var temp = v0; v0 = v; v = temp; advect(1, u, u0, u0, v0, dt); advect(2, v, v0, u0, v0, dt); project(u, v, u0, v0); } var uiCallback = function (d, u, v) { }; function Field(dens, u, v) { // Just exposing the fields here rather than using accessors is a measurable win during display (maybe 5%) // but makes the code ugly. this.setDensity = function (x, y, d) { dens[(x + 1) + (y + 1) * rowSize] = d; } this.getDensity = function (x, y) { return dens[(x + 1) + (y + 1) * rowSize]; } this.setVelocity = function (x, y, xv, yv) { u[(x + 1) + (y + 1) * rowSize] = xv; v[(x + 1) + (y + 1) * rowSize] = yv; } this.getXVelocity = function (x, y) { return u[(x + 1) + (y + 1) * rowSize]; } this.getYVelocity = function (x, y) { return v[(x + 1) + (y + 1) * rowSize]; } this.width = function () { return width; } this.height = function () { return height; } } function queryUI(d, u, v) { for (var i = 0; i < size; i++) u[i] = v[i] = d[i] = 0.0; uiCallback(new Field(d, u, v)); } this.update = function () { queryUI(dens_prev, u_prev, v_prev); vel_step(u, v, u_prev, v_prev, dt); dens_step(dens, dens_prev, u, v, dt); displayFunc(new Field(dens, u, v)); } this.setDisplayFunction = function (func) { displayFunc = func; } this.iterations = function () { return iterations; } this.setIterations = function (iters) { if (iters > 0 && iters <= 100) iterations = iters; } this.setUICallback = function (callback) { uiCallback = callback; } var iterations = 10; var visc = 0.5; var dt = 0.1; var dens; var dens_prev; var u; var u_prev; var v; var v_prev; var width; var height; var rowSize; var size; var displayFunc; function reset() { rowSize = width + 2; size = (width + 2) * (height + 2); dens = new Array(size); dens_prev = new Array(size); u = new Array(size); u_prev = new Array(size); v = new Array(size); v_prev = new Array(size); for (var i = 0; i < size; i++) dens_prev[i] = u_prev[i] = v_prev[i] = dens[i] = u[i] = v[i] = 0; } this.reset = reset; this.setResolution = function (hRes, wRes) { var res = wRes * hRes; if (res > 0 && res < 1000000 && (wRes != width || hRes != height)) { width = wRes; height = hRes; reset(); return true; } return false; } this.setResolution(64, 64); } var NavierStokes = new BenchmarkSuite('NavierStokes', 1484000, [new Benchmark('NavierStokes', runNavierStokes, setupNavierStokes, tearDownNavierStokes)]); /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/scripts/v8-benches/raytrace.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; // The ray tracer code in this file is written by Adam Burmister. It // is available in its original form from: // // http://labs.flog.nz.co/raytracer/ // // It has been modified slightly by Google to work as a standalone // benchmark, but the all the computational code remains // untouched. This file also contains a copy of parts of the Prototype // JavaScript framework which is used by the ray tracer. // Variable used to hold a number that can be used to verify that // the scene was ray traced correctly. var checkNumber; // ------------------------------------------------------------------------ // ------------------------------------------------------------------------ // The following is a copy of parts of the Prototype JavaScript library: // Prototype JavaScript framework, version 1.5.0 // (c) 2005-2007 Sam Stephenson // // Prototype is freely distributable under the terms of an MIT-style license. // For details, see the Prototype web site: http://prototype.conio.net/ var Class = { create: function () { return function () { this.initialize.apply(this, arguments); } } }; Object.extend = function (destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; }; // ------------------------------------------------------------------------ // ------------------------------------------------------------------------ // The rest of this file is the actual ray tracer written by Adam // Burmister. It's a concatenation of the following files: // // flog/color.js // flog/light.js // flog/vector.js // flog/ray.js // flog/scene.js // flog/material/basematerial.js // flog/material/solid.js // flog/material/chessboard.js // flog/shape/baseshape.js // flog/shape/sphere.js // flog/shape/plane.js // flog/intersectioninfo.js // flog/camera.js // flog/background.js // flog/engine.js /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Color = Class.create(); Flog.RayTracer.Color.prototype = { red: 0.0, green: 0.0, blue: 0.0, initialize: function (r, g, b) { if (!r) r = 0.0; if (!g) g = 0.0; if (!b) b = 0.0; this.red = r; this.green = g; this.blue = b; }, add: function (c1, c2) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red + c2.red; result.green = c1.green + c2.green; result.blue = c1.blue + c2.blue; return result; }, addScalar: function (c1, s) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red + s; result.green = c1.green + s; result.blue = c1.blue + s; result.limit(); return result; }, subtract: function (c1, c2) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red - c2.red; result.green = c1.green - c2.green; result.blue = c1.blue - c2.blue; return result; }, multiply: function (c1, c2) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red * c2.red; result.green = c1.green * c2.green; result.blue = c1.blue * c2.blue; return result; }, multiplyScalar: function (c1, f) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red * f; result.green = c1.green * f; result.blue = c1.blue * f; return result; }, divideFactor: function (c1, f) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red / f; result.green = c1.green / f; result.blue = c1.blue / f; return result; }, limit: function () { this.red = (this.red > 0.0) ? ((this.red > 1.0) ? 1.0 : this.red) : 0.0; this.green = (this.green > 0.0) ? ((this.green > 1.0) ? 1.0 : this.green) : 0.0; this.blue = (this.blue > 0.0) ? ((this.blue > 1.0) ? 1.0 : this.blue) : 0.0; }, distance: function (color) { var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue); return d; }, blend: function (c1, c2, w) { var result = new Flog.RayTracer.Color(0, 0, 0); result = Flog.RayTracer.Color.prototype.add( Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w), Flog.RayTracer.Color.prototype.multiplyScalar(c2, w) ); return result; }, brightness: function () { var r = Math.floor(this.red * 255); var g = Math.floor(this.green * 255); var b = Math.floor(this.blue * 255); return (r * 77 + g * 150 + b * 29) >> 8; }, toString: function () { var r = Math.floor(this.red * 255); var g = Math.floor(this.green * 255); var b = Math.floor(this.blue * 255); return "rgb(" + r + "," + g + "," + b + ")"; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Light = Class.create(); Flog.RayTracer.Light.prototype = { position: null, color: null, intensity: 10.0, initialize: function (pos, color, intensity) { this.position = pos; this.color = color; this.intensity = (intensity ? intensity : 10.0); }, toString: function () { return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Vector = Class.create(); Flog.RayTracer.Vector.prototype = { x: 0.0, y: 0.0, z: 0.0, initialize: function (x, y, z) { this.x = (x ? x : 0); this.y = (y ? y : 0); this.z = (z ? z : 0); }, copy: function (vector) { this.x = vector.x; this.y = vector.y; this.z = vector.z; }, normalize: function () { var m = this.magnitude(); return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m); }, magnitude: function () { return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z)); }, cross: function (w) { return new Flog.RayTracer.Vector( -this.z * w.y + this.y * w.z, this.z * w.x - this.x * w.z, -this.y * w.x + this.x * w.y); }, dot: function (w) { return this.x * w.x + this.y * w.y + this.z * w.z; }, add: function (v, w) { return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z); }, subtract: function (v, w) { if (!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']'; return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z); }, multiplyVector: function (v, w) { return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z); }, multiplyScalar: function (v, w) { return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w); }, toString: function () { return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Ray = Class.create(); Flog.RayTracer.Ray.prototype = { position: null, direction: null, initialize: function (pos, dir) { this.position = pos; this.direction = dir; }, toString: function () { return 'Ray [' + this.position + ',' + this.direction + ']'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Scene = Class.create(); Flog.RayTracer.Scene.prototype = { camera: null, shapes: [], lights: [], background: null, initialize: function () { this.camera = new Flog.RayTracer.Camera( new Flog.RayTracer.Vector(0, 0, -5), new Flog.RayTracer.Vector(0, 0, 1), new Flog.RayTracer.Vector(0, 1, 0) ); this.shapes = new Array(); this.lights = new Array(); this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0, 0, 0.5), 0.2); } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; if (typeof (Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {}; Flog.RayTracer.Material.BaseMaterial = Class.create(); Flog.RayTracer.Material.BaseMaterial.prototype = { gloss: 2.0, // [0...infinity] 0 = matt transparency: 0.0, // 0=opaque reflection: 0.0, // [0...infinity] 0 = no reflection refraction: 0.50, hasTexture: false, initialize: function () { }, getColor: function (u, v) { }, wrapUp: function (t) { t = t % 2.0; if (t < -1) t += 2.0; if (t >= 1) t -= 2.0; return t; }, toString: function () { return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture + ']'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Material.Solid = Class.create(); Flog.RayTracer.Material.Solid.prototype = Object.extend( new Flog.RayTracer.Material.BaseMaterial(), { initialize: function (color, reflection, refraction, transparency, gloss) { this.color = color; this.reflection = reflection; this.transparency = transparency; this.gloss = gloss; this.hasTexture = false; }, getColor: function (u, v) { return this.color; }, toString: function () { return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture + ']'; } } ); /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Material.Chessboard = Class.create(); Flog.RayTracer.Material.Chessboard.prototype = Object.extend( new Flog.RayTracer.Material.BaseMaterial(), { colorEven: null, colorOdd: null, density: 0.5, initialize: function (colorEven, colorOdd, reflection, transparency, gloss, density) { this.colorEven = colorEven; this.colorOdd = colorOdd; this.reflection = reflection; this.transparency = transparency; this.gloss = gloss; this.density = density; this.hasTexture = true; }, getColor: function (u, v) { var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density); if (t < 0.0) return this.colorEven; else return this.colorOdd; }, toString: function () { return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture + ']'; } } ); /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; if (typeof (Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; Flog.RayTracer.Shape.Sphere = Class.create(); Flog.RayTracer.Shape.Sphere.prototype = { initialize: function (pos, radius, material) { this.radius = radius; this.position = pos; this.material = material; }, intersect: function (ray) { var info = new Flog.RayTracer.IntersectionInfo(); info.shape = this; var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position); var B = dst.dot(ray.direction); var C = dst.dot(dst) - (this.radius * this.radius); var D = (B * B) - C; if (D > 0) { // intersection! info.isHit = true; info.distance = (-B) - Math.sqrt(D); info.position = Flog.RayTracer.Vector.prototype.add( ray.position, Flog.RayTracer.Vector.prototype.multiplyScalar( ray.direction, info.distance ) ); info.normal = Flog.RayTracer.Vector.prototype.subtract( info.position, this.position ).normalize(); info.color = this.material.getColor(0, 0); } else { info.isHit = false; } return info; }, toString: function () { return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; if (typeof (Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; Flog.RayTracer.Shape.Plane = Class.create(); Flog.RayTracer.Shape.Plane.prototype = { d: 0.0, initialize: function (pos, d, material) { this.position = pos; this.d = d; this.material = material; }, intersect: function (ray) { var info = new Flog.RayTracer.IntersectionInfo(); var Vd = this.position.dot(ray.direction); if (Vd == 0) return info; // no intersection var t = -(this.position.dot(ray.position) + this.d) / Vd; if (t <= 0) return info; info.shape = this; info.isHit = true; info.position = Flog.RayTracer.Vector.prototype.add( ray.position, Flog.RayTracer.Vector.prototype.multiplyScalar( ray.direction, t ) ); info.normal = this.position; info.distance = t; if (this.material.hasTexture) { var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x); var vV = vU.cross(this.position); var u = info.position.dot(vU); var v = info.position.dot(vV); info.color = this.material.getColor(u, v); } else { info.color = this.material.getColor(0, 0); } return info; }, toString: function () { return 'Plane [' + this.position + ', d=' + this.d + ']'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.IntersectionInfo = Class.create(); Flog.RayTracer.IntersectionInfo.prototype = { isHit: false, hitCount: 0, shape: null, position: null, normal: null, color: null, distance: null, initialize: function () { this.color = new Flog.RayTracer.Color(0, 0, 0); }, toString: function () { return 'Intersection [' + this.position + ']'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Camera = Class.create(); Flog.RayTracer.Camera.prototype = { position: null, lookAt: null, equator: null, up: null, screen: null, initialize: function (pos, lookAt, up) { this.position = pos; this.lookAt = lookAt; this.up = up; this.equator = lookAt.normalize().cross(this.up); this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt); }, getRay: function (vx, vy) { var pos = Flog.RayTracer.Vector.prototype.subtract( this.screen, Flog.RayTracer.Vector.prototype.subtract( Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx), Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy) ) ); pos.y = pos.y * -1; var dir = Flog.RayTracer.Vector.prototype.subtract( pos, this.position ); var ray = new Flog.RayTracer.Ray(pos, dir.normalize()); return ray; }, toString: function () { return 'Ray []'; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Background = Class.create(); Flog.RayTracer.Background.prototype = { color: null, ambience: 0.0, initialize: function (color, ambience) { this.color = color; this.ambience = ambience; } } /* Fake a Flog.* namespace */ if (typeof (Flog) == 'undefined') var Flog = {}; if (typeof (Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; Flog.RayTracer.Engine = Class.create(); Flog.RayTracer.Engine.prototype = { canvas: null, /* 2d context we can render to */ initialize: function (options) { this.options = Object.extend({ canvasHeight: 100, canvasWidth: 100, pixelWidth: 2, pixelHeight: 2, renderDiffuse: false, renderShadows: false, renderHighlights: false, renderReflections: false, rayDepth: 2 }, options || {}); this.options.canvasHeight /= this.options.pixelHeight; this.options.canvasWidth /= this.options.pixelWidth; /* TODO: dynamically include other scripts */ }, setPixel: function (x, y, color) { var pxW, pxH; pxW = this.options.pixelWidth; pxH = this.options.pixelHeight; if (this.canvas) { this.canvas.fillStyle = color.toString(); this.canvas.fillRect(x * pxW, y * pxH, pxW, pxH); } else { if (x === y) { checkNumber += color.brightness(); } // print(x * pxW, y * pxH, pxW, pxH); } }, renderScene: function (scene, canvas) { checkNumber = 0; /* Get canvas */ if (canvas) { this.canvas = canvas.getContext("2d"); } else { this.canvas = null; } var canvasHeight = this.options.canvasHeight; var canvasWidth = this.options.canvasWidth; for (var y = 0; y < canvasHeight; y++) { for (var x = 0; x < canvasWidth; x++) { var yp = y * 1.0 / canvasHeight * 2 - 1; var xp = x * 1.0 / canvasWidth * 2 - 1; var ray = scene.camera.getRay(xp, yp); var color = this.getPixelColor(ray, scene); this.setPixel(x, y, color); } } if (checkNumber !== 2321) { throw new Error("Scene rendered incorrectly"); } }, getPixelColor: function (ray, scene) { var info = this.testIntersection(ray, scene, null); if (info.isHit) { var color = this.rayTrace(info, ray, scene, 0); return color; } return scene.background.color; }, testIntersection: function (ray, scene, exclude) { var hits = 0; var best = new Flog.RayTracer.IntersectionInfo(); best.distance = 2000; for (var i = 0; i < scene.shapes.length; i++) { var shape = scene.shapes[i]; if (shape != exclude) { var info = shape.intersect(ray); if (info.isHit && info.distance >= 0 && info.distance < best.distance) { best = info; hits++; } } } best.hitCount = hits; return best; }, getReflectionRay: function (P, N, V) { var c1 = -N.dot(V); var R1 = Flog.RayTracer.Vector.prototype.add( Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2 * c1), V ); return new Flog.RayTracer.Ray(P, R1); }, rayTrace: function (info, ray, scene, depth) { // Calc ambient var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience); var oldColor = color; var shininess = Math.pow(10, info.shape.material.gloss + 1); for (var i = 0; i < scene.lights.length; i++) { var light = scene.lights[i]; // Calc diffuse lighting var v = Flog.RayTracer.Vector.prototype.subtract( light.position, info.position ).normalize(); if (this.options.renderDiffuse) { var L = v.dot(info.normal); if (L > 0.0) { color = Flog.RayTracer.Color.prototype.add( color, Flog.RayTracer.Color.prototype.multiply( info.color, Flog.RayTracer.Color.prototype.multiplyScalar( light.color, L ) ) ); } } // The greater the depth the more accurate the colours, but // this is exponentially (!) expensive if (depth <= this.options.rayDepth) { // calculate reflection ray if (this.options.renderReflections && info.shape.material.reflection > 0) { var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction); var refl = this.testIntersection(reflectionRay, scene, info.shape); if (refl.isHit && refl.distance > 0) { refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1); } else { refl.color = scene.background.color; } color = Flog.RayTracer.Color.prototype.blend( color, refl.color, info.shape.material.reflection ); } // Refraction /* TODO */ } /* Render shadows and highlights */ var shadowInfo = new Flog.RayTracer.IntersectionInfo(); if (this.options.renderShadows) { var shadowRay = new Flog.RayTracer.Ray(info.position, v); shadowInfo = this.testIntersection(shadowRay, scene, info.shape); if (shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/) { var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5); var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5)); color = Flog.RayTracer.Color.prototype.addScalar(vA, dB); } } // Phong specular highlights if (this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0) { var Lv = Flog.RayTracer.Vector.prototype.subtract( info.shape.position, light.position ).normalize(); var E = Flog.RayTracer.Vector.prototype.subtract( scene.camera.position, info.shape.position ).normalize(); var H = Flog.RayTracer.Vector.prototype.subtract( E, Lv ).normalize(); var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess); color = Flog.RayTracer.Color.prototype.add( Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight), color ); } } color.limit(); return color; } }; function renderScene() { var scene = new Flog.RayTracer.Scene(); scene.camera = new Flog.RayTracer.Camera( new Flog.RayTracer.Vector(0, 0, -15), new Flog.RayTracer.Vector(-0.2, 0, 5), new Flog.RayTracer.Vector(0, 1, 0) ); scene.background = new Flog.RayTracer.Background( new Flog.RayTracer.Color(0.5, 0.5, 0.5), 0.4 ); var sphere = new Flog.RayTracer.Shape.Sphere( new Flog.RayTracer.Vector(-1.5, 1.5, 2), 1.5, new Flog.RayTracer.Material.Solid( new Flog.RayTracer.Color(0, 0.5, 0.5), 0.3, 0.0, 0.0, 2.0 ) ); var sphere1 = new Flog.RayTracer.Shape.Sphere( new Flog.RayTracer.Vector(1, 0.25, 1), 0.5, new Flog.RayTracer.Material.Solid( new Flog.RayTracer.Color(0.9, 0.9, 0.9), 0.1, 0.0, 0.0, 1.5 ) ); var plane = new Flog.RayTracer.Shape.Plane( new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(), 1.2, new Flog.RayTracer.Material.Chessboard( new Flog.RayTracer.Color(1, 1, 1), new Flog.RayTracer.Color(0, 0, 0), 0.2, 0.0, 1.0, 0.7 ) ); scene.shapes.push(plane); scene.shapes.push(sphere); scene.shapes.push(sphere1); var light = new Flog.RayTracer.Light( new Flog.RayTracer.Vector(5, 10, -1), new Flog.RayTracer.Color(0.8, 0.8, 0.8) ); var light1 = new Flog.RayTracer.Light( new Flog.RayTracer.Vector(-3, 5, -15), new Flog.RayTracer.Color(0.8, 0.8, 0.8), 100 ); scene.lights.push(light); scene.lights.push(light1); var imageWidth = 100; // $F('imageWidth'); var imageHeight = 100; // $F('imageHeight'); var pixelSize = "5,5".split(','); // $F('pixelSize').split(','); var renderDiffuse = true; // $F('renderDiffuse'); var renderShadows = true; // $F('renderShadows'); var renderHighlights = true; // $F('renderHighlights'); var renderReflections = true; // $F('renderReflections'); var rayDepth = 2;//$F('rayDepth'); var raytracer = new Flog.RayTracer.Engine( { canvasWidth: imageWidth, canvasHeight: imageHeight, pixelWidth: pixelSize[0], pixelHeight: pixelSize[1], "renderDiffuse": renderDiffuse, "renderHighlights": renderHighlights, "renderShadows": renderShadows, "renderReflections": renderReflections, "rayDepth": rayDepth } ); raytracer.renderScene(scene, null, 0); } var RayTrace = new BenchmarkSuite('RayTrace', 739989, [ new Benchmark('RayTrace', renderScene) ]); /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/scripts/v8-benches/regexp.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; // Copyright 2010 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Automatically generated on 2009-01-30. Manually updated on 2010-09-17. // This benchmark is generated by loading 50 of the most popular pages // on the web and logging all regexp operations performed. Each // operation is given a weight that is calculated from an estimate of // the popularity of the pages where it occurs and the number of times // it is executed while loading each page. Furthermore the literal // letters in the data are encoded using ROT13 in a way that does not // affect how the regexps match their input. Finally the strings are // scrambled to exercise the regexp engine on different input strings. var RegExp = new BenchmarkSuite('RegExp', 910985, [ new Benchmark("RegExp", RegExpRun, RegExpSetup, RegExpTearDown) ]); var regExpBenchmark = null; function RegExpSetup() { regExpBenchmark = new RegExpBenchmark(); RegExpRun(); // run once to get system initialized } function RegExpRun() { regExpBenchmark.run(); } function RegExpTearDown() { regExpBenchmark = null; } // Returns an array of n different variants of the input string str. // The variants are computed by randomly rotating one random // character. function computeInputVariants(str, n) { var variants = [str]; for (var i = 1; i < n; i++) { var pos = Math.floor(Math.random() * str.length); var chr = String.fromCharCode((str.charCodeAt(pos) + Math.floor(Math.random() * 128)) % 128); variants[i] = str.substring(0, pos) + chr + str.substring(pos + 1, str.length); } return variants; } function RegExpBenchmark() { var re0 = /^ba/; var re1 = /(((\w+):\/\/)([^\/:]*)(:(\d+))?)?([^#?]*)(\?([^#]*))?(#(.*))?/; var re2 = /^\s*|\s*$/g; var re3 = /\bQBZPbageby_cynprubyqre\b/; var re4 = /,/; var re5 = /\bQBZPbageby_cynprubyqre\b/g; var re6 = /^[\s\xa0]+|[\s\xa0]+$/g; var re7 = /(\d*)(\D*)/g; var re8 = /=/; var re9 = /(^|\s)lhv\-h(\s|$)/; var str0 = 'Zbmvyyn/5.0 (Jvaqbjf; H; Jvaqbjf AG 5.1; ra-HF) NccyrJroXvg/528.9 (XUGZY, yvxr Trpxb) Puebzr/2.0.157.0 Fnsnev/528.9'; var re10 = /\#/g; var re11 = /\./g; var re12 = /'/g; var re13 = /\?[\w\W]*(sevraqvq|punaaryvq|tebhcvq)=([^\&\?#]*)/i; var str1 = 'Fubpxjnir Synfu 9.0 e115'; var re14 = /\s+/g; var re15 = /^\s*(\S*(\s+\S+)*)\s*$/; var re16 = /(-[a-z])/i; var s0 = computeInputVariants('pyvpx', 6511); var s1 = computeInputVariants('uggc://jjj.snprobbx.pbz/ybtva.cuc', 1844); var s2 = computeInputVariants('QBZPbageby_cynprubyqre', 739); var s3 = computeInputVariants('uggc://jjj.snprobbx.pbz/', 598); var s4 = computeInputVariants('uggc://jjj.snprobbx.pbz/fepu.cuc', 454); var s5 = computeInputVariants('qqqq, ZZZ q, llll', 352); var s6 = computeInputVariants('vachggrkg QBZPbageby_cynprubyqre', 312); var s7 = computeInputVariants('/ZlFcnprUbzrcntr/Vaqrk-FvgrUbzr,10000000', 282); var s8 = computeInputVariants('vachggrkg', 177); var s9 = computeInputVariants('528.9', 170); var s10 = computeInputVariants('528', 170); var s11 = computeInputVariants('VCPhygher=ra-HF', 156); var s12 = computeInputVariants('CersreerqPhygher=ra-HF', 156); var s13 = computeInputVariants('xrlcerff', 144); var s14 = computeInputVariants('521', 139); var s15 = computeInputVariants(str0, 139); var s16 = computeInputVariants('qvi .so_zrah', 137); var s17 = computeInputVariants('qvi.so_zrah', 137); var s18 = computeInputVariants('uvqqra_ryrz', 117); var s19 = computeInputVariants('sevraqfgre_naba=nvq%3Qn6ss9p85n868ro9s059pn854735956o3%26ers%3Q%26df%3Q%26vpgl%3QHF', 95); var s20 = computeInputVariants('uggc://ubzr.zlfcnpr.pbz/vaqrk.psz', 93); var s21 = computeInputVariants(str1, 92); var s22 = computeInputVariants('svefg', 85); var s23 = computeInputVariants('uggc://cebsvyr.zlfcnpr.pbz/vaqrk.psz', 85); var s24 = computeInputVariants('ynfg', 85); var s25 = computeInputVariants('qvfcynl', 85); function runBlock0() { for (var i = 0; i < 6511; i++) { re0.exec(s0[i]); } for (var i = 0; i < 1844; i++) { re1.exec(s1[i]); } for (var i = 0; i < 739; i++) { s2[i].replace(re2, ''); } for (var i = 0; i < 598; i++) { re1.exec(s3[i]); } for (var i = 0; i < 454; i++) { re1.exec(s4[i]); } for (var i = 0; i < 352; i++) { /qqqq|qqq|qq|q|ZZZZ|ZZZ|ZZ|Z|llll|ll|l|uu|u|UU|U|zz|z|ff|f|gg|g|sss|ss|s|mmm|mm|m/g.exec(s5[i]); } for (var i = 0; i < 312; i++) { re3.exec(s6[i]); } for (var i = 0; i < 282; i++) { re4.exec(s7[i]); } for (var i = 0; i < 177; i++) { s8[i].replace(re5, ''); } for (var i = 0; i < 170; i++) { s9[i].replace(re6, ''); re7.exec(s10[i]); } for (var i = 0; i < 156; i++) { re8.exec(s11[i]); re8.exec(s12[i]); } for (var i = 0; i < 144; i++) { re0.exec(s13[i]); } for (var i = 0; i < 139; i++) { s14[i].replace(re6, ''); re7.exec(s14[i]); re9.exec(''); /JroXvg\/(\S+)/.exec(s15[i]); } for (var i = 0; i < 137; i++) { s16[i].replace(re10, ''); s16[i].replace(/\[/g, ''); s17[i].replace(re11, ''); } for (var i = 0; i < 117; i++) { s18[i].replace(re2, ''); } for (var i = 0; i < 95; i++) { /(?:^|;)\s*sevraqfgre_ynat=([^;]*)/.exec(s19[i]); } for (var i = 0; i < 93; i++) { s20[i].replace(re12, ''); re13.exec(s20[i]); } for (var i = 0; i < 92; i++) { s21[i].replace(/([a-zA-Z]|\s)+/, ''); } for (var i = 0; i < 85; i++) { s22[i].replace(re14, ''); s22[i].replace(re15, ''); s23[i].replace(re12, ''); s24[i].replace(re14, ''); s24[i].replace(re15, ''); re16.exec(s25[i]); re13.exec(s23[i]); } } var re17 = /(^|[^\\])\"\\\/Qngr\((-?[0-9]+)\)\\\/\"/g; var str2 = '{"anzr":"","ahzoreSbezng":{"PheeraplQrpvznyQvtvgf":2,"PheeraplQrpvznyFrcnengbe":".","VfErnqBayl":gehr,"PheeraplTebhcFvmrf":[3],"AhzoreTebhcFvmrf":[3],"CrepragTebhcFvmrf":[3],"PheeraplTebhcFrcnengbe":",","PheeraplFlzoby":"\xa4","AnAFlzoby":"AnA","PheeraplArtngvirCnggrea":0,"AhzoreArtngvirCnggrea":1,"CrepragCbfvgvirCnggrea":0,"CrepragArtngvirCnggrea":0,"ArtngvirVasvavglFlzoby":"-Vasvavgl","ArtngvirFvta":"-","AhzoreQrpvznyQvtvgf":2,"AhzoreQrpvznyFrcnengbe":".","AhzoreTebhcFrcnengbe":",","PheeraplCbfvgvirCnggrea":0,"CbfvgvirVasvavglFlzoby":"Vasvavgl","CbfvgvirFvta":"+","CrepragQrpvznyQvtvgf":2,"CrepragQrpvznyFrcnengbe":".","CrepragTebhcFrcnengbe":",","CrepragFlzoby":"%","CreZvyyrFlzoby":"\u2030","AngvirQvtvgf":["0","1","2","3","4","5","6","7","8","9"],"QvtvgFhofgvghgvba":1},"qngrGvzrSbezng":{"NZQrfvtangbe":"NZ","Pnyraqne":{"ZvaFhccbegrqQngrGvzr":"@-62135568000000@","ZnkFhccbegrqQngrGvzr":"@253402300799999@","NytbevguzGlcr":1,"PnyraqneGlcr":1,"Renf":[1],"GjbQvtvgLrneZnk":2029,"VfErnqBayl":gehr},"QngrFrcnengbe":"/","SvefgQnlBsJrrx":0,"PnyraqneJrrxEhyr":0,"ShyyQngrGvzrCnggrea":"qqqq, qq ZZZZ llll UU:zz:ff","YbatQngrCnggrea":"qqqq, qq ZZZZ llll","YbatGvzrCnggrea":"UU:zz:ff","ZbaguQnlCnggrea":"ZZZZ qq","CZQrfvtangbe":"CZ","ESP1123Cnggrea":"qqq, qq ZZZ llll UU\':\'zz\':\'ff \'TZG\'","FubegQngrCnggrea":"ZZ/qq/llll","FubegGvzrCnggrea":"UU:zz","FbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq\'G\'UU\':\'zz\':\'ff","GvzrFrcnengbe":":","HavirefnyFbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq UU\':\'zz\':\'ff\'M\'","LrneZbaguCnggrea":"llll ZZZZ","NooerivngrqQnlAnzrf":["Fha","Zba","Ghr","Jrq","Guh","Sev","Fng"],"FubegrfgQnlAnzrf":["Fh","Zb","Gh","Jr","Gu","Se","Fn"],"QnlAnzrf":["Fhaqnl","Zbaqnl","Ghrfqnl","Jrqarfqnl","Guhefqnl","Sevqnl","Fngheqnl"],"NooerivngrqZbaguAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""],"VfErnqBayl":gehr,"AngvirPnyraqneAnzr":"Tertbevna Pnyraqne","NooerivngrqZbaguTravgvirAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguTravgvirAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""]}}'; var str3 = '{"anzr":"ra-HF","ahzoreSbezng":{"PheeraplQrpvznyQvtvgf":2,"PheeraplQrpvznyFrcnengbe":".","VfErnqBayl":snyfr,"PheeraplTebhcFvmrf":[3],"AhzoreTebhcFvmrf":[3],"CrepragTebhcFvmrf":[3],"PheeraplTebhcFrcnengbe":",","PheeraplFlzoby":"$","AnAFlzoby":"AnA","PheeraplArtngvirCnggrea":0,"AhzoreArtngvirCnggrea":1,"CrepragCbfvgvirCnggrea":0,"CrepragArtngvirCnggrea":0,"ArtngvirVasvavglFlzoby":"-Vasvavgl","ArtngvirFvta":"-","AhzoreQrpvznyQvtvgf":2,"AhzoreQrpvznyFrcnengbe":".","AhzoreTebhcFrcnengbe":",","PheeraplCbfvgvirCnggrea":0,"CbfvgvirVasvavglFlzoby":"Vasvavgl","CbfvgvirFvta":"+","CrepragQrpvznyQvtvgf":2,"CrepragQrpvznyFrcnengbe":".","CrepragTebhcFrcnengbe":",","CrepragFlzoby":"%","CreZvyyrFlzoby":"\u2030","AngvirQvtvgf":["0","1","2","3","4","5","6","7","8","9"],"QvtvgFhofgvghgvba":1},"qngrGvzrSbezng":{"NZQrfvtangbe":"NZ","Pnyraqne":{"ZvaFhccbegrqQngrGvzr":"@-62135568000000@","ZnkFhccbegrqQngrGvzr":"@253402300799999@","NytbevguzGlcr":1,"PnyraqneGlcr":1,"Renf":[1],"GjbQvtvgLrneZnk":2029,"VfErnqBayl":snyfr},"QngrFrcnengbe":"/","SvefgQnlBsJrrx":0,"PnyraqneJrrxEhyr":0,"ShyyQngrGvzrCnggrea":"qqqq, ZZZZ qq, llll u:zz:ff gg","YbatQngrCnggrea":"qqqq, ZZZZ qq, llll","YbatGvzrCnggrea":"u:zz:ff gg","ZbaguQnlCnggrea":"ZZZZ qq","CZQrfvtangbe":"CZ","ESP1123Cnggrea":"qqq, qq ZZZ llll UU\':\'zz\':\'ff \'TZG\'","FubegQngrCnggrea":"Z/q/llll","FubegGvzrCnggrea":"u:zz gg","FbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq\'G\'UU\':\'zz\':\'ff","GvzrFrcnengbe":":","HavirefnyFbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq UU\':\'zz\':\'ff\'M\'","LrneZbaguCnggrea":"ZZZZ, llll","NooerivngrqQnlAnzrf":["Fha","Zba","Ghr","Jrq","Guh","Sev","Fng"],"FubegrfgQnlAnzrf":["Fh","Zb","Gh","Jr","Gu","Se","Fn"],"QnlAnzrf":["Fhaqnl","Zbaqnl","Ghrfqnl","Jrqarfqnl","Guhefqnl","Sevqnl","Fngheqnl"],"NooerivngrqZbaguAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""],"VfErnqBayl":snyfr,"AngvirPnyraqneAnzr":"Tertbevna Pnyraqne","NooerivngrqZbaguTravgvirAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguTravgvirAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""]}}'; var str4 = 'HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str5 = 'HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var re18 = /^\s+|\s+$/g; var str6 = 'uggc://jjj.snprobbx.pbz/vaqrk.cuc'; var re19 = /(?:^|\s+)ba(?:\s+|$)/; var re20 = /[+, ]/; var re21 = /ybnqrq|pbzcyrgr/; var str7 = ';;jvaqbj.IjPurpxZbhfrCbfvgvbaNQ_VQ=shapgvba(r){vs(!r)ine r=jvaqbj.rirag;ine c=-1;vs(d1)c=d1.EbyybssCnary;ine bo=IjTrgBow("IjCnayNQ_VQ_"+c);vs(bo&&bo.fglyr.ivfvovyvgl=="ivfvoyr"){ine fns=IjFns?8:0;ine pheK=r.pyvragK+IjBOFpe("U")+fns,pheL=r.pyvragL+IjBOFpe("I")+fns;ine y=IjBOEC(NQ_VQ,bo,"Y"),g=IjBOEC(NQ_VQ,bo,"G");ine e=y+d1.Cnaryf[c].Jvqgu,o=g+d1.Cnaryf[c].Urvtug;vs((pheKe)||(pheLo)){vs(jvaqbj.IjBaEbyybssNQ_VQ)IjBaEbyybssNQ_VQ(c);ryfr IjPybfrNq(NQ_VQ,c,gehr,"");}ryfr erghea;}IjPnapryZbhfrYvfgrareNQ_VQ();};;jvaqbj.IjFrgEbyybssCnaryNQ_VQ=shapgvba(c){ine z="zbhfrzbir",q=qbphzrag,s=IjPurpxZbhfrCbfvgvbaNQ_VQ;c=IjTc(NQ_VQ,c);vs(d1&&d1.EbyybssCnary>-1)IjPnapryZbhfrYvfgrareNQ_VQ();vs(d1)d1.EbyybssCnary=c;gel{vs(q.nqqRiragYvfgrare)q.nqqRiragYvfgrare(z,s,snyfr);ryfr vs(q.nggnpuRirag)q.nggnpuRirag("ba"+z,s);}pngpu(r){}};;jvaqbj.IjPnapryZbhfrYvfgrareNQ_VQ=shapgvba(){ine z="zbhfrzbir",q=qbphzrag,s=IjPurpxZbhfrCbfvgvbaNQ_VQ;vs(d1)d1.EbyybssCnary=-1;gel{vs(q.erzbirRiragYvfgrare)q.erzbirRiragYvfgrare(z,s,snyfr);ryfr vs(q.qrgnpuRirag)q.qrgnpuRirag("ba"+z,s);}pngpu(r){}};;d1.IjTc=d2(n,c){ine nq=d1;vs(vfAnA(c)){sbe(ine v=0;v0){vs(nq.FzV.yratgu>0)nq.FzV+="/";nq.FzV+=vh[v];nq.FtZ[nq.FtZ.yratgu]=snyfr;}}};;d1.IjYvzvg0=d2(n,f){ine nq=d1,vh=f.fcyvg("/");sbe(ine v=0;v0){vs(nq.OvC.yratgu>0)nq.OvC+="/";nq.OvC+=vh[v];}}};;d1.IjRVST=d2(n,c){jvaqbj["IjCnayNQ_VQ_"+c+"_Bow"]=IjTrgBow("IjCnayNQ_VQ_"+c+"_Bow");vs(jvaqbj["IjCnayNQ_VQ_"+c+"_Bow"]==ahyy)frgGvzrbhg("IjRVST(NQ_VQ,"+c+")",d1.rvsg);};;d1.IjNavzSHC=d2(n,c){ine nq=d1;vs(c>nq.Cnaryf.yratgu)erghea;ine cna=nq.Cnaryf[c],nn=gehr,on=gehr,yn=gehr,en=gehr,cn=nq.Cnaryf[0],sf=nq.ShF,j=cn.Jvqgu,u=cn.Urvtug;vs(j=="100%"){j=sf;en=snyfr;yn=snyfr;}vs(u=="100%"){u=sf;nn=snyfr;on=snyfr;}vs(cn.YnY=="Y")yn=snyfr;vs(cn.YnY=="E")en=snyfr;vs(cn.GnY=="G")nn=snyfr;vs(cn.GnY=="O")on=snyfr;ine k=0,l=0;fjvgpu(nq.NshP%8){pnfr 0:oernx;pnfr 1:vs(nn)l=-sf;oernx;pnfr 2:k=j-sf;oernx;pnfr 3:vs(en)k=j;oernx;pnfr 4:k=j-sf;l=u-sf;oernx;pnfr 5:k=j-sf;vs(on)l=u;oernx;pnfr 6:l=u-sf;oernx;pnfr 7:vs(yn)k=-sf;l=u-sf;oernx;}vs(nq.NshP++ 0)||(nethzragf.yratgu==3&&bG>0))){pyrneGvzrbhg(cay.UgU);cay.UgU=frgGvzrbhg(cay.UvqrNpgvba,(nethzragf.yratgu==3?bG:cay.UvqrGvzrbhgInyhr));}};;d1.IjErfrgGvzrbhg=d2(n,c,bG){c=IjTc(n,c);IjPnapryGvzrbhg(n,c);riny("IjFgnegGvzrbhg(NQ_VQ,c"+(nethzragf.yratgu==3?",bG":"")+")");};;d1.IjErfrgNyyGvzrbhgf=d2(n){sbe(ine c=0;c]/g; var str15 = 'FrffvbaQQS2=s6r4579npn4rn2135s904r0s75pp1o5334p6s6pospo12696; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669316860113296&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_dfctwzs-aowb_80=44132r503660'; var str16 = 'FrffvbaQQS2=s6r4579npn4rn2135s904r0s75pp1o5334p6s6pospo12696; AFP_zp_dfctwzs-aowb_80=44132r503660; __hgzm=144631658.1231363638.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.965867047679498800.1231363638.1231363638.1231363638.1; __hgzo=144631658.0.10.1231363638; __hgzp=144631658; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669316860113296&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str17 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231363621014&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231363621014&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Scebsvyr.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=348699119.1231363624&tn_fvq=1231363624&tn_uvq=895511034&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str18 = 'uggc://jjj.yrobapbva.se/yv'; var str19 = 'ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669316860113296&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str20 = 'ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669316860113296&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var s67 = computeInputVariants('e115', 27); var s68 = computeInputVariants('qvfcynl', 27); var s69 = computeInputVariants('cbfvgvba', 27); var s70 = computeInputVariants('uggc://jjj.zlfcnpr.pbz/', 27); var s71 = computeInputVariants('cntrivrj', 27); var s72 = computeInputVariants('VC=74.125.75.3', 27); var s73 = computeInputVariants('ra', 27); var s74 = computeInputVariants(str10, 27); var s75 = computeInputVariants(str11, 27); var s76 = computeInputVariants(str12, 27); var s77 = computeInputVariants(str17, 27); var s78 = computeInputVariants(str18, 27); function runBlock3() { for (var i = 0; i < 27; i++) { s67[i].replace(/[A-Za-z]/g, ''); } for (var i = 0; i < 23; i++) { s68[i].replace(re27, ''); s69[i].replace(re27, ''); } for (var i = 0; i < 22; i++) { 'unaqyr'.replace(re14, ''); 'unaqyr'.replace(re15, ''); 'yvar'.replace(re14, ''); 'yvar'.replace(re15, ''); 'cnerag puebzr6 fvatyr1 gno'.replace(re14, ''); 'cnerag puebzr6 fvatyr1 gno'.replace(re15, ''); 'fyvqre'.replace(re14, ''); 'fyvqre'.replace(re15, ''); re28.exec(''); } for (var i = 0; i < 21; i++) { s70[i].replace(re12, ''); re13.exec(s70[i]); } for (var i = 0; i < 20; i++) { s71[i].replace(re29, ''); s71[i].replace(re30, ''); re19.exec('ynfg'); re19.exec('ba svefg'); re8.exec(s72[i]); } for (var i = 0; i < 19; i++) { re31.exec(s73[i]); } for (var i = 0; i < 18; i++) { s74[i].split(re32); s75[i].split(re32); s76[i].replace(re33, ''); re8.exec('144631658.0.10.1231363570'); re8.exec('144631658.1231363570.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.3426875219718084000.1231363570.1231363570.1231363570.1'); re8.exec(str13); re8.exec(str14); re8.exec('__hgzn=144631658.3426875219718084000.1231363570.1231363570.1231363570.1'); re8.exec('__hgzo=144631658.0.10.1231363570'); re8.exec('__hgzm=144631658.1231363570.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re34.exec(s74[i]); re34.exec(s75[i]); } for (var i = 0; i < 17; i++) { s15[i].match(/zfvr/gi); s15[i].match(/bcren/gi); str15.split(re32); str16.split(re32); 'ohggba'.replace(re14, ''); 'ohggba'.replace(re15, ''); 'puvyq p1 svefg sylbhg pybfrq'.replace(re14, ''); 'puvyq p1 svefg sylbhg pybfrq'.replace(re15, ''); 'pvgvrf'.replace(re14, ''); 'pvgvrf'.replace(re15, ''); 'pybfrq'.replace(re14, ''); 'pybfrq'.replace(re15, ''); 'qry'.replace(re14, ''); 'qry'.replace(re15, ''); 'uqy_zba'.replace(re14, ''); 'uqy_zba'.replace(re15, ''); s77[i].replace(re33, ''); s78[i].replace(/%3P/g, ''); s78[i].replace(/%3R/g, ''); s78[i].replace(/%3q/g, ''); s78[i].replace(re35, ''); 'yvaxyvfg16'.replace(re14, ''); 'yvaxyvfg16'.replace(re15, ''); 'zvahf'.replace(re14, ''); 'zvahf'.replace(re15, ''); 'bcra'.replace(re14, ''); 'bcra'.replace(re15, ''); 'cnerag puebzr5 fvatyr1 ps NU'.replace(re14, ''); 'cnerag puebzr5 fvatyr1 ps NU'.replace(re15, ''); 'cynlre'.replace(re14, ''); 'cynlre'.replace(re15, ''); 'cyhf'.replace(re14, ''); 'cyhf'.replace(re15, ''); 'cb_uqy'.replace(re14, ''); 'cb_uqy'.replace(re15, ''); 'hyJVzt'.replace(re14, ''); 'hyJVzt'.replace(re15, ''); re8.exec('144631658.0.10.1231363638'); re8.exec('144631658.1231363638.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.965867047679498800.1231363638.1231363638.1231363638.1'); re8.exec('4413268q3660'); re8.exec('4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n'); re8.exec('SbeprqRkcvengvba=633669321699093060'); re8.exec('VC=74.125.75.20'); re8.exec(str19); re8.exec(str20); re8.exec('AFP_zp_tfwsbrg-aowb_80=4413268q3660'); re8.exec('FrffvbaQQS2=4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n'); re8.exec('__hgzn=144631658.965867047679498800.1231363638.1231363638.1231363638.1'); re8.exec('__hgzo=144631658.0.10.1231363638'); re8.exec('__hgzm=144631658.1231363638.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re34.exec(str15); re34.exec(str16); } } var re36 = /uers|fep|fryrpgrq/; var re37 = /\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g; var re38 = /^(\w+|\*)$/; var str21 = 'FrffvbaQQS2=s15q53p9n372sn76npr13o271n4s3p5r29p235746p908p58; ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669358527244818&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var str22 = 'FrffvbaQQS2=s15q53p9n372sn76npr13o271n4s3p5r29p235746p908p58; __hgzm=144631658.1231367822.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.4127520630321984500.1231367822.1231367822.1231367822.1; __hgzo=144631658.0.10.1231367822; __hgzp=144631658; ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669358527244818&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str23 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231367803797&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231367803797&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Szrffntvat.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1192552091.1231367807&tn_fvq=1231367807&tn_uvq=1155446857&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str24 = 'ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669358527244818&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str25 = 'ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669358527244818&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var str26 = 'hy.ynat-fryrpgbe'; var re39 = /\\/g; var re40 = / /g; var re41 = /\/\xc4\/t/; var re42 = /\/\xd6\/t/; var re43 = /\/\xdc\/t/; var re44 = /\/\xdf\/t/; var re45 = /\/\xe4\/t/; var re46 = /\/\xf6\/t/; var re47 = /\/\xfc\/t/; var re48 = /\W/g; var re49 = /uers|fep|fglyr/; var s79 = computeInputVariants(str21, 16); var s80 = computeInputVariants(str22, 16); var s81 = computeInputVariants(str23, 16); var s82 = computeInputVariants(str26, 16); function runBlock4() { for (var i = 0; i < 16; i++) { ''.replace(/\*/g, ''); /\bnpgvir\b/.exec('npgvir'); /sversbk/i.exec(s15[i]); re36.exec('glcr'); /zfvr/i.exec(s15[i]); /bcren/i.exec(s15[i]); } for (var i = 0; i < 15; i++) { s79[i].split(re32); s80[i].split(re32); 'uggc://ohyyrgvaf.zlfcnpr.pbz/vaqrk.psz'.replace(re12, ''); s81[i].replace(re33, ''); 'yv'.replace(re37, ''); 'yv'.replace(re18, ''); re8.exec('144631658.0.10.1231367822'); re8.exec('144631658.1231367822.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.4127520630321984500.1231367822.1231367822.1231367822.1'); re8.exec(str24); re8.exec(str25); re8.exec('__hgzn=144631658.4127520630321984500.1231367822.1231367822.1231367822.1'); re8.exec('__hgzo=144631658.0.10.1231367822'); re8.exec('__hgzm=144631658.1231367822.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re34.exec(s79[i]); re34.exec(s80[i]); /\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)["']?(.*?)["']?)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g.exec(s82[i]); re13.exec('uggc://ohyyrgvaf.zlfcnpr.pbz/vaqrk.psz'); re38.exec('yv'); } for (var i = 0; i < 14; i++) { ''.replace(re18, ''); '9.0 e115'.replace(/(\s+e|\s+o[0-9]+)/, ''); 'Funer guvf tnqtrg'.replace(//g, ''); 'Funer guvf tnqtrg'.replace(re39, ''); 'uggc://cebsvyrrqvg.zlfcnpr.pbz/vaqrk.psz'.replace(re12, ''); 'grnfre'.replace(re40, ''); 'grnfre'.replace(re41, ''); 'grnfre'.replace(re42, ''); 'grnfre'.replace(re43, ''); 'grnfre'.replace(re44, ''); 'grnfre'.replace(re45, ''); 'grnfre'.replace(re46, ''); 'grnfre'.replace(re47, ''); 'grnfre'.replace(re48, ''); re16.exec('znetva-gbc'); re16.exec('cbfvgvba'); re19.exec('gno1'); re9.exec('qz'); re9.exec('qg'); re9.exec('zbqobk'); re9.exec('zbqobkva'); re9.exec('zbqgvgyr'); re13.exec('uggc://cebsvyrrqvg.zlfcnpr.pbz/vaqrk.psz'); re26.exec('/vt/znvytnqtrg'); re49.exec('glcr'); } } var re50 = /(?:^|\s+)fryrpgrq(?:\s+|$)/; var re51 = /\&/g; var re52 = /\+/g; var re53 = /\?/g; var re54 = /\t/g; var re55 = /(\$\{nqiHey\})|(\$nqiHey\b)/g; var re56 = /(\$\{cngu\})|(\$cngu\b)/g; function runBlock5() { for (var i = 0; i < 13; i++) { 'purpx'.replace(re14, ''); 'purpx'.replace(re15, ''); 'pvgl'.replace(re14, ''); 'pvgl'.replace(re15, ''); 'qrpe fyvqrgrkg'.replace(re14, ''); 'qrpe fyvqrgrkg'.replace(re15, ''); 'svefg fryrpgrq'.replace(re14, ''); 'svefg fryrpgrq'.replace(re15, ''); 'uqy_rag'.replace(re14, ''); 'uqy_rag'.replace(re15, ''); 'vape fyvqrgrkg'.replace(re14, ''); 'vape fyvqrgrkg'.replace(re15, ''); 'vachggrkg QBZPbageby_cynprubyqre'.replace(re5, ''); 'cnerag puebzr6 fvatyr1 gno fryrpgrq'.replace(re14, ''); 'cnerag puebzr6 fvatyr1 gno fryrpgrq'.replace(re15, ''); 'cb_guz'.replace(re14, ''); 'cb_guz'.replace(re15, ''); 'fhozvg'.replace(re14, ''); 'fhozvg'.replace(re15, ''); re50.exec(''); /NccyrJroXvg\/([^\s]*)/.exec(s15[i]); /XUGZY/.exec(s15[i]); } for (var i = 0; i < 12; i++) { '${cebg}://${ubfg}${cngu}/${dz}'.replace(/(\$\{cebg\})|(\$cebg\b)/g, ''); '1'.replace(re40, ''); '1'.replace(re10, ''); '1'.replace(re51, ''); '1'.replace(re52, ''); '1'.replace(re53, ''); '1'.replace(re39, ''); '1'.replace(re54, ''); '9.0 e115'.replace(/^(.*)\..*$/, ''); '9.0 e115'.replace(/^.*e(.*)$/, ''); ''.replace(re55, ''); ''.replace(re55, ''); s21[i].replace(/^.*\s+(\S+\s+\S+$)/, ''); 'tzk%2Subzrcntr%2Sfgneg%2Sqr%2S'.replace(re30, ''); 'tzk'.replace(re30, ''); 'uggc://${ubfg}${cngu}/${dz}'.replace(/(\$\{ubfg\})|(\$ubfg\b)/g, ''); 'uggc://nqpyvrag.hvzfrei.arg${cngu}/${dz}'.replace(re56, ''); 'uggc://nqpyvrag.hvzfrei.arg/wf.at/${dz}'.replace(/(\$\{dz\})|(\$dz\b)/g, ''); 'frpgvba'.replace(re29, ''); 'frpgvba'.replace(re30, ''); 'fvgr'.replace(re29, ''); 'fvgr'.replace(re30, ''); 'fcrpvny'.replace(re29, ''); 'fcrpvny'.replace(re30, ''); re36.exec('anzr'); /e/.exec('9.0 e115'); } } var re57 = /##yv4##/gi; var re58 = /##yv16##/gi; var re59 = /##yv19##/gi; var str27 = '##yv4##Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.##yv19##Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.##yv16##Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str28 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.##yv19##Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.##yv16##Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str29 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.##yv19##Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str30 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str31 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl. ##N##Yrnea zber##/N##'; var str32 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl. Yrnea zber##/N##'; var str33 = 'Bar Jvaqbjf Yvir VQ trgf lbh vagb Ubgznvy, Zrffratre, Kobk YVIR \u2014 naq bgure cynprf lbh frr #~#argjbexybtb#~#'; var re60 = /(?:^|\s+)bss(?:\s+|$)/; var re61 = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/; var re62 = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; var str34 = '${1}://${2}${3}${4}${5}'; var str35 = ' O=6gnyg0g4znrrn&o=3&f=gc; Q=_lyu=K3bQZGSxnT4lZzD3OS9GNmV3ZGLkAQxRpTyxNmRlZmRmAmNkAQLRqTImqNZjOUEgpTjQnJ5xMKtgoN--; SCF=qy'; var s83 = computeInputVariants(str27, 11); var s84 = computeInputVariants(str28, 11); var s85 = computeInputVariants(str29, 11); var s86 = computeInputVariants(str30, 11); var s87 = computeInputVariants(str31, 11); var s88 = computeInputVariants(str32, 11); var s89 = computeInputVariants(str33, 11); var s90 = computeInputVariants(str34, 11); function runBlock6() { for (var i = 0; i < 11; i++) { s83[i].replace(/##yv0##/gi, ''); s83[i].replace(re57, ''); s84[i].replace(re58, ''); s85[i].replace(re59, ''); s86[i].replace(/##\/o##/gi, ''); s86[i].replace(/##\/v##/gi, ''); s86[i].replace(/##\/h##/gi, ''); s86[i].replace(/##o##/gi, ''); s86[i].replace(/##oe##/gi, ''); s86[i].replace(/##v##/gi, ''); s86[i].replace(/##h##/gi, ''); s87[i].replace(/##n##/gi, ''); s88[i].replace(/##\/n##/gi, ''); s89[i].replace(/#~#argjbexybtb#~#/g, ''); / Zbovyr\//.exec(s15[i]); /##yv1##/gi.exec(s83[i]); /##yv10##/gi.exec(s84[i]); /##yv11##/gi.exec(s84[i]); /##yv12##/gi.exec(s84[i]); /##yv13##/gi.exec(s84[i]); /##yv14##/gi.exec(s84[i]); /##yv15##/gi.exec(s84[i]); re58.exec(s84[i]); /##yv17##/gi.exec(s85[i]); /##yv18##/gi.exec(s85[i]); re59.exec(s85[i]); /##yv2##/gi.exec(s83[i]); /##yv20##/gi.exec(s86[i]); /##yv21##/gi.exec(s86[i]); /##yv22##/gi.exec(s86[i]); /##yv23##/gi.exec(s86[i]); /##yv3##/gi.exec(s83[i]); re57.exec(s83[i]); /##yv5##/gi.exec(s84[i]); /##yv6##/gi.exec(s84[i]); /##yv7##/gi.exec(s84[i]); /##yv8##/gi.exec(s84[i]); /##yv9##/gi.exec(s84[i]); re8.exec('473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29'); re8.exec('SbeprqRkcvengvba=633669325184628362'); re8.exec('FrffvbaQQS2=473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29'); /AbxvnA[^\/]*/.exec(s15[i]); } for (var i = 0; i < 10; i++) { ' bss'.replace(/(?:^|\s+)bss(?:\s+|$)/g, ''); s90[i].replace(/(\$\{0\})|(\$0\b)/g, ''); s90[i].replace(/(\$\{1\})|(\$1\b)/g, ''); s90[i].replace(/(\$\{pbzcyrgr\})|(\$pbzcyrgr\b)/g, ''); s90[i].replace(/(\$\{sentzrag\})|(\$sentzrag\b)/g, ''); s90[i].replace(/(\$\{ubfgcbeg\})|(\$ubfgcbeg\b)/g, ''); s90[i].replace(re56, ''); s90[i].replace(/(\$\{cebgbpby\})|(\$cebgbpby\b)/g, ''); s90[i].replace(/(\$\{dhrel\})|(\$dhrel\b)/g, ''); 'nqfvmr'.replace(re29, ''); 'nqfvmr'.replace(re30, ''); 'uggc://${2}${3}${4}${5}'.replace(/(\$\{2\})|(\$2\b)/g, ''); 'uggc://wf.hv-cbegny.qr${3}${4}${5}'.replace(/(\$\{3\})|(\$3\b)/g, ''); 'arjf'.replace(re40, ''); 'arjf'.replace(re41, ''); 'arjf'.replace(re42, ''); 'arjf'.replace(re43, ''); 'arjf'.replace(re44, ''); 'arjf'.replace(re45, ''); 'arjf'.replace(re46, ''); 'arjf'.replace(re47, ''); 'arjf'.replace(re48, ''); / PC=i=(\d+)&oe=(.)/.exec(str35); re60.exec(' '); re60.exec(' bss'); re60.exec(''); re19.exec(' '); re19.exec('svefg ba'); re19.exec('ynfg vtaber'); re19.exec('ba'); re9.exec('scnq so '); re9.exec('zrqvgobk'); re9.exec('hsgy'); re9.exec('lhv-h'); /Fnsnev|Xbadhrebe|XUGZY/gi.exec(s15[i]); re61.exec('uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf'); re62.exec('#Ybtva_rznvy'); } } var re63 = /\{0\}/g; var str36 = 'FrffvbaQQS2=4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n; ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669321699093060&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_tfwsbrg-aowb_80=4413268q3660'; var str37 = 'FrffvbaQQS2=4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n; AFP_zp_tfwsbrg-aowb_80=4413268q3660; __hgzm=144631658.1231364074.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.2294274870215848400.1231364074.1231364074.1231364074.1; __hgzo=144631658.0.10.1231364074; __hgzp=144631658; ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669321699093060&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str38 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231364057761&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231364057761&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Ssevraqf.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1667363813.1231364061&tn_fvq=1231364061&tn_uvq=1917563877&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str39 = 'ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669321699093060&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str40 = 'ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669321699093060&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var s91 = computeInputVariants(str36, 9); var s92 = computeInputVariants(str37, 9); var s93 = computeInputVariants(str38, 9); function runBlock7() { for (var i = 0; i < 9; i++) { '0'.replace(re40, ''); '0'.replace(re10, ''); '0'.replace(re51, ''); '0'.replace(re52, ''); '0'.replace(re53, ''); '0'.replace(re39, ''); '0'.replace(re54, ''); 'Lrf'.replace(re40, ''); 'Lrf'.replace(re10, ''); 'Lrf'.replace(re51, ''); 'Lrf'.replace(re52, ''); 'Lrf'.replace(re53, ''); 'Lrf'.replace(re39, ''); 'Lrf'.replace(re54, ''); } for (var i = 0; i < 8; i++) { 'Pybfr {0}'.replace(re63, ''); 'Bcra {0}'.replace(re63, ''); s91[i].split(re32); s92[i].split(re32); 'puvyq p1 svefg gnournqref'.replace(re14, ''); 'puvyq p1 svefg gnournqref'.replace(re15, ''); 'uqy_fcb'.replace(re14, ''); 'uqy_fcb'.replace(re15, ''); 'uvag'.replace(re14, ''); 'uvag'.replace(re15, ''); s93[i].replace(re33, ''); 'yvfg'.replace(re14, ''); 'yvfg'.replace(re15, ''); 'at_bhgre'.replace(re30, ''); 'cnerag puebzr5 qbhoyr2 NU'.replace(re14, ''); 'cnerag puebzr5 qbhoyr2 NU'.replace(re15, ''); 'cnerag puebzr5 dhnq5 ps NU osyvax zbarl'.replace(re14, ''); 'cnerag puebzr5 dhnq5 ps NU osyvax zbarl'.replace(re15, ''); 'cnerag puebzr6 fvatyr1'.replace(re14, ''); 'cnerag puebzr6 fvatyr1'.replace(re15, ''); 'cb_qrs'.replace(re14, ''); 'cb_qrs'.replace(re15, ''); 'gnopbagrag'.replace(re14, ''); 'gnopbagrag'.replace(re15, ''); 'iv_svefg_gvzr'.replace(re30, ''); /(^|.)(ronl|qri-ehf3.wbg)(|fgberf|zbgbef|yvirnhpgvbaf|jvxv|rkcerff|punggre).(pbz(|.nh|.pa|.ux|.zl|.ft|.oe|.zk)|pb(.hx|.xe|.am)|pn|qr|se|vg|ay|or|ng|pu|vr|va|rf|cy|cu|fr)$/i.exec('cntrf.ronl.pbz'); re8.exec('144631658.0.10.1231364074'); re8.exec('144631658.1231364074.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.2294274870215848400.1231364074.1231364074.1231364074.1'); re8.exec('4413241q3660'); re8.exec('SbeprqRkcvengvba=633669357391353591'); re8.exec(str39); re8.exec(str40); re8.exec('AFP_zp_kkk-gdzogv_80=4413241q3660'); re8.exec('FrffvbaQQS2=p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7'); re8.exec('__hgzn=144631658.2294274870215848400.1231364074.1231364074.1231364074.1'); re8.exec('__hgzo=144631658.0.10.1231364074'); re8.exec('__hgzm=144631658.1231364074.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7'); re34.exec(s91[i]); re34.exec(s92[i]); } } var re64 = /\b[a-z]/g; var re65 = /^uggc:\/\//; var re66 = /(?:^|\s+)qvfnoyrq(?:\s+|$)/; var str41 = 'uggc://cebsvyr.zlfcnpr.pbz/Zbqhyrf/Nccyvpngvbaf/Cntrf/Pnainf.nfck'; function runBlock8() { for (var i = 0; i < 7; i++) { s21[i].match(/\d+/g); 'nsgre'.replace(re64, ''); 'orsber'.replace(re64, ''); 'obggbz'.replace(re64, ''); 'ohvygva_jrngure.kzy'.replace(re65, ''); 'ohggba'.replace(re37, ''); 'ohggba'.replace(re18, ''); 'qngrgvzr.kzy'.replace(re65, ''); 'uggc://eff.paa.pbz/eff/paa_gbcfgbevrf.eff'.replace(re65, ''); 'vachg'.replace(re37, ''); 'vachg'.replace(re18, ''); 'vafvqr'.replace(re64, ''); 'cbvagre'.replace(re27, ''); 'cbfvgvba'.replace(/[A-Z]/g, ''); 'gbc'.replace(re27, ''); 'gbc'.replace(re64, ''); 'hy'.replace(re37, ''); 'hy'.replace(re18, ''); str26.replace(re37, ''); str26.replace(re18, ''); 'lbhghor_vtbbtyr/i2/lbhghor.kzy'.replace(re65, ''); 'm-vaqrk'.replace(re27, ''); /#([\w-]+)/.exec(str26); re16.exec('urvtug'); re16.exec('znetvaGbc'); re16.exec('jvqgu'); re19.exec('gno0 svefg ba'); re19.exec('gno0 ba'); re19.exec('gno4 ynfg'); re19.exec('gno4'); re19.exec('gno5'); re19.exec('gno6'); re19.exec('gno7'); re19.exec('gno8'); /NqborNVE\/([^\s]*)/.exec(s15[i]); /NccyrJroXvg\/([^ ]*)/.exec(s15[i]); /XUGZY/gi.exec(s15[i]); /^(?:obql|ugzy)$/i.exec('YV'); re38.exec('ohggba'); re38.exec('vachg'); re38.exec('hy'); re38.exec(str26); /^(\w+|\*)/.exec(str26); /znp|jva|yvahk/i.exec('Jva32'); /eton?\([\d\s,]+\)/.exec('fgngvp'); } for (var i = 0; i < 6; i++) { ''.replace(/\r/g, ''); '/'.replace(re40, ''); '/'.replace(re10, ''); '/'.replace(re51, ''); '/'.replace(re52, ''); '/'.replace(re53, ''); '/'.replace(re39, ''); '/'.replace(re54, ''); 'uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/{0}?[NDO]&{1}&{2}&[NDR]'.replace(re63, ''); str41.replace(re12, ''); 'uggc://jjj.snprobbx.pbz/fepu.cuc'.replace(re23, ''); 'freivpr'.replace(re40, ''); 'freivpr'.replace(re41, ''); 'freivpr'.replace(re42, ''); 'freivpr'.replace(re43, ''); 'freivpr'.replace(re44, ''); 'freivpr'.replace(re45, ''); 'freivpr'.replace(re46, ''); 'freivpr'.replace(re47, ''); 'freivpr'.replace(re48, ''); /((ZFVR\s+([6-9]|\d\d)\.))/.exec(s15[i]); re66.exec(''); re50.exec('fryrpgrq'); re8.exec('8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn'); re8.exec('SbeprqRkcvengvba=633669340386893867'); re8.exec('VC=74.125.75.17'); re8.exec('FrffvbaQQS2=8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn'); /Xbadhrebe|Fnsnev|XUGZY/.exec(s15[i]); re13.exec(str41); re49.exec('unfsbphf'); } } var re67 = /zrah_byq/g; var str42 = 'FrffvbaQQS2=473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669325184628362&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var str43 = 'FrffvbaQQS2=473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29; __hgzm=144631658.1231364380.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.3931862196947939300.1231364380.1231364380.1231364380.1; __hgzo=144631658.0.10.1231364380; __hgzp=144631658; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669325184628362&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str44 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_vzntrf_wf&qg=1231364373088&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231364373088&punaary=svz_zlfcnpr_hfre-ivrj-pbzzragf%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Spbzzrag.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1158737789.1231364375&tn_fvq=1231364375&tn_uvq=415520832&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str45 = 'ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669325184628362&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str46 = 'ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669325184628362&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var re68 = /^([#.]?)((?:[\w\u0128-\uffff*_-]|\\.)*)/; var re69 = /\{1\}/g; var re70 = /\s+/; var re71 = /(\$\{4\})|(\$4\b)/g; var re72 = /(\$\{5\})|(\$5\b)/g; var re73 = /\{2\}/g; var re74 = /[^+>] [^+>]/; var re75 = /\bucpyv\s*=\s*([^;]*)/i; var re76 = /\bucuvqr\s*=\s*([^;]*)/i; var re77 = /\bucfie\s*=\s*([^;]*)/i; var re78 = /\bhfucjrn\s*=\s*([^;]*)/i; var re79 = /\bmvc\s*=\s*([^;]*)/i; var re80 = /^((?:[\w\u0128-\uffff*_-]|\\.)+)(#)((?:[\w\u0128-\uffff*_-]|\\.)+)/; var re81 = /^([>+~])\s*(\w*)/i; var re82 = /^>\s*((?:[\w\u0128-\uffff*_-]|\\.)+)/; var re83 = /^[\s[]?shapgvba/; var re84 = /v\/g.tvs#(.*)/i; var str47 = '#Zbq-Vasb-Vasb-WninFpevcgUvag'; var str48 = ',n.svryqOgaPnapry'; var str49 = 'FrffvbaQQS2=p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669357391353591&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_kkk-gdzogv_80=4413241q3660'; var str50 = 'FrffvbaQQS2=p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7; AFP_zp_kkk-gdzogv_80=4413241q3660; AFP_zp_kkk-aowb_80=4413235p3660; __hgzm=144631658.1231367708.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.2770915348920628700.1231367708.1231367708.1231367708.1; __hgzo=144631658.0.10.1231367708; __hgzp=144631658; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669357391353591&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str51 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231367691141&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231367691141&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Sjjj.zlfcnpr.pbz%2S&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=320757904.1231367694&tn_fvq=1231367694&tn_uvq=1758792003&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str52 = 'uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55332979829981?[NDO]&aqu=1&g=7%2S0%2S2009%2014%3N38%3N42%203%20480&af=zfacbegny&cntrAnzr=HF%20UCZFSGJ&t=uggc%3N%2S%2Sjjj.zfa.pbz%2S&f=1024k768&p=24&x=L&oj=994&ou=634&uc=A&{2}&[NDR]'; var str53 = 'cnerag puebzr6 fvatyr1 gno fryrpgrq ovaq qbhoyr2 ps'; var str54 = 'ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669357391353591&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str55 = 'ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669357391353591&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var str56 = 'ne;ng;nh;or;oe;pn;pu;py;pa;qr;qx;rf;sv;se;to;ux;vq;vr;va;vg;wc;xe;zk;zl;ay;ab;am;cu;cy;cg;eh;fr;ft;gu;ge;gj;mn;'; var str57 = 'ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886&GHVQ=1'; var str58 = 'ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886'; var str59 = 'ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886; mvc=m:94043|yn:37.4154|yb:-122.0585|p:HF|ue:1'; var str60 = 'ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886; mvc=m:94043|yn:37.4154|yb:-122.0585|p:HF'; var str61 = 'uggc://gx2.fgp.f-zfa.pbz/oe/uc/11/ra-hf/pff/v/g.tvs#uggc://gx2.fgo.f-zfa.pbz/v/29/4RQP4969777N048NPS4RRR3PO2S7S.wct'; var str62 = 'uggc://gx2.fgp.f-zfa.pbz/oe/uc/11/ra-hf/pff/v/g.tvs#uggc://gx2.fgo.f-zfa.pbz/v/OQ/63NP9O94NS5OQP1249Q9S1ROP7NS3.wct'; var str63 = 'zbmvyyn/5.0 (jvaqbjf; h; jvaqbjf ag 5.1; ra-hf) nccyrjroxvg/528.9 (xugzy, yvxr trpxb) puebzr/2.0.157.0 fnsnev/528.9'; var s94 = computeInputVariants(str42, 5); var s95 = computeInputVariants(str43, 5); var s96 = computeInputVariants(str44, 5); var s97 = computeInputVariants(str47, 5); var s98 = computeInputVariants(str48, 5); var s99 = computeInputVariants(str49, 5); var s100 = computeInputVariants(str50, 5); var s101 = computeInputVariants(str51, 5); var s102 = computeInputVariants(str52, 5); var s103 = computeInputVariants(str53, 5); function runBlock9() { for (var i = 0; i < 5; i++) { s94[i].split(re32); s95[i].split(re32); 'svz_zlfcnpr_hfre-ivrj-pbzzragf,svz_zlfcnpr_havgrq-fgngrf'.split(re20); s96[i].replace(re33, ''); 'zrah_arj zrah_arj_gbttyr zrah_gbttyr'.replace(re67, ''); 'zrah_byq zrah_byq_gbttyr zrah_gbttyr'.replace(re67, ''); re8.exec('102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98'); re8.exec('144631658.0.10.1231364380'); re8.exec('144631658.1231364380.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.3931862196947939300.1231364380.1231364380.1231364380.1'); re8.exec('441326q33660'); re8.exec('SbeprqRkcvengvba=633669341278771470'); re8.exec(str45); re8.exec(str46); re8.exec('AFP_zp_dfctwzssrwh-aowb_80=441326q33660'); re8.exec('FrffvbaQQS2=102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98'); re8.exec('__hgzn=144631658.3931862196947939300.1231364380.1231364380.1231364380.1'); re8.exec('__hgzo=144631658.0.10.1231364380'); re8.exec('__hgzm=144631658.1231364380.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); } for (var i = 0; i < 4; i++) { ' yvfg1'.replace(re14, ''); ' yvfg1'.replace(re15, ''); ' yvfg2'.replace(re14, ''); ' yvfg2'.replace(re15, ''); ' frneputebhc1'.replace(re14, ''); ' frneputebhc1'.replace(re15, ''); s97[i].replace(re68, ''); s97[i].replace(re18, ''); ''.replace(/&/g, ''); ''.replace(re35, ''); '(..-{0})(\|(\d+)|)'.replace(re63, ''); s98[i].replace(re18, ''); '//vzt.jro.qr/vij/FC/${cngu}/${anzr}/${inyhr}?gf=${abj}'.replace(re56, ''); '//vzt.jro.qr/vij/FC/tzk_uc/${anzr}/${inyhr}?gf=${abj}'.replace(/(\$\{anzr\})|(\$anzr\b)/g, ''); 'Jvaqbjf Yvir Ubgznvy{1}'.replace(re69, ''); '{0}{1}'.replace(re63, ''); '{1}'.replace(re69, ''); '{1}'.replace(re63, ''); 'Vzntrf'.replace(re15, ''); 'ZFA'.replace(re15, ''); 'Zncf'.replace(re15, ''); 'Zbq-Vasb-Vasb-WninFpevcgUvag'.replace(re39, ''); 'Arjf'.replace(re15, ''); s99[i].split(re32); s100[i].split(re32); 'Ivqrb'.replace(re15, ''); 'Jro'.replace(re15, ''); 'n'.replace(re39, ''); 'nwnkFgneg'.split(re70); 'nwnkFgbc'.split(re70); 'ovaq'.replace(re14, ''); 'ovaq'.replace(re15, ''); 'oevatf lbh zber. Zber fcnpr (5TO), zber frphevgl, fgvyy serr.'.replace(re63, ''); 'puvyq p1 svefg qrpx'.replace(re14, ''); 'puvyq p1 svefg qrpx'.replace(re15, ''); 'puvyq p1 svefg qbhoyr2'.replace(re14, ''); 'puvyq p1 svefg qbhoyr2'.replace(re15, ''); 'puvyq p2 ynfg'.replace(re14, ''); 'puvyq p2 ynfg'.replace(re15, ''); 'puvyq p2'.replace(re14, ''); 'puvyq p2'.replace(re15, ''); 'puvyq p3'.replace(re14, ''); 'puvyq p3'.replace(re15, ''); 'puvyq p4 ynfg'.replace(re14, ''); 'puvyq p4 ynfg'.replace(re15, ''); 'pbclevtug'.replace(re14, ''); 'pbclevtug'.replace(re15, ''); 'qZFAZR_1'.replace(re14, ''); 'qZFAZR_1'.replace(re15, ''); 'qbhoyr2 ps'.replace(re14, ''); 'qbhoyr2 ps'.replace(re15, ''); 'qbhoyr2'.replace(re14, ''); 'qbhoyr2'.replace(re15, ''); 'uqy_arj'.replace(re14, ''); 'uqy_arj'.replace(re15, ''); 'uc_fubccvatobk'.replace(re30, ''); 'ugzy%2Rvq'.replace(re29, ''); 'ugzy%2Rvq'.replace(re30, ''); s101[i].replace(re33, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/cebgbglcr.wf${4}${5}'.replace(re71, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/cebgbglcr.wf${5}'.replace(re72, ''); s102[i].replace(re73, ''); 'uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55332979829981?[NDO]&{1}&{2}&[NDR]'.replace(re69, ''); 'vztZFSG'.replace(re14, ''); 'vztZFSG'.replace(re15, ''); 'zfasbbg1 ps'.replace(re14, ''); 'zfasbbg1 ps'.replace(re15, ''); s103[i].replace(re14, ''); s103[i].replace(re15, ''); 'cnerag puebzr6 fvatyr1 gno fryrpgrq ovaq'.replace(re14, ''); 'cnerag puebzr6 fvatyr1 gno fryrpgrq ovaq'.replace(re15, ''); 'cevznel'.replace(re14, ''); 'cevznel'.replace(re15, ''); 'erpgnatyr'.replace(re30, ''); 'frpbaqnel'.replace(re14, ''); 'frpbaqnel'.replace(re15, ''); 'haybnq'.split(re70); '{0}{1}1'.replace(re63, ''); '|{1}1'.replace(re69, ''); /(..-HF)(\|(\d+)|)/i.exec('xb-xe,ra-va,gu-gu'); re4.exec('/ZlFcnprNccf/NccPnainf,45000012'); re8.exec('144631658.0.10.1231367708'); re8.exec('144631658.1231367708.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.2770915348920628700.1231367708.1231367708.1231367708.1'); re8.exec('4413235p3660'); re8.exec('441327q73660'); re8.exec('9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473'); re8.exec('SbeprqRkcvengvba=633669350559478880'); re8.exec(str54); re8.exec(str55); re8.exec('AFP_zp_dfctwzs-aowb_80=441327q73660'); re8.exec('AFP_zp_kkk-aowb_80=4413235p3660'); re8.exec('FrffvbaQQS2=9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473'); re8.exec('__hgzn=144631658.2770915348920628700.1231367708.1231367708.1231367708.1'); re8.exec('__hgzo=144631658.0.10.1231367708'); re8.exec('__hgzm=144631658.1231367708.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re34.exec(s99[i]); re34.exec(s100[i]); /ZFVR\s+5[.]01/.exec(s15[i]); /HF(?=;)/i.exec(str56); re74.exec(s97[i]); re28.exec('svefg npgvir svefgNpgvir'); re28.exec('ynfg'); /\bp:(..)/i.exec('m:94043|yn:37.4154|yb:-122.0585|p:HF'); re75.exec(str57); re75.exec(str58); re76.exec(str57); re76.exec(str58); re77.exec(str57); re77.exec(str58); /\bhfucce\s*=\s*([^;]*)/i.exec(str59); re78.exec(str57); re78.exec(str58); /\bjci\s*=\s*([^;]*)/i.exec(str59); re79.exec(str58); re79.exec(str60); re79.exec(str59); /\|p:([a-z]{2})/i.exec('m:94043|yn:37.4154|yb:-122.0585|p:HF|ue:1'); re80.exec(s97[i]); re61.exec('cebgbglcr.wf'); re68.exec(s97[i]); re81.exec(s97[i]); re82.exec(s97[i]); /^Fubpxjnir Synfu (\d)/.exec(s21[i]); /^Fubpxjnir Synfu (\d+)/.exec(s21[i]); re83.exec('[bowrpg tybony]'); re62.exec(s97[i]); re84.exec(str61); re84.exec(str62); /jroxvg/.exec(str63); } } var re85 = /eaq_zbqobkva/; var str64 = '1231365729213'; var str65 = '74.125.75.3-1057165600.29978900'; var str66 = '74.125.75.3-1057165600.29978900.1231365730214'; var str67 = 'Frnepu%20Zvpebfbsg.pbz'; var str68 = 'FrffvbaQQS2=8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn; ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669340386893867&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var str69 = 'FrffvbaQQS2=8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn; __hgzm=144631658.1231365779.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.1877536177953918500.1231365779.1231365779.1231365779.1; __hgzo=144631658.0.10.1231365779; __hgzp=144631658; ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669340386893867&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str70 = 'I=3%26THVQ=757q3ss871q44o7o805n8113n5p72q52'; var str71 = 'I=3&THVQ=757q3ss871q44o7o805n8113n5p72q52'; var str72 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231365765292&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231365765292&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Sohyyrgvaf.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1579793869.1231365768&tn_fvq=1231365768&tn_uvq=2056210897&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str73 = 'frnepu.zvpebfbsg.pbz'; var str74 = 'frnepu.zvpebfbsg.pbz/'; var str75 = 'ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669340386893867&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str76 = 'ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669340386893867&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; function runBlock10() { for (var i = 0; i < 3; i++) { '%3Szxg=ra-HF'.replace(re39, ''); '-8'.replace(re40, ''); '-8'.replace(re10, ''); '-8'.replace(re51, ''); '-8'.replace(re52, ''); '-8'.replace(re53, ''); '-8'.replace(re39, ''); '-8'.replace(re54, ''); '1.5'.replace(re40, ''); '1.5'.replace(re10, ''); '1.5'.replace(re51, ''); '1.5'.replace(re52, ''); '1.5'.replace(re53, ''); '1.5'.replace(re39, ''); '1.5'.replace(re54, ''); '1024k768'.replace(re40, ''); '1024k768'.replace(re10, ''); '1024k768'.replace(re51, ''); '1024k768'.replace(re52, ''); '1024k768'.replace(re53, ''); '1024k768'.replace(re39, ''); '1024k768'.replace(re54, ''); str64.replace(re40, ''); str64.replace(re10, ''); str64.replace(re51, ''); str64.replace(re52, ''); str64.replace(re53, ''); str64.replace(re39, ''); str64.replace(re54, ''); '14'.replace(re40, ''); '14'.replace(re10, ''); '14'.replace(re51, ''); '14'.replace(re52, ''); '14'.replace(re53, ''); '14'.replace(re39, ''); '14'.replace(re54, ''); '24'.replace(re40, ''); '24'.replace(re10, ''); '24'.replace(re51, ''); '24'.replace(re52, ''); '24'.replace(re53, ''); '24'.replace(re39, ''); '24'.replace(re54, ''); str65.replace(re40, ''); str65.replace(re10, ''); str65.replace(re51, ''); str65.replace(re52, ''); str65.replace(re53, ''); str65.replace(re39, ''); str65.replace(re54, ''); str66.replace(re40, ''); str66.replace(re10, ''); str66.replace(re51, ''); str66.replace(re52, ''); str66.replace(re53, ''); str66.replace(re39, ''); str66.replace(re54, ''); '9.0'.replace(re40, ''); '9.0'.replace(re10, ''); '9.0'.replace(re51, ''); '9.0'.replace(re52, ''); '9.0'.replace(re53, ''); '9.0'.replace(re39, ''); '9.0'.replace(re54, ''); '994k634'.replace(re40, ''); '994k634'.replace(re10, ''); '994k634'.replace(re51, ''); '994k634'.replace(re52, ''); '994k634'.replace(re53, ''); '994k634'.replace(re39, ''); '994k634'.replace(re54, ''); '?zxg=ra-HF'.replace(re40, ''); '?zxg=ra-HF'.replace(re10, ''); '?zxg=ra-HF'.replace(re51, ''); '?zxg=ra-HF'.replace(re52, ''); '?zxg=ra-HF'.replace(re53, ''); '?zxg=ra-HF'.replace(re54, ''); 'PAA.pbz'.replace(re25, ''); 'PAA.pbz'.replace(re12, ''); 'PAA.pbz'.replace(re39, ''); 'Qngr & Gvzr'.replace(re25, ''); 'Qngr & Gvzr'.replace(re12, ''); 'Qngr & Gvzr'.replace(re39, ''); 'Frnepu Zvpebfbsg.pbz'.replace(re40, ''); 'Frnepu Zvpebfbsg.pbz'.replace(re54, ''); str67.replace(re10, ''); str67.replace(re51, ''); str67.replace(re52, ''); str67.replace(re53, ''); str67.replace(re39, ''); str68.split(re32); str69.split(re32); str70.replace(re52, ''); str70.replace(re53, ''); str70.replace(re39, ''); str71.replace(re40, ''); str71.replace(re10, ''); str71.replace(re51, ''); str71.replace(re54, ''); 'Jrngure'.replace(re25, ''); 'Jrngure'.replace(re12, ''); 'Jrngure'.replace(re39, ''); 'LbhGhor'.replace(re25, ''); 'LbhGhor'.replace(re12, ''); 'LbhGhor'.replace(re39, ''); str72.replace(re33, ''); 'erzbgr_vsenzr_1'.replace(/^erzbgr_vsenzr_/, ''); str73.replace(re40, ''); str73.replace(re10, ''); str73.replace(re51, ''); str73.replace(re52, ''); str73.replace(re53, ''); str73.replace(re39, ''); str73.replace(re54, ''); str74.replace(re40, ''); str74.replace(re10, ''); str74.replace(re51, ''); str74.replace(re52, ''); str74.replace(re53, ''); str74.replace(re39, ''); str74.replace(re54, ''); 'lhv-h'.replace(/\-/g, ''); re9.exec('p'); re9.exec('qz p'); re9.exec('zbqynory'); re9.exec('lhv-h svefg'); re8.exec('144631658.0.10.1231365779'); re8.exec('144631658.1231365779.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.1877536177953918500.1231365779.1231365779.1231365779.1'); re8.exec(str75); re8.exec(str76); re8.exec('__hgzn=144631658.1877536177953918500.1231365779.1231365779.1231365779.1'); re8.exec('__hgzo=144631658.0.10.1231365779'); re8.exec('__hgzm=144631658.1231365779.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re34.exec(str68); re34.exec(str69); /^$/.exec(''); re31.exec('qr'); /^znk\d+$/.exec(''); /^zva\d+$/.exec(''); /^erfgber$/.exec(''); re85.exec('zbqobkva zbqobk_abcnqqvat '); re85.exec('zbqgvgyr'); re85.exec('eaq_zbqobkva '); re85.exec('eaq_zbqgvgyr '); /frpgvba\d+_pbagragf/.exec('obggbz_ani'); } } var re86 = /;\s*/; var re87 = /(\$\{inyhr\})|(\$inyhr\b)/g; var re88 = /(\$\{abj\})|(\$abj\b)/g; var re89 = /\s+$/; var re90 = /^\s+/; var re91 = /(\\\"|\x00-|\x1f|\x7f-|\x9f|\u00ad|\u0600-|\u0604|\u070f|\u17b4|\u17b5|\u200c-|\u200f|\u2028-|\u202f|\u2060-|\u206f|\ufeff|\ufff0-|\uffff)/g; var re92 = /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/; var re93 = /^([:.#]*)((?:[\w\u0128-\uffff*_-]|\\.)+)/; var re94 = /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/; var str77 = '#fubhgobk .pybfr'; var str78 = 'FrffvbaQQS2=102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669341278771470&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_dfctwzssrwh-aowb_80=441326q33660'; var str79 = 'FrffvbaQQS2=102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98; AFP_zp_dfctwzssrwh-aowb_80=441326q33660; __hgzm=144631658.1231365869.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.1670816052019209000.1231365869.1231365869.1231365869.1; __hgzo=144631658.0.10.1231365869; __hgzp=144631658; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669341278771470&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str80 = 'FrffvbaQQS2=9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669350559478880&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_dfctwzs-aowb_80=441327q73660'; var str81 = 'FrffvbaQQS2=9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473; AFP_zp_dfctwzs-aowb_80=441327q73660; __hgzm=144631658.1231367054.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.1796080716621419500.1231367054.1231367054.1231367054.1; __hgzo=144631658.0.10.1231367054; __hgzp=144631658; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669350559478880&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str82 = '[glcr=fhozvg]'; var str83 = 'n.svryqOga,n.svryqOgaPnapry'; var str84 = 'n.svryqOgaPnapry'; var str85 = 'oyvpxchaxg'; var str86 = 'qvi.bow-nppbeqvba qg'; var str87 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_nccf_wf&qg=1231367052227&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231367052227&punaary=svz_zlfcnpr_nccf-pnainf%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Scebsvyr.zlfcnpr.pbz%2SZbqhyrf%2SNccyvpngvbaf%2SCntrf%2SPnainf.nfck&nq_glcr=grkg&rvq=6083027&rn=0&sez=1&tn_ivq=716357910.1231367056&tn_fvq=1231367056&tn_uvq=1387206491&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str88 = 'uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231365851658&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231365851658&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Scebsvyrrqvg.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1979828129.1231365855&tn_fvq=1231365855&tn_uvq=2085229649&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22'; var str89 = 'uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55023338617756?[NDO]&aqu=1&g=7%2S0%2S2009%2014%3N12%3N47%203%20480&af=zfacbegny&cntrAnzr=HF%20UCZFSGJ&t=uggc%3N%2S%2Sjjj.zfa.pbz%2S&f=0k0&p=43835816&x=A&oj=994&ou=634&uc=A&{2}&[NDR]'; var str90 = 'zrgn[anzr=nwnkHey]'; var str91 = 'anpuevpugra'; var str92 = 'b oS={\'oT\':1.1};x $8n(B){z(B!=o9)};x $S(B){O(!$8n(B))z A;O(B.4L)z\'T\';b S=7t B;O(S==\'2P\'&&B.p4){23(B.7f){12 1:z\'T\';12 3:z/\S/.2g(B.8M)?\'ox\':\'oh\'}}O(S==\'2P\'||S==\'x\'){23(B.nE){12 2V:z\'1O\';12 7I:z\'5a\';12 18:z\'4B\'}O(7t B.I==\'4F\'){O(B.3u)z\'pG\';O(B.8e)z\'1p\'}}z S};x $2p(){b 4E={};Z(b v=0;v<1p.I;v++){Z(b X 1o 1p[v]){b nc=1p[v][X];b 6E=4E[X];O(6E&&$S(nc)==\'2P\'&&$S(6E)==\'2P\')4E[X]=$2p(6E,nc);17 4E[X]=nc}}z 4E};b $E=7p.E=x(){b 1d=1p;O(!1d[1])1d=[p,1d[0]];Z(b X 1o 1d[1])1d[0][X]=1d[1][X];z 1d[0]};b $4D=7p.pJ=x(){Z(b v=0,y=1p.I;v-1:p.3F(2R)>-1},nX:x(){z p.3y(/([.*+?^${}()|[\]\/\\])/t,\'\\$1\')}});2V.E({5V:x(1O){O(p.I<3)z A;O(p.I==4&&p[3]==0&&!1O)z\'p5\';b 3P=[];Z(b v=0;v<3;v++){b 52=(p[v]-0).4h(16);3P.1x((52.I==1)?\'0\'+52:52)}z 1O?3P:\'#\'+3P.2u(\'\')},5U:x(1O){O(p.I!=3)z A;b 1i=[];Z(b v=0;v<3;v++){1i.1x(5K((p[v].I==1)?p[v]+p[v]:p[v],16))}z 1O?1i:\'1i(\'+1i.2u(\',\')+\')\'}});7F.E({3n:x(P){b J=p;P=$2p({\'L\':J,\'V\':A,\'1p\':1S,\'2x\':A,\'4s\':A,\'6W\':A},P);O($2O(P.1p)&&$S(P.1p)!=\'1O\')P.1p=[P.1p];z x(V){b 1d;O(P.V){V=V||H.V;1d=[(P.V===1r)?V:Y P.V(V)];O(P.1p)1d.E(P.1p)}17 1d=P.1p||1p;b 3C=x(){z J.3H($5S(P'; var str93 = 'hagreunyghat'; var str94 = 'ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669341278771470&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str95 = 'ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669350559478880&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q'; var str96 = 'ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669341278771470&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var str97 = 'ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669350559478880&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R='; var str98 = 'shapgvba (){Cuk.Nccyvpngvba.Frghc.Pber();Cuk.Nccyvpngvba.Frghc.Nwnk();Cuk.Nccyvpngvba.Frghc.Synfu();Cuk.Nccyvpngvba.Frghc.Zbqhyrf()}'; function runBlock11() { for (var i = 0; i < 2; i++) { ' .pybfr'.replace(re18, ''); ' n.svryqOgaPnapry'.replace(re18, ''); ' qg'.replace(re18, ''); str77.replace(re68, ''); str77.replace(re18, ''); ''.replace(re39, ''); ''.replace(/^/, ''); ''.split(re86); '*'.replace(re39, ''); '*'.replace(re68, ''); '*'.replace(re18, ''); '.pybfr'.replace(re68, ''); '.pybfr'.replace(re18, ''); '//vzt.jro.qr/vij/FC/tzk_uc/fperra/${inyhr}?gf=${abj}'.replace(re87, ''); '//vzt.jro.qr/vij/FC/tzk_uc/fperra/1024?gf=${abj}'.replace(re88, ''); '//vzt.jro.qr/vij/FC/tzk_uc/jvafvmr/${inyhr}?gf=${abj}'.replace(re87, ''); '//vzt.jro.qr/vij/FC/tzk_uc/jvafvmr/992/608?gf=${abj}'.replace(re88, ''); '300k120'.replace(re30, ''); '300k250'.replace(re30, ''); '310k120'.replace(re30, ''); '310k170'.replace(re30, ''); '310k250'.replace(re30, ''); '9.0 e115'.replace(/^.*\.(.*)\s.*$/, ''); 'Nppbeqvba'.replace(re2, ''); 'Nxghryy\x0a'.replace(re89, ''); 'Nxghryy\x0a'.replace(re90, ''); 'Nccyvpngvba'.replace(re2, ''); 'Oyvpxchaxg\x0a'.replace(re89, ''); 'Oyvpxchaxg\x0a'.replace(re90, ''); 'Svanamra\x0a'.replace(re89, ''); 'Svanamra\x0a'.replace(re90, ''); 'Tnzrf\x0a'.replace(re89, ''); 'Tnzrf\x0a'.replace(re90, ''); 'Ubebfxbc\x0a'.replace(re89, ''); 'Ubebfxbc\x0a'.replace(re90, ''); 'Xvab\x0a'.replace(re89, ''); 'Xvab\x0a'.replace(re90, ''); 'Zbqhyrf'.replace(re2, ''); 'Zhfvx\x0a'.replace(re89, ''); 'Zhfvx\x0a'.replace(re90, ''); 'Anpuevpugra\x0a'.replace(re89, ''); 'Anpuevpugra\x0a'.replace(re90, ''); 'Cuk'.replace(re2, ''); 'ErdhrfgSvavfu'.split(re70); 'ErdhrfgSvavfu.NWNK.Cuk'.split(re70); 'Ebhgr\x0a'.replace(re89, ''); 'Ebhgr\x0a'.replace(re90, ''); str78.split(re32); str79.split(re32); str80.split(re32); str81.split(re32); 'Fcbeg\x0a'.replace(re89, ''); 'Fcbeg\x0a'.replace(re90, ''); 'GI-Fcbg\x0a'.replace(re89, ''); 'GI-Fcbg\x0a'.replace(re90, ''); 'Gbhe\x0a'.replace(re89, ''); 'Gbhe\x0a'.replace(re90, ''); 'Hagreunyghat\x0a'.replace(re89, ''); 'Hagreunyghat\x0a'.replace(re90, ''); 'Ivqrb\x0a'.replace(re89, ''); 'Ivqrb\x0a'.replace(re90, ''); 'Jrggre\x0a'.replace(re89, ''); 'Jrggre\x0a'.replace(re90, ''); str82.replace(re68, ''); str82.replace(re18, ''); str83.replace(re68, ''); str83.replace(re18, ''); str84.replace(re68, ''); str84.replace(re18, ''); 'nqiFreivprObk'.replace(re30, ''); 'nqiFubccvatObk'.replace(re30, ''); 'nwnk'.replace(re39, ''); 'nxghryy'.replace(re40, ''); 'nxghryy'.replace(re41, ''); 'nxghryy'.replace(re42, ''); 'nxghryy'.replace(re43, ''); 'nxghryy'.replace(re44, ''); 'nxghryy'.replace(re45, ''); 'nxghryy'.replace(re46, ''); 'nxghryy'.replace(re47, ''); 'nxghryy'.replace(re48, ''); str85.replace(re40, ''); str85.replace(re41, ''); str85.replace(re42, ''); str85.replace(re43, ''); str85.replace(re44, ''); str85.replace(re45, ''); str85.replace(re46, ''); str85.replace(re47, ''); str85.replace(re48, ''); 'pngrtbel'.replace(re29, ''); 'pngrtbel'.replace(re30, ''); 'pybfr'.replace(re39, ''); 'qvi'.replace(re39, ''); str86.replace(re68, ''); str86.replace(re18, ''); 'qg'.replace(re39, ''); 'qg'.replace(re68, ''); 'qg'.replace(re18, ''); 'rzorq'.replace(re39, ''); 'rzorq'.replace(re68, ''); 'rzorq'.replace(re18, ''); 'svryqOga'.replace(re39, ''); 'svryqOgaPnapry'.replace(re39, ''); 'svz_zlfcnpr_nccf-pnainf,svz_zlfcnpr_havgrq-fgngrf'.split(re20); 'svanamra'.replace(re40, ''); 'svanamra'.replace(re41, ''); 'svanamra'.replace(re42, ''); 'svanamra'.replace(re43, ''); 'svanamra'.replace(re44, ''); 'svanamra'.replace(re45, ''); 'svanamra'.replace(re46, ''); 'svanamra'.replace(re47, ''); 'svanamra'.replace(re48, ''); 'sbphf'.split(re70); 'sbphf.gno sbphfva.gno'.split(re70); 'sbphfva'.split(re70); 'sbez'.replace(re39, ''); 'sbez.nwnk'.replace(re68, ''); 'sbez.nwnk'.replace(re18, ''); 'tnzrf'.replace(re40, ''); 'tnzrf'.replace(re41, ''); 'tnzrf'.replace(re42, ''); 'tnzrf'.replace(re43, ''); 'tnzrf'.replace(re44, ''); 'tnzrf'.replace(re45, ''); 'tnzrf'.replace(re46, ''); 'tnzrf'.replace(re47, ''); 'tnzrf'.replace(re48, ''); 'ubzrcntr'.replace(re30, ''); 'ubebfxbc'.replace(re40, ''); 'ubebfxbc'.replace(re41, ''); 'ubebfxbc'.replace(re42, ''); 'ubebfxbc'.replace(re43, ''); 'ubebfxbc'.replace(re44, ''); 'ubebfxbc'.replace(re45, ''); 'ubebfxbc'.replace(re46, ''); 'ubebfxbc'.replace(re47, ''); 'ubebfxbc'.replace(re48, ''); 'uc_cebzbobk_ugzy%2Puc_cebzbobk_vzt'.replace(re30, ''); 'uc_erpgnatyr'.replace(re30, ''); str87.replace(re33, ''); str88.replace(re33, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf${4}${5}'.replace(re71, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf${5}'.replace(re72, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/qlaYvo.wf${4}${5}'.replace(re71, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/qlaYvo.wf${5}'.replace(re72, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/rssrpgYvo.wf${4}${5}'.replace(re71, ''); 'uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/rssrpgYvo.wf${5}'.replace(re72, ''); str89.replace(re73, ''); 'uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55023338617756?[NDO]&{1}&{2}&[NDR]'.replace(re69, ''); str6.replace(re23, ''); 'xvab'.replace(re40, ''); 'xvab'.replace(re41, ''); 'xvab'.replace(re42, ''); 'xvab'.replace(re43, ''); 'xvab'.replace(re44, ''); 'xvab'.replace(re45, ''); 'xvab'.replace(re46, ''); 'xvab'.replace(re47, ''); 'xvab'.replace(re48, ''); 'ybnq'.split(re70); 'zrqvnzbqgno lhv-anifrg lhv-anifrg-gbc'.replace(re18, ''); 'zrgn'.replace(re39, ''); str90.replace(re68, ''); str90.replace(re18, ''); 'zbhfrzbir'.split(re70); 'zbhfrzbir.gno'.split(re70); str63.replace(/^.*jroxvg\/(\d+(\.\d+)?).*$/, ''); 'zhfvx'.replace(re40, ''); 'zhfvx'.replace(re41, ''); 'zhfvx'.replace(re42, ''); 'zhfvx'.replace(re43, ''); 'zhfvx'.replace(re44, ''); 'zhfvx'.replace(re45, ''); 'zhfvx'.replace(re46, ''); 'zhfvx'.replace(re47, ''); 'zhfvx'.replace(re48, ''); 'zlfcnpr_nccf_pnainf'.replace(re52, ''); str91.replace(re40, ''); str91.replace(re41, ''); str91.replace(re42, ''); str91.replace(re43, ''); str91.replace(re44, ''); str91.replace(re45, ''); str91.replace(re46, ''); str91.replace(re47, ''); str91.replace(re48, ''); 'anzr'.replace(re39, ''); str92.replace(/\b\w+\b/g, ''); 'bow-nppbeqvba'.replace(re39, ''); 'bowrpg'.replace(re39, ''); 'bowrpg'.replace(re68, ''); 'bowrpg'.replace(re18, ''); 'cnenzf%2Rfglyrf'.replace(re29, ''); 'cnenzf%2Rfglyrf'.replace(re30, ''); 'cbchc'.replace(re30, ''); 'ebhgr'.replace(re40, ''); 'ebhgr'.replace(re41, ''); 'ebhgr'.replace(re42, ''); 'ebhgr'.replace(re43, ''); 'ebhgr'.replace(re44, ''); 'ebhgr'.replace(re45, ''); 'ebhgr'.replace(re46, ''); 'ebhgr'.replace(re47, ''); 'ebhgr'.replace(re48, ''); 'freivprobk_uc'.replace(re30, ''); 'fubccvatobk_uc'.replace(re30, ''); 'fubhgobk'.replace(re39, ''); 'fcbeg'.replace(re40, ''); 'fcbeg'.replace(re41, ''); 'fcbeg'.replace(re42, ''); 'fcbeg'.replace(re43, ''); 'fcbeg'.replace(re44, ''); 'fcbeg'.replace(re45, ''); 'fcbeg'.replace(re46, ''); 'fcbeg'.replace(re47, ''); 'fcbeg'.replace(re48, ''); 'gbhe'.replace(re40, ''); 'gbhe'.replace(re41, ''); 'gbhe'.replace(re42, ''); 'gbhe'.replace(re43, ''); 'gbhe'.replace(re44, ''); 'gbhe'.replace(re45, ''); 'gbhe'.replace(re46, ''); 'gbhe'.replace(re47, ''); 'gbhe'.replace(re48, ''); 'gi-fcbg'.replace(re40, ''); 'gi-fcbg'.replace(re41, ''); 'gi-fcbg'.replace(re42, ''); 'gi-fcbg'.replace(re43, ''); 'gi-fcbg'.replace(re44, ''); 'gi-fcbg'.replace(re45, ''); 'gi-fcbg'.replace(re46, ''); 'gi-fcbg'.replace(re47, ''); 'gi-fcbg'.replace(re48, ''); 'glcr'.replace(re39, ''); 'haqrsvarq'.replace(/\//g, ''); str93.replace(re40, ''); str93.replace(re41, ''); str93.replace(re42, ''); str93.replace(re43, ''); str93.replace(re44, ''); str93.replace(re45, ''); str93.replace(re46, ''); str93.replace(re47, ''); str93.replace(re48, ''); 'ivqrb'.replace(re40, ''); 'ivqrb'.replace(re41, ''); 'ivqrb'.replace(re42, ''); 'ivqrb'.replace(re43, ''); 'ivqrb'.replace(re44, ''); 'ivqrb'.replace(re45, ''); 'ivqrb'.replace(re46, ''); 'ivqrb'.replace(re47, ''); 'ivqrb'.replace(re48, ''); 'ivfvgf=1'.split(re86); 'jrggre'.replace(re40, ''); 'jrggre'.replace(re41, ''); 'jrggre'.replace(re42, ''); 'jrggre'.replace(re43, ''); 'jrggre'.replace(re44, ''); 'jrggre'.replace(re45, ''); 'jrggre'.replace(re46, ''); 'jrggre'.replace(re47, ''); 'jrggre'.replace(re48, ''); /#[a-z0-9]+$/i.exec('uggc://jjj.fpuhryreim.arg/Qrsnhyg'); re66.exec('fryrpgrq'); /(?:^|\s+)lhv-ani(?:\s+|$)/.exec('sff lhv-ani'); /(?:^|\s+)lhv-anifrg(?:\s+|$)/.exec('zrqvnzbqgno lhv-anifrg'); /(?:^|\s+)lhv-anifrg-gbc(?:\s+|$)/.exec('zrqvnzbqgno lhv-anifrg'); re91.exec('GnoThvq'); re91.exec('thvq'); /(pbzcngvoyr|jroxvg)/.exec(str63); /.+(?:ei|vg|en|vr)[\/: ]([\d.]+)/.exec(str63); re8.exec('144631658.0.10.1231365869'); re8.exec('144631658.0.10.1231367054'); re8.exec('144631658.1231365869.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.1231367054.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('144631658.1670816052019209000.1231365869.1231365869.1231365869.1'); re8.exec('144631658.1796080716621419500.1231367054.1231367054.1231367054.1'); re8.exec(str94); re8.exec(str95); re8.exec(str96); re8.exec(str97); re8.exec('__hgzn=144631658.1670816052019209000.1231365869.1231365869.1231365869.1'); re8.exec('__hgzn=144631658.1796080716621419500.1231367054.1231367054.1231367054.1'); re8.exec('__hgzo=144631658.0.10.1231365869'); re8.exec('__hgzo=144631658.0.10.1231367054'); re8.exec('__hgzm=144631658.1231365869.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re8.exec('__hgzm=144631658.1231367054.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)'); re34.exec(str78); re34.exec(str79); re34.exec(str81); re74.exec(str77); re74.exec('*'); re74.exec(str82); re74.exec(str83); re74.exec(str86); re74.exec('rzorq'); re74.exec('sbez.nwnk'); re74.exec(str90); re74.exec('bowrpg'); /\/onfr.wf(\?.+)?$/.exec('/uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf'); re28.exec('uvag ynfgUvag ynfg'); re75.exec(''); re76.exec(''); re77.exec(''); re78.exec(''); re80.exec(str77); re80.exec('*'); re80.exec('.pybfr'); re80.exec(str82); re80.exec(str83); re80.exec(str84); re80.exec(str86); re80.exec('qg'); re80.exec('rzorq'); re80.exec('sbez.nwnk'); re80.exec(str90); re80.exec('bowrpg'); re61.exec('qlaYvo.wf'); re61.exec('rssrpgYvo.wf'); re61.exec('uggc://jjj.tzk.arg/qr/?fgnghf=uvajrvf'); re92.exec(' .pybfr'); re92.exec(' n.svryqOgaPnapry'); re92.exec(' qg'); re92.exec(str48); re92.exec('.nwnk'); re92.exec('.svryqOga,n.svryqOgaPnapry'); re92.exec('.svryqOgaPnapry'); re92.exec('.bow-nppbeqvba qg'); re68.exec(str77); re68.exec('*'); re68.exec('.pybfr'); re68.exec(str82); re68.exec(str83); re68.exec(str84); re68.exec(str86); re68.exec('qg'); re68.exec('rzorq'); re68.exec('sbez.nwnk'); re68.exec(str90); re68.exec('bowrpg'); re93.exec(' .pybfr'); re93.exec(' n.svryqOgaPnapry'); re93.exec(' qg'); re93.exec(str48); re93.exec('.nwnk'); re93.exec('.svryqOga,n.svryqOgaPnapry'); re93.exec('.svryqOgaPnapry'); re93.exec('.bow-nppbeqvba qg'); re81.exec(str77); re81.exec('*'); re81.exec(str48); re81.exec('.pybfr'); re81.exec(str82); re81.exec(str83); re81.exec(str84); re81.exec(str86); re81.exec('qg'); re81.exec('rzorq'); re81.exec('sbez.nwnk'); re81.exec(str90); re81.exec('bowrpg'); re94.exec(' .pybfr'); re94.exec(' n.svryqOgaPnapry'); re94.exec(' qg'); re94.exec(str48); re94.exec('.nwnk'); re94.exec('.svryqOga,n.svryqOgaPnapry'); re94.exec('.svryqOgaPnapry'); re94.exec('.bow-nppbeqvba qg'); re94.exec('[anzr=nwnkHey]'); re94.exec(str82); re31.exec('rf'); re31.exec('wn'); re82.exec(str77); re82.exec('*'); re82.exec(str48); re82.exec('.pybfr'); re82.exec(str82); re82.exec(str83); re82.exec(str84); re82.exec(str86); re82.exec('qg'); re82.exec('rzorq'); re82.exec('sbez.nwnk'); re82.exec(str90); re82.exec('bowrpg'); re83.exec(str98); re83.exec('shapgvba sbphf() { [angvir pbqr] }'); re62.exec('#Ybtva'); re62.exec('#Ybtva_cnffjbeq'); re62.exec(str77); re62.exec('#fubhgobkWf'); re62.exec('#fubhgobkWfReebe'); re62.exec('#fubhgobkWfFhpprff'); re62.exec('*'); re62.exec(str82); re62.exec(str83); re62.exec(str86); re62.exec('rzorq'); re62.exec('sbez.nwnk'); re62.exec(str90); re62.exec('bowrpg'); re49.exec('pbagrag'); re24.exec(str6); /xbadhrebe/.exec(str63); /znp/.exec('jva32'); /zbmvyyn/.exec(str63); /zfvr/.exec(str63); /ag\s5\.1/.exec(str63); /bcren/.exec(str63); /fnsnev/.exec(str63); /jva/.exec('jva32'); /jvaqbjf/.exec(str63); } } function run() { for (var i = 0; i < 5; i++) { runBlock0(); runBlock1(); runBlock2(); runBlock3(); runBlock4(); runBlock5(); runBlock6(); runBlock7(); runBlock8(); runBlock9(); runBlock10(); runBlock11(); } } this.run = run; } /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/scripts/v8-benches/richards.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; // Copyright 2006-2008 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // This is a JavaScript implementation of the Richards // benchmark from: // // http://www.cl.cam.ac.uk/~mr10/Bench.html // // The benchmark was originally implemented in BCPL by // Martin Richards. /** * The Richards benchmark simulates the task dispatcher of an * operating system. **/ function runRichards() { var scheduler = new Scheduler(); scheduler.addIdleTask(ID_IDLE, 0, null, COUNT); var queue = new Packet(null, ID_WORKER, KIND_WORK); queue = new Packet(queue, ID_WORKER, KIND_WORK); scheduler.addWorkerTask(ID_WORKER, 1000, queue); queue = new Packet(null, ID_DEVICE_A, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue); queue = new Packet(null, ID_DEVICE_B, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue); scheduler.addDeviceTask(ID_DEVICE_A, 4000, null); scheduler.addDeviceTask(ID_DEVICE_B, 5000, null); scheduler.schedule(); if (scheduler.queueCount != EXPECTED_QUEUE_COUNT || scheduler.holdCount != EXPECTED_HOLD_COUNT) { var msg = "Error during execution: queueCount = " + scheduler.queueCount + ", holdCount = " + scheduler.holdCount + "."; throw new Error(msg); } } var COUNT = 1000; /** * These two constants specify how many times a packet is queued and * how many times a task is put on hold in a correct run of richards. * They don't have any meaning a such but are characteristic of a * correct run so if the actual queue or hold count is different from * the expected there must be a bug in the implementation. **/ var EXPECTED_QUEUE_COUNT = 2322; var EXPECTED_HOLD_COUNT = 928; /** * A scheduler can be used to schedule a set of tasks based on their relative * priorities. Scheduling is done by maintaining a list of task control blocks * which holds tasks and the data queue they are processing. * @constructor */ function Scheduler() { this.queueCount = 0; this.holdCount = 0; this.blocks = new Array(NUMBER_OF_IDS); this.list = null; this.currentTcb = null; this.currentId = null; } var ID_IDLE = 0; var ID_WORKER = 1; var ID_HANDLER_A = 2; var ID_HANDLER_B = 3; var ID_DEVICE_A = 4; var ID_DEVICE_B = 5; var NUMBER_OF_IDS = 6; var KIND_DEVICE = 0; var KIND_WORK = 1; /** * Add an idle task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task * @param {int} count the number of times to schedule the task */ Scheduler.prototype.addIdleTask = function (id, priority, queue, count) { this.addRunningTask(id, priority, queue, new IdleTask(this, 1, count)); }; /** * Add a work task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task */ Scheduler.prototype.addWorkerTask = function (id, priority, queue) { this.addTask(id, priority, queue, new WorkerTask(this, ID_HANDLER_A, 0)); }; /** * Add a handler task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task */ Scheduler.prototype.addHandlerTask = function (id, priority, queue) { this.addTask(id, priority, queue, new HandlerTask(this)); }; /** * Add a handler task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task */ Scheduler.prototype.addDeviceTask = function (id, priority, queue) { this.addTask(id, priority, queue, new DeviceTask(this)); }; /** * Add the specified task and mark it as running. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task * @param {Task} task the task to add */ Scheduler.prototype.addRunningTask = function (id, priority, queue, task) { this.addTask(id, priority, queue, task); this.currentTcb.setRunning(); }; /** * Add the specified task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task * @param {Task} task the task to add */ Scheduler.prototype.addTask = function (id, priority, queue, task) { this.currentTcb = new TaskControlBlock(this.list, id, priority, queue, task); this.list = this.currentTcb; this.blocks[id] = this.currentTcb; }; /** * Execute the tasks managed by this scheduler. */ Scheduler.prototype.schedule = function () { this.currentTcb = this.list; while (this.currentTcb != null) { if (this.currentTcb.isHeldOrSuspended()) { this.currentTcb = this.currentTcb.link; } else { this.currentId = this.currentTcb.id; this.currentTcb = this.currentTcb.run(); } } }; /** * Release a task that is currently blocked and return the next block to run. * @param {int} id the id of the task to suspend */ Scheduler.prototype.release = function (id) { var tcb = this.blocks[id]; if (tcb == null) return tcb; tcb.markAsNotHeld(); if (tcb.priority > this.currentTcb.priority) { return tcb; } else { return this.currentTcb; } }; /** * Block the currently executing task and return the next task control block * to run. The blocked task will not be made runnable until it is explicitly * released, even if new work is added to it. */ Scheduler.prototype.holdCurrent = function () { this.holdCount++; this.currentTcb.markAsHeld(); return this.currentTcb.link; }; /** * Suspend the currently executing task and return the next task control block * to run. If new work is added to the suspended task it will be made runnable. */ Scheduler.prototype.suspendCurrent = function () { this.currentTcb.markAsSuspended(); return this.currentTcb; }; /** * Add the specified packet to the end of the worklist used by the task * associated with the packet and make the task runnable if it is currently * suspended. * @param {Packet} packet the packet to add */ Scheduler.prototype.queue = function (packet) { var t = this.blocks[packet.id]; if (t == null) return t; this.queueCount++; packet.link = null; packet.id = this.currentId; return t.checkPriorityAdd(this.currentTcb, packet); }; /** * A task control block manages a task and the queue of work packages associated * with it. * @param {TaskControlBlock} link the preceding block in the linked block list * @param {int} id the id of this block * @param {int} priority the priority of this block * @param {Packet} queue the queue of packages to be processed by the task * @param {Task} task the task * @constructor */ function TaskControlBlock(link, id, priority, queue, task) { this.link = link; this.id = id; this.priority = priority; this.queue = queue; this.task = task; if (queue == null) { this.state = STATE_SUSPENDED; } else { this.state = STATE_SUSPENDED_RUNNABLE; } } /** * The task is running and is currently scheduled. */ var STATE_RUNNING = 0; /** * The task has packets left to process. */ var STATE_RUNNABLE = 1; /** * The task is not currently running. The task is not blocked as such and may * be started by the scheduler. */ var STATE_SUSPENDED = 2; /** * The task is blocked and cannot be run until it is explicitly released. */ var STATE_HELD = 4; var STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE; var STATE_NOT_HELD = ~STATE_HELD; TaskControlBlock.prototype.setRunning = function () { this.state = STATE_RUNNING; }; TaskControlBlock.prototype.markAsNotHeld = function () { this.state = this.state & STATE_NOT_HELD; }; TaskControlBlock.prototype.markAsHeld = function () { this.state = this.state | STATE_HELD; }; TaskControlBlock.prototype.isHeldOrSuspended = function () { return (this.state & STATE_HELD) != 0 || (this.state == STATE_SUSPENDED); }; TaskControlBlock.prototype.markAsSuspended = function () { this.state = this.state | STATE_SUSPENDED; }; TaskControlBlock.prototype.markAsRunnable = function () { this.state = this.state | STATE_RUNNABLE; }; /** * Runs this task, if it is ready to be run, and returns the next task to run. */ TaskControlBlock.prototype.run = function () { var packet; if (this.state == STATE_SUSPENDED_RUNNABLE) { packet = this.queue; this.queue = packet.link; if (this.queue == null) { this.state = STATE_RUNNING; } else { this.state = STATE_RUNNABLE; } } else { packet = null; } return this.task.run(packet); }; /** * Adds a packet to the worklist of this block's task, marks this as runnable if * necessary, and returns the next runnable object to run (the one * with the highest priority). */ TaskControlBlock.prototype.checkPriorityAdd = function (task, packet) { if (this.queue == null) { this.queue = packet; this.markAsRunnable(); if (this.priority > task.priority) return this; } else { this.queue = packet.addTo(this.queue); } return task; }; TaskControlBlock.prototype.toString = function () { return "tcb { " + this.task + "@" + this.state + " }"; }; /** * An idle task doesn't do any work itself but cycles control between the two * device tasks. * @param {Scheduler} scheduler the scheduler that manages this task * @param {int} v1 a seed value that controls how the device tasks are scheduled * @param {int} count the number of times this task should be scheduled * @constructor */ function IdleTask(scheduler, v1, count) { this.scheduler = scheduler; this.v1 = v1; this.count = count; } IdleTask.prototype.run = function (packet) { this.count--; if (this.count == 0) return this.scheduler.holdCurrent(); if ((this.v1 & 1) == 0) { this.v1 = this.v1 >> 1; return this.scheduler.release(ID_DEVICE_A); } else { this.v1 = (this.v1 >> 1) ^ 0xD008; return this.scheduler.release(ID_DEVICE_B); } }; IdleTask.prototype.toString = function () { return "IdleTask"; }; /** * A task that suspends itself after each time it has been run to simulate * waiting for data from an external device. * @param {Scheduler} scheduler the scheduler that manages this task * @constructor */ function DeviceTask(scheduler) { this.scheduler = scheduler; this.v1 = null; } DeviceTask.prototype.run = function (packet) { if (packet == null) { if (this.v1 == null) return this.scheduler.suspendCurrent(); var v = this.v1; this.v1 = null; return this.scheduler.queue(v); } else { this.v1 = packet; return this.scheduler.holdCurrent(); } }; DeviceTask.prototype.toString = function () { return "DeviceTask"; }; /** * A task that manipulates work packets. * @param {Scheduler} scheduler the scheduler that manages this task * @param {int} v1 a seed used to specify how work packets are manipulated * @param {int} v2 another seed used to specify how work packets are manipulated * @constructor */ function WorkerTask(scheduler, v1, v2) { this.scheduler = scheduler; this.v1 = v1; this.v2 = v2; } WorkerTask.prototype.run = function (packet) { if (packet == null) { return this.scheduler.suspendCurrent(); } else { if (this.v1 == ID_HANDLER_A) { this.v1 = ID_HANDLER_B; } else { this.v1 = ID_HANDLER_A; } packet.id = this.v1; packet.a1 = 0; for (var i = 0; i < DATA_SIZE; i++) { this.v2++; if (this.v2 > 26) this.v2 = 1; packet.a2[i] = this.v2; } return this.scheduler.queue(packet); } }; WorkerTask.prototype.toString = function () { return "WorkerTask"; }; /** * A task that manipulates work packets and then suspends itself. * @param {Scheduler} scheduler the scheduler that manages this task * @constructor */ function HandlerTask(scheduler) { this.scheduler = scheduler; this.v1 = null; this.v2 = null; } HandlerTask.prototype.run = function (packet) { if (packet != null) { if (packet.kind == KIND_WORK) { this.v1 = packet.addTo(this.v1); } else { this.v2 = packet.addTo(this.v2); } } if (this.v1 != null) { var count = this.v1.a1; var v; if (count < DATA_SIZE) { if (this.v2 != null) { v = this.v2; this.v2 = this.v2.link; v.a1 = this.v1.a2[count]; this.v1.a1 = count + 1; return this.scheduler.queue(v); } } else { v = this.v1; this.v1 = this.v1.link; return this.scheduler.queue(v); } } return this.scheduler.suspendCurrent(); }; HandlerTask.prototype.toString = function () { return "HandlerTask"; }; /* --- * * P a c k e t * --- */ var DATA_SIZE = 4; /** * A simple package of data that is manipulated by the tasks. The exact layout * of the payload data carried by a packet is not importaint, and neither is the * nature of the work performed on packets by the tasks. * * Besides carrying data, packets form linked lists and are hence used both as * data and worklists. * @param {Packet} link the tail of the linked list of packets * @param {int} id an ID for this packet * @param {int} kind the type of this packet * @constructor */ function Packet(link, id, kind) { this.link = link; this.id = id; this.kind = kind; this.a1 = 0; this.a2 = new Array(DATA_SIZE); } /** * Add this packet to the end of a worklist, and return the worklist. * @param {Packet} queue the worklist to add this packet to */ Packet.prototype.addTo = function (queue) { this.link = null; if (queue == null) return this; var peek, next = queue; while ((peek = next.link) != null) next = peek; next.link = this; return queue; }; Packet.prototype.toString = function () { return "Packet"; }; var Richards = new BenchmarkSuite('Richards', 35302, [ new Benchmark("Richards", runRichards) ]); /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/scripts/v8-benches/splay.js ================================================ "use strict"; "use strip"; // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () { }; this.TearDown = tearDown ? tearDown : function () { }; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = '7'; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != 'undefined' && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return {runs: 0, elapsed: 0}; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return (data == null) ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; // Copyright 2009 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // This benchmark is based on a JavaScript log processing module used // by the V8 profiler to generate execution time profiles for runs of // JavaScript applications, and it effectively measures how fast the // JavaScript engine is at allocating nodes and reclaiming the memory // used for old nodes. Because of the way splay trees work, the engine // also has to deal with a lot of changes to the large tree object // graph. // Configuration. var kSplayTreeSize = 8000; var kSplayTreeModifications = 80; var kSplayTreePayloadDepth = 5; var splayTree = null; function GeneratePayloadTree(depth, tag) { if (depth == 0) { return { array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], string: 'String for key ' + tag + ' in leaf node' }; } else { return { left: GeneratePayloadTree(depth - 1, tag), right: GeneratePayloadTree(depth - 1, tag) }; } } function GenerateKey() { // The benchmark framework guarantees that Math.random is // deterministic; see base.js. return Math.random(); } function InsertNewNode() { // Insert new node with a unique key. var key; do { key = GenerateKey(); } while (splayTree.find(key) != null); var payload = GeneratePayloadTree(kSplayTreePayloadDepth, String(key)); splayTree.insert(key, payload); return key; } function SplaySetup() { splayTree = new SplayTree(); for (var i = 0; i < kSplayTreeSize; i++) InsertNewNode(); } function SplayTearDown() { // Allow the garbage collector to reclaim the memory // used by the splay tree no matter how we exit the // tear down function. var keys = splayTree.exportKeys(); splayTree = null; // Verify that the splay tree has the right size. var length = keys.length; if (length != kSplayTreeSize) { throw new Error("Splay tree has wrong size"); } // Verify that the splay tree has sorted, unique keys. for (var i = 0; i < length - 1; i++) { if (keys[i] >= keys[i + 1]) { throw new Error("Splay tree not sorted"); } } } function SplayRun() { // Replace a few nodes in the splay tree. for (var i = 0; i < kSplayTreeModifications; i++) { var key = InsertNewNode(); var greatest = splayTree.findGreatestLessThan(key); if (greatest == null) splayTree.remove(key); else splayTree.remove(greatest.key); } } /** * Constructs a Splay tree. A splay tree is a self-balancing binary * search tree with the additional property that recently accessed * elements are quick to access again. It performs basic operations * such as insertion, look-up and removal in O(log(n)) amortized time. * * @constructor */ function SplayTree() { } /** * Pointer to the root node of the tree. * * @type {SplayTree.Node} * @private */ SplayTree.prototype.root_ = null; /** * @return {boolean} Whether the tree is empty. */ SplayTree.prototype.isEmpty = function () { return !this.root_; }; /** * Inserts a node into the tree with the specified key and value if * the tree does not already contain a node with the specified key. If * the value is inserted, it becomes the root of the tree. * * @param {number} key Key to insert into the tree. * @param {*} value Value to insert into the tree. */ SplayTree.prototype.insert = function (key, value) { if (this.isEmpty()) { this.root_ = new SplayTree.Node(key, value); return; } // Splay on the key to move the last node on the search path for // the key to the root of the tree. this.splay_(key); if (this.root_.key == key) { return; } var node = new SplayTree.Node(key, value); if (key > this.root_.key) { node.left = this.root_; node.right = this.root_.right; this.root_.right = null; } else { node.right = this.root_; node.left = this.root_.left; this.root_.left = null; } this.root_ = node; }; /** * Removes a node with the specified key from the tree if the tree * contains a node with this key. The removed node is returned. If the * key is not found, an exception is thrown. * * @param {number} key Key to find and remove from the tree. * @return {SplayTree.Node} The removed node. */ SplayTree.prototype.remove = function (key) { if (this.isEmpty()) { throw Error('Key not found: ' + key); } this.splay_(key); if (this.root_.key != key) { throw Error('Key not found: ' + key); } var removed = this.root_; if (!this.root_.left) { this.root_ = this.root_.right; } else { var right = this.root_.right; this.root_ = this.root_.left; // Splay to make sure that the new root has an empty right child. this.splay_(key); // Insert the original right child as the right child of the new // root. this.root_.right = right; } return removed; }; /** * Returns the node having the specified key or null if the tree doesn't contain * a node with the specified key. * * @param {number} key Key to find in the tree. * @return {SplayTree.Node} Node having the specified key. */ SplayTree.prototype.find = function (key) { if (this.isEmpty()) { return null; } this.splay_(key); return this.root_.key == key ? this.root_ : null; }; /** * @return {SplayTree.Node} Node having the maximum key value. */ SplayTree.prototype.findMax = function (opt_startNode) { if (this.isEmpty()) { return null; } var current = opt_startNode || this.root_; while (current.right) { current = current.right; } return current; }; /** * @return {SplayTree.Node} Node having the maximum key value that * is less than the specified key value. */ SplayTree.prototype.findGreatestLessThan = function (key) { if (this.isEmpty()) { return null; } // Splay on the key to move the node with the given key or the last // node on the search path to the top of the tree. this.splay_(key); // Now the result is either the root node or the greatest node in // the left subtree. if (this.root_.key < key) { return this.root_; } else if (this.root_.left) { return this.findMax(this.root_.left); } else { return null; } }; /** * @return {Array<*>} An array containing all the keys of tree's nodes. */ SplayTree.prototype.exportKeys = function () { var result = []; if (!this.isEmpty()) { this.root_.traverse_(function (node) { result.push(node.key); }); } return result; }; /** * Perform the splay operation for the given key. Moves the node with * the given key to the top of the tree. If no node has the given * key, the last node on the search path is moved to the top of the * tree. This is the simplified top-down splaying algorithm from: * "Self-adjusting Binary Search Trees" by Sleator and Tarjan * * @param {number} key Key to splay the tree on. * @private */ SplayTree.prototype.splay_ = function (key) { if (this.isEmpty()) { return; } // Create a dummy node. The use of the dummy node is a bit // counter-intuitive: The right child of the dummy node will hold // the L tree of the algorithm. The left child of the dummy node // will hold the R tree of the algorithm. Using a dummy node, left // and right will always be nodes and we avoid special cases. var dummy, left, right; dummy = left = right = new SplayTree.Node(null, null); var current = this.root_; while (true) { if (key < current.key) { if (!current.left) { break; } if (key < current.left.key) { // Rotate right. var tmp = current.left; current.left = tmp.right; tmp.right = current; current = tmp; if (!current.left) { break; } } // Link right. right.left = current; right = current; current = current.left; } else if (key > current.key) { if (!current.right) { break; } if (key > current.right.key) { // Rotate left. var tmp = current.right; current.right = tmp.left; tmp.left = current; current = tmp; if (!current.right) { break; } } // Link left. left.right = current; left = current; current = current.right; } else { break; } } // Assemble. left.right = current.left; right.left = current.right; current.left = dummy.right; current.right = dummy.left; this.root_ = current; }; /** * Constructs a Splay tree node. * * @param {number} key Key. * @param {*} value Value. */ SplayTree.Node = function (key, value) { this.key = key; this.value = value; }; /** * @type {SplayTree.Node} */ SplayTree.Node.prototype.left = null; /** * @type {SplayTree.Node} */ SplayTree.Node.prototype.right = null; /** * Performs an ordered traversal of the subtree starting at * this SplayTree.Node. * * @param {function(SplayTree.Node)} f Visitor function. * @private */ SplayTree.Node.prototype.traverse_ = function (f) { var current = this; while (current) { var left = current.left; if (left) left.traverse_(f); f(current); current = current.right; } }; var Splay = new BenchmarkSuite('Splay', 81491, [ new Benchmark("Splay", SplayRun, SplaySetup, SplayTearDown) ]); /* run_harness.js */ var print = console.log; function Run() { BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, NotifyError: AddError, NotifyResult: AddResult, NotifyScore: AddScore, }); } var harnessErrorCount = 0; function ShowProgress(name) { print("PROGRESS", name); } function AddError(name, error) { print("ERROR", name, error); print(error.stack); harnessErrorCount++; } function AddResult(name, result) { print("RESULT", name, result); } function AddScore(score) { print("SCORE", score); } function main() { Run(); } ================================================ FILE: benches/src/lib.rs ================================================ #![allow(unused_crate_dependencies)] ================================================ FILE: cli/ABOUT.md ================================================ # 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_cli`**][cli] - Boa's CLI && REPL implementation - [**`boa_ast`**][ast] - Boa's ECMAScript Abstract Syntax Tree. - [**`boa_engine`**][engine] - Boa's implementation of ECMAScript builtin objects and execution. - [**`boa_gc`**][gc] - Boa's garbage collector. - [**`boa_icu_provider`**][icu] - Boa's ICU4X data provider. - [**`boa_interner`**][interner] - Boa's string interner. - [**`boa_macros`**][macros] - Boa's macros. - [**`boa_parser`**][parser] - Boa's lexer and parser. - [**`boa_runtime`**][runtime] - Boa's `WebAPI` features. - [**`boa_string`**][string] - Boa's ECMAScript string implementation. - [**`boa_wintertc`**][wintertc] - Boa's `WinterTC` (TC55) Minimum Common Web API implementation. - [**`tag_ptr`**][tag_ptr] - Utility library that enables a pointer to be associated with a tag of type `usize`. - [**`small_btree`**][small_btree] - Utility library that adds the `SmallBTreeMap` data structure. [boa-conformance]: https://boajs.dev/conformance [boa-web]: https://boajs.dev/ [boa-playground]: https://boajs.dev/playground [ast]: https://docs.rs/boa_ast/latest/boa_ast/index.html [engine]: https://docs.rs/boa_engine/latest/boa_engine/index.html [gc]: https://docs.rs/boa_gc/latest/boa_gc/index.html [interner]: https://docs.rs/boa_interner/latest/boa_interner/index.html [parser]: https://docs.rs/boa_parser/latest/boa_parser/index.html [icu]: https://docs.rs/boa_icu_provider/latest/boa_icu_provider/index.html [runtime]: https://docs.rs/boa_runtime/latest/boa_runtime/index.html [string]: https://docs.rs/boa_string/latest/boa_string/index.html [wintertc]: https://docs.rs/boa_wintertc/latest/boa_wintertc/index.html [tag_ptr]: https://docs.rs/tag_ptr/latest/tag_ptr/index.html [small_btree]: https://docs.rs/small_btree/latest/small_btree/index.html [macros]: https://docs.rs/boa_macros/latest/boa_macros/index.html [cli]: https://crates.io/crates/boa_cli ================================================ FILE: cli/Cargo.toml ================================================ [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", "float16", "flowgraph", "temporal", "trace", "xsum"] } boa_parser.workspace = true boa_gc.workspace = true boa_runtime.workspace = true rustyline = { workspace = true, features = ["derive", "with-file-history"] } clap = { workspace = true, features = ["derive"] } serde_json.workspace = true colored.workspace = true regex.workspace = true phf = { workspace = true, features = ["macros"] } dhat = { workspace = true, optional = true } color-eyre.workspace = true cow-utils.workspace = true futures-concurrency.workspace = true futures-lite.workspace = true async-channel.workspace = true rustls.workspace = true [features] default = [ "boa_engine/annex-b", "boa_engine/experimental", "boa_engine/intl_bundled", "boa_engine/native-backtrace", "fast-allocator", "fetch", ] dhat = ["dep:dhat"] fast-allocator = ["dep:mimalloc-safe", "dep:jemallocator"] fetch = ["boa_runtime/fetch", "boa_runtime/reqwest-blocking"] [target.x86_64-unknown-linux-gnu.dependencies] jemallocator = { workspace = true, optional = true } [target.'cfg(target_os = "windows")'.dependencies] mimalloc-safe = { workspace = true, optional = true, features = [ "skip_collect_on_exit", ] } [[bin]] name = "boa" doc = false path = "src/main.rs" [lints] workspace = true [package.metadata.docs.rs] all-features = true ================================================ FILE: cli/README.md ================================================ # Boa CLI Boa CLI is `Boa`'s REPL implementation to execute `JavaScript` directly from your CLI. ## Installation `boa_cli` can be installed directly via `Cargo`. ```shell cargo install boa_cli ``` ## Usage Once installed, your good to go! To execute some JavaScript source code, navigate to the directory of your choice and type: ```shell boa test.js ``` Or if you'd like to use Boa's REPL, simply type: ```shell boa ``` You can also pipe JavaScript into Boa: ```shell echo 'console.log(1 + 2)' | boa cat script.js | boa boa < script.js ``` ## CLI Options ```txt Usage: boa [OPTIONS] [FILE]... Arguments: [FILE]... The JavaScript file(s) to be evaluated Options: --strict Run in strict mode -a, --dump-ast [] Dump the AST to stdout with the given format [possible values: debug, json, json-pretty] -t, --trace Dump the AST to stdout with the given format --vi Use vi mode in the REPL -O, --optimize Enable bytecode compiler optimizations --optimizer-statistics Print optimizer statistics (requires -O) --flowgraph [] Generate instruction flowgraph. Default is Graphviz [possible values: graphviz, mermaid] --flowgraph-direction Specifies the direction of the flowgraph. Default is top-top-bottom [possible values: top-to-bottom, bottom-to-top, left-to-right, right-to-left] --debug-object Inject debugging object `$boa` -m, --module Treats the input files as modules -r, --root Root path from where the module resolver will try to load the modules [default: .] -e, --expression Execute a JavaScript expression then exit -q, --quiet Suppress the welcome banner when starting the REPL -h, --help Print help (see more with '--help') -V, --version Print version ``` ## REPL Commands When running the interactive REPL (`boa` with no file arguments), the following dot-commands are available: | Command | Description | | -------------- | ----------------------------------- | | `.help` | Show available REPL commands | | `.exit` | Exit the REPL | | `.clear` | Clear the terminal screen | | `.load ` | Load and evaluate a JavaScript file | You can also press `Ctrl+C` to abort the current expression, or `Ctrl+D` to exit. ## Features Boa's CLI currently has a variety of features (as listed in `Options`). Features include: - Implemented runtime features (please note that only `Console` is currently implemented) - AST Visibility: View the compiled Boa AST (--dump-ast) - Tracing: Enabling a vm tracing when executing any JavaScript - Flowgraphs: View a generated (with various provided options) - Debugging: Boa's CLI comes with an implemented `$boa` debug object with various functionality (see documentation). Have an idea for a feature? Feel free to submit an issue and/or contribute! ================================================ FILE: cli/src/debug/function.rs ================================================ use boa_engine::{ Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, builtins::function::OrdinaryFunction, js_string, object::ObjectInitializer, vm::flowgraph::{Direction, Graph}, }; use cow_utils::CowUtils; use crate::FlowgraphFormat; fn flowgraph_parse_format_option(value: &JsValue) -> JsResult { if value.is_undefined() { return Ok(FlowgraphFormat::Mermaid); } if let Some(string) = value.as_string() { return match string.to_std_string_escaped().cow_to_lowercase().as_ref() { "mermaid" => Ok(FlowgraphFormat::Mermaid), "graphviz" => Ok(FlowgraphFormat::Graphviz), format => Err(JsNativeError::typ() .with_message(format!("Unknown format type '{format}'")) .into()), }; } Err(JsNativeError::typ() .with_message("format type must be a string") .into()) } fn flowgraph_parse_direction_option(value: &JsValue) -> JsResult { if value.is_undefined() { return Ok(Direction::LeftToRight); } if let Some(string) = value.as_string() { return match string.to_std_string_escaped().cow_to_lowercase().as_ref() { "leftright" | "lr" => Ok(Direction::LeftToRight), "rightleft" | "rl" => Ok(Direction::RightToLeft), "topbottom" | "tb" => Ok(Direction::TopToBottom), "bottomtop" | "bt " => Ok(Direction::BottomToTop), direction => Err(JsNativeError::typ() .with_message(format!("Unknown direction type '{direction}'")) .into()), }; } Err(JsNativeError::typ() .with_message("direction type must be a string") .into()) } /// Get functions instruction flowgraph fn flowgraph(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let Some(value) = args.first() else { return Err(JsNativeError::typ() .with_message("expected function argument") .into()); }; let Some(object) = value.as_object() else { return Err(JsNativeError::typ() .with_message(format!("expected object, got {}", value.type_of())) .into()); }; let mut format = FlowgraphFormat::Mermaid; let mut direction = Direction::LeftToRight; if let Some(arguments) = args.get(1) { if let Some(arguments) = arguments.as_object() { format = flowgraph_parse_format_option(&arguments.get(js_string!("format"), context)?)?; direction = flowgraph_parse_direction_option( &arguments.get(js_string!("direction"), context)?, )?; } else { format = flowgraph_parse_format_option(arguments)?; } } let Some(function) = object.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("expected an ordinary function object") .into()); }; let code = function.codeblock(); let mut graph = Graph::new(direction); code.to_graph(graph.subgraph(String::default())); let result = match format { FlowgraphFormat::Graphviz => graph.to_graphviz_format(), FlowgraphFormat::Mermaid => graph.to_mermaid_format(), }; Ok(JsValue::new(js_string!(result))) } fn bytecode(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let Some(value) = args.first() else { return Err(JsNativeError::typ() .with_message("expected function argument") .into()); }; let Some(object) = value.as_object() else { return Err(JsNativeError::typ() .with_message(format!("expected object, got {}", value.type_of())) .into()); }; let Some(function) = object.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("expected an ordinary function object") .into()); }; let code = function.codeblock(); println!("{code}"); Ok(JsValue::undefined()) } fn set_trace_flag_in_function_object(object: &JsObject, value: bool) -> JsResult<()> { let Some(function) = object.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("expected an ordinary function object") .into()); }; let code = function.codeblock(); code.set_traceable(value); Ok(()) } /// Trace function. fn trace(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0); let this = args.get_or_undefined(1); let Some(callable) = value.as_callable() else { return Err(JsNativeError::typ() .with_message("expected callable object") .into()); }; let arguments = args.get(2..).unwrap_or(&[]); set_trace_flag_in_function_object(&callable, true)?; let result = callable.call(this, arguments, context); set_trace_flag_in_function_object(&callable, false)?; result } fn traceable(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let value = args.get_or_undefined(0); let traceable = args.get_or_undefined(1).to_boolean(); let Some(callable) = value.as_callable() else { return Err(JsNativeError::typ() .with_message("expected callable object") .into()); }; set_trace_flag_in_function_object(&callable, traceable)?; Ok(value.clone()) } pub(super) fn create_object(context: &mut Context) -> JsObject { ObjectInitializer::new(context) .function( NativeFunction::from_fn_ptr(flowgraph), js_string!("flowgraph"), 1, ) .function( NativeFunction::from_fn_ptr(bytecode), js_string!("bytecode"), 1, ) .function(NativeFunction::from_fn_ptr(trace), js_string!("trace"), 1) .function( NativeFunction::from_fn_ptr(traceable), js_string!("traceable"), 2, ) .build() } ================================================ FILE: cli/src/debug/gc.rs ================================================ use boa_engine::{ Context, JsObject, JsResult, JsValue, NativeFunction, js_string, object::ObjectInitializer, }; /// Trigger garbage collection. fn collect(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { boa_gc::force_collect(); Ok(JsValue::undefined()) } pub(super) fn create_object(context: &mut Context) -> JsObject { ObjectInitializer::new(context) .function( NativeFunction::from_fn_ptr(collect), js_string!("collect"), 0, ) .build() } ================================================ FILE: cli/src/debug/limits.rs ================================================ use boa_engine::{ Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, js_string, object::{FunctionObjectBuilder, ObjectInitializer}, property::Attribute, }; fn get_loop(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let max = context.runtime_limits().loop_iteration_limit(); Ok(JsValue::from(max)) } fn set_loop(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0).to_length(context)?; context.runtime_limits_mut().set_loop_iteration_limit(value); Ok(JsValue::undefined()) } fn get_stack(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let max = context.runtime_limits().stack_size_limit(); Ok(JsValue::from(max)) } fn set_stack(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0).to_length(context)?; let Ok(value) = value.try_into() else { return Err(JsNativeError::range() .with_message(format!("Argument {value} greater than usize::MAX")) .into()); }; context.runtime_limits_mut().set_stack_size_limit(value); Ok(JsValue::undefined()) } fn get_recursion(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let max = context.runtime_limits().recursion_limit(); Ok(JsValue::from(max)) } fn set_recursion(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0).to_length(context)?; let Ok(value) = value.try_into() else { return Err(JsNativeError::range() .with_message(format!("Argument {value} greater than usize::MAX")) .into()); }; context.runtime_limits_mut().set_recursion_limit(value); Ok(JsValue::undefined()) } fn get_backtrace(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let max = context.runtime_limits().backtrace_limit(); Ok(JsValue::from(max)) } fn set_backtrace(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0).to_length(context)?; let Ok(value) = value.try_into() else { return Err(JsNativeError::range() .with_message(format!("Argument {value} greater than usize::MAX")) .into()); }; context.runtime_limits_mut().set_backtrace_limit(value); Ok(JsValue::undefined()) } pub(super) fn create_object(context: &mut Context) -> JsObject { let get_loop = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_loop)) .name(js_string!("get loop")) .length(0) .build(); let set_loop = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_loop)) .name(js_string!("set loop")) .length(1) .build(); let get_stack = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_stack)) .name(js_string!("get stack")) .length(0) .build(); let set_stack = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_stack)) .name(js_string!("set stack")) .length(1) .build(); let get_recursion = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_recursion)) .name(js_string!("get recursion")) .length(0) .build(); let set_recursion = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_recursion)) .name(js_string!("set recursion")) .length(1) .build(); let get_backtrace = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_backtrace)) .name(js_string!("get backtrace")) .length(0) .build(); let set_backtrace = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_backtrace)) .name(js_string!("set backtrace")) .length(1) .build(); ObjectInitializer::new(context) .accessor( js_string!("loop"), Some(get_loop), Some(set_loop), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( js_string!("stack"), Some(get_stack), Some(set_stack), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( js_string!("recursion"), Some(get_recursion), Some(set_recursion), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( js_string!("backtrace"), Some(get_backtrace), Some(set_backtrace), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .build() } ================================================ FILE: cli/src/debug/mod.rs ================================================ // Allow lint so it, doesn't warn about `JsResult<>` unneeded return on functions. #![allow(clippy::unnecessary_wraps)] use boa_engine::{Context, JsObject, js_string, object::ObjectInitializer, property::Attribute}; mod function; mod gc; mod limits; mod object; mod optimizer; mod realm; mod shape; mod string; fn create_boa_object(context: &mut Context) -> JsObject { let function_module = function::create_object(context); let object_module = object::create_object(context); let shape_module = shape::create_object(context); let optimizer_module = optimizer::create_object(context); let gc_module = gc::create_object(context); let realm_module = realm::create_object(context); let limits_module = limits::create_object(context); let string_module = string::create_string(context); ObjectInitializer::new(context) .property( js_string!("function"), function_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("object"), object_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("shape"), shape_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("optimizer"), optimizer_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("gc"), gc_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("realm"), realm_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("limits"), limits_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("string"), string_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .build() } #[allow(clippy::redundant_pub_crate)] pub(crate) fn init_boa_debug_object(context: &mut Context) { let boa_object = create_boa_object(context); context .register_global_property( js_string!("$boa"), boa_object, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .expect("cannot fail with the default object"); } ================================================ FILE: cli/src/debug/object.rs ================================================ use boa_engine::{ Context, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, js_string, object::{IndexProperties, ObjectInitializer}, }; /// Returns objects pointer in memory. fn id(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let Some(value) = args.first() else { return Err(JsNativeError::typ() .with_message("expected object argument") .into()); }; let Some(object) = value.as_object() else { return Err(JsNativeError::typ() .with_message(format!("expected object, got {}", value.type_of())) .into()); }; let ptr: *const _ = object.as_ref(); Ok(js_string!(format!("0x{:X}", ptr.cast::<()>() as usize)).into()) } /// Returns objects pointer in memory. fn indexed_storage_type(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let Some(value) = args.first() else { return Err(JsNativeError::typ() .with_message("expected object argument") .into()); }; let Some(object) = value.as_object() else { return Err(JsNativeError::typ() .with_message(format!("expected object, got {}", value.type_of())) .into()); }; let typ = match object.borrow().properties().index_properties() { IndexProperties::DenseI32(_) => "DenseI32", IndexProperties::DenseF64(_) => "DenseF64", IndexProperties::DenseElement(_) => "DenseElement", IndexProperties::SparseElement(_) => "SparseElement", IndexProperties::SparseProperty(_) => "SparseProperty", }; Ok(js_string!(typ).into()) } pub(super) fn create_object(context: &mut Context) -> JsObject { ObjectInitializer::new(context) .function(NativeFunction::from_fn_ptr(id), js_string!("id"), 1) .function( NativeFunction::from_fn_ptr(indexed_storage_type), js_string!("indexedStorageType"), 1, ) .build() } ================================================ FILE: cli/src/debug/optimizer.rs ================================================ use boa_engine::{ Context, JsArgs, JsObject, JsResult, JsValue, NativeFunction, js_string, object::{FunctionObjectBuilder, ObjectInitializer}, optimizer::OptimizerOptions, property::Attribute, }; fn get_constant_folding(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { Ok(context .optimizer_options() .contains(OptimizerOptions::CONSTANT_FOLDING) .into()) } fn set_constant_folding(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0).to_boolean(); let mut options = context.optimizer_options(); options.set(OptimizerOptions::CONSTANT_FOLDING, value); context.set_optimizer_options(options); Ok(JsValue::undefined()) } fn get_statistics(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { Ok(context .optimizer_options() .contains(OptimizerOptions::STATISTICS) .into()) } fn set_statistics(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0).to_boolean(); let mut options = context.optimizer_options(); options.set(OptimizerOptions::STATISTICS, value); context.set_optimizer_options(options); Ok(JsValue::undefined()) } pub(super) fn create_object(context: &mut Context) -> JsObject { let get_constant_folding = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_fn_ptr(get_constant_folding), ) .name("get constantFolding") .length(0) .build(); let set_constant_folding = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_fn_ptr(set_constant_folding), ) .name("set constantFolding") .length(1) .build(); let get_statistics = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_statistics)) .name("get statistics") .length(0) .build(); let set_statistics = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_statistics)) .name("set statistics") .length(1) .build(); ObjectInitializer::new(context) .accessor( js_string!("constantFolding"), Some(get_constant_folding), Some(set_constant_folding), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( js_string!("statistics"), Some(get_statistics), Some(set_statistics), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .build() } ================================================ FILE: cli/src/debug/realm.rs ================================================ use boa_engine::{ Context, JsObject, JsResult, JsValue, NativeFunction, js_string, object::ObjectInitializer, }; /// Creates a new ECMAScript Realm and returns the global object of the realm. fn create(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let context = &mut Context::default(); Ok(context.global_object().into()) } pub(super) fn create_object(context: &mut Context) -> JsObject { ObjectInitializer::new(context) .function(NativeFunction::from_fn_ptr(create), js_string!("create"), 0) .build() } ================================================ FILE: cli/src/debug/shape.rs ================================================ use boa_engine::{ Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, js_string, object::ObjectInitializer, }; fn get_object(args: &[JsValue], position: usize) -> JsResult { let value = args.get_or_undefined(position); let Some(object) = value.as_object() else { return Err(JsNativeError::typ() .with_message(format!( "expected object in argument position {position}, got {}", value.type_of() )) .into()); }; Ok(object) } /// Returns object's shape pointer in memory. fn id(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let object = get_object(args, 0)?; let object = object.borrow(); let shape = object.shape(); Ok(js_string!(format!("0x{:X}", shape.to_addr_usize())).into()) } /// Returns object's shape type. fn r#type(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let object = get_object(args, 0)?; let object = object.borrow(); let shape = object.shape(); Ok(if shape.is_shared() { js_string!("shared") } else { js_string!("unique") } .into()) } /// Returns object's shape type. fn same(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let lhs = get_object(args, 0)?; let rhs = get_object(args, 1)?; let lhs_shape_ptr = { let object = lhs.borrow(); let shape = object.shape(); shape.to_addr_usize() }; let rhs_shape_ptr = { let object = rhs.borrow(); let shape = object.shape(); shape.to_addr_usize() }; Ok(JsValue::new(lhs_shape_ptr == rhs_shape_ptr)) } pub(super) fn create_object(context: &mut Context) -> JsObject { ObjectInitializer::new(context) .function(NativeFunction::from_fn_ptr(id), js_string!("id"), 1) .function(NativeFunction::from_fn_ptr(r#type), js_string!("type"), 1) .function(NativeFunction::from_fn_ptr(same), js_string!("same"), 2) .build() } ================================================ FILE: cli/src/debug/string.rs ================================================ use boa_engine::{ Context, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, js_string, object::ObjectInitializer, property::Attribute, string::JsStrVariant, }; fn storage(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let Some(value) = args.first() else { return Err(JsNativeError::typ() .with_message("expected string argument") .into()); }; let Some(string) = value.as_string() else { return Err(JsNativeError::typ() .with_message(format!("expected string, got {}", value.type_of())) .into()); }; let storage = if string.is_static() { "static" } else { "heap" }; Ok(js_string!(storage).into()) } fn encoding(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let Some(value) = args.first() else { return Err(JsNativeError::typ() .with_message("expected string argument") .into()); }; let Some(string) = value.as_string() else { return Err(JsNativeError::typ() .with_message(format!("expected string, got {}", value.type_of())) .into()); }; let str = string.as_str(); let encoding = match str.variant() { JsStrVariant::Latin1(_) => "latin1", JsStrVariant::Utf16(_) => "utf16", }; Ok(js_string!(encoding).into()) } fn summary(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let Some(value) = args.first() else { return Err(JsNativeError::typ() .with_message("expected string argument") .into()); }; let Some(string) = value.as_string() else { return Err(JsNativeError::typ() .with_message(format!("expected string, got {}", value.type_of())) .into()); }; let storage = if string.is_static() { "static" } else { "heap" }; let encoding = match string.as_str().variant() { JsStrVariant::Latin1(_) => "latin1", JsStrVariant::Utf16(_) => "utf16", }; let summary = ObjectInitializer::new(context) .property(js_string!("storage"), js_string!(storage), Attribute::all()) .property( js_string!("encoding"), js_string!(encoding), Attribute::all(), ) .build(); Ok(summary.into()) } pub(super) fn create_string(context: &mut Context) -> JsObject { ObjectInitializer::new(context) .function( NativeFunction::from_fn_ptr(storage), js_string!("storage"), 1, ) .function( NativeFunction::from_fn_ptr(encoding), js_string!("encoding"), 1, ) .function( NativeFunction::from_fn_ptr(summary), js_string!("summary"), 1, ) .build() } ================================================ FILE: cli/src/executor.rs ================================================ use std::{ cell::RefCell, collections::{BTreeMap, VecDeque}, mem, rc::Rc, }; use boa_engine::{ Context, JsResult, context::time::JsInstant, job::{GenericJob, Job, JobExecutor, NativeAsyncJob, PromiseJob, TimeoutJob}, }; use futures_concurrency::future::FutureGroup; use futures_lite::{StreamExt, future}; use crate::{logger::SharedExternalPrinterLogger, uncaught_job_error}; pub(crate) struct Executor { promise_jobs: RefCell>, async_jobs: RefCell>, timeout_jobs: RefCell>>, generic_jobs: RefCell>, finalization_registry_jobs: RefCell>, printer: SharedExternalPrinterLogger, } impl Executor { pub(crate) fn new(printer: SharedExternalPrinterLogger) -> Self { Self { promise_jobs: RefCell::default(), async_jobs: RefCell::default(), timeout_jobs: RefCell::default(), generic_jobs: RefCell::default(), finalization_registry_jobs: RefCell::default(), printer, } } pub(crate) fn clear(&self) { self.promise_jobs.borrow_mut().clear(); self.async_jobs.borrow_mut().clear(); self.timeout_jobs.borrow_mut().clear(); self.generic_jobs.borrow_mut().clear(); } fn is_empty(&self) -> bool { self.promise_jobs.borrow().is_empty() && self.async_jobs.borrow().is_empty() && self.timeout_jobs.borrow().is_empty() && self.generic_jobs.borrow().is_empty() } fn drain_timeout_jobs(&self, context: &mut Context) { let now = context.clock().now(); let mut timeouts_borrow = self.timeout_jobs.borrow_mut(); let mut jobs_to_keep = timeouts_borrow.split_off(&now); jobs_to_keep.retain(|_, jobs| { jobs.retain(|job| !job.is_cancelled()); !jobs.is_empty() }); let jobs_to_run = mem::replace(&mut *timeouts_borrow, jobs_to_keep); drop(timeouts_borrow); for jobs in jobs_to_run.into_values() { for job in jobs { if !job.is_cancelled() && let Err(e) = job.call(context) { self.printer.print(uncaught_job_error(&e)); } } } } fn drain_generic_jobs(&self, context: &mut Context) { let job = self.generic_jobs.borrow_mut().pop_front(); if let Some(generic) = job && let Err(err) = generic.call(context) { self.printer.print(uncaught_job_error(&err)); } } } impl JobExecutor for Executor { fn enqueue_job(self: Rc, job: Job, context: &mut Context) { match job { Job::PromiseJob(job) => self.promise_jobs.borrow_mut().push_back(job), Job::AsyncJob(job) => self.async_jobs.borrow_mut().push_back(job), Job::TimeoutJob(job) => { let now = context.clock().now(); self.timeout_jobs .borrow_mut() .entry(now + job.timeout()) .or_default() .push(job); } Job::GenericJob(job) => self.generic_jobs.borrow_mut().push_back(job), Job::FinalizationRegistryCleanupJob(job) => { self.finalization_registry_jobs.borrow_mut().push_back(job); } job => self.printer.print(format!("unsupported job type {job:?}")), } } fn run_jobs(self: Rc, context: &mut Context) -> JsResult<()> { future::block_on(self.run_jobs_async(&RefCell::new(context))) } async fn run_jobs_async(self: Rc, context: &RefCell<&mut Context>) -> JsResult<()> { let mut group = FutureGroup::new(); let mut fr_group = FutureGroup::new(); loop { for job in mem::take(&mut *self.async_jobs.borrow_mut()) { group.insert(job.call(context)); } for job in mem::take(&mut *self.finalization_registry_jobs.borrow_mut()) { fr_group.insert(job.call(context)); } if let Some(Err(e)) = future::poll_once(group.next()).await.flatten() { self.printer.print(uncaught_job_error(&e)); } // We particularly want to make this check here such that the // event loop is cancelled almost immediately after the channel with // the reader gets closed. if self.is_empty() && group.is_empty() { // Run finalizers with a lower priority than every other type of // job. if let Some(Err(e)) = future::poll_once(fr_group.next()).await.flatten() { self.printer.print(uncaught_job_error(&e)); } // Finalizers could enqueue new jobs, so we cannot just exit. if self.is_empty() { return Ok(()); } } { let context = &mut context.borrow_mut(); self.drain_timeout_jobs(context); self.drain_generic_jobs(context); let jobs = mem::take(&mut *self.promise_jobs.borrow_mut()); for job in jobs { if let Err(e) = job.call(context) { self.printer.print(uncaught_job_error(&e)); } } } context.borrow_mut().clear_kept_objects(); future::yield_now().await; } } } ================================================ FILE: cli/src/helper.rs ================================================ use boa_engine::{ast::scope::Scope, interner::Interner}; use boa_parser::Source; use colored::{Color, Colorize}; use phf::{Set, phf_set}; use regex::{Captures, Regex, Replacer}; use rustyline::{ Completer, Helper, Hinter, error::ReadlineError, highlight::{CmdKind, Highlighter}, validate::{ValidationContext, ValidationResult, Validator}, }; use std::borrow::Cow::{self, Borrowed}; 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, }; const READLINE_COLOR: Color = Color::Cyan; #[allow(clippy::upper_case_acronyms, clippy::redundant_pub_crate)] #[derive(Completer, Helper, Hinter)] pub(crate) struct RLHelper { strict: bool, highlighter: LineHighlighter, colored_prompt: String, } impl RLHelper { pub(crate) fn new(prompt: &str, strict: bool) -> Self { Self { highlighter: LineHighlighter::new(), strict, colored_prompt: prompt.color(READLINE_COLOR).bold().to_string(), } } } impl Validator for RLHelper { fn validate( &self, context: &mut ValidationContext<'_>, ) -> Result { let mut parser = boa_parser::Parser::new(Source::from_bytes(context.input())); if self.strict { parser.set_strict(); } match parser.parse_script(&Scope::new_global(), &mut Interner::new()) { // Skip scope analysis errors, those can be caused by not having the // context's Scope to resolve variables. Ok(_) | Err(boa_parser::Error::ScopeAnalysis { .. }) => { Ok(ValidationResult::Valid(None)) } Err(boa_parser::Error::AbruptEnd) => Ok(ValidationResult::Incomplete), Err(err) => Ok(ValidationResult::Invalid(Some(format!( "\n{} {}", "Uncaught SyntaxError".red(), err.to_string().red() )))), } } fn validate_while_typing(&self) -> bool { false } } impl Highlighter for RLHelper { fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { self.highlighter.highlight(line, pos) } // Must match signature of Highlighter::highlight_prompt, can't elide lifetimes. #[allow(single_use_lifetimes)] fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, default: bool, ) -> Cow<'b, str> { if default { Borrowed(&self.colored_prompt) } else { Borrowed(prompt) } } fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { hint.into() } 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, _: CmdKind) -> 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 { regex: Regex, } impl LineHighlighter { fn new() -> Self { // Precompiles the regex to avoid creating it again after every highlight let regex = Regex::new( r#"(?x) (?P\b[$_\p{ID_Start}][$_\p{ID_Continue}\u{200C}\u{200D}]*\b) | (?P"([^"\\]|\\.)*") | (?P'([^'\\]|\\.)*') | (?P`([^`\\]|\\.)*`) | (?P[+\-/*%~^!&|=<>;:]) | (?P0[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"); Self { regex } } } impl Highlighter for LineHighlighter { fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> { use std::fmt::Write; struct Colorizer; impl Replacer for Colorizer { // Changing to map_or_else moves the handling of "identifier" after all other kinds, // which reads worse than this version. #[allow(clippy::option_if_let_else)] fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) { let colored = if let Some(cap) = caps.name("identifier") { let cap = cap.as_str(); let colored = match cap { "true" | "false" | "null" | "Infinity" | "globalThis" => { cap.color(PROPERTY_COLOR) } "undefined" => cap.color(UNDEFINED_COLOR), identifier if KEYWORDS.contains(identifier) => { cap.color(KEYWORD_COLOR).bold() } _ => cap.color(IDENTIFIER_COLOR), }; Some(colored) } else if let Some(cap) = caps .name("string_double_quote") .or_else(|| caps.name("string_single_quote")) .or_else(|| caps.name("template_literal")) { Some(cap.as_str().color(STRING_COLOR)) } else if let Some(cap) = caps.name("op") { Some(cap.as_str().color(OPERATOR_COLOR)) } else { caps.name("number") .map(|cap| cap.as_str().color(NUMBER_COLOR)) }; if let Some(colored) = colored { write!(dst, "{colored}").expect("could not append data to dst"); } else { dst.push_str(&caps[0]); } } } self.regex.replace_all(line, Colorizer) } } ================================================ FILE: cli/src/logger.rs ================================================ use boa_engine::{Context, Finalize, JsResult, Trace}; use boa_runtime::{ConsoleState, Logger}; use rustyline::ExternalPrinter; use std::fmt::Debug; use std::io::Write; use std::sync::{Arc, Mutex}; #[derive(Clone, Trace, Finalize)] pub(crate) struct SharedExternalPrinterLogger { #[unsafe_ignore_trace] inner: Arc>>>, } impl SharedExternalPrinterLogger { pub(crate) fn new() -> Self { Self { inner: Arc::new(Mutex::new(None)), } } pub(crate) fn set(&self, inner: T) { self.inner .lock() .expect("printer lock failed") .replace(Box::new(inner)); } pub(crate) fn print(&self, message: String) { if let Some(l) = &mut *self.inner.lock().expect("printer lock failed") { // Ignore errors, there's nothing we can do at this point. drop(l.print(message)); } else { drop(std::io::stdout().write_all(message.as_bytes())); } } } impl Debug for SharedExternalPrinterLogger { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SharedExternalPrinterLogger").finish() } } impl Logger for SharedExternalPrinterLogger { #[inline] fn log(&self, msg: String, state: &ConsoleState, _context: &mut Context) -> JsResult<()> { let indent = state.indent(); self.print(format!("{msg:>indent$}\n")); Ok(()) } #[inline] fn info(&self, msg: String, state: &ConsoleState, context: &mut Context) -> JsResult<()> { self.log(msg, state, context) } #[inline] fn warn(&self, msg: String, state: &ConsoleState, context: &mut Context) -> JsResult<()> { self.log(msg, state, context) } #[inline] fn error(&self, msg: String, state: &ConsoleState, _context: &mut Context) -> JsResult<()> { let indent = state.indent(); self.print(format!("{msg:>indent$}\n")); Ok(()) } } ================================================ FILE: cli/src/main.rs ================================================ //! A CLI implementation for `boa_engine` that comes complete with file execution and a REPL. #![doc = include_str!("../ABOUT.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg", html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg" )] #![cfg_attr(not(test), deny(clippy::unwrap_used))] #![allow(clippy::print_stdout, clippy::print_stderr)] mod debug; mod executor; mod helper; mod logger; use crate::executor::Executor; use crate::logger::SharedExternalPrinterLogger; use async_channel::Sender; use boa_engine::JsValue; use boa_engine::error::JsErasedError; use boa_engine::job::NativeAsyncJob; use boa_engine::{ Context, JsError, Source, builtins::promise::PromiseState, context::ContextBuilder, module::{Module, SimpleModuleLoader}, optimizer::OptimizerOptions, script::Script, vm::flowgraph::{Direction, Graph}, }; use boa_parser::source::ReadChar; use clap::{Parser, ValueEnum, ValueHint}; use color_eyre::{ Result, Section, eyre::{WrapErr, eyre}, }; use colored::Colorize; use debug::init_boa_debug_object; use rustyline::{EditMode, Editor, config::Config, error::ReadlineError}; use std::time::{Duration, Instant}; use std::{ fs::OpenOptions, io::{self, IsTerminal, Read, Write}, path::{Path, PathBuf}, rc::Rc, thread, }; // ---- #[cfg(all( target_arch = "x86_64", target_os = "linux", target_env = "gnu", feature = "dhat" ))] use jemallocator as _; #[cfg(all( target_arch = "x86_64", target_os = "linux", target_env = "gnu", feature = "fast-allocator", not(feature = "dhat") ))] #[global_allocator] static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; #[cfg(all(target_os = "windows", feature = "dhat"))] use mimalloc_safe as _; #[cfg(all( target_os = "windows", feature = "fast-allocator", not(feature = "dhat") ))] #[global_allocator] static ALLOC: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc; #[cfg(feature = "dhat")] #[global_allocator] static ALLOC: dhat::Alloc = dhat::Alloc; /// CLI configuration for Boa. static CLI_HISTORY: &str = ".boa_history"; // Added #[allow(clippy::option_option)] because to StructOpt an Option> // 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")] #[allow(clippy::struct_excessive_bools)] // NOTE: Allow having more than 3 bools in struct struct Opt { /// The JavaScript file(s) to be evaluated. #[arg(name = "FILE", value_hint = ValueHint::FilePath)] files: Vec, /// Run in strict mode. #[arg(long)] strict: bool, /// 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>, /// 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, /// Report parsing and execution timings. #[arg(long)] time: bool, #[arg(long, short = 'O', group = "optimizer")] optimize: bool, #[arg(long, requires = "optimizer")] optimizer_statistics: 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>, /// Specifies the direction of the flowgraph. Default is top-top-bottom. #[arg( long, value_name = "FORMAT", ignore_case = true, value_enum, requires = "graph" )] flowgraph_direction: Option, /// Inject debugging object `$boa`. #[arg(long)] debug_object: bool, /// Treats the input files as modules. #[arg(long, short = 'm', group = "mod")] module: bool, /// Root path from where the module resolver will try to load the modules. #[arg(long, short = 'r', default_value_os_t = PathBuf::from("."), requires = "mod")] root: PathBuf, /// Execute a JavaScript expression then exit. Files (see above) will be /// executed prior to the expression. #[arg(long, short = 'e')] expression: Option, /// Suppress the welcome banner when starting the REPL. #[arg(long, short = 'q')] quiet: bool, } impl Opt { /// Returns whether a dump flag has been used. const fn has_dump_flag(&self) -> bool { self.dump_ast.is_some() } } /// The different types of format available for dumping. #[derive(Debug, Copy, Clone, Default, ValueEnum)] enum DumpFormat { // 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`. #[default] 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 format: . Graphviz, /// Generates in mermaid format: . Mermaid, } /// Represents the direction of the instruction flowgraph. #[derive(Debug, Clone, Copy, ValueEnum)] enum FlowgraphDirection { TopToBottom, BottomToTop, LeftToRight, RightToLeft, } struct Timer<'a> { name: &'static str, start: Instant, counters: &'a mut Vec<(&'static str, Duration)>, } impl Drop for Timer<'_> { fn drop(&mut self) { self.counters.push((self.name, self.start.elapsed())); } } struct Counters { counters: Option>, } impl Counters { fn new(enabled: bool) -> Self { Self { counters: enabled.then_some(Vec::new()), } } fn new_timer(&mut self, name: &'static str) -> Option> { self.counters.as_mut().map(|counters| Timer { name, start: Instant::now(), counters, }) } } impl Drop for Counters { fn drop(&mut self) { let Some(counters) = self.counters.take() else { return; }; if counters.is_empty() { return; } let max_width = counters .iter() .map(|(name, _)| name.len()) .max() .unwrap_or(0) .max("Total".len()) + 1; // +1 for the colon let mut total = Duration::ZERO; eprintln!(); for (name, elapsed) in &counters { eprintln!( "{: 1 { eprintln!("{:(src: Source<'_, R>, args: &Opt, context: &mut Context) -> Result<()> { if let Some(arg) = args.dump_ast { let mut counters = Counters::new(args.time); let arg = arg.unwrap_or_default(); let mut parser = boa_parser::Parser::new(src); let dump = if args.module { let scope = context.realm().scope().clone(); let module = { let _timer = counters.new_timer("Parsing"); parser .parse_module(&scope, context.interner_mut()) .map_err(|e| eyre!("Uncaught SyntaxError: {e}"))? }; let _timer = counters.new_timer("AST generation"); match arg { DumpFormat::Json => serde_json::to_string(&module) .expect("could not convert AST to a JSON string"), DumpFormat::JsonPretty => serde_json::to_string_pretty(&module) .expect("could not convert AST to a pretty JSON string"), DumpFormat::Debug => format!("{module:#?}"), } } else { let scope = context.realm().scope().clone(); let mut script = { let _timer = counters.new_timer("Parsing"); parser .parse_script(&scope, context.interner_mut()) .map_err(|e| eyre!("Uncaught SyntaxError: {e}"))? }; if args.optimize { context.optimize_statement_list(script.statements_mut()); } let _timer = counters.new_timer("AST generation"); match arg { DumpFormat::Json => serde_json::to_string(&script) .expect("could not convert AST to a JSON string"), DumpFormat::JsonPretty => serde_json::to_string_pretty(&script) .expect("could not convert AST to a pretty JSON string"), DumpFormat::Debug => format!("{script:#?}"), } }; drop(counters); println!("{dump}"); } Ok(()) } fn generate_flowgraph( context: &mut Context, src: Source<'_, R>, format: FlowgraphFormat, direction: Option, ) -> Result { let script = Script::parse(src, None, context).map_err(|e| e.into_erased(context))?; let code = script .codeblock(context) .map_err(|e| e.into_erased(context))?; 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(graph.subgraph(String::default())); let result = match format { FlowgraphFormat::Graphviz => graph.to_graphviz_format(), FlowgraphFormat::Mermaid => graph.to_mermaid_format(), }; Ok(result) } #[must_use] fn uncaught_error(error: &JsError) -> String { format!( "{} {}\n", "Uncaught Error:".red().bold(), error.to_string().red() ) } #[must_use] fn uncaught_job_error(error: &JsError) -> String { format!( "{} {}\n", "Uncaught Error (during job evaluation):".red().bold(), error.to_string().red() ) } fn evaluate_expr( line: &str, args: &Opt, context: &mut Context, printer: &SharedExternalPrinterLogger, ) -> Result<()> { if args.has_dump_flag() { dump(Source::from_bytes(line), args, context)?; } else if let Some(flowgraph) = args.flowgraph { match generate_flowgraph( context, Source::from_bytes(line), flowgraph.unwrap_or(FlowgraphFormat::Graphviz), args.flowgraph_direction, ) { Ok(v) => println!("{v}"), Err(v) => eprintln!("{v:?}"), } } else { let mut counters = Counters::new(args.time); let script = { let _timer = counters.new_timer("Parsing"); Script::parse(Source::from_bytes(line), None, context) }; match script { Ok(script) => { let result = { let _timer = counters.new_timer("Execution"); let result = script.evaluate(context); if let Err(err) = context.run_jobs() { printer.print(uncaught_job_error(&err)); } result }; match result { Ok(v) => printer.print(format!("{}\n", v.display())), Err(ref v) => printer.print(uncaught_error(v)), } } Err(ref v) => printer.print(uncaught_error(v)), } } Ok(()) } fn evaluate_file( file: &Path, args: &Opt, context: &mut Context, loader: &SimpleModuleLoader, printer: &SharedExternalPrinterLogger, ) -> Result<()> { if args.has_dump_flag() { return dump(Source::from_filepath(file)?, args, context); } if let Some(flowgraph) = args.flowgraph { let flowgraph = generate_flowgraph( context, Source::from_filepath(file)?, flowgraph.unwrap_or(FlowgraphFormat::Graphviz), args.flowgraph_direction, )?; println!("{flowgraph}"); return Ok(()); } if args.module { let source = Source::from_filepath(file)?; let mut counters = Counters::new(args.time); let module = { let _timer = counters.new_timer("Parsing"); Module::parse(source, None, context) }; let module = module.map_err(|e| e.into_erased(context))?; loader.insert( file.canonicalize() .wrap_err("could not canonicalize input file path")?, module.clone(), ); let promise = { let _timer = counters.new_timer("Execution"); let promise = module.load_link_evaluate(context); context.run_jobs().map_err(|err| err.into_erased(context))?; Ok::<_, JsErasedError>(promise) }?; let result = promise.state(); return match result { PromiseState::Pending => Err(eyre!("module didn't execute")), PromiseState::Fulfilled(_) => Ok(()), PromiseState::Rejected(err) => { Err(JsError::from_opaque(err).into_erased(context).into()) } }; } let source = Source::from_filepath(file)?; let mut counters = Counters::new(args.time); let script = { let _timer = counters.new_timer("Parsing"); Script::parse(source, None, context) }; let script = script.map_err(|e| e.into_erased(context))?; let result = { let _timer = counters.new_timer("Execution"); let result = script.evaluate(context); context.run_jobs().map_err(|err| err.into_erased(context))?; result }; match result { Ok(v) => { if !v.is_undefined() { println!("{}", v.display()); } } Err(v) => printer.print(uncaught_error(&v)), } Ok(()) } fn evaluate_files( args: &Opt, context: &mut Context, loader: &SimpleModuleLoader, printer: &SharedExternalPrinterLogger, ) -> Result<()> { for file in &args.files { evaluate_file(file, args, context, loader, printer)?; } Ok(()) } #[expect(clippy::too_many_lines)] fn main() -> Result<()> { color_eyre::config::HookBuilder::default() .display_location_section(false) .display_env_section(false) .install()?; rustls::crypto::ring::default_provider() .install_default() .map_err(|_| eyre!("could not install ring as the default crypto provider"))?; #[cfg(feature = "dhat")] let _profiler = dhat::Profiler::new_heap(); let args = Opt::parse(); // A channel of expressions to run. let (sender, receiver) = async_channel::unbounded(); let printer = SharedExternalPrinterLogger::new(); let executor = Rc::new(Executor::new(printer.clone())); let loader = Rc::new(SimpleModuleLoader::new(&args.root).map_err(|e| eyre!(e.to_string()))?); let context = &mut ContextBuilder::new() .job_executor(executor.clone()) .module_loader(loader.clone()) .build() .map_err(|e| eyre!(e.to_string()))?; // Strict mode context.strict(args.strict); // Add `console`. add_runtime(printer.clone(), context); // Trace Output context.set_trace(args.trace); if args.debug_object { init_boa_debug_object(context); } // Configure optimizer options let mut optimizer_options = OptimizerOptions::empty(); optimizer_options.set(OptimizerOptions::STATISTICS, args.optimizer_statistics); optimizer_options.set(OptimizerOptions::OPTIMIZE_ALL, args.optimize); context.set_optimizer_options(optimizer_options); if !args.files.is_empty() { evaluate_files(&args, context, &loader, &printer)?; if let Some(ref expr) = args.expression { evaluate_expr(expr, &args, context, &printer)?; } return Ok(()); } else if let Some(ref expr) = args.expression { evaluate_expr(expr, &args, context, &printer)?; return Ok(()); } else if !io::stdin().is_terminal() { let mut input = String::new(); io::stdin() .read_to_string(&mut input) .wrap_err("failed to read stdin")?; return if input.is_empty() { Ok(()) } else { evaluate_expr(&input, &args, context, &printer) }; } // Print the welcome banner unless --quiet is passed. if !args.quiet { let version = env!("CARGO_PKG_VERSION"); println!("{}", format!("Welcome to Boa v{version}").bold()); println!( "Type {} for more information, {} to exit.", "\".help\"".green(), "Ctrl+D".green() ); println!(); } let handle = start_readline_thread(sender, printer.clone(), args.vi_mode, args.strict); // TODO: Replace the `__BOA_LOAD_FILE__` string sentinel with a `CliCommand` enum // (e.g. `Exec(String)` / `LoadFile(PathBuf)`) for type-safe cross-thread communication. let exec = executor.clone(); let eval_loop = NativeAsyncJob::new(async move |context| { while let Ok(line) = receiver.recv().await { let printer_clone = printer.clone(); if let Some(file_path) = line.strip_prefix("__BOA_LOAD_FILE__:") { let path = Path::new(file_path); if path.exists() { let mut context = context.borrow_mut(); if let Err(e) = evaluate_file(path, &args, &mut context, &loader, &printer_clone) { printer_clone.print(format!("{e}\n")); } } else { printer_clone.print(format!( "{} file '{}' not found\n", "Error:".red().bold(), file_path )); } continue; } // schedule a new evaluation job that can run asynchronously // with the other evaluations. let eval_script = NativeAsyncJob::new(async move |context| { let script = match Script::parse(Source::from_bytes(&line), None, &mut context.borrow_mut()) { Ok(script) => script, Err(err) => { printer_clone.print(uncaught_error(&err)); return Ok(JsValue::undefined()); } }; // TODO: would be better to avoid blocking until the // script finishes executing, but need to think about how // to change the API of `evaluate_async` to enable that. // (or I guess we could also implement web workers) let value = match script.evaluate(&mut context.borrow_mut()) { Ok(value) => value, Err(err) => { printer_clone.print(uncaught_job_error(&err)); return Ok(JsValue::undefined()); } }; printer_clone.print(format!("{}\n", value.display())); Ok(JsValue::undefined()) }); context.borrow_mut().enqueue_job(eval_script.into()); } // channel was closed, so clear the executor queue to abort all // pending jobs and exit. exec.clear(); Ok(JsValue::undefined()) }); context.enqueue_job(eval_loop.into()); let result = context.run_jobs().map_err(|e| e.into_erased(context)); handle.join().expect("failed to join thread"); Ok(result?) } fn readline_thread_main( sender: &Sender, printer_out: &SharedExternalPrinterLogger, vi_mode: bool, strict: bool, ) -> Result<()> { let config = Config::builder() .keyseq_timeout(Some(1)) .edit_mode(if vi_mode { EditMode::Vi } else { EditMode::Emacs }) .build(); let mut editor = Editor::with_config(config).wrap_err("failed to set the editor configuration")?; if let Ok(printer) = editor.create_external_printer() { printer_out.set(printer); } // Check if the history file exists. If it doesn't, create it. OpenOptions::new() .read(true) .write(true) .create(true) .truncate(false) .open(CLI_HISTORY)?; editor .load_history(CLI_HISTORY) .wrap_err("failed to read history file `.boa_history`")?; let readline = ">> "; editor.set_helper(Some(helper::RLHelper::new(readline, strict))); loop { match editor.readline(readline).map(|l| l.trim().to_string()) { Ok(line) if line == ".exit" => break, Err(ReadlineError::Eof) => break, Err(ReadlineError::Interrupted) => { println!("(To exit, press Ctrl+D or type .exit)"); } Ok(ref line) if line == ".help" => { println!("REPL Commands:"); println!(" {} Show this help message", ".help".green()); println!(" {} Exit the REPL", ".exit".green()); println!(" {} Clear the terminal screen", ".clear".green()); println!( " {} Load and evaluate a JavaScript file", ".load ".green() ); println!(); println!("Press {} to abort the current expression.", "Ctrl+C".bold()); println!("Press {} to exit the REPL.", "Ctrl+D".bold()); } Ok(ref line) if line == ".clear" => { print!("\x1B[2J\x1B[3J\x1B[1;1H"); io::stdout().flush().ok(); } Ok(ref line) if line == ".load" || line.starts_with(".load ") => { let file = line.strip_prefix(".load").unwrap_or("").trim(); if file.is_empty() { eprintln!("{}", "Usage: .load ".yellow()); } else { sender.send_blocking(format!("__BOA_LOAD_FILE__:{file}"))?; thread::sleep(Duration::from_millis(10)); } } Ok(line) => { editor.add_history_entry(&line).map_err(io::Error::other)?; sender.send_blocking(line)?; thread::sleep(Duration::from_millis(10)); } Err(err) => { let final_error = eyre!("could not read the next line of the input"); let final_error = if let Err(e) = editor.save_history(CLI_HISTORY) { final_error.error(e) } else { final_error }; return Err(final_error.error(err)); } } } editor.save_history(CLI_HISTORY)?; Ok(()) } /// Create the readline thread which sends lines from stdin back to the main thread. fn start_readline_thread( sender: Sender, printer_out: SharedExternalPrinterLogger, vi_mode: bool, strict: bool, ) -> thread::JoinHandle<()> { thread::spawn( move || match readline_thread_main(&sender, &printer_out, vi_mode, strict) { Ok(()) => {} Err(e) => eprintln!("readline thread failed: {e}"), }, ) } /// Adds the CLI runtime to the context with default options. fn add_runtime(printer: SharedExternalPrinterLogger, context: &mut Context) { boa_runtime::register( ( boa_runtime::extensions::ConsoleExtension(printer), #[cfg(feature = "fetch")] boa_runtime::extensions::FetchExtension( boa_runtime::fetch::BlockingReqwestFetcher::default(), ), ), None, context, ) .expect("should not fail while registering the runtime"); } ================================================ FILE: clippy.toml ================================================ doc-valid-idents = ['ECMAScript', 'JavaScript', 'SpiderMonkey', 'GitHub'] allow-print-in-tests = true disallowed-methods = [ { path = "str::to_ascii_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_lowercase` instead." }, { path = "str::to_ascii_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_uppercase` instead." }, { path = "str::to_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_lowercase` instead." }, { path = "str::to_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_uppercase` instead." }, { path = "str::replace", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replace` instead." }, { path = "str::replacen", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replacen` instead." }, ] ================================================ FILE: core/ast/ABOUT.md ================================================ # 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_cli`**][cli] - Boa's CLI && REPL implementation - [**`boa_ast`**][ast] - Boa's ECMAScript Abstract Syntax Tree. - [**`boa_engine`**][engine] - Boa's implementation of ECMAScript builtin objects and execution. - [**`boa_gc`**][gc] - Boa's garbage collector. - [**`boa_icu_provider`**][icu] - Boa's ICU4X data provider. - [**`boa_interner`**][interner] - Boa's string interner. - [**`boa_macros`**][macros] - Boa's macros. - [**`boa_parser`**][parser] - Boa's lexer and parser. - [**`boa_runtime`**][runtime] - Boa's `WebAPI` features. - [**`boa_string`**][string] - Boa's ECMAScript string implementation. - [**`boa_wintertc`**][wintertc] - Boa's `WinterTC` (TC55) Minimum Common Web API implementation. - [**`tag_ptr`**][tag_ptr] - Utility library that enables a pointer to be associated with a tag of type `usize`. - [**`small_btree`**][small_btree] - Utility library that adds the `SmallBTreeMap` data structure. [boa-conformance]: https://boajs.dev/conformance [boa-web]: https://boajs.dev/ [boa-playground]: https://boajs.dev/playground [ast]: https://docs.rs/boa_ast/latest/boa_ast/index.html [engine]: https://docs.rs/boa_engine/latest/boa_engine/index.html [gc]: https://docs.rs/boa_gc/latest/boa_gc/index.html [interner]: https://docs.rs/boa_interner/latest/boa_interner/index.html [parser]: https://docs.rs/boa_parser/latest/boa_parser/index.html [icu]: https://docs.rs/boa_icu_provider/latest/boa_icu_provider/index.html [runtime]: https://docs.rs/boa_runtime/latest/boa_runtime/index.html [string]: https://docs.rs/boa_string/latest/boa_string/index.html [wintertc]: https://docs.rs/boa_wintertc/latest/boa_wintertc/index.html [tag_ptr]: https://docs.rs/tag_ptr/latest/tag_ptr/index.html [small_btree]: https://docs.rs/small_btree/latest/small_btree/index.html [macros]: https://docs.rs/boa_macros/latest/boa_macros/index.html [cli]: https://crates.io/crates/boa_cli ================================================ FILE: core/ast/Cargo.toml ================================================ [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] annex-b = [] serde = ["dep:serde", "boa_interner/serde", "bitflags/serde", "num-bigint/serde"] arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"] [dependencies] boa_interner.workspace = true boa_macros.workspace = true boa_string.workspace = true rustc-hash = { workspace = true, features = ["std"] } bitflags.workspace = true num-bigint.workspace = true serde = { workspace = true, features = ["derive"], optional = true } arbitrary = { workspace = true, features = ["derive"], optional = true } indexmap.workspace = true strum.workspace = true [lints] workspace = true [package.metadata.docs.rs] all-features = true ================================================ FILE: core/ast/src/declaration/export.rs ================================================ //! Export declaration AST nodes. //! //! This module contains `export` declaration AST nodes. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript specification][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-exports //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export use super::{ImportAttribute, ModuleSpecifier, VarDeclaration}; use crate::{ Declaration, Expression, function::{ AsyncFunctionDeclaration, AsyncGeneratorDeclaration, ClassDeclaration, FunctionDeclaration, GeneratorDeclaration, }, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::Sym; use std::ops::ControlFlow; /// The kind of re-export in an [`ExportDeclaration`]. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum ReExportKind { /// Namespaced Re-export (`export * as name from "module-name"`). Namespaced { /// Reexported name for the imported module. name: Option, }, /// Re-export list (`export { export1, export2 as alias2 } from "module-name"`). Named { /// List of the required re-exports of the re-exported module. names: Box<[ExportSpecifier]>, }, } impl VisitWith for ReExportKind { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::Namespaced { name: Some(name) } => visitor.visit_sym(name), Self::Namespaced { name: None } => ControlFlow::Continue(()), Self::Named { names } => { for name in &**names { visitor.visit_export_specifier(name)?; } ControlFlow::Continue(()) } } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::Namespaced { name: Some(name) } => visitor.visit_sym_mut(name), Self::Namespaced { name: None } => ControlFlow::Continue(()), Self::Named { names } => { for name in &mut **names { visitor.visit_export_specifier_mut(name)?; } ControlFlow::Continue(()) } } } } /// An export declaration AST node. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq)] pub enum ExportDeclaration { /// Re-export. ReExport { /// The kind of reexport declared. kind: ReExportKind, /// Reexported module specifier. specifier: ModuleSpecifier, /// Re-export attributes. attributes: Box<[ImportAttribute]>, }, /// List of exports. List(Box<[ExportSpecifier]>), /// Variable statement export. VarStatement(VarDeclaration), /// Declaration export. Declaration(Declaration), /// Default function export. DefaultFunctionDeclaration(FunctionDeclaration), /// Default generator export. DefaultGeneratorDeclaration(GeneratorDeclaration), /// Default async function export. DefaultAsyncFunctionDeclaration(AsyncFunctionDeclaration), /// Default async generator export. DefaultAsyncGeneratorDeclaration(AsyncGeneratorDeclaration), /// Default class declaration export. DefaultClassDeclaration(Box), /// Default assignment expression export. DefaultAssignmentExpression(Expression), } impl VisitWith for ExportDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::ReExport { specifier, kind, attributes, } => { visitor.visit_module_specifier(specifier)?; visitor.visit_re_export_kind(kind)?; for attribute in &**attributes { visitor.visit_import_attribute(attribute)?; } ControlFlow::Continue(()) } Self::List(list) => { for item in &**list { visitor.visit_export_specifier(item)?; } ControlFlow::Continue(()) } Self::VarStatement(var) => visitor.visit_var_declaration(var), Self::Declaration(decl) => visitor.visit_declaration(decl), Self::DefaultFunctionDeclaration(f) => visitor.visit_function_declaration(f), Self::DefaultGeneratorDeclaration(g) => visitor.visit_generator_declaration(g), Self::DefaultAsyncFunctionDeclaration(af) => { visitor.visit_async_function_declaration(af) } Self::DefaultAsyncGeneratorDeclaration(ag) => { visitor.visit_async_generator_declaration(ag) } Self::DefaultClassDeclaration(c) => visitor.visit_class_declaration(c), Self::DefaultAssignmentExpression(expr) => visitor.visit_expression(expr), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::ReExport { specifier, kind, attributes, } => { visitor.visit_module_specifier_mut(specifier)?; visitor.visit_re_export_kind_mut(kind)?; for attribute in &mut **attributes { visitor.visit_import_attribute_mut(attribute)?; } ControlFlow::Continue(()) } Self::List(list) => { for item in &mut **list { visitor.visit_export_specifier_mut(item)?; } ControlFlow::Continue(()) } Self::VarStatement(var) => visitor.visit_var_declaration_mut(var), Self::Declaration(decl) => visitor.visit_declaration_mut(decl), Self::DefaultFunctionDeclaration(f) => visitor.visit_function_declaration_mut(f), Self::DefaultGeneratorDeclaration(g) => visitor.visit_generator_declaration_mut(g), Self::DefaultAsyncFunctionDeclaration(af) => { visitor.visit_async_function_declaration_mut(af) } Self::DefaultAsyncGeneratorDeclaration(ag) => { visitor.visit_async_generator_declaration_mut(ag) } Self::DefaultClassDeclaration(c) => visitor.visit_class_declaration_mut(c), Self::DefaultAssignmentExpression(expr) => visitor.visit_expression_mut(expr), } } } /// Export specifier /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub struct ExportSpecifier { alias: Sym, private_name: Sym, string_literal: bool, } impl ExportSpecifier { /// Creates a new [`ExportSpecifier`]. #[inline] #[must_use] pub const fn new(alias: Sym, private_name: Sym, string_literal: bool) -> Self { Self { alias, private_name, string_literal, } } /// Gets the original alias. #[inline] #[must_use] pub const fn alias(self) -> Sym { self.alias } /// Gets the private name of the export inside the module. #[inline] #[must_use] pub const fn private_name(self) -> Sym { self.private_name } /// Returns `true` if the private name of the specifier was a `StringLiteral`. #[inline] #[must_use] pub const fn string_literal(&self) -> bool { self.string_literal } } impl VisitWith for ExportSpecifier { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_sym(&self.alias)?; visitor.visit_sym(&self.private_name) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_sym_mut(&mut self.alias)?; visitor.visit_sym_mut(&mut self.private_name) } } /// The name under which a reexported binding is exported by a module. /// /// This differs slightly from the spec, since `[[ImportName]]` can be either a name, `all-but-default` /// or `all`, but the last two exports can be identified with the `export_name` field from /// [`ExportEntry`], which joins both variants into a single `Star` variant. #[derive(Debug, Clone, Copy)] pub enum ReExportImportName { /// A binding of the imported module. Name(Sym), /// All exports of the module. Star, } /// [`ExportEntry`][spec] record. /// /// [spec]: https://tc39.es/ecma262/#table-exportentry-records #[derive(Debug, Clone)] pub enum ExportEntry { /// An ordinary export entry Ordinary(LocalExportEntry), /// A star reexport entry. StarReExport { /// The module from where this reexport will import. module_request: Sym, /// The import attributes for this reexport. attributes: Box<[ImportAttribute]>, }, /// A reexport entry with an export name. ReExport(IndirectExportEntry), } impl From for ExportEntry { fn from(v: IndirectExportEntry) -> Self { Self::ReExport(v) } } impl From for ExportEntry { fn from(v: LocalExportEntry) -> Self { Self::Ordinary(v) } } /// A local export entry #[derive(Debug, Clone, Copy)] pub struct LocalExportEntry { local_name: Sym, export_name: Sym, } impl LocalExportEntry { /// Creates a new `LocalExportEntry`. #[must_use] pub const fn new(local_name: Sym, export_name: Sym) -> Self { Self { local_name, export_name, } } /// Gets the local name of this export entry. #[must_use] pub const fn local_name(&self) -> Sym { self.local_name } /// Gets the export name of this export entry. #[must_use] pub const fn export_name(&self) -> Sym { self.export_name } } /// A reexported export entry. #[derive(Debug, Clone)] pub struct IndirectExportEntry { module_request: Sym, import_name: ReExportImportName, export_name: Sym, attributes: Box<[ImportAttribute]>, } impl IndirectExportEntry { /// Creates a new `IndirectExportEntry`. #[must_use] pub fn new( module_request: Sym, import_name: ReExportImportName, export_name: Sym, attributes: Box<[ImportAttribute]>, ) -> Self { Self { module_request, import_name, export_name, attributes, } } /// Gets the module from where this entry reexports. #[must_use] pub const fn module_request(&self) -> Sym { self.module_request } /// Gets the import name of the reexport. #[must_use] pub const fn import_name(&self) -> ReExportImportName { self.import_name } /// Gets the public alias of the reexport. #[must_use] pub const fn export_name(&self) -> Sym { self.export_name } /// Gets the import attributes. #[must_use] pub fn attributes(&self) -> &[ImportAttribute] { &self.attributes } } ================================================ FILE: core/ast/src/declaration/import.rs ================================================ //! Import declaration AST nodes. //! //! This module contains `import` declaration AST nodes. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript specification][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-imports //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import use std::ops::ControlFlow; use crate::{ expression::Identifier, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::Sym; use super::{ImportAttribute, ModuleSpecifier}; /// The kind of import in an [`ImportDeclaration`]. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum ImportKind { /// Default (`import defaultName from "module-name"`) or unnamed (`import "module-name"`). DefaultOrUnnamed, /// Namespaced import (`import * as name from "module-name"`). Namespaced { /// Binding for the namespace created from the exports of the imported module. binding: Identifier, }, /// Import list (`import { export1, export2 as alias2 } from "module-name"`). Named { /// List of the required exports of the imported module. names: Box<[ImportSpecifier]>, }, } impl VisitWith for ImportKind { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::DefaultOrUnnamed => ControlFlow::Continue(()), Self::Namespaced { binding } => visitor.visit_identifier(binding), Self::Named { names } => { for name in &**names { visitor.visit_import_specifier(name)?; } ControlFlow::Continue(()) } } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::DefaultOrUnnamed => ControlFlow::Continue(()), Self::Namespaced { binding } => visitor.visit_identifier_mut(binding), Self::Named { names } => { for name in &mut **names { visitor.visit_import_specifier_mut(name)?; } ControlFlow::Continue(()) } } } } /// An import declaration AST node. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct ImportDeclaration { /// Binding for the default export of `specifier`. default: Option, /// See [`ImportKind`]. kind: ImportKind, /// Module specifier. specifier: ModuleSpecifier, /// Import attributes. attributes: Box<[ImportAttribute]>, } impl ImportDeclaration { /// Creates a new import declaration. #[inline] #[must_use] pub fn new( default: Option, kind: ImportKind, specifier: ModuleSpecifier, attributes: Box<[ImportAttribute]>, ) -> Self { Self { default, kind, specifier, attributes, } } /// Gets the binding for the default export of the module. #[inline] #[must_use] pub const fn default(&self) -> Option { self.default } /// Gets the module specifier of the import declaration. #[inline] #[must_use] pub const fn specifier(&self) -> ModuleSpecifier { self.specifier } /// Gets the import kind of the import declaration. #[inline] #[must_use] pub const fn kind(&self) -> &ImportKind { &self.kind } /// Gets the import attributes of the import declaration. #[inline] #[must_use] pub const fn attributes(&self) -> &[ImportAttribute] { &self.attributes } } impl VisitWith for ImportDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(default) = &self.default { visitor.visit_identifier(default)?; } visitor.visit_import_kind(&self.kind)?; visitor.visit_module_specifier(&self.specifier)?; for attribute in &*self.attributes { visitor.visit_import_attribute(attribute)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(default) = &mut self.default { visitor.visit_identifier_mut(default)?; } visitor.visit_import_kind_mut(&mut self.kind)?; visitor.visit_module_specifier_mut(&mut self.specifier)?; for attribute in &mut *self.attributes { visitor.visit_import_attribute_mut(attribute)?; } ControlFlow::Continue(()) } } /// Import specifier /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ImportSpecifier { binding: Identifier, export_name: Sym, } impl ImportSpecifier { /// Creates a new [`ImportSpecifier`]. #[inline] #[must_use] pub const fn new(binding: Identifier, export_name: Sym) -> Self { Self { binding, export_name, } } /// Gets the binding of the import specifier. #[inline] #[must_use] pub const fn binding(self) -> Identifier { self.binding } /// Gets the optional export name of the import. #[inline] #[must_use] pub const fn export_name(self) -> Sym { self.export_name } } impl VisitWith for ImportSpecifier { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_identifier(&self.binding)?; visitor.visit_sym(&self.export_name) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_identifier_mut(&mut self.binding)?; visitor.visit_sym_mut(&mut self.export_name) } } /// The name under which the imported binding is exported by a module. #[derive(Debug, Clone, Copy)] pub enum ImportName { /// The namespace object of the imported module. Namespace, /// A binding of the imported module. Name(Sym), } /// [`ImportEntry`][spec] record. /// /// [spec]: https://tc39.es/ecma262/#table-importentry-record-fields #[derive(Debug, Clone)] pub struct ImportEntry { module_request: Sym, import_name: ImportName, local_name: Identifier, attributes: Box<[ImportAttribute]>, } impl ImportEntry { /// Creates a new `ImportEntry`. #[must_use] pub fn new( module_request: Sym, import_name: ImportName, local_name: Identifier, attributes: Box<[ImportAttribute]>, ) -> Self { Self { module_request, import_name, local_name, attributes, } } /// Gets the module from where the binding must be imported. #[must_use] pub const fn module_request(&self) -> Sym { self.module_request } /// Gets the import name of the imported binding. #[must_use] pub const fn import_name(&self) -> ImportName { self.import_name } /// Gets the local name of the imported binding. #[must_use] pub const fn local_name(&self) -> Identifier { self.local_name } /// Gets the import attributes. #[must_use] pub fn attributes(&self) -> &[ImportAttribute] { &self.attributes } } ================================================ FILE: core/ast/src/declaration/mod.rs ================================================ //! 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::{ AsyncFunctionDeclaration, AsyncGeneratorDeclaration, FunctionDeclaration, GeneratorDeclaration, }; use crate::{ function::ClassDeclaration, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; mod export; mod import; mod variable; pub use export::*; pub use import::*; 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum Declaration { /// See [`FunctionDeclaration`] FunctionDeclaration(FunctionDeclaration), /// See [`GeneratorDeclaration`] GeneratorDeclaration(GeneratorDeclaration), /// See [`AsyncFunctionDeclaration`] AsyncFunctionDeclaration(AsyncFunctionDeclaration), /// See [`AsyncGeneratorDeclaration`] AsyncGeneratorDeclaration(AsyncGeneratorDeclaration), /// See [`ClassDeclaration`] ClassDeclaration(Box), /// See [`LexicalDeclaration`] Lexical(LexicalDeclaration), } impl ToIndentedString for Declaration { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { match self { Self::FunctionDeclaration(f) => f.to_indented_string(interner, indentation), Self::GeneratorDeclaration(g) => g.to_indented_string(interner, indentation), Self::AsyncFunctionDeclaration(af) => af.to_indented_string(interner, indentation), Self::AsyncGeneratorDeclaration(ag) => ag.to_indented_string(interner, indentation), Self::ClassDeclaration(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 where V: Visitor<'a>, { match self { Self::FunctionDeclaration(f) => visitor.visit_function_declaration(f), Self::GeneratorDeclaration(g) => visitor.visit_generator_declaration(g), Self::AsyncFunctionDeclaration(af) => visitor.visit_async_function_declaration(af), Self::AsyncGeneratorDeclaration(ag) => visitor.visit_async_generator_declaration(ag), Self::ClassDeclaration(c) => visitor.visit_class_declaration(c), Self::Lexical(ld) => visitor.visit_lexical_declaration(ld), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::FunctionDeclaration(f) => visitor.visit_function_declaration_mut(f), Self::GeneratorDeclaration(g) => visitor.visit_generator_declaration_mut(g), Self::AsyncFunctionDeclaration(af) => visitor.visit_async_function_declaration_mut(af), Self::AsyncGeneratorDeclaration(ag) => { visitor.visit_async_generator_declaration_mut(ag) } Self::ClassDeclaration(c) => visitor.visit_class_declaration_mut(c), Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld), } } } /// Module specifier. /// /// This is equivalent to the [`ModuleSpecifier`] production. /// /// [`FromClause`]: https://tc39.es/ecma262/#prod-ModuleSpecifier #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub struct ModuleSpecifier { module: Sym, } impl ModuleSpecifier { /// Creates a `ModuleSpecifier` from a `Sym`. #[must_use] pub const fn new(module: Sym) -> Self { Self { module } } /// Gets the inner `Sym` of the module specifier. #[inline] #[must_use] pub const fn sym(self) -> Sym { self.module } } impl From for ModuleSpecifier { #[inline] fn from(module: Sym) -> Self { Self::new(module) } } impl VisitWith for ModuleSpecifier { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_sym(&self.module) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_sym_mut(&mut self.module) } } /// An import attribute entry in an [`ImportDeclaration`]. /// /// This is a key-value pair, where the key is an identifier or string literal, /// and the value is a string literal. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-imports #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub struct ImportAttribute { key: Sym, value: Sym, } impl ImportAttribute { /// Creates a new `ImportAttribute`. #[inline] #[must_use] pub const fn new(key: Sym, value: Sym) -> Self { Self { key, value } } /// Gets the attribute key. #[inline] #[must_use] pub const fn key(self) -> Sym { self.key } /// Gets the attribute value. #[inline] #[must_use] pub const fn value(self) -> Sym { self.value } } impl VisitWith for ImportAttribute { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_sym(&self.key)?; visitor.visit_sym(&self.value) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_sym_mut(&mut self.key)?; visitor.visit_sym_mut(&mut self.value) } } ================================================ FILE: core/ast/src/declaration/variable.rs ================================================ //! Variable related declarations. use super::Declaration; use crate::{ Statement, expression::{Expression, Identifier}, join_nodes, pattern::Pattern, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::{convert::TryFrom, fmt::Write as _, ops::ControlFlow}; /// 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 [hoisting]. /// /// [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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct VarDeclaration(pub VariableList); impl From 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 where V: Visitor<'a>, { visitor.visit_variable_list(&self.0) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum LexicalDeclaration { /// A [const] 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 [let] 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), /// A [using] declaration creates a block-scoped resource that is automatically /// disposed when control exits the block. /// /// [using]: https://tc39.es/proposal-explicit-resource-management/ Using(VariableList), /// An [await using] declaration creates a block-scoped resource that is automatically /// disposed asynchronously when control exits the block. /// /// [await using]: https://tc39.es/proposal-explicit-resource-management/ AwaitUsing(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) | Self::Using(list) | Self::AwaitUsing(list) => { list } } } /// Returns `true` if the declaration is a `const` declaration. #[must_use] pub const fn is_const(&self) -> bool { matches!(self, Self::Const(_)) } } impl From 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::Using(_) => "using", Self::AwaitUsing(_) => "await using", }, self.variable_list().to_interned_string(interner) ) } } impl VisitWith for LexicalDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::Const(vars) | Self::Let(vars) | Self::Using(vars) | Self::AwaitUsing(vars) => { visitor.visit_variable_list(vars) } } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::Const(vars) | Self::Let(vars) | Self::Using(vars) | Self::AwaitUsing(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 = "arbitrary", 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 { 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 where V: Visitor<'a>, { for variable in &*self.list { visitor.visit_variable(variable)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for variable in &mut *self.list { 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> for VariableList { type Error = TryFromVariableListError; fn try_from(value: Box<[Variable]>) -> Result { Self::new(value).ok_or(TryFromVariableListError(())) } } impl TryFrom> for VariableList { type Error = TryFromVariableListError; fn try_from(value: Vec) -> Result { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Variable { binding: Binding, init: Option, } 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 { let _ = write!(buf, " = {}", 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) -> 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) -> 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 where V: Visitor<'a>, { visitor.visit_binding(&self.binding)?; if let Some(init) = &self.init { visitor.visit_expression(init)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_binding_mut(&mut self.binding)?; if let Some(init) = &mut self.init { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum Binding { /// A single identifier binding. Identifier(Identifier), /// A pattern binding. Pattern(Pattern), } impl From for Binding { fn from(id: Identifier) -> Self { Self::Identifier(id) } } impl From 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(pattern) => pattern.to_interned_string(interner), } } } impl VisitWith for Binding { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow 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 where V: VisitorMut<'a>, { match self { Self::Identifier(id) => visitor.visit_identifier_mut(id), Self::Pattern(pattern) => visitor.visit_pattern_mut(pattern), } } } ================================================ FILE: core/ast/src/expression/access.rs ================================================ //! 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::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{Span, Spanned}; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; use super::Identifier; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum PropertyAccessField { /// A constant property field, such as `x.prop`. Const(Identifier), /// An expression property field, such as `x["val"]`. Expr(Box), } impl Spanned for PropertyAccessField { #[inline] fn span(&self) -> Span { match self { Self::Const(identifier) => identifier.span(), Self::Expr(expression) => expression.span(), } } } impl From for PropertyAccessField { #[inline] fn from(id: Identifier) -> Self { Self::Const(id) } } impl From 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 where V: Visitor<'a>, { match self { Self::Const(sym) => visitor.visit_sym(sym.sym_ref()), Self::Expr(expr) => visitor.visit_expression(expr), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::Const(sym) => visitor.visit_sym_mut(sym.sym_mut()), 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 = "arbitrary", 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 Spanned for PropertyAccess { #[inline] fn span(&self) -> Span { match self { Self::Simple(access) => access.span(), Self::Private(access) => access.span(), Self::Super(access) => access.span(), } } } 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 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 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 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct SimplePropertyAccess { target: Box, 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(target: Expression, field: F) -> Self where F: Into, { Self { target: target.into(), field: field.into(), } } } impl Spanned for SimplePropertyAccess { #[inline] fn span(&self) -> Span { Span::new(self.target.span().start(), self.field.span().end()) } } 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(ident) => { format!("{target}.{}", interner.resolve_expect(ident.sym())) } PropertyAccessField::Expr(ref expr) => { format!("{target}[{}]", expr.to_interned_string(interner)) } } } } impl From 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 where V: Visitor<'a>, { 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 where V: VisitorMut<'a>, { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct PrivatePropertyAccess { target: Box, field: PrivateName, span: Span, } impl PrivatePropertyAccess { /// Creates a `GetPrivateField` AST Expression. #[inline] #[must_use] pub fn new(value: Expression, field: PrivateName, span: Span) -> Self { Self { target: value.into(), field, span, } } /// 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 Spanned for PrivatePropertyAccess { #[inline] fn span(&self) -> Span { self.span } } 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 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 where V: Visitor<'a>, { visitor.visit_expression(&self.target)?; visitor.visit_private_name(&self.field) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct SuperPropertyAccess { field: PropertyAccessField, span: Span, } impl SuperPropertyAccess { /// Creates a new property access field node. #[must_use] pub const fn new(field: PropertyAccessField, span: Span) -> Self { Self { field, span } } /// Gets the name of the field to retrieve. #[inline] #[must_use] pub const fn field(&self) -> &PropertyAccessField { &self.field } } impl Spanned for SuperPropertyAccess { #[inline] fn span(&self) -> Span { self.span } } 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.sym())) } PropertyAccessField::Expr(field) => { format!("super[{}]", field.to_interned_string(interner)) } } } } impl From 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 where V: Visitor<'a>, { visitor.visit_property_access_field(&self.field) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_property_access_field_mut(&mut self.field) } } ================================================ FILE: core/ast/src/expression/await.rs ================================================ //! Await expression Expression. use core::ops::ControlFlow; use super::Expression; use crate::{ Span, Spanned, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Await { target: Box, span: Span, } impl Await { /// Create a new [`Await`] node. #[must_use] pub const fn new(target: Box, span: Span) -> Self { Self { target, span } } /// Return the target expression that should be awaited. #[inline] #[must_use] pub const fn target(&self) -> &Expression { &self.target } } impl Spanned for Await { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for Await { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!("await {}", self.target.to_indented_string(interner, 0)) } } impl From 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 where V: Visitor<'a>, { visitor.visit_expression(&self.target) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.target) } } ================================================ FILE: core/ast/src/expression/call.rs ================================================ use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{Span, Spanned, join_nodes}; 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Call { function: Box, args: Box<[Expression]>, span: Span, } impl Call { /// Creates a new `Call` AST Expression. #[inline] #[must_use] pub fn new(function: Expression, args: Box<[Expression]>, span: Span) -> Self { Self { function: Box::new(function), args, span, } } /// 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 Spanned for Call { #[inline] fn span(&self) -> Span { self.span } } 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 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 where V: Visitor<'a>, { visitor.visit_expression(&self.function)?; for expr in &*self.args { visitor.visit_expression(expr)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.function)?; for expr in &mut *self.args { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct SuperCall { args: Box<[Expression]>, span: Span, } impl SuperCall { /// Creates a new `SuperCall` AST node. pub fn new(args: A, span: Span) -> Self where A: Into>, { Self { args: args.into(), span, } } /// Retrieves the arguments of the super call. #[must_use] pub const fn arguments(&self) -> &[Expression] { &self.args } } impl Spanned for SuperCall { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for SuperCall { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!("super({})", join_nodes(interner, &self.args)) } } impl From 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 where V: Visitor<'a>, { for expr in &*self.args { visitor.visit_expression(expr)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for expr in &mut *self.args { visitor.visit_expression_mut(expr)?; } ControlFlow::Continue(()) } } /// The phase of a dynamic import call. /// /// Determines how the imported module is handled: /// - `Evaluation` (default): `import(specifier)` — loads, links, and evaluates the module. /// - `Defer`: `import.defer(specifier)` — deferred evaluation of the module. /// - `Source`: `import.source(specifier)` — source phase import. /// /// More information: /// - [import-defer proposal][defer] /// - [source-phase-imports proposal][source] /// /// [defer]: https://github.com/tc39/proposal-defer-import-eval /// [source]: https://github.com/tc39/proposal-source-phase-imports #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum ImportPhase { /// `import(specifier)` — standard dynamic import. #[default] Evaluation, /// `import.defer(specifier)` — deferred import evaluation. Defer, /// `import.source(specifier)` — source phase import. Source, } /// The `import()` syntax, commonly called dynamic import, is a function-like expression that allows /// loading an ECMAScript module asynchronously and dynamically into a potentially non-module /// environment. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-ImportCall /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ImportCall { specifier: Box, options: Option>, phase: ImportPhase, span: Span, } impl ImportCall { /// Creates a new `ImportCall` AST node. #[inline] #[must_use] pub fn new(specifier: S, options: Option, phase: ImportPhase, span: Span) -> Self where S: Into, { Self { specifier: Box::new(specifier.into()), options: options.map(Box::new), phase, span, } } /// Retrieves the specifier (first argument) of the import call. #[inline] #[must_use] pub const fn specifier(&self) -> &Expression { &self.specifier } /// Retrieves the options (second argument) of the import call, if present. /// /// This is used for import attributes: /// ```js /// import("foo.json", { with: { type: "json" } }) /// ``` #[inline] #[must_use] pub fn options(&self) -> Option<&Expression> { self.options.as_deref() } /// Returns the phase of this import call. #[inline] #[must_use] pub const fn phase(&self) -> ImportPhase { self.phase } /// Gets the module specifier of the import call. /// /// This is an alias for [`Self::specifier`] for backwards compatibility. #[inline] #[must_use] #[deprecated(since = "0.21.0", note = "use `specifier` instead")] pub const fn argument(&self) -> &Expression { &self.specifier } } impl Spanned for ImportCall { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for ImportCall { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { let phase_str = match self.phase { ImportPhase::Evaluation => "", ImportPhase::Defer => ".defer", ImportPhase::Source => ".source", }; if let Some(options) = &self.options { format!( "import{}({}, {})", phase_str, self.specifier.to_interned_string(interner), options.to_interned_string(interner) ) } else { format!( "import{}({})", phase_str, self.specifier.to_interned_string(interner) ) } } } impl From for Expression { #[inline] fn from(call: ImportCall) -> Self { Self::ImportCall(call) } } impl VisitWith for ImportCall { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_expression(&self.specifier)?; if let Some(options) = &self.options { visitor.visit_expression(options)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.specifier)?; if let Some(options) = &mut self.options { visitor.visit_expression_mut(options)?; } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/expression/identifier.rs ================================================ //! Local identifier Expression. use crate::{ Span, Spanned, ToStringEscaped, visitor::{VisitWith, Visitor, VisitorMut}, }; 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))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Identifier { ident: Sym, span: Span, } impl PartialEq for Identifier { #[inline] fn eq(&self, other: &Sym) -> bool { self.ident == *other } } impl PartialEq 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, span: Span) -> Self { Self { ident, span } } /// Retrieves the identifier's string symbol in the interner. #[inline] #[must_use] pub const fn sym(self) -> Sym { self.ident } /// Retrieves the identifier's string symbol in the interner. #[inline] #[must_use] pub const fn sym_ref(&self) -> &Sym { &self.ident } /// Retrieves the identifier's string symbol in the interner. #[inline] #[must_use] pub const fn sym_mut(&mut self) -> &mut Sym { &mut self.ident } } impl Spanned for Identifier { #[inline] fn span(&self) -> Span { self.span } } 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 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 where V: Visitor<'a>, { visitor.visit_sym(&self.ident) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_sym_mut(&mut self.ident) } } ================================================ FILE: core/ast/src/expression/import_meta.rs ================================================ //! `import.meta` ECMAScript expression. use crate::{ Span, Spanned, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; use super::Expression; /// ECMAScript's `ImportMeta` expression AST node. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ImportMeta { span: Span, } impl ImportMeta { /// Creates a new [`ImportMeta`] AST Expression. #[inline] #[must_use] pub const fn new(span: Span) -> Self { Self { span } } } impl Spanned for ImportMeta { #[inline] fn span(&self) -> Span { self.span } } impl From for Expression { #[inline] fn from(value: ImportMeta) -> Self { Expression::ImportMeta(value) } } impl ToInternedString for ImportMeta { #[inline] fn to_interned_string(&self, _interner: &Interner) -> String { String::from("import.meta") } } impl VisitWith for ImportMeta { fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/expression/literal/array.rs ================================================ //! Array declaration Expression. use crate::expression::Expression; use crate::expression::operator::assign::{AssignOp, AssignTarget}; use crate::pattern::{ArrayPattern, ArrayPatternElement, Pattern}; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{Span, Spanned}; 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ArrayLiteral { arr: Box<[Option]>, has_trailing_comma_spread: bool, span: Span, } impl ArrayLiteral { /// Creates a new array literal. pub fn new(array: A, has_trailing_comma_spread: bool, span: Span) -> Self where A: Into]>>, { Self { arr: array.into(), has_trailing_comma_spread, span, } } /// 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 { if self.has_trailing_comma_spread() { return None; } let mut bindings = Vec::new(); for (i, expr) in self.arr.iter().enumerate() { let Some(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) => { if assign.op() != AssignOp::Assign { return None; } match assign.lhs() { AssignTarget::Identifier(ident) => { let mut init = assign.rhs().clone(); init.set_anonymous_function_definition_name(ident); bindings.push(ArrayPatternElement::SingleName { ident: *ident, default_init: Some(init), }); } AssignTarget::Access(access) => { bindings.push(ArrayPatternElement::PropertyAccess { access: access.clone(), default_init: Some(assign.rhs().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(), default_init: None, }); } _ => return None, } } Some(ArrayPattern::new(bindings.into(), self.span)) } } impl Spanned for ArrayLiteral { #[inline] fn span(&self) -> Span { self.span } } impl AsRef<[Option]> for ArrayLiteral { #[inline] fn as_ref(&self) -> &[Option] { &self.arr } } impl AsMut<[Option]> for ArrayLiteral { #[inline] fn as_mut(&mut self) -> &mut [Option] { &mut self.arr } } impl ToInternedString for ArrayLiteral { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { let mut buf = String::from("["); let mut elements = self.arr.iter().peekable(); while let Some(element) = elements.next() { if let Some(e) = element { buf.push_str(&e.to_interned_string(interner)); if elements.peek().is_some() { buf.push_str(", "); } } else if elements.peek().is_some() { buf.push_str(", "); } else { buf.push(','); } } buf.push(']'); buf } } impl From 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 where V: Visitor<'a>, { for expr in self.arr.iter().flatten() { visitor.visit_expression(expr)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for expr in self.arr.iter_mut().flatten() { visitor.visit_expression_mut(expr)?; } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/expression/literal/mod.rs ================================================ //! 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, ObjectMethodDefinition, PropertyDefinition}; pub use template::{TemplateElement, TemplateLiteral}; use crate::{ LinearSpan, LinearSpanIgnoreEq, Span, Spanned, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, PartialEq)] pub struct Literal { kind: LiteralKind, span: Span, linear_span: LinearSpanIgnoreEq, } impl Literal { /// Create a new [`Literal`]. #[inline] #[must_use] pub fn new>(kind: T, span: Span) -> Self { Self { kind: kind.into(), span, linear_span: LinearSpanIgnoreEq(LinearSpan::default()), } } /// Create a new [`Literal`] with a [`LinearSpan`] for source text tracking. #[inline] #[must_use] pub fn with_linear_span>( kind: T, span: Span, linear_span: LinearSpan, ) -> Self { Self { kind: kind.into(), span, linear_span: LinearSpanIgnoreEq(linear_span), } } /// Get reference to the [`LiteralKind`] of [`Literal`]. #[inline] #[must_use] pub const fn kind(&self) -> &LiteralKind { &self.kind } /// Get mutable reference to the [`LiteralKind`] of [`Literal`]. #[inline] #[must_use] pub const fn kind_mut(&mut self) -> &mut LiteralKind { &mut self.kind } /// Get the [`LinearSpan`] of this literal in the source text. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Get position of the node. #[inline] #[must_use] pub const fn as_string(&self) -> Option { if let LiteralKind::String(sym) = self.kind() { return Some(*sym); } None } /// Check if [`Literal`] is a [`LiteralKind::Undefined`]. #[inline] #[must_use] pub const fn is_undefined(&self) -> bool { matches!(self.kind(), LiteralKind::Undefined) } } impl Spanned for Literal { #[inline] fn span(&self) -> Span { self.span } } impl From for Expression { #[inline] fn from(lit: Literal) -> Self { Self::Literal(lit) } } impl ToInternedString for Literal { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { self.kind().to_interned_string(interner) } } impl VisitWith for Literal { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let LiteralKind::String(sym) = &self.kind { visitor.visit_sym(sym) } else { ControlFlow::Continue(()) } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let LiteralKind::String(sym) = &mut self.kind { visitor.visit_sym_mut(sym) } else { ControlFlow::Continue(()) } } } /// 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))] #[derive(Clone, Debug, PartialEq)] pub enum LiteralKind { /// 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), /// 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, /// This represents the JavaScript `undefined` value, it does not reference the `undefined` global variable, /// it will directly evaluate to `undefined`. /// /// NOTE: This is used for optimizations. Undefined, } /// Manual implementation, because `Undefined` is never constructed during parsing. #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for LiteralKind { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let c = >::arbitrary(u)? % 6; match c { 0 => Ok(Self::String(::arbitrary(u)?)), 1 => Ok(Self::Num(::arbitrary(u)?)), 2 => Ok(Self::Int(::arbitrary(u)?)), 3 => Ok(Self::BigInt(Box::new( ::arbitrary(u)?, ))), 4 => Ok(Self::Bool(::arbitrary(u)?)), 5 => Ok(Self::Null), _ => unreachable!(), } } } impl From for LiteralKind { #[inline] fn from(string: Sym) -> Self { Self::String(string) } } impl From for LiteralKind { #[inline] fn from(num: f64) -> Self { Self::Num(num) } } impl From for LiteralKind { #[inline] fn from(i: i32) -> Self { Self::Int(i) } } impl From for LiteralKind { #[inline] fn from(i: BigInt) -> Self { Self::BigInt(Box::new(i)) } } impl From> for LiteralKind { #[inline] fn from(i: Box) -> Self { Self::BigInt(i) } } impl From for LiteralKind { #[inline] fn from(b: bool) -> Self { Self::Bool(b) } } impl ToInternedString for LiteralKind { #[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) => format!("{num}n"), Self::Bool(v) => v.to_string(), Self::Null => "null".to_owned(), Self::Undefined => "undefined".to_owned(), } } } ================================================ FILE: core/ast/src/expression/literal/object.rs ================================================ //! Object Expression. use crate::{ LinearPosition, LinearSpan, LinearSpanIgnoreEq, Span, Spanned, block_to_string, expression::{ Expression, Identifier, RESERVED_IDENTIFIERS_STRICT, operator::assign::{AssignOp, AssignTarget}, }, function::{FormalParameterList, FunctionBody}, join_nodes, operations::{ContainsSymbol, contains}, pattern::{ObjectPattern, ObjectPatternElement}, property::{MethodDefinitionKind, PropertyName}, scope::FunctionScopes, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use core::{fmt::Write as _, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ObjectLiteral { properties: Box<[PropertyDefinition]>, span: Span, } impl ObjectLiteral { /// Create a new [`ObjectLiteral`]. #[inline] #[must_use] pub fn new(properties: T, span: Span) -> Self where T: Into>, { Self { properties: properties.into(), span, } } /// 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 { let mut bindings = 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; } bindings.push(ObjectPatternElement::SingleName { ident: *ident, name: PropertyName::Literal(*ident), default_init: None, }); } PropertyDefinition::Property(name, expr) => match (name, expr) { (PropertyName::Literal(name), Expression::Identifier(ident)) if name.sym() == ident.sym() => { if strict && *name == Sym::EVAL { return None; } if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name.sym()) { return None; } 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)) => { if assign.op() != AssignOp::Assign { return None; } match assign.lhs() { AssignTarget::Identifier(ident) => { if let Some(name) = name.literal() { if name.sym() == ident.sym() { if strict && name == Sym::EVAL { return None; } if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name.sym()) { return None; } } let mut init = assign.rhs().clone(); init.set_anonymous_function_definition_name(ident); bindings.push(ObjectPatternElement::SingleName { ident: *ident, name: PropertyName::Literal(name), default_init: Some(init), }); } 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 }); } Expression::PropertyAccess(access) => { bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess { access: access.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; } let mut expr = expr.clone(); expr.set_anonymous_function_definition_name(ident); bindings.push(ObjectPatternElement::SingleName { ident: *ident, name: PropertyName::Literal(*ident), default_init: Some(expr), }); } } } Some(ObjectPattern::new(bindings.into(), self.span)) } } impl Spanned for ObjectLiteral { #[inline] fn span(&self) -> Span { self.span } } 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 { match property { PropertyDefinition::IdentifierReference(ident) => { let _ = writeln!( buf, "{indentation}{},", interner.resolve_expect(ident.sym()) ); } PropertyDefinition::Property(key, value) => { let _ = writeln!( buf, "{indentation}{}: {},", key.to_interned_string(interner), value.to_no_indent_string(interner, indent_n + 1) ); } PropertyDefinition::SpreadObject(key) => { let _ = writeln!(buf, "{indentation}...{},", key.to_interned_string(interner)); } PropertyDefinition::MethodDefinition(m) => { buf.push_str(&m.to_indented_string(interner, indent_n)); } PropertyDefinition::CoverInitializedName(ident, expr) => { let _ = writeln!( buf, "{indentation}{} = {},", interner.resolve_expect(ident.sym()), expr.to_no_indent_string(interner, indent_n + 1) ); } } } let _ = write!(buf, "{}}}", " ".repeat(indent_n)); buf } } impl From 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 where V: Visitor<'a>, { for pd in &*self.properties { visitor.visit_property_definition(pd)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for pd in &mut *self.properties { visitor.visit_property_definition_mut(pd)?; } ControlFlow::Continue(()) } } /// 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 = "arbitrary", 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(ObjectMethodDefinition), /// 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 where V: Visitor<'a>, { match self { Self::IdentifierReference(id) => visitor.visit_identifier(id), Self::Property(pn, expr) => { visitor.visit_property_name(pn)?; visitor.visit_expression(expr) } Self::MethodDefinition(m) => visitor.visit_object_method_definition(m), Self::SpreadObject(expr) => visitor.visit_expression(expr), Self::CoverInitializedName(id, expr) => { visitor.visit_identifier(id)?; visitor.visit_expression(expr) } } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::IdentifierReference(id) => visitor.visit_identifier_mut(id), Self::Property(pn, expr) => { visitor.visit_property_name_mut(pn)?; visitor.visit_expression_mut(expr) } Self::MethodDefinition(m) => visitor.visit_object_method_definition_mut(m), Self::SpreadObject(expr) => visitor.visit_expression_mut(expr), Self::CoverInitializedName(id, expr) => { visitor.visit_identifier_mut(id)?; visitor.visit_expression_mut(expr) } } } } /// A method definition. /// /// This type is specific to object method definitions. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ObjectMethodDefinition { pub(crate) name: PropertyName, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, kind: MethodDefinitionKind, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, } impl ObjectMethodDefinition { /// Creates a new object method definition. #[inline] #[must_use] pub fn new( name: PropertyName, parameters: FormalParameterList, body: FunctionBody, kind: MethodDefinitionKind, start_linear_pos: LinearPosition, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); let linear_span = LinearSpan::new(start_linear_pos, body.linear_pos_end()).into(); Self { name, parameters, body, contains_direct_eval, kind, scopes: FunctionScopes::default(), linear_span, } } /// Returns the name of the object method definition. #[inline] #[must_use] pub const fn name(&self) -> &PropertyName { &self.name } /// Returns the parameters of the object method definition. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Returns the body of the object method definition. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } /// Returns the kind of the object method definition. #[inline] #[must_use] pub const fn kind(&self) -> MethodDefinitionKind { self.kind } /// Gets the scopes of the object method definition. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the object method definition contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl ToIndentedString for ObjectMethodDefinition { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { let indentation = " ".repeat(indent_n + 1); let prefix = match &self.kind { MethodDefinitionKind::Get => "get ", MethodDefinitionKind::Set => "set ", MethodDefinitionKind::Ordinary => "", MethodDefinitionKind::Generator => "*", MethodDefinitionKind::AsyncGenerator => "async *", MethodDefinitionKind::Async => "async ", }; let name = self.name.to_interned_string(interner); let parameters = join_nodes(interner, self.parameters.as_ref()); let body = block_to_string(&self.body.statements, interner, indent_n + 1); format!("{indentation}{prefix}{name}({parameters}) {body},\n") } } impl VisitWith for ObjectMethodDefinition { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_property_name(&self.name)?; visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_property_name_mut(&mut self.name)?; visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } ================================================ FILE: core/ast/src/expression/literal/template.rs ================================================ //! Template literal Expression. use crate::{ Span, Spanned, expression::Expression, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, Sym, ToInternedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// 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))] #[derive(Clone, Debug, PartialEq)] pub struct TemplateLiteral { elements: Box<[TemplateElement]>, span: Span, } /// Manual implementation, because string and expression in the element list must always appear in order. #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for TemplateLiteral { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let len = u.arbitrary_len::>()?; let mut elements = Vec::with_capacity(len); for i in 0..len { if i & 1 == 0 { elements.push(TemplateElement::String( ::arbitrary(u)?, )); } else { elements.push(TemplateElement::Expr(Expression::arbitrary(u)?)); } } Ok(Self::new(elements.into_boxed_slice(), Span::arbitrary(u)?)) } } impl From for Expression { #[inline] fn from(tem: TemplateLiteral) -> Self { Self::TemplateLiteral(tem) } } impl TemplateLiteral { /// Creates a new `TemplateLiteral` from a list of [`TemplateElement`]s. #[inline] #[must_use] pub fn new(elements: Box<[TemplateElement]>, span: Span) -> Self { Self { elements, span } } /// Gets the element list of this `TemplateLiteral`. #[must_use] pub const fn elements(&self) -> &[TemplateElement] { &self.elements } } impl Spanned for TemplateLiteral { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for TemplateLiteral { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { let mut buf = "`".to_owned(); for elt in &self.elements { match elt { TemplateElement::String(s) => { let _ = write!(buf, "{}", interner.resolve_expect(*s)); } TemplateElement::Expr(n) => { let _ = write!(buf, "${{{}}}", n.to_interned_string(interner)); } } } buf.push('`'); buf } } impl VisitWith for TemplateLiteral { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { for element in &*self.elements { visitor.visit_template_element(element)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for element in &mut *self.elements { visitor.visit_template_element_mut(element)?; } ControlFlow::Continue(()) } } /// 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 = "arbitrary", 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 VisitWith for TemplateElement { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow 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 where V: VisitorMut<'a>, { match self { Self::String(sym) => visitor.visit_sym_mut(sym), Self::Expr(expr) => visitor.visit_expression_mut(expr), } } } ================================================ FILE: core/ast/src/expression/mod.rs ================================================ //! 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 self::{ access::PropertyAccess, literal::{ArrayLiteral, ObjectLiteral, TemplateLiteral}, operator::{Assign, Binary, BinaryInPrivate, Conditional, Unary, Update}, }; use super::{ Spanned, Statement, function::AsyncArrowFunction, function::{ ArrowFunction, AsyncFunctionExpression, AsyncGeneratorExpression, ClassExpression, FunctionExpression, GeneratorExpression, }, }; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; use literal::Literal; mod r#await; mod call; mod identifier; mod import_meta; mod new; mod new_target; mod optional; mod parenthesized; mod regexp; mod spread; mod tagged_template; mod this; mod r#yield; use crate::{ Span, visitor::{VisitWith, Visitor, VisitorMut}, }; pub use r#await::Await; pub use call::{Call, ImportCall, ImportPhase, SuperCall}; pub use identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT}; pub use import_meta::ImportMeta; pub use new::New; pub use new_target::NewTarget; pub use optional::{Optional, OptionalOperation, OptionalOperationKind}; pub use parenthesized::Parenthesized; pub use regexp::RegExpLiteral; pub use spread::Spread; pub use tagged_template::TaggedTemplate; pub use this::This; pub use r#yield::Yield; 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 = "arbitrary", 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 non–strict 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(This), /// See [`Identifier`]. Identifier(Identifier), /// See [`Literal`]. Literal(Literal), /// See [`RegExpLiteral`]. RegExpLiteral(RegExpLiteral), /// See [`ArrayLiteral`]. ArrayLiteral(ArrayLiteral), /// See [`ObjectLiteral`]. ObjectLiteral(ObjectLiteral), /// See [`Spread`], Spread(Spread), /// See [`FunctionExpression`]. FunctionExpression(FunctionExpression), /// See [`ArrowFunction`]. ArrowFunction(ArrowFunction), /// See [`AsyncArrowFunction`]. AsyncArrowFunction(AsyncArrowFunction), /// See [`GeneratorExpression`]. GeneratorExpression(GeneratorExpression), /// See [`AsyncFunctionExpression`]. AsyncFunctionExpression(AsyncFunctionExpression), /// See [`AsyncGeneratorExpression`]. AsyncGeneratorExpression(AsyncGeneratorExpression), /// See [`ClassExpression`]. ClassExpression(Box), /// See [`TemplateLiteral`]. TemplateLiteral(TemplateLiteral), /// See [`PropertyAccess`]. PropertyAccess(PropertyAccess), /// See [`New`]. New(New), /// See [`Call`]. Call(Call), /// See [`SuperCall`]. SuperCall(SuperCall), /// See [`ImportCall`]. ImportCall(ImportCall), /// See [`Optional`]. Optional(Optional), /// See [`TaggedTemplate`]. TaggedTemplate(TaggedTemplate), /// The `new.target` pseudo-property expression. NewTarget(NewTarget), /// The `import.meta` pseudo-property expression. ImportMeta(ImportMeta), /// See [`Assign`]. Assign(Assign), /// See [`Unary`]. Unary(Unary), /// See [`Unary`]. Update(Update), /// See [`Binary`]. Binary(Binary), /// See [`BinaryInPrivate`]. BinaryInPrivate(BinaryInPrivate), /// See [`Conditional`]. Conditional(Conditional), /// See [`Await`]. Await(Await), /// See [`Yield`]. Yield(Yield), /// See [`Parenthesized`]. Parenthesized(Parenthesized), } 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) => this.to_interned_string(interner), 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::FunctionExpression(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::ClassExpression(cl) => cl.to_indented_string(interner, indentation), Self::GeneratorExpression(r#gen) => r#gen.to_indented_string(interner, indentation), Self::AsyncFunctionExpression(asf) => asf.to_indented_string(interner, indentation), Self::AsyncGeneratorExpression(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::ImportCall(impc) => impc.to_interned_string(interner), Self::Optional(opt) => opt.to_interned_string(interner), Self::NewTarget(new_target) => new_target.to_interned_string(interner), Self::ImportMeta(import_meta) => import_meta.to_interned_string(interner), 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::Update(update) => update.to_interned_string(interner), Self::Binary(bin) => bin.to_interned_string(interner), Self::BinaryInPrivate(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::Parenthesized(expr) => expr.to_interned_string(interner), Self::RegExpLiteral(regexp) => regexp.to_interned_string(interner), } } /// 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::FunctionExpression(f) => f.name().is_none(), Self::GeneratorExpression(f) => f.name().is_none(), Self::AsyncGeneratorExpression(f) => f.name().is_none(), Self::AsyncFunctionExpression(f) => f.name().is_none(), Self::ClassExpression(f) => f.name().is_none(), Self::Parenthesized(p) => p.expression().is_anonymous_function_definition(), _ => false, } } /// Sets the name of an anonymous function definition. /// /// This is used to set the name of a function expression when it is assigned to a variable. /// If the function already has a name, this does nothing. pub fn set_anonymous_function_definition_name(&mut self, name: &Identifier) { match self { Self::ArrowFunction(f) if f.name().is_none() => f.name = Some(*name), Self::AsyncArrowFunction(f) if f.name().is_none() => f.name = Some(*name), Self::FunctionExpression(f) if f.name().is_none() => f.name = Some(*name), Self::GeneratorExpression(f) if f.name().is_none() => f.name = Some(*name), Self::AsyncGeneratorExpression(f) if f.name().is_none() => f.name = Some(*name), Self::AsyncFunctionExpression(f) if f.name().is_none() => f.name = Some(*name), Self::ClassExpression(f) if f.name().is_none() => f.name = Some(*name), Self::Parenthesized(p) => p.expression.set_anonymous_function_definition_name(name), _ => {} } } /// Returns the expression without any outer parenthesized expressions. #[must_use] #[inline] pub const fn flatten(&self) -> &Self { let mut expression = self; while let Self::Parenthesized(p) = expression { expression = p.expression(); } expression } } impl Spanned for Expression { #[inline] fn span(&self) -> Span { match self { Self::This(this) => this.span(), Self::Identifier(id) => id.span(), Self::Literal(lit) => lit.span(), Self::ArrayLiteral(arr) => arr.span(), Self::ObjectLiteral(o) => o.span(), Self::Spread(sp) => sp.span(), Self::FunctionExpression(f) => f.span(), Self::AsyncArrowFunction(f) => f.span(), Self::ArrowFunction(arrf) => arrf.span(), Self::ClassExpression(cl) => cl.span(), Self::GeneratorExpression(r#gen) => r#gen.span(), Self::AsyncFunctionExpression(asf) => asf.span(), Self::AsyncGeneratorExpression(asgen) => asgen.span(), Self::TemplateLiteral(tem) => tem.span(), Self::PropertyAccess(prop) => prop.span(), Self::New(new) => new.span(), Self::Call(call) => call.span(), Self::SuperCall(supc) => supc.span(), Self::ImportCall(impc) => impc.span(), Self::Optional(opt) => opt.span(), Self::NewTarget(new_target) => new_target.span(), Self::ImportMeta(import_meta) => import_meta.span(), Self::TaggedTemplate(tag) => tag.span(), Self::Assign(assign) => assign.span(), Self::Unary(unary) => unary.span(), Self::Update(update) => update.span(), Self::Binary(bin) => bin.span(), Self::BinaryInPrivate(bin) => bin.span(), Self::Conditional(cond) => cond.span(), Self::Await(aw) => aw.span(), Self::Yield(yi) => yi.span(), Self::Parenthesized(expr) => expr.span(), Self::RegExpLiteral(regexp) => regexp.span(), } } } impl From 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 where V: Visitor<'a>, { match self { Self::This(this) => visitor.visit_this(this), Self::Identifier(id) => visitor.visit_identifier(id), Self::Literal(lit) => visitor.visit_literal(lit), Self::RegExpLiteral(regexp) => visitor.visit_reg_exp_literal(regexp), Self::ArrayLiteral(arlit) => visitor.visit_array_literal(arlit), Self::ObjectLiteral(olit) => visitor.visit_object_literal(olit), Self::Spread(sp) => visitor.visit_spread(sp), Self::FunctionExpression(f) => visitor.visit_function_expression(f), Self::ArrowFunction(af) => visitor.visit_arrow_function(af), Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function(af), Self::GeneratorExpression(g) => visitor.visit_generator_expression(g), Self::AsyncFunctionExpression(af) => visitor.visit_async_function_expression(af), Self::AsyncGeneratorExpression(ag) => visitor.visit_async_generator_expression(ag), Self::ClassExpression(c) => visitor.visit_class_expression(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::ImportCall(ic) => visitor.visit_import_call(ic), 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::Update(u) => visitor.visit_update(u), Self::Binary(b) => visitor.visit_binary(b), Self::BinaryInPrivate(b) => visitor.visit_binary_in_private(b), Self::Conditional(c) => visitor.visit_conditional(c), Self::Await(a) => visitor.visit_await(a), Self::Yield(y) => visitor.visit_yield(y), Self::Parenthesized(e) => visitor.visit_parenthesized(e), Self::NewTarget(new_target) => visitor.visit_new_target(new_target), Self::ImportMeta(import_meta) => visitor.visit_import_meta(import_meta), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::This(this) => visitor.visit_this_mut(this), Self::Identifier(id) => visitor.visit_identifier_mut(id), Self::Literal(lit) => visitor.visit_literal_mut(lit), Self::RegExpLiteral(regexp) => visitor.visit_reg_exp_literal_mut(regexp), 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::FunctionExpression(f) => visitor.visit_function_expression_mut(f), Self::ArrowFunction(af) => visitor.visit_arrow_function_mut(af), Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function_mut(af), Self::GeneratorExpression(g) => visitor.visit_generator_expression_mut(g), Self::AsyncFunctionExpression(af) => visitor.visit_async_function_expression_mut(af), Self::AsyncGeneratorExpression(ag) => visitor.visit_async_generator_expression_mut(ag), Self::ClassExpression(c) => visitor.visit_class_expression_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::ImportCall(ic) => visitor.visit_import_call_mut(ic), 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::Update(u) => visitor.visit_update_mut(u), Self::Binary(b) => visitor.visit_binary_mut(b), Self::BinaryInPrivate(b) => visitor.visit_binary_in_private_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::Parenthesized(e) => visitor.visit_parenthesized_mut(e), Self::NewTarget(new_target) => visitor.visit_new_target_mut(new_target), Self::ImportMeta(import_meta) => visitor.visit_import_meta_mut(import_meta), } } } ================================================ FILE: core/ast/src/expression/new.rs ================================================ use crate::expression::Call; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{Span, Spanned}; 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 = "arbitrary", 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 for New { #[inline] fn from(call: Call) -> Self { Self { call } } } impl Spanned for New { #[inline] fn span(&self) -> Span { self.call.span() } } impl ToInternedString for New { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!("new {}", self.call.to_interned_string(interner)) } } impl From 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 where V: Visitor<'a>, { visitor.visit_call(&self.call) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_call_mut(&mut self.call) } } ================================================ FILE: core/ast/src/expression/new_target.rs ================================================ //! `target.new` ECMAScript expression. use crate::{ Span, Spanned, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; use super::Expression; /// ECMAScript's `NewTarget` expression AST node. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct NewTarget { span: Span, } impl NewTarget { /// Creates a new [`NewTarget`] AST Expression. #[inline] #[must_use] pub const fn new(span: Span) -> Self { Self { span } } } impl Spanned for NewTarget { #[inline] fn span(&self) -> Span { self.span } } impl From for Expression { #[inline] fn from(value: NewTarget) -> Self { Expression::NewTarget(value) } } impl ToInternedString for NewTarget { #[inline] fn to_interned_string(&self, _interner: &Interner) -> String { String::from("new.target") } } impl VisitWith for NewTarget { fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/expression/operator/assign/mod.rs ================================================ #![allow(clippy::doc_link_with_quotes)] //! 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, Sym, ToInternedString}; use crate::{ Span, Spanned, expression::{Expression, access::PropertyAccess, identifier::Identifier}, pattern::Pattern, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Assign { op: AssignOp, lhs: Box, rhs: Box, } 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 Spanned for Assign { #[inline] fn span(&self) -> Span { Span::new(self.lhs.span().start(), self.rhs.span().end()) } } 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 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 where V: Visitor<'a>, { visitor.visit_assign_target(&self.lhs)?; visitor.visit_expression(&self.rhs) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { 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 = "arbitrary", 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) -> Option { match expression { Expression::ObjectLiteral(object) => { let pattern = object.to_pattern(strict)?; Some(Self::Pattern(pattern.into())) } Expression::ArrayLiteral(array) => { let pattern = array.to_pattern(strict)?; Some(Self::Pattern(pattern.into())) } e => Self::from_expression_simple(e, strict), } } /// 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. /// /// The `AssignmentTargetType` of the expression must be `simple`. #[must_use] pub fn from_expression_simple(expression: &Expression, strict: bool) -> Option { match expression { Expression::Identifier(id) if strict && (id.sym() == Sym::EVAL || id.sym() == Sym::ARGUMENTS) => { None } Expression::Identifier(id) => Some(Self::Identifier(*id)), Expression::PropertyAccess(access) => Some(Self::Access(access.clone())), Expression::Parenthesized(p) => Self::from_expression_simple(p.expression(), strict), _ => None, } } } impl Spanned for AssignTarget { #[inline] fn span(&self) -> Span { match self { AssignTarget::Identifier(identifier) => identifier.span(), AssignTarget::Access(property_access) => property_access.span(), AssignTarget::Pattern(pattern) => pattern.span(), } } } 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 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 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 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), } } } ================================================ FILE: core/ast/src/expression/operator/assign/op.rs ================================================ /// 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 = "arbitrary", 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()) } } ================================================ FILE: core/ast/src/expression/operator/binary/mod.rs ================================================ //! 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 crate::{ Span, Spanned, expression::Expression, function::PrivateName, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; pub use op::*; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Binary { op: BinaryOp, lhs: Box, rhs: Box, } 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 } /// Gets the left hand side of the binary operation. #[inline] #[must_use] pub fn lhs_mut(&mut self) -> &mut Expression { &mut self.lhs } /// Gets the right hand side of the binary operation. #[inline] #[must_use] pub fn rhs_mut(&mut self) -> &mut Expression { &mut self.rhs } } impl Spanned for Binary { #[inline] fn span(&self) -> Span { Span::new(self.lhs.span().start(), self.rhs.span().end()) } } 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 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 where V: Visitor<'a>, { visitor.visit_expression(&self.lhs)?; visitor.visit_expression(&self.rhs) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.lhs)?; visitor.visit_expression_mut(&mut self.rhs) } } /// Binary [relational][relat] `In` expression with a private name on the left hand side. /// /// Because the left hand side must be a private name, this is a separate type from [`Binary`]. /// /// [relat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#relational_operators #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct BinaryInPrivate { lhs: PrivateName, rhs: Box, } impl BinaryInPrivate { /// Creates a `BinaryInPrivate` AST Expression. #[inline] #[must_use] pub fn new(lhs: PrivateName, rhs: Expression) -> Self { Self { lhs, rhs: Box::new(rhs), } } /// Gets the left hand side of the binary operation. #[inline] #[must_use] pub const fn lhs(&self) -> &PrivateName { &self.lhs } /// Gets the right hand side of the binary operation. #[inline] #[must_use] pub const fn rhs(&self) -> &Expression { &self.rhs } } impl Spanned for BinaryInPrivate { #[inline] fn span(&self) -> Span { Span::new(self.lhs.span().start(), self.rhs.span().end()) } } impl ToInternedString for BinaryInPrivate { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!( "#{} in {}", interner.resolve_expect(self.lhs.description()), self.rhs.to_interned_string(interner) ) } } impl From for Expression { #[inline] fn from(op: BinaryInPrivate) -> Self { Self::BinaryInPrivate(op) } } impl VisitWith for BinaryInPrivate { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_private_name(&self.lhs)?; visitor.visit_expression(&self.rhs) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_private_name_mut(&mut self.lhs)?; visitor.visit_expression_mut(&mut self.rhs) } } ================================================ FILE: core/ast/src/expression/operator/binary/op.rs ================================================ //! 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 = "arbitrary", 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 for BinaryOp { #[inline] fn from(op: ArithmeticOp) -> Self { Self::Arithmetic(op) } } impl From for BinaryOp { #[inline] fn from(op: BitwiseOp) -> Self { Self::Bitwise(op) } } impl From for BinaryOp { #[inline] fn from(op: RelationalOp) -> Self { Self::Relational(op) } } impl From 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 = "arbitrary", 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 = "arbitrary", 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 = "arbitrary", 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 = "arbitrary", 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()) } } ================================================ FILE: core/ast/src/expression/operator/conditional.rs ================================================ use crate::{ Span, Spanned, expression::Expression, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Conditional { condition: Box, if_true: Box, if_false: Box, } 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 Spanned for Conditional { #[inline] fn span(&self) -> Span { Span::new(self.condition.span().start(), self.if_false.span().end()) } } 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 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 where V: Visitor<'a>, { visitor.visit_expression(&self.condition)?; 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 where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.condition)?; visitor.visit_expression_mut(&mut self.if_true)?; visitor.visit_expression_mut(&mut self.if_false) } } ================================================ FILE: core/ast/src/expression/operator/mod.rs ================================================ //! 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. //! - A [`Update`] expression. //! - A [`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 mod update; pub use self::{ assign::Assign, binary::{Binary, BinaryInPrivate}, conditional::Conditional, unary::Unary, update::Update, }; ================================================ FILE: core/ast/src/expression/operator/unary/mod.rs ================================================ //! Unary expression nodes. //! //! A unary expression comprises any operation applied to a single expression. Some examples include: //! //! - The [`delete`][del] operator. //! - The [bitwise NOT][not] operator (`~`). //! //! The full list of valid unary operators is defined in [`UnaryOp`]. //! //! [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 crate::{ Span, Spanned, expression::Expression, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; pub use op::*; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Unary { op: UnaryOp, target: Box, span: Span, } impl Unary { /// Creates a new `UnaryOp` AST Expression. #[inline] #[must_use] pub fn new(op: UnaryOp, target: Expression, span: Span) -> Self { Self { op, target: Box::new(target), span, } } /// 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() } /// Gets the target of this unary operator. #[inline] #[must_use] pub fn target_mut(&mut self) -> &mut Expression { self.target.as_mut() } } impl Spanned for Unary { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for Unary { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!("{} {}", self.op, self.target.to_interned_string(interner)) } } impl From 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 where V: Visitor<'a>, { visitor.visit_expression(&self.target) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.target) } } ================================================ FILE: core/ast/src/expression/operator/unary/op.rs ================================================ /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum UnaryOp { /// 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 `NOT`ing 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::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()) } } ================================================ FILE: core/ast/src/expression/operator/update/mod.rs ================================================ //! Update expression nodes. //! //! A update expression increments or decrements it's operand and returns a value //! //! - [Increment and decrement operations][mdn] (`++`, `--`). //! //! The full list of valid update operators is defined in [`UpdateOp`]. //! //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#increment_and_decrement mod op; use crate::{ Expression, Span, Spanned, expression::{Identifier, access::PropertyAccess}, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; pub use op::*; /// A update expression is an operation with only one operand. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-UpdateExpression /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#increment_and_decrement #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Update { op: UpdateOp, target: Box, span: Span, } impl Update { /// Creates a new `Update` AST expression. #[inline] #[must_use] pub fn new(op: UpdateOp, target: UpdateTarget, span: Span) -> Self { Self { op, target: Box::new(target), span, } } /// Gets the update operation of the expression. #[inline] #[must_use] pub const fn op(&self) -> UpdateOp { self.op } /// Gets the target of this update operator. #[inline] #[must_use] pub fn target(&self) -> &UpdateTarget { self.target.as_ref() } } impl Spanned for Update { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for Update { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { match self.op { UpdateOp::IncrementPost | UpdateOp::DecrementPost => { format!("{}{}", self.target.to_interned_string(interner), self.op) } UpdateOp::IncrementPre | UpdateOp::DecrementPre => { format!("{}{}", self.op, self.target.to_interned_string(interner)) } } } } impl From for Expression { #[inline] fn from(op: Update) -> Self { Self::Update(op) } } impl VisitWith for Update { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self.target.as_ref() { UpdateTarget::Identifier(ident) => visitor.visit_identifier(ident), UpdateTarget::PropertyAccess(access) => visitor.visit_property_access(access), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match &mut *self.target { UpdateTarget::Identifier(ident) => visitor.visit_identifier_mut(ident), UpdateTarget::PropertyAccess(access) => visitor.visit_property_access_mut(access), } } } /// A update expression can only be performed on identifier expressions or property access expressions. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-UpdateExpression /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#increment_and_decrement #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum UpdateTarget { /// An [`Identifier`] expression. Identifier(Identifier), /// An [`PropertyAccess`] expression. PropertyAccess(PropertyAccess), } impl ToInternedString for UpdateTarget { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { match self { Self::Identifier(identifier) => identifier.to_interned_string(interner), Self::PropertyAccess(access) => access.to_interned_string(interner), } } } ================================================ FILE: core/ast/src/expression/operator/update/op.rs ================================================ /// A update operator is one that takes a single operand/argument and performs an operation. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-UpdateExpression /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#increment_and_decrement #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum UpdateOp { /// 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, } impl UpdateOp { /// Retrieves the operation as a static string. const fn as_str(self) -> &'static str { match self { Self::IncrementPost | Self::IncrementPre => "++", Self::DecrementPost | Self::DecrementPre => "--", } } } impl std::fmt::Display for UpdateOp { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } ================================================ FILE: core/ast/src/expression/optional.rs ================================================ use super::{Expression, access::PropertyAccessField}; use crate::{ Span, Spanned, function::PrivateName, join_nodes, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// List of valid operations in an [`Optional`] chain. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", 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 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 { visitor.visit_expression(arg)?; } ControlFlow::Continue(()) } } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow 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() { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct OptionalOperation { kind: OptionalOperationKind, shorted: bool, span: Span, } impl OptionalOperation { /// Creates a new `OptionalOperation`. #[inline] #[must_use] pub const fn new(kind: OptionalOperationKind, shorted: bool, span: Span) -> Self { Self { kind, shorted, span, } } /// 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 Spanned for OptionalOperation { #[inline] fn span(&self) -> Span { self.span } } 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.sym())); } if let OptionalOperationKind::PrivatePropertyAccess { field } = &self.kind { return format!(".#{}", interner.resolve_expect(field.description())); } String::new() }; match &self.kind { OptionalOperationKind::SimplePropertyAccess { field } => match field { PropertyAccessField::Const(name) => { buf.push_str(&interner.resolve_expect(name.sym()).to_string()); } PropertyAccessField::Expr(expr) => { let _ = write!(buf, "[{}]", expr.to_interned_string(interner)); } }, OptionalOperationKind::PrivatePropertyAccess { field } => { let _ = write!(buf, "#{}", interner.resolve_expect(field.description())); } OptionalOperationKind::Call { args } => { let _ = write!(buf, "({})", join_nodes(interner, args)); } } buf } } impl VisitWith for OptionalOperation { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_optional_operation_kind(&self.kind) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Optional { target: Box, chain: Box<[OptionalOperation]>, span: Span, } impl Optional { /// Creates a new `Optional` expression. #[inline] #[must_use] pub fn new(target: Expression, chain: Box<[OptionalOperation]>, span: Span) -> Self { Self { target: Box::new(target), chain, span, } } /// 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 Spanned for Optional { #[inline] fn span(&self) -> Span { self.span } } impl From 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 } } impl VisitWith for Optional { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_expression(&self.target)?; for op in &*self.chain { visitor.visit_optional_operation(op)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.target)?; for op in &mut *self.chain { visitor.visit_optional_operation_mut(op)?; } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/expression/parenthesized.rs ================================================ use super::Expression; use crate::{ Span, Spanned, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; /// A parenthesized expression. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-grouping-operator /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Grouping #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Parenthesized { pub(crate) expression: Box, span: Span, } impl Parenthesized { /// Creates a parenthesized expression. #[inline] #[must_use] pub fn new(expression: Expression, span: Span) -> Self { Self { expression: Box::new(expression), span, } } /// Gets the expression of this parenthesized expression. #[inline] #[must_use] pub const fn expression(&self) -> &Expression { &self.expression } } impl Spanned for Parenthesized { #[inline] fn span(&self) -> Span { self.span } } impl From for Expression { fn from(p: Parenthesized) -> Self { Self::Parenthesized(p) } } impl ToInternedString for Parenthesized { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!("({})", self.expression.to_interned_string(interner)) } } impl VisitWith for Parenthesized { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_expression(&self.expression) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.expression) } } ================================================ FILE: core/ast/src/expression/regexp.rs ================================================ //! This module contains the ECMAScript representation regular expressions. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-literals-regular-expression-literals //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions use std::ops::ControlFlow; use boa_interner::{Interner, Sym, ToInternedString}; use crate::{ Span, Spanned, visitor::{VisitWith, Visitor, VisitorMut}, }; use super::Expression; /// Regular expressions in ECMAScript. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-literals-regular-expression-literals /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RegExpLiteral { pattern: Sym, flags: Sym, span: Span, } impl RegExpLiteral { /// Create a new [`RegExpLiteral`]. #[inline] #[must_use] pub const fn new(pattern: Sym, flags: Sym, span: Span) -> Self { Self { pattern, flags, span, } } /// Get the pattern part of the [`RegExpLiteral`]. #[inline] #[must_use] pub const fn pattern(&self) -> Sym { self.pattern } /// Get the flags part of the [`RegExpLiteral`]. #[inline] #[must_use] pub const fn flags(&self) -> Sym { self.flags } } impl Spanned for RegExpLiteral { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for RegExpLiteral { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { let pattern = interner.resolve_expect(self.pattern); let flags = interner.resolve_expect(self.flags); format!("/{pattern}/{flags}") } } impl From for Expression { #[inline] fn from(value: RegExpLiteral) -> Self { Self::RegExpLiteral(value) } } impl VisitWith for RegExpLiteral { #[inline] fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_sym(&self.pattern)?; visitor.visit_sym(&self.flags) } #[inline] fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_sym_mut(&mut self.pattern)?; visitor.visit_sym_mut(&mut self.flags) } } ================================================ FILE: core/ast/src/expression/spread.rs ================================================ use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; use crate::{ Span, Spanned, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Spread { target: Box, span: Span, } impl Spread { /// Creates a [`Spread`] AST Expression. #[inline] #[must_use] pub fn new(target: Expression, span: Span) -> Self { Self { target: Box::new(target), span, } } /// Gets the target expression to be expanded by the spread operator. #[inline] #[must_use] pub const fn target(&self) -> &Expression { &self.target } } impl Spanned for Spread { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for Spread { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!("...{}", self.target().to_interned_string(interner)) } } impl From 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 where V: Visitor<'a>, { visitor.visit_expression(&self.target) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.target) } } ================================================ FILE: core/ast/src/expression/tagged_template.rs ================================================ use super::Expression; use crate::{ Span, Spanned, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, Sym, ToInternedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct TaggedTemplate { tag: Box, raws: Box<[Sym]>, cookeds: Box<[Option]>, exprs: Box<[Expression]>, identifier: u64, span: Span, } 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]>, exprs: Box<[Expression]>, identifier: u64, span: Span, ) -> Self { Self { tag: tag.into(), raws, cookeds, exprs, identifier, span, } } /// 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] { &self.cookeds } /// Gets the interpolated expressions of the template. #[inline] #[must_use] pub const fn exprs(&self) -> &[Expression] { &self.exprs } /// Gets the unique identifier of the template. #[inline] #[must_use] pub const fn identifier(&self) -> u64 { self.identifier } } impl Spanned for TaggedTemplate { #[inline] fn span(&self) -> Span { self.span } } impl ToInternedString for TaggedTemplate { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { let mut buf = format!("{}`", self.tag.to_interned_string(interner)); let mut exprs = self.exprs.iter(); for raw in &self.raws { let _ = write!(buf, "{}", interner.resolve_expect(*raw)); if let Some(expr) = exprs.next() { let _ = write!(buf, "${{{}}}", expr.to_interned_string(interner)); } } buf.push('`'); buf } } impl From 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 where V: Visitor<'a>, { visitor.visit_expression(&self.tag)?; for raw in &*self.raws { visitor.visit_sym(raw)?; } for cooked in self.cookeds.iter().flatten() { visitor.visit_sym(cooked)?; } for expr in &*self.exprs { visitor.visit_expression(expr)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.tag)?; for raw in &mut *self.raws { visitor.visit_sym_mut(raw)?; } for cooked in self.cookeds.iter_mut().flatten() { visitor.visit_sym_mut(cooked)?; } for expr in &mut *self.exprs { visitor.visit_expression_mut(expr)?; } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/expression/this.rs ================================================ //! `this` ECMAScript expression. use crate::{ Span, Spanned, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; use super::Expression; /// ECMAScript's `this` expression AST node. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct This { span: Span, } impl This { /// Creates a new This AST Expression. #[inline] #[must_use] pub const fn new(span: Span) -> Self { Self { span } } } impl Spanned for This { #[inline] fn span(&self) -> Span { self.span } } impl From for Expression { #[inline] fn from(value: This) -> Self { Expression::This(value) } } impl ToInternedString for This { #[inline] fn to_interned_string(&self, _interner: &Interner) -> String { String::from("this") } } impl VisitWith for This { fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/expression/yield.rs ================================================ use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; use crate::{ Span, Spanned, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Yield { target: Option>, delegate: bool, span: Span, } impl Yield { /// Creates a [`Yield`] AST Expression. #[inline] #[must_use] pub fn new(expr: Option, delegate: bool, span: Span) -> Self { Self { target: expr.map(Box::new), delegate, span, } } /// 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 } } impl Spanned for Yield { #[inline] fn span(&self) -> Span { self.span } } impl From 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 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 where V: VisitorMut<'a>, { if let Some(expr) = &mut self.target { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } } ================================================ FILE: core/ast/src/function/arrow_function.rs ================================================ use super::{FormalParameterList, FunctionBody}; use crate::operations::{ContainsSymbol, contains}; use crate::scope::FunctionScopes; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{LinearSpan, LinearSpanIgnoreEq, Span, Spanned}; use crate::{ expression::{Expression, Identifier}, join_nodes, }; use boa_interner::{Interner, ToIndentedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ArrowFunction { pub(crate) name: Option, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, span: Span, } impl ArrowFunction { /// Creates a new `ArrowFunctionDecl` AST Expression. #[inline] #[must_use] pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, span: Span, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), span, } } /// Gets the name of the arrow function. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } /// Sets the name of the arrow function. #[inline] pub fn set_name(&mut self, name: Option) { 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) -> &FunctionBody { &self.body } /// Returns the scopes of the arrow function. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the arrow function contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl Spanned for ArrowFunction { #[inline] fn span(&self) -> Span { self.span } } 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 { let _ = write!( buf, ") => {{\n{}{}}}", self.body.to_indented_string(interner, indentation + 1), " ".repeat(indentation) ); } buf } } impl From 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 where V: Visitor<'a>, { if let Some(ident) = &self.name { visitor.visit_identifier(ident)?; } visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(ident) = &mut self.name { visitor.visit_identifier_mut(ident)?; } visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } ================================================ FILE: core/ast/src/function/async_arrow_function.rs ================================================ use super::{FormalParameterList, FunctionBody}; use crate::operations::{ContainsSymbol, contains}; use crate::scope::FunctionScopes; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{LinearSpan, LinearSpanIgnoreEq, Span, Spanned}; use crate::{ expression::{Expression, Identifier}, join_nodes, }; use boa_interner::{Interner, ToIndentedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct AsyncArrowFunction { pub(crate) name: Option, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, span: Span, } impl AsyncArrowFunction { /// Creates a new `AsyncArrowFunction` AST Expression. #[inline] #[must_use] pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, span: Span, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), span, } } /// Gets the name of the async arrow function. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } /// Sets the name of the async arrow function. #[inline] pub fn set_name(&mut self, name: Option) { self.name = name; } /// Gets the list of parameters of the async arrow function. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the async arrow function. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } /// Returns the scopes of the async arrow function. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the function declaration contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl Spanned for AsyncArrowFunction { #[inline] fn span(&self) -> Span { self.span } } 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 { let _ = write!( buf, ") => {{\n{}{}}}", self.body.to_indented_string(interner, indentation + 1), " ".repeat(indentation) ); } buf } } impl From 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 where V: Visitor<'a>, { if let Some(ident) = &self.name { visitor.visit_identifier(ident)?; } visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(ident) = &mut self.name { visitor.visit_identifier_mut(ident)?; } visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } ================================================ FILE: core/ast/src/function/async_function.rs ================================================ //! Async Function Expression. use super::{FormalParameterList, FunctionBody}; use crate::{ Declaration, LinearSpan, LinearSpanIgnoreEq, Span, Spanned, block_to_string, expression::{Expression, Identifier}, join_nodes, operations::{ContainsSymbol, contains}, scope::{FunctionScopes, Scope}, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// An async function declaration. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-AsyncFunctionDeclaration /// [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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct AsyncFunctionDeclaration { name: Identifier, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, } impl AsyncFunctionDeclaration { /// Creates a new async function declaration. #[inline] #[must_use] pub fn new( name: Identifier, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), } } /// Gets the name of the async function declaration. #[inline] #[must_use] pub const fn name(&self) -> Identifier { self.name } /// Gets the list of parameters of the async function declaration. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the async function declaration. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } /// Gets the scopes of the async function declaration. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the async function declaration contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl ToIndentedString for AsyncFunctionDeclaration { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { format!( "async function {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), block_to_string(&self.body.statements, interner, indentation) ) } } impl VisitWith for AsyncFunctionDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_identifier(&self.name)?; visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_identifier_mut(&mut self.name)?; visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } impl From for Declaration { #[inline] fn from(f: AsyncFunctionDeclaration) -> Self { Self::AsyncFunctionDeclaration(f) } } /// An async function expression. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-AsyncFunctionExpression /// [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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct AsyncFunctionExpression { pub(crate) name: Option, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) has_binding_identifier: bool, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) name_scope: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, span: Span, } impl AsyncFunctionExpression { /// Creates a new async function expression. #[inline] #[must_use] pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, has_binding_identifier: bool, span: Span, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), span, } } /// Gets the name of the async function expression. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } /// Gets the list of parameters of the async function expression. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the async function expression. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } /// Returns whether the async function expression has a binding identifier. #[inline] #[must_use] pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } /// Gets the name scope of the async function expression. #[inline] #[must_use] pub const fn name_scope(&self) -> Option<&Scope> { self.name_scope.as_ref() } /// Gets the scopes of the async function expression. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the async function expression contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl Spanned for AsyncFunctionExpression { #[inline] fn span(&self) -> Span { self.span } } impl ToIndentedString for AsyncFunctionExpression { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { let mut buf = "async function".to_owned(); if self.has_binding_identifier && let Some(name) = self.name { let _ = write!(buf, " {}", interner.resolve_expect(name.sym())); } let _ = write!(buf, "({}", join_nodes(interner, self.parameters.as_ref())); if self.body().statements().is_empty() { buf.push_str(") {}"); } else { let _ = write!( buf, ") {{\n{}{}}}", self.body.to_indented_string(interner, indentation + 1), " ".repeat(indentation) ); } buf } } impl From for Expression { #[inline] fn from(expr: AsyncFunctionExpression) -> Self { Self::AsyncFunctionExpression(expr) } } impl VisitWith for AsyncFunctionExpression { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(ident) = &self.name { visitor.visit_identifier(ident)?; } visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(ident) = &mut self.name { visitor.visit_identifier_mut(ident)?; } visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } ================================================ FILE: core/ast/src/function/async_generator.rs ================================================ //! Async Generator Expression use super::{FormalParameterList, FunctionBody}; use crate::operations::{ContainsSymbol, contains}; use crate::scope::{FunctionScopes, Scope}; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ Declaration, Spanned, block_to_string, expression::{Expression, Identifier}, join_nodes, }; use crate::{LinearSpan, LinearSpanIgnoreEq, Span}; use boa_interner::{Interner, ToIndentedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// An async generator declaration. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorDeclaration /// [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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct AsyncGeneratorDeclaration { name: Identifier, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, } impl AsyncGeneratorDeclaration { /// Creates a new async generator declaration. #[inline] #[must_use] pub fn new( name: Identifier, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), } } /// Gets the name of the async generator declaration. #[inline] #[must_use] pub const fn name(&self) -> Identifier { self.name } /// Gets the list of parameters of the async generator declaration. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the async generator declaration. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } /// Gets the scopes of the async generator declaration. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the async generator declaration contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl ToIndentedString for AsyncGeneratorDeclaration { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { format!( "async function* {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), block_to_string(&self.body.statements, interner, indentation) ) } } impl VisitWith for AsyncGeneratorDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_identifier(&self.name)?; visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_identifier_mut(&mut self.name)?; visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } impl From for Declaration { #[inline] fn from(f: AsyncGeneratorDeclaration) -> Self { Self::AsyncGeneratorDeclaration(f) } } /// An async generator expression. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorExpression /// [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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct AsyncGeneratorExpression { pub(crate) name: Option, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) has_binding_identifier: bool, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) name_scope: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, span: Span, } impl AsyncGeneratorExpression { /// Creates a new async generator expression. #[inline] #[must_use] pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, has_binding_identifier: bool, span: Span, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), span, } } /// Gets the name of the async generator expression. #[inline] #[must_use] pub const fn name(&self) -> Option { 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) -> &FunctionBody { &self.body } /// Returns whether the async generator expression has a binding identifier. #[inline] #[must_use] pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } /// Gets the name scope of the async generator expression. #[inline] #[must_use] pub const fn name_scope(&self) -> Option<&Scope> { self.name_scope.as_ref() } /// Gets the scopes of the async generator expression. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the async generator expression contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl Spanned for AsyncGeneratorExpression { #[inline] fn span(&self) -> Span { self.span } } impl ToIndentedString for AsyncGeneratorExpression { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { let mut buf = "async function*".to_owned(); if self.has_binding_identifier && let Some(name) = self.name { let _ = write!(buf, " {}", interner.resolve_expect(name.sym())); } let _ = write!( buf, "({}) {}", join_nodes(interner, self.parameters.as_ref()), block_to_string(&self.body.statements, interner, indentation) ); buf } } impl From for Expression { #[inline] fn from(expr: AsyncGeneratorExpression) -> Self { Self::AsyncGeneratorExpression(expr) } } impl VisitWith for AsyncGeneratorExpression { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(ident) = &self.name { visitor.visit_identifier(ident)?; } visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(ident) = &mut self.name { visitor.visit_identifier_mut(ident)?; } visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } ================================================ FILE: core/ast/src/function/class.rs ================================================ use super::{FormalParameterList, FunctionBody, FunctionExpression}; use crate::{ Declaration, LinearPosition, LinearSpan, LinearSpanIgnoreEq, Span, Spanned, block_to_string, expression::{Expression, Identifier}, join_nodes, operations::{ContainsSymbol, contains}, property::{MethodDefinitionKind, PropertyName}, scope::{FunctionScopes, Scope}, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use core::{fmt::Write as _, ops::ControlFlow}; use std::hash::Hash; /// A class declaration. /// /// 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 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ClassDeclaration { name: Identifier, pub(crate) super_ref: Option, pub(crate) constructor: Option, pub(crate) elements: Box<[ClassElement]>, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) name_scope: Scope, } impl ClassDeclaration { /// Creates a new class declaration. #[inline] #[must_use] pub fn new( name: Identifier, super_ref: Option, constructor: Option, elements: Box<[ClassElement]>, ) -> Self { Self { name, super_ref, constructor, elements, name_scope: Scope::default(), } } /// Returns the name of the class declaration. #[inline] #[must_use] pub const fn name(&self) -> Identifier { self.name } /// Returns the super class ref of the class declaration. #[inline] #[must_use] pub const fn super_ref(&self) -> Option<&Expression> { self.super_ref.as_ref() } /// Returns the constructor of the class declaration. #[inline] #[must_use] pub const fn constructor(&self) -> Option<&FunctionExpression> { self.constructor.as_ref() } /// Gets the list of all fields defined on the class declaration. #[inline] #[must_use] pub const fn elements(&self) -> &[ClassElement] { &self.elements } /// Gets the scope containing the class name binding. #[inline] #[must_use] pub const fn name_scope(&self) -> &Scope { &self.name_scope } } impl ToIndentedString for ClassDeclaration { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { let mut buf = format!("class {}", interner.resolve_expect(self.name.sym())); if let Some(super_ref) = self.super_ref.as_ref() { let _ = write!(buf, " extends {}", super_ref.to_interned_string(interner)); } if self.elements.is_empty() && self.constructor().is_none() { buf.push_str(" {}"); return buf; } let indentation = " ".repeat(indent_n + 1); buf.push_str(" {\n"); if let Some(expr) = &self.constructor { let _ = writeln!( buf, "{indentation}constructor({}) {}", join_nodes(interner, expr.parameters().as_ref()), block_to_string(&expr.body.statements, interner, indent_n + 1) ); } for element in &self.elements { buf.push_str(&element.to_indented_string(interner, indent_n)); } buf.push('}'); buf } } impl VisitWith for ClassDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_identifier(&self.name)?; if let Some(expr) = &self.super_ref { visitor.visit_expression(expr)?; } if let Some(func) = &self.constructor { visitor.visit_function_expression(func)?; } for elem in &*self.elements { visitor.visit_class_element(elem)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_identifier_mut(&mut self.name)?; if let Some(expr) = &mut self.super_ref { visitor.visit_expression_mut(expr)?; } if let Some(func) = &mut self.constructor { visitor.visit_function_expression_mut(func)?; } for elem in &mut *self.elements { visitor.visit_class_element_mut(elem)?; } ControlFlow::Continue(()) } } impl From for Declaration { fn from(f: ClassDeclaration) -> Self { Self::ClassDeclaration(Box::new(f)) } } /// A class expression. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-ClassExpression /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ClassExpression { pub(crate) name: Option, pub(crate) super_ref: Option, pub(crate) constructor: Option, pub(crate) elements: Box<[ClassElement]>, span: Span, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) name_scope: Option, } impl ClassExpression { /// Creates a new class expression. #[inline] #[must_use] pub fn new( name: Option, super_ref: Option, constructor: Option, elements: Box<[ClassElement]>, has_binding_identifier: bool, span: Span, ) -> Self { let name_scope = if has_binding_identifier { Some(Scope::default()) } else { None }; Self { name, super_ref, constructor, elements, span, name_scope, } } /// Returns the name of the class expression. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } /// Returns the super class ref of the class expression. #[inline] #[must_use] pub const fn super_ref(&self) -> Option<&Expression> { self.super_ref.as_ref() } /// Returns the constructor of the class expression. #[inline] #[must_use] pub const fn constructor(&self) -> Option<&FunctionExpression> { self.constructor.as_ref() } /// Gets the list of all fields defined on the class expression. #[inline] #[must_use] pub const fn elements(&self) -> &[ClassElement] { &self.elements } /// Gets the scope containing the class name binding if it exists. #[inline] #[must_use] pub const fn name_scope(&self) -> Option<&Scope> { self.name_scope.as_ref() } } impl Spanned for ClassExpression { #[inline] fn span(&self) -> Span { self.span } } impl ToIndentedString for ClassExpression { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { let mut buf = "class".to_string(); if self.name_scope.is_some() && let Some(name) = self.name { let _ = write!(buf, " {}", interner.resolve_expect(name.sym())); } if let Some(super_ref) = self.super_ref.as_ref() { let _ = write!(buf, " extends {}", super_ref.to_interned_string(interner)); } if self.elements.is_empty() && self.constructor().is_none() { buf.push_str(" {}"); return buf; } let indentation = " ".repeat(indent_n + 1); buf.push_str(" {\n"); if let Some(expr) = &self.constructor { let _ = writeln!( buf, "{indentation}constructor({}) {}", join_nodes(interner, expr.parameters().as_ref()), block_to_string(&expr.body.statements, interner, indent_n + 1) ); } for element in &self.elements { buf.push_str(&element.to_indented_string(interner, indent_n)); } buf.push('}'); buf } } impl From for Expression { fn from(expr: ClassExpression) -> Self { Self::ClassExpression(Box::new(expr)) } } impl VisitWith for ClassExpression { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(ident) = &self.name { visitor.visit_identifier(ident)?; } if let Some(expr) = &self.super_ref { visitor.visit_expression(expr)?; } if let Some(func) = &self.constructor { visitor.visit_function_expression(func)?; } for elem in &*self.elements { visitor.visit_class_element(elem)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(ident) = &mut self.name { visitor.visit_identifier_mut(ident)?; } if let Some(expr) = &mut self.super_ref { visitor.visit_expression_mut(expr)?; } if let Some(func) = &mut self.constructor { visitor.visit_function_expression_mut(func)?; } for elem in &mut *self.elements { visitor.visit_class_element_mut(elem)?; } ControlFlow::Continue(()) } } /// The body of a class' static block, as defined by the [spec]. /// /// Just an alias for [`Script`](crate::Script), since it has the same exact semantics. /// /// [spec]: https://tc39.es/ecma262/#prod-ClassStaticBlockBody #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct StaticBlockBody { pub(crate) body: FunctionBody, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, } impl StaticBlockBody { /// Creates a new static block body. #[inline] #[must_use] pub fn new(body: FunctionBody) -> Self { Self { body, scopes: FunctionScopes::default(), } } /// Gets the body static block. #[inline] #[must_use] pub const fn statements(&self) -> &FunctionBody { &self.body } /// Gets the scopes of the static block body. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } } /// An element that can be within a class. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ClassElement #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum ClassElement { /// A method definition. MethodDefinition(ClassMethodDefinition), /// A field definition. FieldDefinition(ClassFieldDefinition), /// A static field definition, accessible from the class constructor object StaticFieldDefinition(ClassFieldDefinition), /// A private field definition, only accessible inside the class declaration. PrivateFieldDefinition(PrivateFieldDefinition), /// A private static field definition, only accessible from static methods and fields inside the /// class declaration. PrivateStaticFieldDefinition(PrivateFieldDefinition), /// A static block, where a class can have initialization logic for its static fields. StaticBlock(StaticBlockBody), } /// A non-private class element field definition. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-FieldDefinition #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ClassFieldDefinition { pub(crate) name: PropertyName, pub(crate) initializer: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, } impl ClassFieldDefinition { /// Creates a new class field definition. #[inline] #[must_use] pub fn new(name: PropertyName, initializer: Option) -> Self { Self { name, initializer, scope: Scope::default(), } } /// Returns the name of the class field definition. #[inline] #[must_use] pub const fn name(&self) -> &PropertyName { &self.name } /// Returns the initializer of the class field definition. #[inline] #[must_use] pub const fn initializer(&self) -> Option<&Expression> { self.initializer.as_ref() } /// Returns the scope of the class field definition. #[inline] #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } } /// A private class element field definition. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-FieldDefinition #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct PrivateFieldDefinition { pub(crate) name: PrivateName, pub(crate) initializer: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, } impl PrivateFieldDefinition { /// Creates a new private field definition. #[inline] #[must_use] pub fn new(name: PrivateName, initializer: Option) -> Self { Self { name, initializer, scope: Scope::default(), } } /// Returns the name of the private field definition. #[inline] #[must_use] pub const fn name(&self) -> &PrivateName { &self.name } /// Returns the initializer of the private field definition. #[inline] #[must_use] pub const fn initializer(&self) -> Option<&Expression> { self.initializer.as_ref() } /// Returns the scope of the private field definition. #[inline] #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } } impl ToIndentedString for ClassElement { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { let indentation = " ".repeat(indent_n + 1); match self { Self::MethodDefinition(m) => m.to_indented_string(interner, indent_n), Self::FieldDefinition(field) => match &field.initializer { Some(expr) => { format!( "{indentation}{} = {};\n", field.name.to_interned_string(interner), expr.to_no_indent_string(interner, indent_n + 1) ) } None => { format!( "{indentation}{};\n", field.name.to_interned_string(interner), ) } }, Self::StaticFieldDefinition(field) => match &field.initializer { Some(expr) => { format!( "{indentation}static {} = {};\n", field.name.to_interned_string(interner), expr.to_no_indent_string(interner, indent_n + 1) ) } None => { format!( "{indentation}static {};\n", field.name.to_interned_string(interner), ) } }, Self::PrivateFieldDefinition(PrivateFieldDefinition { name, initializer, .. }) => match initializer { 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()), ) } }, Self::PrivateStaticFieldDefinition(PrivateFieldDefinition { name, initializer, .. }) => match initializer { 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()), ) } }, Self::StaticBlock(block) => { format!( "{indentation}static {}\n", block_to_string(&block.body.statements, interner, indent_n + 1) ) } } } } impl VisitWith for ClassElement { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::MethodDefinition(m) => { match &m.name { ClassElementName::PropertyName(pn) => { visitor.visit_property_name(pn)?; } ClassElementName::PrivateName(pn) => { visitor.visit_private_name(pn)?; } } visitor.visit_formal_parameter_list(&m.parameters)?; visitor.visit_function_body(&m.body) } Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => { visitor.visit_property_name(&field.name)?; if let Some(expr) = &field.initializer { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) } } Self::PrivateFieldDefinition(PrivateFieldDefinition { name, initializer, .. }) | Self::PrivateStaticFieldDefinition(PrivateFieldDefinition { name, initializer, .. }) => { visitor.visit_private_name(name)?; if let Some(expr) = initializer { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) } } Self::StaticBlock(block) => visitor.visit_function_body(&block.body), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::MethodDefinition(m) => { match m.name { ClassElementName::PropertyName(ref mut pn) => { visitor.visit_property_name_mut(pn)?; } ClassElementName::PrivateName(ref mut pn) => { visitor.visit_private_name_mut(pn)?; } } visitor.visit_formal_parameter_list_mut(&mut m.parameters)?; visitor.visit_function_body_mut(&mut m.body) } Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => { visitor.visit_property_name_mut(&mut field.name)?; if let Some(expr) = &mut field.initializer { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } Self::PrivateFieldDefinition(PrivateFieldDefinition { name, initializer, .. }) | Self::PrivateStaticFieldDefinition(PrivateFieldDefinition { name, initializer, .. }) => { visitor.visit_private_name_mut(name)?; if let Some(expr) = initializer { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } Self::StaticBlock(block) => visitor.visit_function_body_mut(&mut block.body), } } } /// A method definition. /// /// This type is specific to class method definitions. /// It includes private names and the information about whether the method is static or not. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ClassMethodDefinition { name: ClassElementName, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, kind: MethodDefinitionKind, is_static: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, } impl ClassMethodDefinition { /// Creates a new class method definition. #[inline] #[must_use] pub fn new( name: ClassElementName, parameters: FormalParameterList, body: FunctionBody, kind: MethodDefinitionKind, is_static: bool, start_linear_pos: LinearPosition, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); let linear_span = LinearSpan::new(start_linear_pos, body.linear_pos_end()); Self { name, parameters, body, contains_direct_eval, kind, is_static, scopes: FunctionScopes::default(), linear_span: linear_span.into(), } } /// Returns the name of the class method definition. #[inline] #[must_use] pub const fn name(&self) -> &ClassElementName { &self.name } /// Returns the parameters of the class method definition. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Returns the body of the class method definition. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } /// Returns the kind of the class method definition. #[inline] #[must_use] pub const fn kind(&self) -> MethodDefinitionKind { self.kind } /// Returns whether the class method definition is static. #[inline] #[must_use] pub const fn is_static(&self) -> bool { self.is_static } /// Returns whether the class method definition is private. #[inline] #[must_use] pub const fn is_private(&self) -> bool { self.name.is_private() } /// Gets the scopes of the class method definition. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the class method definition contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl ToIndentedString for ClassMethodDefinition { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { let indentation = " ".repeat(indent_n + 1); let prefix = match (self.is_static, &self.kind) { (true, MethodDefinitionKind::Get) => "static get ", (true, MethodDefinitionKind::Set) => "static set ", (true, MethodDefinitionKind::Ordinary) => "static ", (true, MethodDefinitionKind::Generator) => "static *", (true, MethodDefinitionKind::AsyncGenerator) => "static async *", (true, MethodDefinitionKind::Async) => "static async ", (false, MethodDefinitionKind::Get) => "get ", (false, MethodDefinitionKind::Set) => "set ", (false, MethodDefinitionKind::Ordinary) => "", (false, MethodDefinitionKind::Generator) => "*", (false, MethodDefinitionKind::AsyncGenerator) => "async *", (false, MethodDefinitionKind::Async) => "async ", }; let name = self.name.to_interned_string(interner); let parameters = join_nodes(interner, self.parameters.as_ref()); let body = block_to_string(&self.body.statements, interner, indent_n + 1); format!("{indentation}{prefix}{name}({parameters}) {body}\n") } } /// A class element name. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ClassElementName #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum ClassElementName { /// A property name. PropertyName(PropertyName), /// A private name. PrivateName(PrivateName), } impl ClassElementName { /// Returns whether the class element name is private. #[inline] #[must_use] pub const fn is_private(&self) -> bool { matches!(self, Self::PrivateName(_)) } } impl ToInternedString for ClassElementName { fn to_interned_string(&self, interner: &Interner) -> String { match &self { Self::PropertyName(name) => name.to_interned_string(interner), Self::PrivateName(name) => format!("#{}", interner.resolve_expect(name.description())), } } } /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct PrivateName { /// The `[[Description]]` internal slot of the private name. description: Sym, span: Span, } impl PrivateName { /// Create a new private name. #[inline] #[must_use] pub const fn new(description: Sym, span: Span) -> Self { Self { description, span } } /// Get the description of the private name. #[inline] #[must_use] pub const fn description(&self) -> Sym { self.description } } impl Spanned for PrivateName { #[inline] fn span(&self) -> Span { self.span } } impl VisitWith for PrivateName { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_sym(&self.description) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_sym_mut(&mut self.description) } } ================================================ FILE: core/ast/src/function/generator.rs ================================================ use super::{FormalParameterList, FunctionBody}; use crate::{ Declaration, LinearSpan, LinearSpanIgnoreEq, Span, Spanned, block_to_string, expression::{Expression, Identifier}, join_nodes, operations::{ContainsSymbol, contains}, scope::{FunctionScopes, Scope}, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// A generator declaration. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-GeneratorDeclaration /// [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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct GeneratorDeclaration { name: Identifier, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, } impl GeneratorDeclaration { /// Creates a new generator declaration. #[inline] #[must_use] pub fn new( name: Identifier, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), } } /// Gets the name of the generator declaration. #[inline] #[must_use] pub const fn name(&self) -> 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) -> &FunctionBody { &self.body } /// Returns the scopes of the generator declaration. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the generator declaration contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl ToIndentedString for GeneratorDeclaration { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { format!( "function* {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), block_to_string(&self.body.statements, interner, indentation) ) } } impl VisitWith for GeneratorDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_identifier(&self.name)?; visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_identifier_mut(&mut self.name)?; visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } impl From for Declaration { #[inline] fn from(f: GeneratorDeclaration) -> Self { Self::GeneratorDeclaration(f) } } /// A generator expression. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-GeneratorExpression /// [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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct GeneratorExpression { pub(crate) name: Option, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) has_binding_identifier: bool, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) name_scope: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, span: Span, } impl GeneratorExpression { /// Creates a new generator expression. #[inline] #[must_use] pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, has_binding_identifier: bool, span: Span, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), span, } } /// Gets the name of the generator expression. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } /// Gets the list of parameters of the generator expression. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the generator expression. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } /// Returns whether the generator expression has a binding identifier. #[inline] #[must_use] pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } /// Gets the name scope of the generator expression. #[inline] #[must_use] pub const fn name_scope(&self) -> Option<&Scope> { self.name_scope.as_ref() } /// Gets the scopes of the generator expression. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the generator expression contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl Spanned for GeneratorExpression { #[inline] fn span(&self) -> Span { self.span } } impl ToIndentedString for GeneratorExpression { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { let mut buf = "function*".to_owned(); if self.has_binding_identifier && let Some(name) = self.name { let _ = write!(buf, " {}", interner.resolve_expect(name.sym())); } let _ = write!( buf, "({}) {}", join_nodes(interner, self.parameters.as_ref()), block_to_string(&self.body.statements, interner, indentation) ); buf } } impl From for Expression { #[inline] fn from(expr: GeneratorExpression) -> Self { Self::GeneratorExpression(expr) } } impl VisitWith for GeneratorExpression { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(ident) = &self.name { visitor.visit_identifier(ident)?; } visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(ident) = &mut self.name { visitor.visit_identifier_mut(ident)?; } visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } ================================================ FILE: core/ast/src/function/mod.rs ================================================ //! This module contains Function and Class AST nodes. //! //! ECMAScript defines multiple types of functions and classes. //! They are split into different AST nodes to reduce ambiguity and to make the AST more readable. //! //! - Functions: //! - [`FunctionDeclaration`] //! - [`FunctionExpression`] //! - Async functions: //! - [`AsyncFunctionDeclaration`] //! - [`AsyncFunctionExpression`] //! - Generators //! - [`GeneratorDeclaration`] //! - [`GeneratorExpression`] //! - Async Generators //! - [`AsyncGeneratorDeclaration`] //! - [`AsyncGeneratorExpression`] //! - Arrow Functions //! - [`ArrowFunction`] //! - Async Arrow Functions //! - [`AsyncArrowFunction`] //! - Classes //! - [`ClassDeclaration`] //! - [`ClassExpression`] mod arrow_function; mod async_arrow_function; mod async_function; mod async_generator; mod class; mod generator; mod ordinary_function; mod parameters; use std::ops::ControlFlow; pub use arrow_function::ArrowFunction; pub use async_arrow_function::AsyncArrowFunction; pub use async_function::{AsyncFunctionDeclaration, AsyncFunctionExpression}; pub use async_generator::{AsyncGeneratorDeclaration, AsyncGeneratorExpression}; use boa_interner::{Interner, ToIndentedString}; pub use class::{ ClassDeclaration, ClassElement, ClassElementName, ClassExpression, ClassFieldDefinition, ClassMethodDefinition, PrivateFieldDefinition, PrivateName, StaticBlockBody, }; pub use generator::{GeneratorDeclaration, GeneratorExpression}; pub use ordinary_function::{FunctionDeclaration, FunctionExpression}; pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags}; use crate::{ LinearPosition, Span, Spanned, StatementList, StatementListItem, visitor::{VisitWith, Visitor, VisitorMut}, }; /// A Function body. /// /// Since `Script` and `FunctionBody` have the same semantics, this is currently /// only an alias of the former. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-FunctionBody #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct FunctionBody { pub(crate) statements: StatementList, span: Span, } impl FunctionBody { /// Creates a new `FunctionBody` AST node. #[inline] #[must_use] pub fn new(statements: StatementList, span: Span) -> Self { Self { statements, span } } /// Gets the list of statements. #[inline] #[must_use] pub const fn statements(&self) -> &[StatementListItem] { self.statements.statements() } /// Gets the statement list. #[inline] #[must_use] pub const fn statement_list(&self) -> &StatementList { &self.statements } /// Get the strict mode. #[inline] #[must_use] pub const fn strict(&self) -> bool { self.statements.strict() } /// Get end of linear position in source code. #[inline] #[must_use] pub const fn linear_pos_end(&self) -> LinearPosition { self.statements.linear_pos_end() } } impl Spanned for FunctionBody { #[inline] fn span(&self) -> Span { self.span } } impl ToIndentedString for FunctionBody { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { self.statements.to_indented_string(interner, indentation) } } impl VisitWith for FunctionBody { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { for statement in &*self.statements { visitor.visit_statement_list_item(statement)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for statement in &mut *self.statements.statements { visitor.visit_statement_list_item_mut(statement)?; } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/function/ordinary_function.rs ================================================ use super::{FormalParameterList, FunctionBody}; use crate::{ Declaration, LinearSpan, LinearSpanIgnoreEq, Span, Spanned, block_to_string, expression::{Expression, Identifier}, join_nodes, operations::{ContainsSymbol, contains}, scope::{FunctionScopes, Scope}, scope_analyzer::{ analyze_binding_escapes, collect_bindings, optimize_scope_indices_function_constructor, }, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString}; use core::{fmt::Write as _, ops::ControlFlow}; /// A function declaration. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct FunctionDeclaration { name: Identifier, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, linear_span: LinearSpanIgnoreEq, } impl FunctionDeclaration { /// Creates a new function declaration. #[inline] #[must_use] pub fn new( name: Identifier, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), } } /// Gets the name of the function declaration. #[inline] #[must_use] pub const fn name(&self) -> 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) -> &FunctionBody { &self.body } /// Gets the scopes of the function declaration. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> LinearSpan { self.linear_span.0 } /// Returns `true` if the function declaration contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } } impl ToIndentedString for FunctionDeclaration { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { format!( "function {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), block_to_string(&self.body.statements, interner, indentation) ) } } impl VisitWith for FunctionDeclaration { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_identifier(&self.name)?; visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_identifier_mut(&mut self.name)?; visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } impl From for Declaration { #[inline] fn from(f: FunctionDeclaration) -> Self { Self::FunctionDeclaration(f) } } /// A function expression. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-FunctionExpression /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug)] pub struct FunctionExpression { pub(crate) name: Option, pub(crate) parameters: FormalParameterList, pub(crate) body: FunctionBody, pub(crate) has_binding_identifier: bool, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) name_scope: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, span: Span, linear_span: Option, } impl PartialEq for FunctionExpression { fn eq(&self, other: &Self) -> bool { // all fields except for `linear_span` self.name == other.name && self.parameters == other.parameters && self.body == other.body && self.has_binding_identifier == other.has_binding_identifier && self.contains_direct_eval == other.contains_direct_eval && self.name_scope == other.name_scope && self.scopes == other.scopes && self.span == other.span } } impl Spanned for FunctionExpression { #[inline] fn span(&self) -> Span { self.span } } impl FunctionExpression { /// Creates a new function expression. #[inline] #[must_use] pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: Option, has_binding_identifier: bool, span: Span, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), #[allow(clippy::redundant_closure_for_method_calls)] linear_span, span, } } /// Gets the name of the function expression. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } /// Gets the list of parameters of the function expression. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the function expression. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &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 } /// Gets the name scope of the function expression. #[inline] #[must_use] pub const fn name_scope(&self) -> Option<&Scope> { self.name_scope.as_ref() } /// Gets the scopes of the function expression. #[inline] #[must_use] pub const fn scopes(&self) -> &FunctionScopes { &self.scopes } /// Gets linear span of the function declaration. #[inline] #[must_use] pub const fn linear_span(&self) -> Option { self.linear_span } /// Returns `true` if the function expression contains a direct call to `eval`. #[inline] #[must_use] pub const fn contains_direct_eval(&self) -> bool { self.contains_direct_eval } /// Analyze the scope of the function expression. /// /// # Errors /// Any scope or binding errors that happened during the analysis. pub fn analyze_scope( &mut self, strict: bool, scope: &Scope, interner: &Interner, ) -> Result<(), &'static str> { collect_bindings(self, strict, false, scope, interner)?; analyze_binding_escapes(self, false, scope.clone(), interner)?; optimize_scope_indices_function_constructor(self, scope); Ok(()) } } impl ToIndentedString for FunctionExpression { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { let mut buf = "function".to_owned(); if self.has_binding_identifier && let Some(name) = self.name { let _ = write!(buf, " {}", interner.resolve_expect(name.sym())); } let _ = write!( buf, "({}) {}", join_nodes(interner, self.parameters.as_ref()), block_to_string(&self.body.statements, interner, indentation) ); buf } } impl From for Expression { #[inline] fn from(expr: FunctionExpression) -> Self { Self::FunctionExpression(expr) } } impl VisitWith for FunctionExpression { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(ident) = &self.name { visitor.visit_identifier(ident)?; } visitor.visit_formal_parameter_list(&self.parameters)?; visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(ident) = &mut self.name { visitor.visit_identifier_mut(ident)?; } visitor.visit_formal_parameter_list_mut(&mut self.parameters)?; visitor.visit_function_body_mut(&mut self.body) } } ================================================ FILE: core/ast/src/function/parameters.rs ================================================ use crate::{ declaration::{Binding, Variable}, expression::Expression, operations::bound_names, 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) -> Self { let mut flags = FormalParameterListFlags::default(); let mut length = 0; let mut names = FxHashSet::default(); for parameter in ¶meters { 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> for FormalParameterList { fn from(parameters: Vec) -> Self { Self::from_parameters(parameters) } } impl From 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 where V: Visitor<'a>, { for parameter in &*self.parameters { visitor.visit_formal_parameter(parameter)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for parameter in &mut *self.parameters { visitor.visit_formal_parameter_mut(parameter)?; } // TODO recompute flags ControlFlow::Continue(()) } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for FormalParameterList { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let params: Vec = u.arbitrary()?; Ok(Self::from(params)) } } bitflags! { /// Flags for a [`FormalParameterList`]. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[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 = "arbitrary", 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(variable: D, is_rest_param: bool) -> Self where D: Into, { 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 where V: Visitor<'a>, { visitor.visit_variable(&self.variable) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_variable_mut(&mut self.variable) } } ================================================ FILE: core/ast/src/keyword/mod.rs ================================================ //! 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::Sym; use boa_macros::utf16; use std::{convert::TryFrom, error, fmt, str::FromStr}; #[cfg(test)] mod tests; /// 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 `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 `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 `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, /// The `using` keyword. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/proposal-explicit-resource-management/ /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using Using, } 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 { 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::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::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::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")), Self::Using => ("using", utf16!("using")), } } /// Converts the keyword to a symbol in the given interner. #[must_use] pub const fn to_sym(self) -> Sym { match self { Self::Await => Sym::AWAIT, Self::Async => Sym::ASYNC, Self::Break => Sym::BREAK, Self::Case => Sym::CASE, Self::Catch => Sym::CATCH, Self::Class => Sym::CLASS, Self::Continue => Sym::CONTINUE, Self::Const => Sym::CONST, Self::Debugger => Sym::DEBUGGER, Self::Default => Sym::DEFAULT, Self::Delete => Sym::DELETE, Self::Do => Sym::DO, Self::Else => Sym::ELSE, Self::Enum => Sym::ENUM, Self::Export => Sym::EXPORT, Self::Extends => Sym::EXTENDS, Self::Finally => Sym::FINALLY, Self::For => Sym::FOR, Self::Function => Sym::FUNCTION, Self::If => Sym::IF, Self::In => Sym::IN, Self::InstanceOf => Sym::INSTANCEOF, Self::Import => Sym::IMPORT, Self::Let => Sym::LET, Self::New => Sym::NEW, Self::Of => Sym::OF, Self::Return => Sym::RETURN, Self::Super => Sym::SUPER, Self::Switch => Sym::SWITCH, Self::This => Sym::THIS, Self::Throw => Sym::THROW, Self::Try => Sym::TRY, Self::TypeOf => Sym::TYPEOF, Self::Var => Sym::VAR, Self::Void => Sym::VOID, Self::While => Sym::WHILE, Self::With => Sym::WITH, Self::Yield => Sym::YIELD, Self::Using => Sym::USING, } } } impl TryFrom for BinaryOp { type Error = KeywordToBinaryOpError; fn try_from(value: Keyword) -> Result { value.as_binary_op().ok_or(KeywordToBinaryOpError) } } /// Error returned when a [`Keyword`] cannot be converted into a [`BinaryOp`]. #[derive(Debug, Clone, Copy)] pub struct KeywordToBinaryOpError; impl fmt::Display for KeywordToBinaryOpError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "keyword is not a binary operator") } } impl error::Error for KeywordToBinaryOpError {} /// 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 {} impl FromStr for Keyword { type Err = KeywordError; fn from_str(s: &str) -> Result { 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), "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), "of" => Ok(Self::Of), "return" => Ok(Self::Return), "super" => Ok(Self::Super), "switch" => Ok(Self::Switch), "this" => Ok(Self::This), "throw" => Ok(Self::Throw), "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), "using" => Ok(Self::Using), _ => Err(KeywordError), } } } impl fmt::Display for Keyword { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str().0, f) } } ================================================ FILE: core/ast/src/keyword/tests.rs ================================================ #![allow(clippy::cognitive_complexity)] use super::*; /// Gets a list of all the keywords. fn all_keywords() -> impl Iterator { [ Keyword::Await, Keyword::Async, Keyword::Break, Keyword::Case, Keyword::Catch, Keyword::Class, Keyword::Continue, Keyword::Const, Keyword::Debugger, Keyword::Default, Keyword::Delete, Keyword::Do, Keyword::Else, Keyword::Enum, Keyword::Export, Keyword::Extends, Keyword::Finally, Keyword::For, Keyword::Function, Keyword::If, Keyword::In, Keyword::InstanceOf, Keyword::Import, Keyword::Let, Keyword::New, Keyword::Of, Keyword::Return, Keyword::Super, Keyword::Switch, Keyword::This, Keyword::Throw, Keyword::Try, Keyword::TypeOf, Keyword::Var, Keyword::Void, Keyword::While, Keyword::With, Keyword::Yield, ] .into_iter() } #[test] fn as_binary_op() { for k in all_keywords() { match k.as_binary_op() { Some(BinaryOp::Relational(RelationalOp::InstanceOf)) => { assert_eq!(k, Keyword::InstanceOf); } Some(BinaryOp::Relational(RelationalOp::In)) => assert_eq!(k, Keyword::In), None => { assert_ne!(k, Keyword::InstanceOf); assert_ne!(k, Keyword::In); } _ => unreachable!("unknown binary operator for keyword {k:?} found"), } } } #[test] fn as_str() { for k in all_keywords() { match k.as_str() { ("await", utf16) => { assert_eq!(k, Keyword::Await); assert_eq!(utf16, utf16!("await")); } ("async", utf16) => { assert_eq!(k, Keyword::Async); assert_eq!(utf16, utf16!("async")); } ("break", utf16) => { assert_eq!(k, Keyword::Break); assert_eq!(utf16, utf16!("break")); } ("case", utf16) => { assert_eq!(k, Keyword::Case); assert_eq!(utf16, utf16!("case")); } ("catch", utf16) => { assert_eq!(k, Keyword::Catch); assert_eq!(utf16, utf16!("catch")); } ("class", utf16) => { assert_eq!(k, Keyword::Class); assert_eq!(utf16, utf16!("class")); } ("continue", utf16) => { assert_eq!(k, Keyword::Continue); assert_eq!(utf16, utf16!("continue")); } ("const", utf16) => { assert_eq!(k, Keyword::Const); assert_eq!(utf16, utf16!("const")); } ("debugger", utf16) => { assert_eq!(k, Keyword::Debugger); assert_eq!(utf16, utf16!("debugger")); } ("default", utf16) => { assert_eq!(k, Keyword::Default); assert_eq!(utf16, utf16!("default")); } ("delete", utf16) => { assert_eq!(k, Keyword::Delete); assert_eq!(utf16, utf16!("delete")); } ("do", utf16) => { assert_eq!(k, Keyword::Do); assert_eq!(utf16, utf16!("do")); } ("else", utf16) => { assert_eq!(k, Keyword::Else); assert_eq!(utf16, utf16!("else")); } ("enum", utf16) => { assert_eq!(k, Keyword::Enum); assert_eq!(utf16, utf16!("enum")); } ("extends", utf16) => { assert_eq!(k, Keyword::Extends); assert_eq!(utf16, utf16!("extends")); } ("export", utf16) => { assert_eq!(k, Keyword::Export); assert_eq!(utf16, utf16!("export")); } ("finally", utf16) => { assert_eq!(k, Keyword::Finally); assert_eq!(utf16, utf16!("finally")); } ("for", utf16) => { assert_eq!(k, Keyword::For); assert_eq!(utf16, utf16!("for")); } ("function", utf16) => { assert_eq!(k, Keyword::Function); assert_eq!(utf16, utf16!("function")); } ("if", utf16) => { assert_eq!(k, Keyword::If); assert_eq!(utf16, utf16!("if")); } ("in", utf16) => { assert_eq!(k, Keyword::In); assert_eq!(utf16, utf16!("in")); } ("instanceof", utf16) => { assert_eq!(k, Keyword::InstanceOf); assert_eq!(utf16, utf16!("instanceof")); } ("import", utf16) => { assert_eq!(k, Keyword::Import); assert_eq!(utf16, utf16!("import")); } ("let", utf16) => { assert_eq!(k, Keyword::Let); assert_eq!(utf16, utf16!("let")); } ("new", utf16) => { assert_eq!(k, Keyword::New); assert_eq!(utf16, utf16!("new")); } ("of", utf16) => { assert_eq!(k, Keyword::Of); assert_eq!(utf16, utf16!("of")); } ("return", utf16) => { assert_eq!(k, Keyword::Return); assert_eq!(utf16, utf16!("return")); } ("super", utf16) => { assert_eq!(k, Keyword::Super); assert_eq!(utf16, utf16!("super")); } ("switch", utf16) => { assert_eq!(k, Keyword::Switch); assert_eq!(utf16, utf16!("switch")); } ("this", utf16) => { assert_eq!(k, Keyword::This); assert_eq!(utf16, utf16!("this")); } ("throw", utf16) => { assert_eq!(k, Keyword::Throw); assert_eq!(utf16, utf16!("throw")); } ("try", utf16) => { assert_eq!(k, Keyword::Try); assert_eq!(utf16, utf16!("try")); } ("typeof", utf16) => { assert_eq!(k, Keyword::TypeOf); assert_eq!(utf16, utf16!("typeof")); } ("var", utf16) => { assert_eq!(k, Keyword::Var); assert_eq!(utf16, utf16!("var")); } ("void", utf16) => { assert_eq!(k, Keyword::Void); assert_eq!(utf16, utf16!("void")); } ("while", utf16) => { assert_eq!(k, Keyword::While); assert_eq!(utf16, utf16!("while")); } ("with", utf16) => { assert_eq!(k, Keyword::With); assert_eq!(utf16, utf16!("with")); } ("yield", utf16) => { assert_eq!(k, Keyword::Yield); assert_eq!(utf16, utf16!("yield")); } (_, _) => unreachable!("unknown keyword {k:?} found"), } } } #[test] fn to_sym() { for k in all_keywords() { match k.to_sym() { Sym::AWAIT => assert_eq!(k, Keyword::Await), Sym::ASYNC => assert_eq!(k, Keyword::Async), Sym::BREAK => assert_eq!(k, Keyword::Break), Sym::CASE => assert_eq!(k, Keyword::Case), Sym::CATCH => assert_eq!(k, Keyword::Catch), Sym::CLASS => assert_eq!(k, Keyword::Class), Sym::CONTINUE => assert_eq!(k, Keyword::Continue), Sym::CONST => assert_eq!(k, Keyword::Const), Sym::DEBUGGER => assert_eq!(k, Keyword::Debugger), Sym::DEFAULT => assert_eq!(k, Keyword::Default), Sym::DELETE => assert_eq!(k, Keyword::Delete), Sym::DO => assert_eq!(k, Keyword::Do), Sym::ELSE => assert_eq!(k, Keyword::Else), Sym::ENUM => assert_eq!(k, Keyword::Enum), Sym::EXPORT => assert_eq!(k, Keyword::Export), Sym::EXTENDS => assert_eq!(k, Keyword::Extends), Sym::FINALLY => assert_eq!(k, Keyword::Finally), Sym::FOR => assert_eq!(k, Keyword::For), Sym::FUNCTION => assert_eq!(k, Keyword::Function), Sym::IF => assert_eq!(k, Keyword::If), Sym::IN => assert_eq!(k, Keyword::In), Sym::INSTANCEOF => assert_eq!(k, Keyword::InstanceOf), Sym::IMPORT => assert_eq!(k, Keyword::Import), Sym::LET => assert_eq!(k, Keyword::Let), Sym::NEW => assert_eq!(k, Keyword::New), Sym::OF => assert_eq!(k, Keyword::Of), Sym::RETURN => assert_eq!(k, Keyword::Return), Sym::SUPER => assert_eq!(k, Keyword::Super), Sym::SWITCH => assert_eq!(k, Keyword::Switch), Sym::THIS => assert_eq!(k, Keyword::This), Sym::THROW => assert_eq!(k, Keyword::Throw), Sym::TRY => assert_eq!(k, Keyword::Try), Sym::TYPEOF => assert_eq!(k, Keyword::TypeOf), Sym::VAR => assert_eq!(k, Keyword::Var), Sym::VOID => assert_eq!(k, Keyword::Void), Sym::WHILE => assert_eq!(k, Keyword::While), Sym::WITH => assert_eq!(k, Keyword::With), Sym::YIELD => assert_eq!(k, Keyword::Yield), _ => unreachable!("unknown keyword {k:?} found"), } } } #[test] fn try_into_binary_op() { for k in all_keywords() { match k { Keyword::InstanceOf | Keyword::In => assert!(BinaryOp::try_from(k).is_ok()), Keyword::Await | Keyword::Async | Keyword::Break | Keyword::Case | Keyword::Catch | Keyword::Class | Keyword::Continue | Keyword::Const | Keyword::Debugger | Keyword::Default | Keyword::Delete | Keyword::Do | Keyword::Else | Keyword::Enum | Keyword::Export | Keyword::Extends | Keyword::Finally | Keyword::For | Keyword::Function | Keyword::If | Keyword::Import | Keyword::Let | Keyword::New | Keyword::Of | Keyword::Return | Keyword::Super | Keyword::Switch | Keyword::This | Keyword::Throw | Keyword::Try | Keyword::TypeOf | Keyword::Using | Keyword::Var | Keyword::Void | Keyword::While | Keyword::With | Keyword::Yield => assert!(BinaryOp::try_from(k).is_err()), } } } #[test] fn from_str() { for k in all_keywords() { let str = k.as_str().0; assert_eq!(str.parse::().unwrap(), k); } for invalid in ["", "testing", "invalid keyword"] { let result = invalid.parse::(); let error = result.unwrap_err(); assert_eq!(error.to_string(), "invalid token"); } } ================================================ FILE: core/ast/src/lib.rs ================================================ //! 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. //! //! [grammar]: https://tc39.es/ecma262/#sec-syntactic-grammar //! [early]: https://tc39.es/ecma262/#sec-static-semantic-rules #![doc = include_str!("../ABOUT.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg", html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg" )] #![cfg_attr(not(test), forbid(clippy::unwrap_used))] #![allow( clippy::module_name_repetitions, clippy::too_many_lines, clippy::option_if_let_else )] mod module_item_list; mod position; mod punctuator; mod source; mod source_text; 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 scope; pub mod scope_analyzer; pub mod statement; pub mod visitor; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use boa_string::{JsStr, JsString}; use expression::Identifier; pub use self::{ declaration::Declaration, expression::Expression, keyword::Keyword, module_item_list::{ModuleItem, ModuleItemList}, position::{ LinearPosition, LinearSpan, LinearSpanIgnoreEq, Position, PositionGroup, Span, Spanned, }, punctuator::Punctuator, source::{Module, Script}, source_text::SourceText, statement::Statement, statement_list::{StatementList, StatementListItem}, }; /// Utility to join multiple Nodes into a single string. fn join_nodes(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() } } pub(crate) trait ToJsString { fn to_js_string(&self, interner: &Interner) -> JsString; } impl ToJsString for Sym { #[allow(clippy::cast_possible_truncation)] fn to_js_string(&self, interner: &Interner) -> JsString { let utf16 = interner.resolve_expect(*self).utf16(); if interner.is_latin1(*self) { let bytes: Vec = utf16.iter().map(|&c| c as u8).collect(); JsString::from(JsStr::latin1(&bytes)) } else { JsString::from(utf16) } } } impl ToJsString for Identifier { fn to_js_string(&self, interner: &Interner) -> JsString { self.sym().to_js_string(interner) } } ================================================ FILE: core/ast/src/module_item_list/mod.rs ================================================ //! Module item list AST nodes. //! //! More information: //! - [ECMAScript specification][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-modules use crate::{ StatementListItem, declaration::{ ExportDeclaration, ExportEntry, ExportSpecifier, ImportAttribute, ImportDeclaration, ImportEntry, ImportKind, ImportName, IndirectExportEntry, LocalExportEntry, ModuleSpecifier, ReExportImportName, ReExportKind, }, operations::{BoundNamesVisitor, bound_names}, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::Sym; use indexmap::IndexSet; use rustc_hash::{FxHashSet, FxHasher}; use std::{convert::Infallible, hash::BuildHasherDefault, ops::ControlFlow}; /// Module item list AST node. /// /// It contains a list of module items. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ModuleItemList #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default, PartialEq)] pub struct ModuleItemList { items: Box<[ModuleItem]>, } impl ModuleItemList { /// Gets the list of module items. #[inline] #[must_use] pub const fn items(&self) -> &[ModuleItem] { &self.items } /// Abstract operation [`ExportedNames`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-exportednames #[inline] #[must_use] pub fn exported_names(&self) -> Vec { #[derive(Debug)] struct ExportedItemsVisitor<'vec>(&'vec mut Vec); impl<'ast> Visitor<'ast> for ExportedItemsVisitor<'_> { type BreakTy = Infallible; fn visit_import_declaration( &mut self, _: &'ast ImportDeclaration, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_statement_list_item( &mut self, _: &'ast StatementListItem, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_export_specifier( &mut self, node: &'ast ExportSpecifier, ) -> ControlFlow { self.0.push(node.alias()); ControlFlow::Continue(()) } fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, ) -> ControlFlow { match node { ExportDeclaration::ReExport { kind, .. } => { match kind { ReExportKind::Namespaced { name: Some(name) } => self.0.push(*name), ReExportKind::Namespaced { name: None } => {} ReExportKind::Named { names } => { for specifier in &**names { self.visit_export_specifier(specifier)?; } } } ControlFlow::Continue(()) } ExportDeclaration::List(list) => { for specifier in &**list { self.visit_export_specifier(specifier)?; } ControlFlow::Continue(()) } ExportDeclaration::VarStatement(var) => { BoundNamesVisitor(self.0).visit_var_declaration(var) } ExportDeclaration::Declaration(decl) => { BoundNamesVisitor(self.0).visit_declaration(decl) } ExportDeclaration::DefaultFunctionDeclaration(_) | ExportDeclaration::DefaultGeneratorDeclaration(_) | ExportDeclaration::DefaultAsyncFunctionDeclaration(_) | ExportDeclaration::DefaultAsyncGeneratorDeclaration(_) | ExportDeclaration::DefaultClassDeclaration(_) | ExportDeclaration::DefaultAssignmentExpression(_) => { self.0.push(Sym::DEFAULT); ControlFlow::Continue(()) } } } } let mut names = Vec::new(); let _ = ExportedItemsVisitor(&mut names).visit_module_item_list(self); names } /// Abstract operation [`ExportedBindings`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-exportedbindings #[inline] #[must_use] pub fn exported_bindings(&self) -> FxHashSet { #[derive(Debug)] struct ExportedBindingsVisitor<'vec>(&'vec mut FxHashSet); impl<'ast> Visitor<'ast> for ExportedBindingsVisitor<'_> { type BreakTy = Infallible; fn visit_import_declaration( &mut self, _: &'ast ImportDeclaration, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_statement_list_item( &mut self, _: &'ast StatementListItem, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_export_specifier( &mut self, node: &'ast ExportSpecifier, ) -> ControlFlow { self.0.insert(node.private_name()); ControlFlow::Continue(()) } fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, ) -> ControlFlow { let name = match node { ExportDeclaration::ReExport { .. } => return ControlFlow::Continue(()), ExportDeclaration::List(list) => { for specifier in &**list { self.visit_export_specifier(specifier)?; } return ControlFlow::Continue(()); } ExportDeclaration::DefaultAssignmentExpression(expr) => { return BoundNamesVisitor(self.0).visit_expression(expr); } ExportDeclaration::VarStatement(var) => { return BoundNamesVisitor(self.0).visit_var_declaration(var); } ExportDeclaration::Declaration(decl) => { return BoundNamesVisitor(self.0).visit_declaration(decl); } ExportDeclaration::DefaultFunctionDeclaration(f) => f.name(), ExportDeclaration::DefaultGeneratorDeclaration(g) => g.name(), ExportDeclaration::DefaultAsyncFunctionDeclaration(af) => af.name(), ExportDeclaration::DefaultAsyncGeneratorDeclaration(ag) => ag.name(), ExportDeclaration::DefaultClassDeclaration(cl) => cl.name(), }; self.0.insert(name.sym()); ControlFlow::Continue(()) } } let mut names = FxHashSet::default(); let _ = ExportedBindingsVisitor(&mut names).visit_module_item_list(self); names } /// Operation [`ModuleRequests`][spec]. /// /// Gets the list of modules that need to be fetched by the module resolver to link this module. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-modulerequests #[inline] #[must_use] pub fn requests(&self) -> IndexSet> { #[derive(Debug)] struct RequestsVisitor<'vec>(&'vec mut IndexSet>); impl<'ast> Visitor<'ast> for RequestsVisitor<'_> { type BreakTy = Infallible; fn visit_statement_list_item( &mut self, _: &'ast StatementListItem, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_module_specifier( &mut self, node: &'ast ModuleSpecifier, ) -> ControlFlow { self.0.insert(node.sym()); ControlFlow::Continue(()) } } let mut requests = IndexSet::default(); let _ = RequestsVisitor(&mut requests).visit_module_item_list(self); requests } /// Operation [`ImportEntries`][spec]. /// /// Gets the list of import entries of this module. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-importentries #[inline] #[must_use] pub fn import_entries(&self) -> Vec { #[derive(Debug)] struct ImportEntriesVisitor<'vec>(&'vec mut Vec); impl<'ast> Visitor<'ast> for ImportEntriesVisitor<'_> { type BreakTy = Infallible; fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { ModuleItem::ImportDeclaration(import) => self.visit_import_declaration(import), ModuleItem::ExportDeclaration(_) | ModuleItem::StatementListItem(_) => { ControlFlow::Continue(()) } } } fn visit_import_declaration( &mut self, node: &'ast ImportDeclaration, ) -> ControlFlow { let module = node.specifier().sym(); let attributes: Box<[ImportAttribute]> = Box::from(node.attributes()); if let Some(default) = node.default() { self.0.push(ImportEntry::new( module, ImportName::Name(Sym::DEFAULT), default, attributes.clone(), )); } match node.kind() { ImportKind::DefaultOrUnnamed => {} ImportKind::Namespaced { binding } => { self.0.push(ImportEntry::new( module, ImportName::Namespace, *binding, attributes.clone(), )); } ImportKind::Named { names } => { for name in &**names { self.0.push(ImportEntry::new( module, ImportName::Name(name.export_name()), name.binding(), attributes.clone(), )); } } } ControlFlow::Continue(()) } } let mut entries = Vec::default(); let _ = ImportEntriesVisitor(&mut entries).visit_module_item_list(self); entries } /// Operation [`ExportEntries`][spec]. /// /// Gets the list of export entries of this module. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-exportentries #[inline] #[must_use] pub fn export_entries(&self) -> Vec { #[derive(Debug)] struct ExportEntriesVisitor<'vec>(&'vec mut Vec); impl<'ast> Visitor<'ast> for ExportEntriesVisitor<'_> { type BreakTy = Infallible; fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { ModuleItem::ExportDeclaration(import) => self.visit_export_declaration(import), ModuleItem::ImportDeclaration(_) | ModuleItem::StatementListItem(_) => { ControlFlow::Continue(()) } } } fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, ) -> ControlFlow { let name = match node { ExportDeclaration::ReExport { kind, specifier, attributes, } => { let module = specifier.sym(); let attrs = attributes.clone(); match kind { ReExportKind::Namespaced { name: Some(name) } => { self.0.push( IndirectExportEntry::new( module, ReExportImportName::Star, *name, attrs.clone(), ) .into(), ); } ReExportKind::Namespaced { name: None } => { self.0.push(ExportEntry::StarReExport { module_request: module, attributes: attrs.clone(), }); } ReExportKind::Named { names } => { for name in &**names { self.0.push( IndirectExportEntry::new( module, ReExportImportName::Name(name.private_name()), name.alias(), attrs.clone(), ) .into(), ); } } } return ControlFlow::Continue(()); } ExportDeclaration::List(names) => { for name in &**names { self.0.push( LocalExportEntry::new(name.private_name(), name.alias()).into(), ); } return ControlFlow::Continue(()); } ExportDeclaration::VarStatement(var) => { for name in bound_names(var) { self.0.push(LocalExportEntry::new(name, name).into()); } return ControlFlow::Continue(()); } ExportDeclaration::Declaration(decl) => { for name in bound_names(decl) { self.0.push(LocalExportEntry::new(name, name).into()); } return ControlFlow::Continue(()); } ExportDeclaration::DefaultFunctionDeclaration(f) => f.name().sym(), ExportDeclaration::DefaultGeneratorDeclaration(g) => g.name().sym(), ExportDeclaration::DefaultAsyncFunctionDeclaration(af) => af.name().sym(), ExportDeclaration::DefaultAsyncGeneratorDeclaration(ag) => ag.name().sym(), ExportDeclaration::DefaultClassDeclaration(c) => c.name().sym(), ExportDeclaration::DefaultAssignmentExpression(_) => Sym::DEFAULT_EXPORT, }; self.0 .push(LocalExportEntry::new(name, Sym::DEFAULT).into()); ControlFlow::Continue(()) } } let mut entries = Vec::default(); let _ = ExportEntriesVisitor(&mut entries).visit_module_item_list(self); entries } } impl From for ModuleItemList where T: Into>, { #[inline] fn from(items: T) -> Self { Self { items: items.into(), } } } impl VisitWith for ModuleItemList { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { for item in &*self.items { visitor.visit_module_item(item)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for item in &mut *self.items { visitor.visit_module_item_mut(item)?; } ControlFlow::Continue(()) } } /// Module item AST node. /// /// This is an extension over a [`StatementList`](crate::StatementList), which can also include /// multiple [`ImportDeclaration`] and [`ExportDeclaration`] nodes, along with /// [`StatementListItem`] nodes. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ModuleItem #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq)] pub enum ModuleItem { /// See [`ImportDeclaration`]. ImportDeclaration(ImportDeclaration), /// See [`ExportDeclaration`]. ExportDeclaration(Box), /// See [`StatementListItem`]. StatementListItem(StatementListItem), } impl VisitWith for ModuleItem { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::ImportDeclaration(i) => visitor.visit_import_declaration(i), Self::ExportDeclaration(e) => visitor.visit_export_declaration(e), Self::StatementListItem(s) => visitor.visit_statement_list_item(s), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::ImportDeclaration(i) => visitor.visit_import_declaration_mut(i), Self::ExportDeclaration(e) => visitor.visit_export_declaration_mut(e), Self::StatementListItem(s) => visitor.visit_statement_list_item_mut(s), } } } ================================================ FILE: core/ast/src/operations/mod.rs ================================================ //! 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::{Interner, Sym}; use rustc_hash::FxHashSet; use crate::{ Declaration, Expression, LinearSpan, ModuleItem, Script, Statement, StatementList, StatementListItem, declaration::{ Binding, ExportDeclaration, ImportDeclaration, LexicalDeclaration, VarDeclaration, Variable, }, expression::{ Await, Call, Identifier, NewTarget, OptionalOperationKind, SuperCall, This, Yield, access::{PrivatePropertyAccess, SuperPropertyAccess}, literal::PropertyDefinition, operator::BinaryInPrivate, }, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, ClassElementName, ClassExpression, FormalParameterList, FunctionBody, FunctionDeclaration, FunctionExpression, GeneratorDeclaration, GeneratorExpression, PrivateFieldDefinition, }, statement::{ LabelledItem, With, iteration::{ForLoopInitializer, IterableLoopInitializer}, }, visitor::{NodeRef, VisitWith, Visitor}, }; #[cfg(test)] mod tests; /// 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, /// A direct call to `eval`. DirectEval, } /// 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(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 ContainsVisitor { fn visit_contains_eval(&mut self, contains_direct_eval: bool) -> ControlFlow<()> { if self.0 == ContainsSymbol::DirectEval && contains_direct_eval { ControlFlow::Break(()) } else { ControlFlow::Continue(()) } } } impl<'ast> Visitor<'ast> for ContainsVisitor { type BreakTy = (); fn visit_with(&mut self, node: &'ast With) -> ControlFlow { self.visit_expression(node.expression())?; node.statement().visit_with(self) } fn visit_call(&mut self, node: &'ast Call) -> ControlFlow { if self.0 == ContainsSymbol::DirectEval && let Expression::Identifier(ident) = node.function().flatten() && ident.sym() == Sym::EVAL { return ControlFlow::Break(()); } self.visit_expression(node.function())?; for arg in node.args() { self.visit_expression(arg)?; } ControlFlow::Continue(()) } fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow { if self.0 == ContainsSymbol::EvalOrArguments && (node.sym() == Sym::EVAL || node.sym() == Sym::ARGUMENTS) { return ControlFlow::Break(()); } ControlFlow::Continue(()) } fn visit_function_expression( &mut self, node: &'ast FunctionExpression, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_function_declaration( &mut self, node: &'ast FunctionDeclaration, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_async_function_expression( &mut self, node: &'ast AsyncFunctionExpression, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_async_function_declaration( &mut self, node: &'ast AsyncFunctionDeclaration, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_generator_expression( &mut self, node: &'ast GeneratorExpression, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_generator_declaration( &mut self, node: &'ast GeneratorDeclaration, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_async_generator_expression( &mut self, node: &'ast AsyncGeneratorExpression, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_async_generator_declaration( &mut self, node: &'ast AsyncGeneratorDeclaration, ) -> ControlFlow { self.visit_contains_eval(node.contains_direct_eval)?; ControlFlow::Continue(()) } fn visit_class_expression( &mut self, node: &'ast ClassExpression, ) -> ControlFlow { 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) } fn visit_class_declaration( &mut self, node: &'ast ClassDeclaration, ) -> ControlFlow { 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 { match node { ClassElement::MethodDefinition(m) => { if self.0 == ContainsSymbol::DirectEval { return ControlFlow::Continue(()); } if let ClassElementName::PropertyName(name) = m.name() { name.visit_with(self) } else { ControlFlow::Continue(()) } } ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => field.name.visit_with(self), _ => ControlFlow::Continue(()), } } fn visit_property_definition( &mut self, node: &'ast PropertyDefinition, ) -> ControlFlow { if let PropertyDefinition::MethodDefinition(m) = node { if self.0 == ContainsSymbol::DirectEval { return ControlFlow::Continue(()); } if self.0 == ContainsSymbol::MethodDefinition { return ControlFlow::Break(()); } return m.name().visit_with(self); } node.visit_with(self) } fn visit_arrow_function( &mut self, node: &'ast ArrowFunction, ) -> ControlFlow { if ![ ContainsSymbol::NewTarget, ContainsSymbol::SuperProperty, ContainsSymbol::SuperCall, ContainsSymbol::Super, ContainsSymbol::This, ContainsSymbol::DirectEval, ] .contains(&self.0) { return ControlFlow::Continue(()); } node.visit_with(self) } fn visit_async_arrow_function( &mut self, node: &'ast AsyncArrowFunction, ) -> ControlFlow { if ![ ContainsSymbol::NewTarget, ContainsSymbol::SuperProperty, ContainsSymbol::SuperCall, ContainsSymbol::Super, ContainsSymbol::This, ContainsSymbol::DirectEval, ] .contains(&self.0) { return ControlFlow::Continue(()); } node.visit_with(self) } fn visit_super_property_access( &mut self, node: &'ast SuperPropertyAccess, ) -> ControlFlow { 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 { if [ContainsSymbol::SuperCall, ContainsSymbol::Super].contains(&self.0) { return ControlFlow::Break(()); } node.visit_with(self) } fn visit_yield(&mut self, node: &'ast Yield) -> ControlFlow { if self.0 == ContainsSymbol::YieldExpression { return ControlFlow::Break(()); } node.visit_with(self) } fn visit_await(&mut self, node: &'ast Await) -> ControlFlow { if self.0 == ContainsSymbol::AwaitExpression { return ControlFlow::Break(()); } node.visit_with(self) } fn visit_this(&mut self, _node: &'ast This) -> ControlFlow { if self.0 == ContainsSymbol::This { return ControlFlow::Break(()); } ControlFlow::Continue(()) } fn visit_new_target(&mut self, _node: &'ast NewTarget) -> ControlFlow { if self.0 == ContainsSymbol::NewTarget { return ControlFlow::Break(()); } ControlFlow::Continue(()) } } 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(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 { if node.sym() == Sym::ARGUMENTS { ControlFlow::Break(()) } else { ControlFlow::Continue(()) } } fn visit_function_expression( &mut self, _: &'ast FunctionExpression, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_function_declaration( &mut self, _: &'ast FunctionDeclaration, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_async_function_expression( &mut self, _: &'ast AsyncFunctionExpression, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_async_function_declaration( &mut self, _: &'ast AsyncFunctionDeclaration, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_generator_expression( &mut self, _: &'ast GeneratorExpression, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_generator_declaration( &mut self, _: &'ast GeneratorDeclaration, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_async_generator_expression( &mut self, _: &'ast AsyncGeneratorExpression, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_async_generator_declaration( &mut self, _: &'ast AsyncGeneratorDeclaration, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { if let ClassElement::MethodDefinition(m) = node && let ClassElementName::PropertyName(name) = m.name() { return name.visit_with(self); } node.visit_with(self) } fn visit_property_definition( &mut self, node: &'ast PropertyDefinition, ) -> ControlFlow { if let PropertyDefinition::MethodDefinition(m) = node { m.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_new(params: &FormalParameterList, body: &FunctionBody) -> bool { contains(params, ContainsSymbol::SuperCall) || contains(body, ContainsSymbol::SuperCall) } /// A container that [`BoundNamesVisitor`] can use to push the found identifiers. pub(crate) trait IdentList { fn add(&mut self, value: Sym, function: bool); } impl IdentList for Vec { fn add(&mut self, value: Sym, _function: bool) { self.push(value); } } impl IdentList for Vec<(Sym, bool)> { fn add(&mut self, value: Sym, function: bool) { self.push((value, function)); } } impl IdentList for FxHashSet { fn add(&mut self, value: Sym, _function: bool) { self.insert(value); } } /// The [`Visitor`] used to obtain the bound names of a node. #[derive(Debug)] pub(crate) struct BoundNamesVisitor<'a, T: IdentList>(pub(crate) &'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.0.add(node.sym(), false); ControlFlow::Continue(()) } fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow { ControlFlow::Continue(()) } fn visit_function_expression( &mut self, node: &'ast FunctionExpression, ) -> ControlFlow { if let Some(ident) = node.name() { self.0.add(ident.sym(), true); } ControlFlow::Continue(()) } fn visit_function_declaration( &mut self, node: &'ast FunctionDeclaration, ) -> ControlFlow { self.0.add(node.name().sym(), true); ControlFlow::Continue(()) } fn visit_generator_expression( &mut self, node: &'ast GeneratorExpression, ) -> ControlFlow { if let Some(ident) = node.name() { self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } fn visit_generator_declaration( &mut self, node: &'ast GeneratorDeclaration, ) -> ControlFlow { self.0.add(node.name().sym(), false); ControlFlow::Continue(()) } fn visit_async_function_expression( &mut self, node: &'ast AsyncFunctionExpression, ) -> ControlFlow { if let Some(ident) = node.name() { self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } fn visit_async_function_declaration( &mut self, node: &'ast AsyncFunctionDeclaration, ) -> ControlFlow { self.0.add(node.name().sym(), false); ControlFlow::Continue(()) } fn visit_async_generator_expression( &mut self, node: &'ast AsyncGeneratorExpression, ) -> ControlFlow { if let Some(ident) = node.name() { self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } fn visit_async_generator_declaration( &mut self, node: &'ast AsyncGeneratorDeclaration, ) -> ControlFlow { self.0.add(node.name().sym(), false); ControlFlow::Continue(()) } fn visit_class_expression( &mut self, node: &'ast ClassExpression, ) -> ControlFlow { if let Some(ident) = node.name() { self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } fn visit_class_declaration( &mut self, node: &'ast ClassDeclaration, ) -> ControlFlow { self.0.add(node.name().sym(), false); ControlFlow::Continue(()) } fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, ) -> ControlFlow { match node { ExportDeclaration::VarStatement(var) => self.visit_var_declaration(var)?, ExportDeclaration::Declaration(decl) => self.visit_declaration(decl)?, ExportDeclaration::DefaultFunctionDeclaration(f) => { self.0.add(f.name().sym(), true); } ExportDeclaration::DefaultGeneratorDeclaration(g) => { self.0.add(g.name().sym(), false); } ExportDeclaration::DefaultAsyncFunctionDeclaration(af) => { self.0.add(af.name().sym(), false); } ExportDeclaration::DefaultAsyncGeneratorDeclaration(ag) => { self.0.add(ag.name().sym(), false); } ExportDeclaration::DefaultClassDeclaration(cl) => { self.0.add(cl.name().sym(), false); } ExportDeclaration::DefaultAssignmentExpression(_) => { self.0.add(Sym::DEFAULT_EXPORT, false); } ExportDeclaration::ReExport { .. } | ExportDeclaration::List(_) => {} } 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 where &'a N: Into>, { let mut names = Vec::new(); let _ = 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_script(&mut self, node: &'ast Script) -> ControlFlow { top_level_lexicals(node.statements(), self.0); ControlFlow::Continue(()) } fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { top_level_lexicals(node.statement_list(), self.0); ControlFlow::Continue(()) } fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { // ModuleItem : ImportDeclaration ModuleItem::ImportDeclaration(import) => { // 1. Return the BoundNames of ImportDeclaration. BoundNamesVisitor(self.0).visit_import_declaration(import) } // ModuleItem : ExportDeclaration ModuleItem::ExportDeclaration(export) => { // 1. If ExportDeclaration is export VariableStatement, return a new empty List. if matches!(export.as_ref(), ExportDeclaration::VarStatement(_)) { ControlFlow::Continue(()) } else { // 2. Return the BoundNames of ExportDeclaration. BoundNamesVisitor(self.0).visit_export_declaration(export) } } // ModuleItem : StatementListItem ModuleItem::StatementListItem(item) => { // 1. Return LexicallyDeclaredNames of StatementListItem. self.visit_statement_list_item(item) } } } fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow { ControlFlow::Continue(()) } fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { if let Statement::Labelled(labelled) = node { return self.visit_labelled(labelled); } ControlFlow::Continue(()) } fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow { BoundNamesVisitor(self.0).visit_declaration(node) } fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { LabelledItem::FunctionDeclaration(f) => { BoundNamesVisitor(self.0).visit_function_declaration(f) } LabelledItem::Statement(_) => ControlFlow::Continue(()), } } fn visit_function_expression( &mut self, node: &'ast FunctionExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_function_declaration( &mut self, node: &'ast FunctionDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_function_expression( &mut self, node: &'ast AsyncFunctionExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_function_declaration( &mut self, node: &'ast AsyncFunctionDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_generator_expression( &mut self, node: &'ast GeneratorExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_generator_declaration( &mut self, node: &'ast GeneratorDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_generator_expression( &mut self, node: &'ast AsyncGeneratorExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_generator_declaration( &mut self, node: &'ast AsyncGeneratorDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_arrow_function( &mut self, node: &'ast AsyncArrowFunction, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { if let ClassElement::StaticBlock(block) = node { self.visit_function_body(&block.body)?; } ControlFlow::Continue(()) } fn visit_import_declaration( &mut self, node: &'ast ImportDeclaration, ) -> ControlFlow { BoundNamesVisitor(self.0).visit_import_declaration(node) } fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, ) -> ControlFlow { if matches!(node, ExportDeclaration::VarStatement(_)) { return ControlFlow::Continue(()); } BoundNamesVisitor(self.0).visit_export_declaration(node) } } /// 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 where &'a N: Into>, { let mut names = Vec::new(); let _ = 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<(Sym, bool)> where &'a N: Into>, { let mut names = Vec::new(); let _ = 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); impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { type BreakTy = Infallible; fn visit_script(&mut self, node: &'ast Script) -> ControlFlow { top_level_vars(node.statements(), self.0); ControlFlow::Continue(()) } fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { top_level_vars(node.statement_list(), self.0); ControlFlow::Continue(()) } fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { // ModuleItem : ImportDeclaration ModuleItem::ImportDeclaration(_) => { // 1. Return a new empty List. ControlFlow::Continue(()) } // ModuleItem : ExportDeclaration ModuleItem::ExportDeclaration(export) => { // 1. If ExportDeclaration is export VariableStatement, return BoundNames of ExportDeclaration. if let ExportDeclaration::VarStatement(var) = export.as_ref() { BoundNamesVisitor(self.0).visit_var_declaration(var) } else { // 2. Return a new empty List. ControlFlow::Continue(()) } } ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item), } } fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Empty | Statement::Debugger | Statement::Expression(_) | Statement::Continue(_) | Statement::Break(_) | Statement::Return(_) | Statement::Throw(_) => ControlFlow::Continue(()), Statement::Block(node) => self.visit(node), Statement::Var(node) => self.visit(node), Statement::If(node) => self.visit(node), Statement::DoWhileLoop(node) => self.visit(node), Statement::WhileLoop(node) => self.visit(node), Statement::ForLoop(node) => self.visit(node), Statement::ForInLoop(node) => self.visit(node), Statement::ForOfLoop(node) => self.visit(node), Statement::Switch(node) => self.visit(node), Statement::Labelled(node) => self.visit(node), Statement::Try(node) => self.visit(node), Statement::With(node) => self.visit(node), } } fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { match node { StatementListItem::Statement(stmt) => self.visit_statement(stmt), StatementListItem::Declaration(_) => ControlFlow::Continue(()), } } fn visit_variable(&mut self, node: &'ast Variable) -> ControlFlow { BoundNamesVisitor(self.0).visit_variable(node) } fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow { if let Some(node) = node.else_node() { self.visit(node)?; } self.visit(node.body()) } fn visit_do_while_loop( &mut self, node: &'ast crate::statement::DoWhileLoop, ) -> ControlFlow { self.visit(node.body()) } fn visit_while_loop( &mut self, node: &'ast crate::statement::WhileLoop, ) -> ControlFlow { self.visit(node.body()) } fn visit_for_loop( &mut self, node: &'ast crate::statement::ForLoop, ) -> ControlFlow { if let Some(ForLoopInitializer::Var(node)) = node.init() { BoundNamesVisitor(self.0).visit_var_declaration(node)?; } self.visit(node.body()) } fn visit_for_in_loop( &mut self, node: &'ast crate::statement::ForInLoop, ) -> ControlFlow { if let IterableLoopInitializer::Var(node) = node.initializer() { BoundNamesVisitor(self.0).visit_variable(node)?; } self.visit(node.body()) } fn visit_for_of_loop( &mut self, node: &'ast crate::statement::ForOfLoop, ) -> ControlFlow { if let IterableLoopInitializer::Var(node) = node.initializer() { BoundNamesVisitor(self.0).visit_variable(node)?; } self.visit(node.body()) } fn visit_with(&mut self, node: &'ast With) -> ControlFlow { self.visit(node.statement()) } fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow { for case in node.cases() { self.visit(case)?; } if let Some(node) = node.default() { self.visit(node)?; } ControlFlow::Continue(()) } fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { LabelledItem::FunctionDeclaration(_) => ControlFlow::Continue(()), LabelledItem::Statement(stmt) => self.visit(stmt), } } fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow { if let Some(node) = node.finally() { self.visit(node)?; } if let Some(node) = node.catch() { self.visit(node.block())?; } self.visit(node.block()) } fn visit_function_expression( &mut self, node: &'ast FunctionExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_function_declaration( &mut self, node: &'ast FunctionDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_function_expression( &mut self, node: &'ast AsyncFunctionExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_function_declaration( &mut self, node: &'ast AsyncFunctionDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_generator_expression( &mut self, node: &'ast GeneratorExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_generator_declaration( &mut self, node: &'ast GeneratorDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_generator_expression( &mut self, node: &'ast AsyncGeneratorExpression, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_async_generator_declaration( &mut self, node: &'ast AsyncGeneratorDeclaration, ) -> ControlFlow { self.visit_function_body(node.body()) } fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { if let ClassElement::StaticBlock(block) = node { self.visit_function_body(&block.body)?; } node.visit_with(self) } fn visit_import_declaration( &mut self, _: &'ast ImportDeclaration, ) -> ControlFlow { ControlFlow::Continue(()) } fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, ) -> ControlFlow { match node { ExportDeclaration::VarStatement(var) => { BoundNamesVisitor(self.0).visit_var_declaration(var) } _ => ControlFlow::Continue(()), } } } /// 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 where &'a N: Into>, { let mut names = FxHashSet::default(); let _ = VarDeclaredNamesVisitor(&mut names).visit(node.into()); names } /// Utility function that collects the top level lexicals of a statement list into `names`. /// /// This is equivalent to the [`TopLevelLexicallyDeclaredNames`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames fn top_level_lexicals(stmts: &StatementList, names: &mut T) { for stmt in stmts.statements() { if let StatementListItem::Declaration(decl) = stmt { match decl.as_ref() { // Note // At the top level of a function, or script, function declarations are treated like // var declarations rather than like lexical declarations. Declaration::FunctionDeclaration(_) | Declaration::GeneratorDeclaration(_) | Declaration::AsyncFunctionDeclaration(_) | Declaration::AsyncGeneratorDeclaration(_) => {} Declaration::ClassDeclaration(class) => { let _ = BoundNamesVisitor(names).visit_class_declaration(class); } Declaration::Lexical(decl) => { let _ = BoundNamesVisitor(names).visit_lexical_declaration(decl); } } } } } /// Utility function that collects the top level vars of a statement list into `names`. /// /// This is equivalent to the [`TopLevelVarDeclaredNames`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet) { for stmt in stmts.statements() { match stmt { StatementListItem::Declaration(decl) => { match decl.as_ref() { // Note // At the top level of a function, or script, function declarations are treated like // var declarations rather than like lexical declarations. Declaration::FunctionDeclaration(f) => { let _ = BoundNamesVisitor(names).visit_function_declaration(f); } Declaration::GeneratorDeclaration(f) => { let _ = BoundNamesVisitor(names).visit_generator_declaration(f); } Declaration::AsyncFunctionDeclaration(f) => { let _ = BoundNamesVisitor(names).visit_async_function_declaration(f); } Declaration::AsyncGeneratorDeclaration(f) => { let _ = BoundNamesVisitor(names).visit_async_generator_declaration(f); } Declaration::ClassDeclaration(_) | Declaration::Lexical(_) => {} } } StatementListItem::Statement(stmt) => { let mut stmt = Some(stmt.as_ref()); while let Some(Statement::Labelled(labelled)) = stmt.as_ref() { match labelled.item() { LabelledItem::FunctionDeclaration(f) => { let _ = BoundNamesVisitor(names).visit_function_declaration(f); stmt = None; } LabelledItem::Statement(s) => stmt = Some(s), } } if let Some(stmt) = stmt { let _ = VarDeclaredNamesVisitor(names).visit(stmt); } } } } } /// Returns `true` if all private identifiers in a node are valid. /// /// This is equivalent to the [`AllPrivateIdentifiersValid`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-allprivateidentifiersvalid #[must_use] #[inline] pub fn all_private_identifiers_valid<'a, N>(node: &'a N, private_names: Vec) -> bool where &'a N: Into>, { AllPrivateIdentifiersValidVisitor(private_names) .visit(node.into()) .is_continue() } struct AllPrivateIdentifiersValidVisitor(Vec); impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { type BreakTy = (); fn visit_class_expression( &mut self, node: &'ast ClassExpression, ) -> ControlFlow { if let Some(node) = node.super_ref() { self.visit(node)?; } let mut names = self.0.clone(); for element in node.elements() { match element { ClassElement::MethodDefinition(m) => { if let ClassElementName::PrivateName(name) = m.name() { names.push(name.description()); } } ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. }) | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { name, .. }) => { names.push(name.description()); } _ => {} } } let mut visitor = Self(names); if let Some(node) = node.constructor() { visitor.visit(node)?; } for element in node.elements() { match element { ClassElement::MethodDefinition(m) => { if let ClassElementName::PropertyName(name) = m.name() { visitor.visit(name)?; } visitor.visit(m.parameters())?; visitor.visit(m.body())?; } ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { visitor.visit(&field.name)?; if let Some(expression) = &field.initializer { visitor.visit(expression)?; } } ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { initializer, .. }) | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { initializer, .. }) => { if let Some(expression) = initializer { visitor.visit(expression)?; } } ClassElement::StaticBlock(block) => { visitor.visit(&block.body)?; } } } ControlFlow::Continue(()) } fn visit_class_declaration( &mut self, node: &'ast ClassDeclaration, ) -> ControlFlow { if let Some(node) = node.super_ref() { self.visit(node)?; } let mut names = self.0.clone(); for element in node.elements() { match element { ClassElement::MethodDefinition(m) => { if let ClassElementName::PrivateName(name) = m.name() { names.push(name.description()); } } ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. }) | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { name, .. }) => { names.push(name.description()); } _ => {} } } let mut visitor = Self(names); if let Some(node) = node.constructor() { visitor.visit(node)?; } for element in node.elements() { match element { ClassElement::MethodDefinition(m) => { if let ClassElementName::PropertyName(name) = m.name() { visitor.visit(name)?; } visitor.visit(m.parameters())?; visitor.visit(m.body())?; } ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { visitor.visit(&field.name)?; if let Some(expression) = &field.initializer { visitor.visit(expression)?; } } ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { initializer, .. }) | ClassElement::PrivateStaticFieldDefinition(PrivateFieldDefinition { initializer, .. }) => { if let Some(expression) = initializer { visitor.visit(expression)?; } } ClassElement::StaticBlock(block) => { visitor.visit(&block.body)?; } } } ControlFlow::Continue(()) } fn visit_private_property_access( &mut self, node: &'ast PrivatePropertyAccess, ) -> ControlFlow { if self.0.contains(&node.field().description()) { self.visit(node.target()) } else { ControlFlow::Break(()) } } fn visit_binary_in_private( &mut self, node: &'ast BinaryInPrivate, ) -> ControlFlow { if self.0.contains(&node.lhs().description()) { self.visit(node.rhs()) } else { ControlFlow::Break(()) } } fn visit_optional_operation_kind( &mut self, node: &'ast OptionalOperationKind, ) -> ControlFlow { match node { OptionalOperationKind::SimplePropertyAccess { field } => { self.visit_property_access_field(field) } OptionalOperationKind::PrivatePropertyAccess { field } => { if self.0.contains(&field.description()) { ControlFlow::Continue(()) } else { ControlFlow::Break(()) } } OptionalOperationKind::Call { args } => { for arg in args { self.visit_expression(arg)?; } ControlFlow::Continue(()) } } } } /// Errors that can occur when checking labels. #[derive(Debug, Clone, Copy)] pub enum CheckLabelsError { /// A label was used multiple times. DuplicateLabel(Sym), /// A `break` statement was used with a label that was not defined. UndefinedBreakTarget(Sym), /// A `continue` statement was used with a label that was not defined. UndefinedContinueTarget(Sym), /// A `break` statement was used in a non-looping context. IllegalBreakStatement, /// A `continue` statement was used in a non-looping context. IllegalContinueStatement, } impl CheckLabelsError { /// Returns an error message based on the error. #[must_use] pub fn message(&self, interner: &Interner) -> String { match self { Self::DuplicateLabel(label) => { format!("duplicate label: {}", interner.resolve_expect(*label)) } Self::UndefinedBreakTarget(label) => { format!( "undefined break target: {}", interner.resolve_expect(*label) ) } Self::UndefinedContinueTarget(label) => format!( "undefined continue target: {}", interner.resolve_expect(*label) ), Self::IllegalBreakStatement => "illegal break statement".into(), Self::IllegalContinueStatement => "illegal continue statement".into(), } } } /// This function checks multiple syntax errors conditions for labels, `break` and `continue`. /// /// The following syntax errors are checked: /// - [`ContainsDuplicateLabels`][ContainsDuplicateLabels] /// - [`ContainsUndefinedBreakTarget`][ContainsUndefinedBreakTarget] /// - [`ContainsUndefinedContinueTarget`][ContainsUndefinedContinueTarget] /// - Early errors for [`BreakStatement`][BreakStatement] /// - Early errors for [`ContinueStatement`][ContinueStatement] /// /// [ContainsDuplicateLabels]: https://tc39.es/ecma262/#sec-static-semantics-containsduplicatelabels /// [ContainsUndefinedBreakTarget]: https://tc39.es/ecma262/#sec-static-semantics-containsundefinedbreaktarget /// [ContainsUndefinedContinueTarget]: https://tc39.es/ecma262/#sec-static-semantics-containsundefinedcontinuetarget /// [BreakStatement]: https://tc39.es/ecma262/#sec-break-statement-static-semantics-early-errors /// [ContinueStatement]: https://tc39.es/ecma262/#sec-continue-statement-static-semantics-early-errors /// /// # Errors /// /// This function returns an error for the first syntax error that is found. pub fn check_labels(node: &N) -> Result<(), CheckLabelsError> where N: VisitWith, { #[derive(Debug, Clone)] struct CheckLabelsResolver { labels: FxHashSet, continue_iteration_labels: FxHashSet, continue_labels: Option>, iteration: bool, switch: bool, } impl<'ast> Visitor<'ast> for CheckLabelsResolver { type BreakTy = CheckLabelsError; fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Block(node) => self.visit_block(node), Statement::Var(_) | Statement::Empty | Statement::Debugger | Statement::Expression(_) | Statement::Return(_) | Statement::Throw(_) => ControlFlow::Continue(()), Statement::If(node) => self.visit_if(node), Statement::DoWhileLoop(node) => self.visit_do_while_loop(node), Statement::WhileLoop(node) => self.visit_while_loop(node), Statement::ForLoop(node) => self.visit_for_loop(node), Statement::ForInLoop(node) => self.visit_for_in_loop(node), Statement::ForOfLoop(node) => self.visit_for_of_loop(node), Statement::Switch(node) => self.visit_switch(node), Statement::Labelled(node) => self.visit_labelled(node), Statement::Try(node) => self.visit_try(node), Statement::Continue(node) => self.visit_continue(node), Statement::Break(node) => self.visit_break(node), Statement::With(with) => self.visit_with(with), } } fn visit_block( &mut self, node: &'ast crate::statement::Block, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); self.visit_statement_list(node.statement_list())?; self.continue_labels = continue_labels; ControlFlow::Continue(()) } fn visit_break( &mut self, node: &'ast crate::statement::Break, ) -> ControlFlow { if let Some(label) = node.label() { if !self.labels.contains(&label) { return ControlFlow::Break(CheckLabelsError::UndefinedBreakTarget(label)); } } else if !self.iteration && !self.switch { return ControlFlow::Break(CheckLabelsError::IllegalBreakStatement); } ControlFlow::Continue(()) } fn visit_continue( &mut self, node: &'ast crate::statement::Continue, ) -> ControlFlow { if !self.iteration { return ControlFlow::Break(CheckLabelsError::IllegalContinueStatement); } if let Some(label) = node.label() && !self.continue_iteration_labels.contains(&label) { return ControlFlow::Break(CheckLabelsError::UndefinedContinueTarget(label)); } ControlFlow::Continue(()) } fn visit_do_while_loop( &mut self, node: &'ast crate::statement::DoWhileLoop, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); let continue_iteration_labels = self.continue_iteration_labels.clone(); if let Some(continue_labels) = &continue_labels { self.continue_iteration_labels.extend(continue_labels); } let iteration = self.iteration; self.iteration = true; self.visit_statement(node.body())?; self.continue_iteration_labels = continue_iteration_labels; self.continue_labels = continue_labels; self.iteration = iteration; ControlFlow::Continue(()) } fn visit_while_loop( &mut self, node: &'ast crate::statement::WhileLoop, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); let continue_iteration_labels = self.continue_iteration_labels.clone(); if let Some(continue_labels) = &continue_labels { self.continue_iteration_labels.extend(continue_labels); } let iteration = self.iteration; self.iteration = true; self.visit_statement(node.body())?; self.continue_iteration_labels = continue_iteration_labels; self.continue_labels = continue_labels; self.iteration = iteration; ControlFlow::Continue(()) } fn visit_for_loop( &mut self, node: &'ast crate::statement::ForLoop, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); let continue_iteration_labels = self.continue_iteration_labels.clone(); if let Some(continue_labels) = &continue_labels { self.continue_iteration_labels.extend(continue_labels); } let iteration = self.iteration; self.iteration = true; self.visit_statement(node.body())?; self.continue_iteration_labels = continue_iteration_labels; self.continue_labels = continue_labels; self.iteration = iteration; ControlFlow::Continue(()) } fn visit_for_in_loop( &mut self, node: &'ast crate::statement::ForInLoop, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); let continue_iteration_labels = self.continue_iteration_labels.clone(); if let Some(continue_labels) = &continue_labels { self.continue_iteration_labels.extend(continue_labels); } let iteration = self.iteration; self.iteration = true; self.visit_statement(node.body())?; self.continue_iteration_labels = continue_iteration_labels; self.continue_labels = continue_labels; self.iteration = iteration; ControlFlow::Continue(()) } fn visit_for_of_loop( &mut self, node: &'ast crate::statement::ForOfLoop, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); let continue_iteration_labels = self.continue_iteration_labels.clone(); if let Some(continue_labels) = &continue_labels { self.continue_iteration_labels.extend(continue_labels); } let iteration = self.iteration; self.iteration = true; self.visit_statement(node.body())?; self.continue_iteration_labels = continue_iteration_labels; self.continue_labels = continue_labels; self.iteration = iteration; ControlFlow::Continue(()) } fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); if let StatementListItem::Statement(stmt) = node { self.visit_statement(stmt)?; } self.continue_labels = continue_labels; ControlFlow::Continue(()) } fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow { let continue_labels = self.continue_labels.take(); self.visit_statement(node.body())?; if let Some(stmt) = node.else_node() { self.visit_statement(stmt)?; } self.continue_labels = continue_labels; ControlFlow::Continue(()) } fn visit_switch( &mut self, node: &'ast crate::statement::Switch, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); let switch = self.switch; self.switch = true; for case in node.cases() { self.visit_statement_list(case.body())?; } if let Some(default) = node.default() { self.visit_statement_list(default)?; } self.continue_labels = continue_labels; self.switch = switch; ControlFlow::Continue(()) } fn visit_labelled( &mut self, node: &'ast crate::statement::Labelled, ) -> ControlFlow { let continue_labels = self.continue_labels.clone(); if let Some(continue_labels) = &mut self.continue_labels { continue_labels.insert(node.label()); } else { let mut continue_labels = FxHashSet::default(); continue_labels.insert(node.label()); self.continue_labels = Some(continue_labels); } if !self.labels.insert(node.label()) { return ControlFlow::Break(CheckLabelsError::DuplicateLabel(node.label())); } self.visit_labelled_item(node.item())?; self.labels.remove(&node.label()); self.continue_labels = continue_labels; ControlFlow::Continue(()) } fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { LabelledItem::Statement(stmt) => self.visit_statement(stmt), LabelledItem::FunctionDeclaration(_) => ControlFlow::Continue(()), } } fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow { let continue_labels = self.continue_labels.take(); self.visit_block(node.block())?; if let Some(catch) = node.catch() { self.visit_block(catch.block())?; } if let Some(finally) = node.finally() { self.visit_block(finally.block())?; } self.continue_labels = continue_labels; ControlFlow::Continue(()) } fn visit_module_item_list( &mut self, node: &'ast crate::ModuleItemList, ) -> ControlFlow { let continue_labels = self.continue_labels.take(); for item in node.items() { self.visit_module_item(item)?; } self.continue_labels = continue_labels; ControlFlow::Continue(()) } fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { ModuleItem::ImportDeclaration(_) | ModuleItem::ExportDeclaration(_) => { ControlFlow::Continue(()) } ModuleItem::StatementListItem(node) => self.visit_statement_list_item(node), } } } let mut visitor = CheckLabelsResolver { labels: FxHashSet::default(), continue_iteration_labels: FxHashSet::default(), continue_labels: None, iteration: false, switch: false, }; if let ControlFlow::Break(error) = node.visit_with(&mut visitor) { Err(error) } else { Ok(()) } } /// Returns `true` if the given node contains a `CoverInitializedName`. #[must_use] pub fn contains_invalid_object_literal(node: &N) -> bool where N: VisitWith, { #[derive(Debug, Clone)] struct ContainsInvalidObjectLiteral {} impl<'ast> Visitor<'ast> for ContainsInvalidObjectLiteral { type BreakTy = (); fn visit_object_literal( &mut self, node: &'ast crate::expression::literal::ObjectLiteral, ) -> ControlFlow { for pd in node.properties() { if let PropertyDefinition::CoverInitializedName(..) = pd { return ControlFlow::Break(()); } self.visit_property_definition(pd)?; } ControlFlow::Continue(()) } } let mut visitor = ContainsInvalidObjectLiteral {}; node.visit_with(&mut visitor).is_break() } /// The type of a lexically scoped declaration. #[derive(Copy, Clone, Debug)] pub enum LexicallyScopedDeclaration<'a> { /// See [`LexicalDeclaration`] LexicalDeclaration(&'a LexicalDeclaration), /// See [`FunctionDeclaration`] FunctionDeclaration(&'a FunctionDeclaration), /// See [`GeneratorDeclaration`] GeneratorDeclaration(&'a GeneratorDeclaration), /// See [`AsyncFunctionDeclaration`] AsyncFunctionDeclaration(&'a AsyncFunctionDeclaration), /// See [`AsyncGeneratorDeclaration`] AsyncGeneratorDeclaration(&'a AsyncGeneratorDeclaration), /// See [`ClassDeclaration`] ClassDeclaration(&'a ClassDeclaration), /// A default assignment expression as an export declaration. /// /// Only valid inside module exports. AssignmentExpression(&'a Expression), } impl LexicallyScopedDeclaration<'_> { /// Return the bound names of the declaration. #[must_use] pub fn bound_names(&self) -> Vec { match *self { Self::LexicalDeclaration(v) => bound_names(v), Self::FunctionDeclaration(f) => bound_names(f), Self::GeneratorDeclaration(g) => bound_names(g), Self::AsyncFunctionDeclaration(f) => bound_names(f), Self::AsyncGeneratorDeclaration(g) => bound_names(g), Self::ClassDeclaration(cl) => bound_names(cl), Self::AssignmentExpression(expr) => bound_names(expr), } } } impl<'ast> From<&'ast Declaration> for LexicallyScopedDeclaration<'ast> { fn from(value: &'ast Declaration) -> LexicallyScopedDeclaration<'ast> { match value { Declaration::FunctionDeclaration(f) => Self::FunctionDeclaration(f), Declaration::GeneratorDeclaration(g) => Self::GeneratorDeclaration(g), Declaration::AsyncFunctionDeclaration(af) => Self::AsyncFunctionDeclaration(af), Declaration::AsyncGeneratorDeclaration(ag) => Self::AsyncGeneratorDeclaration(ag), Declaration::ClassDeclaration(c) => Self::ClassDeclaration(c), Declaration::Lexical(lex) => Self::LexicalDeclaration(lex), } } } /// Returns a list of lexically scoped declarations of the given node. /// /// This is equivalent to the [`LexicallyScopedDeclarations`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallyscopeddeclarations #[must_use] pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec> where &'a N: Into>, { let mut declarations = Vec::new(); let _ = LexicallyScopedDeclarationsVisitor(&mut declarations).visit(node.into()); declarations } /// The [`Visitor`] used to obtain the lexically scoped declarations of a node. #[derive(Debug)] struct LexicallyScopedDeclarationsVisitor<'a, 'ast>(&'a mut Vec>); impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_, 'ast> { type BreakTy = Infallible; // ScriptBody : StatementList fn visit_script(&mut self, node: &'ast Script) -> ControlFlow { // 1. Return TopLevelLexicallyScopedDeclarations of StatementList. TopLevelLexicallyScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) } fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { // 1. Return TopLevelVarScopedDeclarations of StatementList. TopLevelLexicallyScopedDeclarationsVisitor(self.0) .visit_statement_list(node.statement_list()) } fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, ) -> ControlFlow { let decl = match node { // ExportDeclaration : // export ExportFromClause FromClause ; // export NamedExports ; // export VariableStatement ExportDeclaration::ReExport { .. } | ExportDeclaration::List(_) | ExportDeclaration::VarStatement(_) => { // 1. Return a new empty List. return ControlFlow::Continue(()); } // ExportDeclaration : export Declaration ExportDeclaration::Declaration(decl) => { // 1. Return a List whose sole element is DeclarationPart of Declaration. decl.into() } // ExportDeclaration : export default HoistableDeclaration // 1. Return a List whose sole element is DeclarationPart of HoistableDeclaration. ExportDeclaration::DefaultFunctionDeclaration(f) => { LexicallyScopedDeclaration::FunctionDeclaration(f) } ExportDeclaration::DefaultGeneratorDeclaration(g) => { LexicallyScopedDeclaration::GeneratorDeclaration(g) } ExportDeclaration::DefaultAsyncFunctionDeclaration(af) => { LexicallyScopedDeclaration::AsyncFunctionDeclaration(af) } ExportDeclaration::DefaultAsyncGeneratorDeclaration(ag) => { LexicallyScopedDeclaration::AsyncGeneratorDeclaration(ag) } // ExportDeclaration : export default ClassDeclaration ExportDeclaration::DefaultClassDeclaration(c) => { // 1. Return a List whose sole element is ClassDeclaration. LexicallyScopedDeclaration::ClassDeclaration(c) } // ExportDeclaration : export default AssignmentExpression ; ExportDeclaration::DefaultAssignmentExpression(expr) => { // 1. Return a List whose sole element is this ExportDeclaration. LexicallyScopedDeclaration::AssignmentExpression(expr) } }; self.0.push(decl); ControlFlow::Continue(()) } fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { match node { // StatementListItem : Statement StatementListItem::Statement(statement) => { // 1. If Statement is Statement : LabelledStatement , return LexicallyScopedDeclarations of LabelledStatement. if let Statement::Labelled(labelled) = statement.as_ref() { self.visit_labelled(labelled) } else { // 2. Return a new empty List. ControlFlow::Continue(()) } } // StatementListItem : Declaration StatementListItem::Declaration(declaration) => { // 1. Return a List whose sole element is DeclarationPart of Declaration. self.0.push(declaration.as_ref().into()); ControlFlow::Continue(()) } } } fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { // LabelledItem : FunctionDeclaration LabelledItem::FunctionDeclaration(f) => { // 1. Return « FunctionDeclaration ». self.0 .push(LexicallyScopedDeclaration::FunctionDeclaration(f)); } // LabelledItem : Statement LabelledItem::Statement(_) => { // 1. Return a new empty List. } } ControlFlow::Continue(()) } fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item), ModuleItem::ExportDeclaration(export) => self.visit_export_declaration(export), // ModuleItem : ImportDeclaration ModuleItem::ImportDeclaration(_) => { // 1. Return a new empty List. ControlFlow::Continue(()) } } } } /// The [`Visitor`] used to obtain the top level lexically scoped declarations of a node. /// /// This is equivalent to the [`TopLevelLexicallyScopedDeclarations`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallyscopeddeclarations #[derive(Debug)] struct TopLevelLexicallyScopedDeclarationsVisitor<'a, 'ast>( &'a mut Vec>, ); impl<'ast> Visitor<'ast> for TopLevelLexicallyScopedDeclarationsVisitor<'_, 'ast> { type BreakTy = Infallible; fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { match node { // StatementListItem : Declaration StatementListItem::Declaration(d) => match d.as_ref() { // 1. If Declaration is Declaration : HoistableDeclaration , then Declaration::FunctionDeclaration(_) | Declaration::GeneratorDeclaration(_) | Declaration::AsyncFunctionDeclaration(_) | Declaration::AsyncGeneratorDeclaration(_) => { // a. Return a new empty List. } // 2. Return « Declaration ». Declaration::ClassDeclaration(cl) => { self.0 .push(LexicallyScopedDeclaration::ClassDeclaration(cl)); } Declaration::Lexical(lex) => { self.0 .push(LexicallyScopedDeclaration::LexicalDeclaration(lex)); } }, // StatementListItem : Statement StatementListItem::Statement(_) => { // 1. Return a new empty List. } } ControlFlow::Continue(()) } } /// The type of a var scoped declaration. #[derive(Clone, Debug)] pub enum VarScopedDeclaration { /// See [`VarDeclaration`] VariableDeclaration(Variable), /// See [`FunctionDeclaration`] FunctionDeclaration(FunctionDeclaration), /// See [`GeneratorDeclaration`] GeneratorDeclaration(GeneratorDeclaration), /// See [`AsyncFunctionDeclaration`] AsyncFunctionDeclaration(AsyncFunctionDeclaration), /// See [`AsyncGeneratorDeclaration`] AsyncGeneratorDeclaration(AsyncGeneratorDeclaration), } impl VarScopedDeclaration { /// Return the bound names of the declaration. #[must_use] pub fn bound_names(&self) -> Vec { match self { Self::VariableDeclaration(v) => bound_names(v), Self::FunctionDeclaration(f) => bound_names(f), Self::GeneratorDeclaration(g) => bound_names(g), Self::AsyncFunctionDeclaration(f) => bound_names(f), Self::AsyncGeneratorDeclaration(g) => bound_names(g), } } /// Return [`LinearSpan`] of this declaration (if there is). #[must_use] pub fn linear_span(&self) -> Option { match self { VarScopedDeclaration::FunctionDeclaration(f) => Some(f.linear_span()), VarScopedDeclaration::GeneratorDeclaration(f) => Some(f.linear_span()), VarScopedDeclaration::AsyncFunctionDeclaration(f) => Some(f.linear_span()), VarScopedDeclaration::AsyncGeneratorDeclaration(f) => Some(f.linear_span()), VarScopedDeclaration::VariableDeclaration(_) => None, } } } /// Returns a list of var scoped declarations of the given node. /// /// This is equivalent to the [`VarScopedDeclarations`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-varscopeddeclarations #[must_use] pub fn var_scoped_declarations<'a, N>(node: &'a N) -> Vec where &'a N: Into>, { let mut declarations = Vec::new(); let _ = VarScopedDeclarationsVisitor(&mut declarations).visit(node.into()); declarations } /// The [`Visitor`] used to obtain the var scoped declarations of a node. #[derive(Debug)] struct VarScopedDeclarationsVisitor<'a>(&'a mut Vec); impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> { type BreakTy = Infallible; // ScriptBody : StatementList fn visit_script(&mut self, node: &'ast Script) -> ControlFlow { // 1. Return TopLevelVarScopedDeclarations of StatementList. TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) } fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { // 1. Return TopLevelVarScopedDeclarations of StatementList. TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statement_list()) } fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Block(s) => self.visit(s), Statement::Var(s) => self.visit(s), Statement::If(s) => self.visit(s), Statement::DoWhileLoop(s) => self.visit(s), Statement::WhileLoop(s) => self.visit(s), Statement::ForLoop(s) => self.visit(s), Statement::ForInLoop(s) => self.visit(s), Statement::ForOfLoop(s) => self.visit(s), Statement::Switch(s) => self.visit(s), Statement::Labelled(s) => self.visit(s), Statement::Try(s) => self.visit(s), Statement::With(s) => self.visit(s), Statement::Empty | Statement::Debugger | Statement::Expression(_) | Statement::Continue(_) | Statement::Break(_) | Statement::Return(_) | Statement::Throw(_) => ControlFlow::Continue(()), } } fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { match node { StatementListItem::Declaration(_) => ControlFlow::Continue(()), StatementListItem::Statement(s) => self.visit(s.as_ref()), } } fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow { for var in node.0.as_ref() { self.0 .push(VarScopedDeclaration::VariableDeclaration(var.clone())); } ControlFlow::Continue(()) } fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow { self.visit(node.body())?; if let Some(else_node) = node.else_node() { self.visit(else_node)?; } ControlFlow::Continue(()) } fn visit_do_while_loop( &mut self, node: &'ast crate::statement::DoWhileLoop, ) -> ControlFlow { self.visit(node.body())?; ControlFlow::Continue(()) } fn visit_while_loop( &mut self, node: &'ast crate::statement::WhileLoop, ) -> ControlFlow { self.visit(node.body())?; ControlFlow::Continue(()) } fn visit_for_loop( &mut self, node: &'ast crate::statement::ForLoop, ) -> ControlFlow { if let Some(ForLoopInitializer::Var(v)) = node.init() { self.visit(v)?; } self.visit(node.body())?; ControlFlow::Continue(()) } fn visit_for_in_loop( &mut self, node: &'ast crate::statement::ForInLoop, ) -> ControlFlow { if let IterableLoopInitializer::Var(var) = node.initializer() { self.0 .push(VarScopedDeclaration::VariableDeclaration(var.clone())); } self.visit(node.body())?; ControlFlow::Continue(()) } fn visit_for_of_loop( &mut self, node: &'ast crate::statement::ForOfLoop, ) -> ControlFlow { if let IterableLoopInitializer::Var(var) = node.initializer() { self.0 .push(VarScopedDeclaration::VariableDeclaration(var.clone())); } self.visit(node.body())?; ControlFlow::Continue(()) } fn visit_with(&mut self, node: &'ast With) -> ControlFlow { self.visit(node.statement())?; ControlFlow::Continue(()) } fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow { for case in node.cases() { self.visit(case)?; } if let Some(default) = node.default() { self.visit(default)?; } ControlFlow::Continue(()) } fn visit_case(&mut self, node: &'ast crate::statement::Case) -> ControlFlow { self.visit(node.body())?; ControlFlow::Continue(()) } fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { LabelledItem::Statement(s) => self.visit(s), LabelledItem::FunctionDeclaration(_) => ControlFlow::Continue(()), } } fn visit_catch(&mut self, node: &'ast crate::statement::Catch) -> ControlFlow { self.visit(node.block())?; ControlFlow::Continue(()) } fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { // ModuleItem : ExportDeclaration ModuleItem::ExportDeclaration(decl) => { if let ExportDeclaration::VarStatement(var) = decl.as_ref() { // 1. If ExportDeclaration is export VariableStatement, return VarScopedDeclarations of VariableStatement. self.visit_var_declaration(var)?; } // 2. Return a new empty List. } ModuleItem::StatementListItem(item) => { self.visit_statement_list_item(item)?; } // ModuleItem : ImportDeclaration ModuleItem::ImportDeclaration(_) => { // 1. Return a new empty List. } } ControlFlow::Continue(()) } } /// The [`Visitor`] used to obtain the top level var scoped declarations of a node. /// /// This is equivalent to the [`TopLevelVarScopedDeclarations`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvarscopeddeclarations #[derive(Debug)] struct TopLevelVarScopedDeclarationsVisitor<'a>(&'a mut Vec); impl<'ast> Visitor<'ast> for TopLevelVarScopedDeclarationsVisitor<'_> { type BreakTy = Infallible; fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { match node { StatementListItem::Declaration(d) => { match d.as_ref() { Declaration::FunctionDeclaration(f) => { self.0 .push(VarScopedDeclaration::FunctionDeclaration(f.clone())); } Declaration::GeneratorDeclaration(f) => { self.0 .push(VarScopedDeclaration::GeneratorDeclaration(f.clone())); } Declaration::AsyncFunctionDeclaration(f) => { self.0 .push(VarScopedDeclaration::AsyncFunctionDeclaration(f.clone())); } Declaration::AsyncGeneratorDeclaration(f) => { self.0 .push(VarScopedDeclaration::AsyncGeneratorDeclaration(f.clone())); } _ => {} } ControlFlow::Continue(()) } StatementListItem::Statement(statement) => { if let Statement::Labelled(labelled) = statement.as_ref() { self.visit(labelled) } else { VarScopedDeclarationsVisitor(self.0).visit(statement.as_ref()) } } } } fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { LabelledItem::Statement(Statement::Labelled(s)) => self.visit(s), LabelledItem::Statement(s) => { VarScopedDeclarationsVisitor(self.0).visit(s)?; ControlFlow::Continue(()) } LabelledItem::FunctionDeclaration(f) => { self.0 .push(VarScopedDeclaration::FunctionDeclaration(f.clone())); ControlFlow::Continue(()) } } } } /// Returns a list function declaration names that are directly contained in a statement lists /// `Block`, `CaseClause` or `DefaultClause`. /// If the function declaration would cause an early error it is not included in the list. /// /// This behavior is used in the following annexB sections: /// * [B.3.2.1 Changes to FunctionDeclarationInstantiation][spec0] /// * [B.3.2.2 Changes to GlobalDeclarationInstantiation][spec1] /// * [B.3.2.3 Changes to EvalDeclarationInstantiation][spec2] /// /// [spec0]: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation /// [spec1]: https://tc39.es/ecma262/#sec-web-compat-globaldeclarationinstantiation /// [spec2]: https://tc39.es/ecma262/#sec-web-compat-evaldeclarationinstantiation #[must_use] pub fn annex_b_function_declarations_names<'a, N>(node: &'a N) -> Vec where &'a N: Into>, { let mut declarations = Vec::new(); let _ = AnnexBFunctionDeclarationNamesVisitor(&mut declarations).visit(node.into()); declarations } /// The [`Visitor`] used for [`annex_b_function_declarations_names`]. #[derive(Debug)] struct AnnexBFunctionDeclarationNamesVisitor<'a>(&'a mut Vec); impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> { type BreakTy = Infallible; fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { match node { StatementListItem::Statement(node) => self.visit(node.as_ref()), StatementListItem::Declaration(_) => ControlFlow::Continue(()), } } fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Block(node) => self.visit(node), Statement::If(node) => self.visit(node), Statement::DoWhileLoop(node) => self.visit(node), Statement::WhileLoop(node) => self.visit(node), Statement::ForLoop(node) => self.visit(node), Statement::ForInLoop(node) => self.visit(node), Statement::ForOfLoop(node) => self.visit(node), Statement::Switch(node) => self.visit(node), Statement::Labelled(node) => self.visit(node), Statement::Try(node) => self.visit(node), Statement::With(node) => self.visit(node), _ => ControlFlow::Continue(()), } } fn visit_block(&mut self, node: &'ast crate::statement::Block) -> ControlFlow { self.visit(node.statement_list())?; for statement in node.statement_list().statements() { if let StatementListItem::Declaration(declaration) = statement && let Declaration::FunctionDeclaration(function) = &**declaration { let name = function.name(); self.0.push(name.sym()); } } let lexically_declared_names = lexically_declared_names_legacy(node.statement_list()); self.0 .retain(|name| !lexically_declared_names.contains(&(*name, false))); ControlFlow::Continue(()) } fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow { for case in node.cases() { self.visit(case)?; for statement in case.body().statements() { if let StatementListItem::Declaration(declaration) = statement && let Declaration::FunctionDeclaration(function) = &**declaration { let name = function.name(); self.0.push(name.sym()); } } } if let Some(default) = node.default() { self.visit(default)?; for statement in default.statements() { if let StatementListItem::Declaration(declaration) = statement && let Declaration::FunctionDeclaration(function) = declaration.as_ref() { let name = function.name(); self.0.push(name.sym()); } } } let lexically_declared_names = lexically_declared_names_legacy(node); self.0 .retain(|name| !lexically_declared_names.contains(&(*name, false))); ControlFlow::Continue(()) } fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow { self.visit(node.block())?; if let Some(catch) = node.catch() { self.visit(catch.block())?; if let Some(Binding::Pattern(pattern)) = catch.parameter() { let bound_names = bound_names(pattern); self.0.retain(|name| !bound_names.contains(name)); } } if let Some(finally) = node.finally() { self.visit(finally.block())?; } ControlFlow::Continue(()) } fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow { if let Some(node) = node.else_node() { self.visit(node)?; } self.visit(node.body()) } fn visit_do_while_loop( &mut self, node: &'ast crate::statement::DoWhileLoop, ) -> ControlFlow { self.visit(node.body()) } fn visit_while_loop( &mut self, node: &'ast crate::statement::WhileLoop, ) -> ControlFlow { self.visit(node.body()) } fn visit_for_loop( &mut self, node: &'ast crate::statement::ForLoop, ) -> ControlFlow { self.visit(node.body())?; if let Some(ForLoopInitializer::Lexical(node)) = node.init() { let bound_names = bound_names(&node.declaration); self.0.retain(|name| !bound_names.contains(name)); } ControlFlow::Continue(()) } fn visit_for_in_loop( &mut self, node: &'ast crate::statement::ForInLoop, ) -> ControlFlow { self.visit(node.body())?; if let IterableLoopInitializer::Let(node) = node.initializer() { let bound_names = bound_names(node); self.0.retain(|name| !bound_names.contains(name)); } if let IterableLoopInitializer::Const(node) = node.initializer() { let bound_names = bound_names(node); self.0.retain(|name| !bound_names.contains(name)); } ControlFlow::Continue(()) } fn visit_for_of_loop( &mut self, node: &'ast crate::statement::ForOfLoop, ) -> ControlFlow { self.visit(node.body())?; if let IterableLoopInitializer::Let(node) = node.initializer() { let bound_names = bound_names(node); self.0.retain(|name| !bound_names.contains(name)); } if let IterableLoopInitializer::Const(node) = node.initializer() { let bound_names = bound_names(node); self.0.retain(|name| !bound_names.contains(name)); } ControlFlow::Continue(()) } fn visit_labelled( &mut self, node: &'ast crate::statement::Labelled, ) -> ControlFlow { if let LabelledItem::Statement(node) = node.item() { self.visit(node)?; } ControlFlow::Continue(()) } fn visit_with(&mut self, node: &'ast With) -> ControlFlow { self.visit(node.statement()) } } /// Returns `true` if the given statement returns a value. #[must_use] pub fn returns_value<'a, N>(node: &'a N) -> bool where &'a N: Into>, { ReturnsValueVisitor.visit(node.into()).is_break() } /// The [`Visitor`] used for [`returns_value`]. #[derive(Debug)] struct ReturnsValueVisitor; impl<'ast> Visitor<'ast> for ReturnsValueVisitor { type BreakTy = (); fn visit_block(&mut self, node: &'ast crate::statement::Block) -> ControlFlow { for statement in node.statement_list().statements() { match statement { StatementListItem::Declaration(_) => {} StatementListItem::Statement(node) => self.visit(node.as_ref())?, } } ControlFlow::Continue(()) } fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Empty | Statement::Var(_) => {} Statement::Block(node) => self.visit(node)?, Statement::Labelled(node) => self.visit(node)?, _ => return ControlFlow::Break(()), } ControlFlow::Continue(()) } fn visit_case(&mut self, node: &'ast crate::statement::Case) -> ControlFlow { for statement in node.body().statements() { match statement { StatementListItem::Declaration(_) => {} StatementListItem::Statement(node) => self.visit(node.as_ref())?, } } ControlFlow::Continue(()) } fn visit_labelled( &mut self, node: &'ast crate::statement::Labelled, ) -> ControlFlow { match node.item() { LabelledItem::Statement(node) => self.visit(node)?, LabelledItem::FunctionDeclaration(_) => {} } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/operations/tests.rs ================================================ use boa_interner::Interner; use crate::{ Position, Span, Statement, expression::{Call, Identifier, NewTarget, This}, operations::{ContainsSymbol, contains}, statement::With, }; fn empty_span() -> Span { Span::new(Position::new(1, 1), Position::new(1, 1)) } #[test] fn check_contains_this_in_with_statement_expression() { let node = With::new(This::new(empty_span()).into(), Statement::Empty); assert!(contains(&node, ContainsSymbol::This)); } #[test] fn check_contains_new_target_in_with_statement_expression() { let node = With::new(NewTarget::new(empty_span()).into(), Statement::Empty); assert!(contains(&node, ContainsSymbol::NewTarget)); } #[test] fn check_contains_new_target_in_call_function_position() { let node = Call::new( NewTarget::new(empty_span()).into(), Box::default(), empty_span(), ); assert!(contains(&node, ContainsSymbol::NewTarget)); } #[test] fn check_contains_this_in_call_argument_position() { let mut interner = Interner::new(); let function_name = Identifier::new(interner.get_or_intern("func"), Span::new((1, 1), (1, 5))); let node = Call::new( function_name.into(), vec![This::new(empty_span()).into()].into_boxed_slice(), empty_span(), ); assert!(contains(&node, ContainsSymbol::This)); } #[test] fn check_contains_new_target_in_call_argument_position() { let mut interner = Interner::new(); let function_name = Identifier::new(interner.get_or_intern("func"), Span::new((1, 1), (1, 5))); let node = Call::new( function_name.into(), vec![NewTarget::new(empty_span()).into()].into_boxed_slice(), empty_span(), ); assert!(contains(&node, ContainsSymbol::NewTarget)); } ================================================ FILE: core/ast/src/pattern.rs ================================================ //! 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, Span, Spanned, expression::{Identifier, access::PropertyAccess}, property::PropertyName, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::{fmt::Write as _, 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 = "arbitrary", 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 Spanned for Pattern { #[inline] fn span(&self) -> Span { match self { Pattern::Object(object_pattern) => object_pattern.span(), Pattern::Array(array_pattern) => array_pattern.span(), } } } impl From for Pattern { fn from(obj: ObjectPattern) -> Self { Self::Object(obj) } } impl From for Pattern { fn from(obj: ArrayPattern) -> Self { Self::Array(obj) } } 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 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 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ObjectPattern { elements: Box<[ObjectPatternElement]>, span: Span, } impl ToInternedString for ObjectPattern { fn to_interned_string(&self, interner: &Interner) -> String { let mut buf = "{".to_owned(); for (i, binding) in self.elements.iter().enumerate() { let binding = binding.to_interned_string(interner); let str = if i == self.elements.len() - 1 { format!("{binding} ") } else { format!("{binding},") }; buf.push_str(&str); } if self.elements.is_empty() { buf.push(' '); } buf.push('}'); buf } } impl ObjectPattern { /// Creates a new object binding pattern. #[inline] #[must_use] pub const fn new(elements: Box<[ObjectPatternElement]>, span: Span) -> Self { Self { elements, span } } /// Gets the bindings for the object binding pattern. #[inline] #[must_use] pub const fn bindings(&self) -> &[ObjectPatternElement] { &self.elements } /// Returns true if the object binding pattern has a rest element. #[inline] #[must_use] pub const fn has_rest(&self) -> bool { matches!( self.elements.last(), Some(ObjectPatternElement::RestProperty { .. }) ) } } impl Spanned for ObjectPattern { #[inline] fn span(&self) -> Span { self.span } } impl VisitWith for ObjectPattern { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { for elem in &*self.elements { visitor.visit_object_pattern_element(elem)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for elem in &mut *self.elements { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ArrayPattern { bindings: Box<[ArrayPatternElement]>, span: Span, } impl ToInternedString for ArrayPattern { fn to_interned_string(&self, interner: &Interner) -> String { let mut buf = "[".to_owned(); for (i, binding) in self.bindings.iter().enumerate() { if i == self.bindings.len() - 1 { match binding { ArrayPatternElement::Elision => { let _ = write!(buf, "{}, ", binding.to_interned_string(interner)); } _ => { let _ = write!(buf, "{} ", binding.to_interned_string(interner)); } } } else { let _ = write!(buf, "{},", 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]>, span: Span) -> Self { Self { bindings, span } } /// Gets the bindings for the array binding pattern. #[inline] #[must_use] pub const fn bindings(&self) -> &[ArrayPatternElement] { &self.bindings } } impl Spanned for ArrayPattern { #[inline] fn span(&self) -> Span { self.span } } impl VisitWith for ArrayPattern { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { for elem in &*self.bindings { visitor.visit_array_pattern_element(elem)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for elem in &mut *self.bindings { 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 = "arbitrary", 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, }, /// `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, }, /// `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, }, /// `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, }, /// 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, }, } 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.sym()), interner.resolve_expect(ident.sym()) ) } PropertyName::Computed(node) => { format!( " [{}] : {}", node.to_interned_string(interner), interner.resolve_expect(ident.sym()) ) } }; if let Some(init) = default_init { let _ = write!(buf, " = {}", init.to_interned_string(interner)); } buf } Self::RestProperty { ident } => { 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.sym()), 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 { let _ = write!(buf, " = {}", 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.sym()), pattern.to_interned_string(interner), ) } PropertyName::Computed(node) => { format!( " [{}] : {}", node.to_interned_string(interner), pattern.to_interned_string(interner), ) } }; if let Some(init) = default_init { let _ = write!(buf, " = {}", init.to_interned_string(interner)); } buf } } } } impl VisitWith for ObjectPatternElement { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::SingleName { name, ident, default_init, } => { visitor.visit_property_name(name)?; 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, } => { visitor.visit_property_name(name)?; 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, } => { visitor.visit_property_name(name)?; 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 where V: VisitorMut<'a>, { match self { Self::SingleName { name, ident, default_init, } => { visitor.visit_property_name_mut(name)?; 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, } => { visitor.visit_property_name_mut(name)?; 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, } => { visitor.visit_property_name_mut(name)?; 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 = "arbitrary", 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, }, /// `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, /// An optional default value for the variable, in case the index element doesn't exist. default_init: Option, }, /// 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, }, /// `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(init) = default_init { let _ = write!(buf, " = {}", init.to_interned_string(interner)); } buf } Self::PropertyAccess { access, default_init, } => { let mut buf = format!(" {}", access.to_interned_string(interner)); if let Some(init) = default_init { let _ = write!(buf, " = {}", init.to_interned_string(interner)); } buf } Self::Pattern { pattern, default_init, } => { let mut buf = format!(" {}", pattern.to_interned_string(interner)); if let Some(init) = default_init { let _ = write!(buf, " = {}", 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 where V: Visitor<'a>, { match self { Self::SingleName { ident, default_init, } => { visitor.visit_identifier(ident)?; if let Some(expr) = default_init { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) } } Self::PropertyAccess { access, default_init, } => { visitor.visit_property_access(access)?; if let Some(expr) = default_init { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) } } Self::PropertyAccessRest { access } => visitor.visit_property_access(access), Self::Pattern { pattern, default_init, } => { 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 where V: VisitorMut<'a>, { match self { Self::SingleName { ident, default_init, } => { visitor.visit_identifier_mut(ident)?; if let Some(expr) = default_init { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } Self::PropertyAccess { access, default_init, } => { visitor.visit_property_access_mut(access)?; if let Some(expr) = default_init { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } Self::PropertyAccessRest { access } => visitor.visit_property_access_mut(access), Self::Pattern { pattern, default_init, } => { 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(()) } } } } ================================================ FILE: core/ast/src/position.rs ================================================ use std::{ cmp::Ordering, fmt::{self, Debug}, 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 Default for Position { /// Creates a new [`Position`] with line and column set to `1`. #[inline] fn default() -> Self { Self::new(1, 1) } } impl Position { /// Creates a new `Position` from Non-Zero values. /// /// # Panics /// /// Will panic if the line number or column number is zero. #[inline] #[track_caller] #[must_use] pub const 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) } } impl From for Position { #[inline] fn from(value: PositionGroup) -> Self { value.pos } } impl From<(NonZeroU32, NonZeroU32)> for Position { #[inline] fn from(value: (NonZeroU32, NonZeroU32)) -> Self { Position { line_number: value.0, column_number: value.1, } } } impl From<(u32, u32)> for Position { #[inline] #[track_caller] fn from(value: (u32, u32)) -> Self { Position::new(value.0, value.1) } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for Span { fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { Ok(Span::new(Position::new(1, 1), Position::new(1, 1))) } } /// Linear position in the ECMAScript source code. /// /// Stores linear position in the source code. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct LinearPosition { pos: usize, } impl LinearPosition { /// Creates a new `LinearPosition`. #[inline] #[must_use] pub const fn new(pos: usize) -> Self { Self { pos } } /// Gets the linear position. #[inline] #[must_use] pub const fn pos(self) -> usize { self.pos } } impl fmt::Display for LinearPosition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.pos()) } } /// 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(Clone, Copy, PartialEq, Eq, Hash)] pub struct Span { start: Position, end: Position, } impl Debug for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Span(({}, {}), ({}, {}))", self.start.line_number, self.start.column_number, self.end.line_number, self.end.column_number, ) } } 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: T, end: U) -> Self where T: Into, U: Into, { let start = start.into(); let end = end.into(); 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(self, other: S) -> bool where S: Into, { let other = other.into(); self.start <= other.start && self.end >= other.end } } impl From for Span { fn from(pos: Position) -> Self { Self { start: pos, end: pos, } } } impl Spanned for Span { #[inline] fn span(&self) -> Span { *self } } impl Spanned for &T { #[inline] fn span(&self) -> Span { T::span(*self) } } impl PartialOrd for Span { fn partial_cmp(&self, other: &Self) -> Option { 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) } } /// An element of the AST or any type that can be located in the source with a Span. pub trait Spanned { /// Returns a span from the current type. #[must_use] fn span(&self) -> Span; } /// A linear span in the ECMAScript source code. /// /// Stores a linear start position and a linear end position. /// /// Note that linear 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, Default, Clone, Copy, PartialEq, Eq, Hash)] pub struct LinearSpan { start: LinearPosition, end: LinearPosition, } impl LinearSpan { /// Creates a new `LinearSpan`. /// /// # Panics /// /// Panics if the start position is bigger than the end position. #[inline] #[track_caller] #[must_use] pub const fn new(start: LinearPosition, end: LinearPosition) -> Self { assert!( start.pos <= end.pos, "a linear span cannot start after its end" ); Self { start, end } } /// Test if the span is empty. #[inline] #[must_use] pub fn is_empty(self) -> bool { self.start == self.end } /// Gets the starting position of the span. #[inline] #[must_use] pub const fn start(self) -> LinearPosition { self.start } /// Gets the final position of the span. #[inline] #[must_use] pub const fn end(self) -> LinearPosition { self.end } /// Checks if this span inclusively contains another span or position. pub fn contains(self, other: S) -> bool where S: Into, { let other = other.into(); self.start <= other.start && self.end >= other.end } /// Gets the starting position of the span. #[inline] #[must_use] pub fn union(self, other: impl Into) -> Self { let other: Self = other.into(); Self { start: LinearPosition::new(self.start.pos.min(other.start.pos)), end: LinearPosition::new(self.end.pos.max(other.end.pos)), } } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for LinearSpan { fn arbitrary(_: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let zero_pos = LinearPosition::new(0); Ok(Self::new(zero_pos, zero_pos)) } } impl From for LinearSpan { fn from(pos: LinearPosition) -> Self { Self { start: pos, end: pos, } } } impl PartialOrd for LinearSpan { fn partial_cmp(&self, other: &Self) -> Option { 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 } } } /// Stores a `LinearSpan` but `PartialEq`, `Eq` always return true. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, Copy)] pub struct LinearSpanIgnoreEq(pub LinearSpan); impl PartialEq for LinearSpanIgnoreEq { fn eq(&self, _: &Self) -> bool { true } } impl From for LinearSpanIgnoreEq { fn from(value: LinearSpan) -> Self { Self(value) } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for LinearSpanIgnoreEq { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { Ok(Self(LinearSpan::arbitrary(u)?)) } } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// A position group of `LinearPosition` and `Position` related to the same position in the ECMAScript source code. pub struct PositionGroup { pos: Position, linear_pos: LinearPosition, } impl PositionGroup { /// Creates a new `PositionGroup`. #[inline] #[must_use] pub const fn new(pos: Position, linear_pos: LinearPosition) -> Self { Self { pos, linear_pos } } /// Get the `Position`. #[inline] #[must_use] pub fn position(&self) -> Position { self.pos } /// Get the `LinearPosition`. #[inline] #[must_use] pub fn linear_position(&self) -> LinearPosition { self.linear_pos } /// Gets the line number of the position. #[inline] #[must_use] pub const fn line_number(&self) -> u32 { self.pos.line_number() } /// Gets the column number of the position. #[inline] #[must_use] pub const fn column_number(&self) -> u32 { self.pos.column_number() } } #[cfg(test)] mod tests { #![allow(clippy::similar_names)] #![allow(unused_must_use)] use super::{LinearPosition, LinearSpan, Position, Span}; /// Checks that we cannot create a position with 0 as the column. #[test] #[should_panic(expected = "column number cannot be 0")] fn invalid_position_column() { Position::new(10, 0); } /// Checks that we cannot create a position with 0 as the line. #[test] #[should_panic(expected = "line number cannot be 0")] 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 `PartialEq` implementation of `LinearPosition` is consistent. #[test] fn linear_position_equality() { assert_eq!(LinearPosition::new(1050), LinearPosition::new(1050)); assert_ne!(LinearPosition::new(1050), LinearPosition::new(1051)); } /// 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 `PartialOrd` implementation of `LinearPosition` is consistent. #[test] fn linear_position_order() { assert!(LinearPosition::new(1050) < LinearPosition::new(1051)); assert!(LinearPosition::new(1149) > LinearPosition::new(1050)); } /// 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(expected = "a span cannot start after its end")] fn invalid_span() { let a = Position::new(10, 30); let b = Position::new(10, 50); Span::new(b, a); } /// Checks that we cannot create an invalid linear span. #[test] #[should_panic(expected = "a linear span cannot start after its end")] fn invalid_linear_span() { let a = LinearPosition::new(1030); let b = LinearPosition::new(1050); LinearSpan::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); Span::new(a, b); Span::new(a, a); Span::from(a); } /// Checks that we can create valid linear spans. #[test] fn linear_span_creation() { let a = LinearPosition::new(1030); let b = LinearPosition::new(1050); LinearSpan::new(a, b); let span_aa = LinearSpan::new(a, a); assert_eq!(LinearSpan::from(a), span_aa); } /// 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 `PartialEq` implementation of `LinearSpan` is consistent. #[test] fn linear_span_equality() { let a = LinearPosition::new(1030); let b = LinearPosition::new(1050); let c = LinearPosition::new(1150); let span_ab = LinearSpan::new(a, b); let span_ab_2 = LinearSpan::new(a, b); let span_ac = LinearSpan::new(a, c); let span_bc = LinearSpan::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); } /// 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 `LinearSpan::contains()` method works properly. #[test] fn linear_span_contains() { let a = LinearPosition::new(1050); let b = LinearPosition::new(1080); let c = LinearPosition::new(1120); let d = LinearPosition::new(1125); let span_ac = LinearSpan::new(a, c); assert!(span_ac.contains(b)); let span_ab = LinearSpan::new(a, b); let span_cd = LinearSpan::new(c, d); assert!(!span_ab.contains(span_cd)); assert!(span_ab.contains(b)); let span_ad = LinearSpan::new(a, d); let span_bc = LinearSpan::new(b, c); assert!(span_ad.contains(span_bc)); assert!(!span_bc.contains(span_ad)); let span_ac = LinearSpan::new(a, c); let span_bd = LinearSpan::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); } /// Checks that the ordering of linear spans is correct. #[test] fn linear_span_ordering() { let a = LinearPosition::new(1050); let b = LinearPosition::new(1052); let c = LinearPosition::new(1120); let d = LinearPosition::new(1125); let span_ab = LinearSpan::new(a, b); let span_cd = LinearSpan::new(c, d); let span_ac = LinearSpan::new(a, c); let span_bd = LinearSpan::new(b, d); assert!(span_ab < span_cd); assert!(span_cd > span_ab); assert_eq!(span_bd.partial_cmp(&span_ac), None); assert_eq!(span_ac.partial_cmp(&span_bd), None); } /// Checks that the ordering of linear spans is correct. #[test] fn linear_union() { let a = LinearPosition::new(1050); let b = LinearPosition::new(1052); let c = LinearPosition::new(1120); let d = LinearPosition::new(1125); let span_ab = LinearSpan::new(a, b); let span_ad = LinearSpan::new(a, d); let span_bc = LinearSpan::new(b, c); let span_cd = LinearSpan::new(c, d); let span_ac = LinearSpan::new(a, c); let span_bd = LinearSpan::new(b, d); assert_eq!(span_bd.union(a), span_ad); assert_eq!(span_ab.union(a), span_ab); assert_eq!(span_bd.union(span_ac), span_ad); assert_eq!(span_ac.union(span_bd), span_ad); assert_eq!(span_ac.union(span_bd), span_ad); assert_eq!(span_ac.union(b), span_ac); assert_eq!(span_bc.union(span_ab), span_ac); assert_eq!(span_ab.union(span_bc), span_ac); assert_eq!(span_ac.union(span_ab), span_ac); assert_eq!(span_cd.union(a), span_ad); assert_eq!(span_cd.union(span_bc), span_bd); } } // TODO: union Span & LinearSpan into `SpanBase` and then: // * Span = SpanBase; // * LinearSpan = SpanBase; // ? ================================================ FILE: core/ast/src/property.rs ================================================ //! Property definition related types, used in object literals and class definitions. use super::{Expression, Spanned}; use crate::{ expression::Identifier, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToInternedString}; use core::ops::ControlFlow; /// `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 = "arbitrary", 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(Identifier), /// 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 { if let Self::Literal(ident) = self { Some(*ident) } 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 fn prop_name(&self) -> Option { match self { Self::Literal(ident) => Some(*ident), Self::Computed(Expression::Literal(lit)) => lit .as_string() .map(|value| Identifier::new(value, lit.span())), Self::Computed(_) => None, } } } impl ToInternedString for PropertyName { fn to_interned_string(&self, interner: &Interner) -> String { match self { Self::Literal(key) => interner.resolve_expect(key.sym()).to_string(), Self::Computed(key) => format!("[{}]", key.to_interned_string(interner)), } } } impl From for PropertyName { fn from(name: Identifier) -> Self { Self::Literal(name) } } impl From 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 where V: Visitor<'a>, { match self { Self::Literal(ident) => visitor.visit_sym(ident.sym_ref()), Self::Computed(expr) => visitor.visit_expression(expr), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::Literal(ident) => visitor.visit_sym_mut(ident.sym_mut()), Self::Computed(expr) => visitor.visit_expression_mut(expr), } } } /// The kind of a method definition. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Copy, Clone, Debug, PartialEq)] pub enum MethodDefinitionKind { /// A getter method. Get, /// A setter method. Set, /// An ordinary method. Ordinary, /// A generator method. Generator, /// An async generator method. AsyncGenerator, /// An async method. Async, } ================================================ FILE: core/ast/src/punctuator/mod.rs ================================================ //! 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::fmt::{Display, Error, Formatter}; #[cfg(test)] mod tests; /// 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, strum::EnumIter)] 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 { 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 { 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::Exp => Some(BinaryOp::Arithmetic(ArithmeticOp::Exp)), 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 TryFrom for AssignOp { // TO-DO: proper error type type Error = String; fn try_from(punct: Punctuator) -> Result { punct .as_assign_op() .ok_or_else(|| format!("No assignment operator for {punct}")) } } impl TryFrom for BinaryOp { // TO-DO: proper error type type Error = String; fn try_from(punct: Punctuator) -> Result { punct .as_binary_op() .ok_or_else(|| format!("No binary operator for {punct}")) } } impl Display for Punctuator { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { f.write_str(self.as_str()) } } impl From for Box { fn from(p: Punctuator) -> Self { p.as_str().into() } } ================================================ FILE: core/ast/src/punctuator/tests.rs ================================================ #![allow(clippy::cognitive_complexity)] use super::*; /// Gets an iterator over all the existing punctuators. fn all_punctuators() -> impl Iterator { [ Punctuator::Add, Punctuator::And, Punctuator::Arrow, Punctuator::Assign, Punctuator::AssignAdd, Punctuator::AssignAnd, Punctuator::AssignBoolAnd, Punctuator::AssignBoolOr, Punctuator::AssignCoalesce, Punctuator::AssignDiv, Punctuator::AssignLeftSh, Punctuator::AssignMod, Punctuator::AssignMul, Punctuator::AssignOr, Punctuator::AssignPow, Punctuator::AssignRightSh, Punctuator::AssignSub, Punctuator::AssignURightSh, Punctuator::AssignXor, Punctuator::BoolAnd, Punctuator::BoolOr, Punctuator::CloseBlock, Punctuator::CloseBracket, Punctuator::CloseParen, Punctuator::Coalesce, Punctuator::Colon, Punctuator::Comma, Punctuator::Dec, Punctuator::Div, Punctuator::Dot, Punctuator::Eq, Punctuator::GreaterThan, Punctuator::GreaterThanOrEq, Punctuator::Inc, Punctuator::LeftSh, Punctuator::LessThan, Punctuator::LessThanOrEq, Punctuator::Mod, Punctuator::Mul, Punctuator::Neg, Punctuator::Not, Punctuator::NotEq, Punctuator::OpenBlock, Punctuator::OpenBracket, Punctuator::OpenParen, Punctuator::Optional, Punctuator::Or, Punctuator::Exp, Punctuator::Question, Punctuator::RightSh, Punctuator::Semicolon, Punctuator::Spread, Punctuator::StrictEq, Punctuator::StrictNotEq, Punctuator::Sub, Punctuator::URightSh, Punctuator::Xor, ] .into_iter() } #[test] fn as_assign_op() { for p in all_punctuators() { match p.as_assign_op() { Some(AssignOp::Assign) => assert_eq!(p, Punctuator::Assign), Some(AssignOp::Add) => assert_eq!(p, Punctuator::AssignAdd), Some(AssignOp::And) => assert_eq!(p, Punctuator::AssignAnd), Some(AssignOp::BoolAnd) => assert_eq!(p, Punctuator::AssignBoolAnd), Some(AssignOp::BoolOr) => assert_eq!(p, Punctuator::AssignBoolOr), Some(AssignOp::Coalesce) => assert_eq!(p, Punctuator::AssignCoalesce), Some(AssignOp::Div) => assert_eq!(p, Punctuator::AssignDiv), Some(AssignOp::Shl) => assert_eq!(p, Punctuator::AssignLeftSh), Some(AssignOp::Mod) => assert_eq!(p, Punctuator::AssignMod), Some(AssignOp::Mul) => assert_eq!(p, Punctuator::AssignMul), Some(AssignOp::Or) => assert_eq!(p, Punctuator::AssignOr), Some(AssignOp::Exp) => assert_eq!(p, Punctuator::AssignPow), Some(AssignOp::Shr) => assert_eq!(p, Punctuator::AssignRightSh), Some(AssignOp::Sub) => assert_eq!(p, Punctuator::AssignSub), Some(AssignOp::Ushr) => assert_eq!(p, Punctuator::AssignURightSh), Some(AssignOp::Xor) => assert_eq!(p, Punctuator::AssignXor), None => assert!( ![ Punctuator::Assign, Punctuator::AssignAdd, Punctuator::AssignAnd, Punctuator::AssignBoolAnd, Punctuator::AssignBoolOr, Punctuator::AssignCoalesce, Punctuator::AssignDiv, Punctuator::AssignLeftSh, Punctuator::AssignMod, Punctuator::AssignMul, Punctuator::AssignOr, Punctuator::AssignPow, Punctuator::AssignRightSh, Punctuator::AssignSub, Punctuator::AssignURightSh, Punctuator::AssignXor, ] .contains(&p) ), } } } #[test] fn as_binary_op() { for p in all_punctuators() { match p.as_binary_op() { Some(BinaryOp::Arithmetic(ArithmeticOp::Add)) => assert_eq!(p, Punctuator::Add), Some(BinaryOp::Arithmetic(ArithmeticOp::Sub)) => assert_eq!(p, Punctuator::Sub), Some(BinaryOp::Arithmetic(ArithmeticOp::Mul)) => assert_eq!(p, Punctuator::Mul), Some(BinaryOp::Arithmetic(ArithmeticOp::Div)) => assert_eq!(p, Punctuator::Div), Some(BinaryOp::Arithmetic(ArithmeticOp::Mod)) => assert_eq!(p, Punctuator::Mod), Some(BinaryOp::Bitwise(BitwiseOp::And)) => assert_eq!(p, Punctuator::And), Some(BinaryOp::Bitwise(BitwiseOp::Or)) => assert_eq!(p, Punctuator::Or), Some(BinaryOp::Bitwise(BitwiseOp::Xor)) => assert_eq!(p, Punctuator::Xor), Some(BinaryOp::Logical(LogicalOp::And)) => assert_eq!(p, Punctuator::BoolAnd), Some(BinaryOp::Logical(LogicalOp::Or)) => assert_eq!(p, Punctuator::BoolOr), Some(BinaryOp::Logical(LogicalOp::Coalesce)) => assert_eq!(p, Punctuator::Coalesce), Some(BinaryOp::Relational(RelationalOp::Equal)) => assert_eq!(p, Punctuator::Eq), Some(BinaryOp::Relational(RelationalOp::NotEqual)) => assert_eq!(p, Punctuator::NotEq), Some(BinaryOp::Relational(RelationalOp::StrictEqual)) => { assert_eq!(p, Punctuator::StrictEq); } Some(BinaryOp::Relational(RelationalOp::StrictNotEqual)) => { assert_eq!(p, Punctuator::StrictNotEq); } Some(BinaryOp::Relational(RelationalOp::LessThan)) => { assert_eq!(p, Punctuator::LessThan); } Some(BinaryOp::Relational(RelationalOp::GreaterThan)) => { assert_eq!(p, Punctuator::GreaterThan); } Some(BinaryOp::Relational(RelationalOp::GreaterThanOrEqual)) => { assert_eq!(p, Punctuator::GreaterThanOrEq); } Some(BinaryOp::Relational(RelationalOp::LessThanOrEqual)) => { assert_eq!(p, Punctuator::LessThanOrEq); } Some(BinaryOp::Bitwise(BitwiseOp::Shl)) => assert_eq!(p, Punctuator::LeftSh), Some(BinaryOp::Bitwise(BitwiseOp::Shr)) => assert_eq!(p, Punctuator::RightSh), Some(BinaryOp::Bitwise(BitwiseOp::UShr)) => assert_eq!(p, Punctuator::URightSh), Some(BinaryOp::Comma) => assert_eq!(p, Punctuator::Comma), Some(BinaryOp::Arithmetic(ArithmeticOp::Exp)) => { assert_eq!(p, Punctuator::Exp); } None => assert!( ![ Punctuator::Add, Punctuator::Sub, Punctuator::Mul, Punctuator::Div, Punctuator::Mod, Punctuator::And, Punctuator::Or, Punctuator::Xor, Punctuator::BoolAnd, Punctuator::BoolOr, Punctuator::Coalesce, Punctuator::Eq, Punctuator::NotEq, Punctuator::StrictEq, Punctuator::StrictNotEq, Punctuator::LessThan, Punctuator::GreaterThan, Punctuator::GreaterThanOrEq, Punctuator::LessThanOrEq, Punctuator::LeftSh, Punctuator::RightSh, Punctuator::URightSh, Punctuator::Comma ] .contains(&p) ), Some(BinaryOp::Relational(RelationalOp::In | RelationalOp::InstanceOf)) => { unreachable!() } } } } #[test] fn as_str() { for p in all_punctuators() { match p.as_str() { "+" => assert_eq!(p, Punctuator::Add), "&" => assert_eq!(p, Punctuator::And), "=>" => assert_eq!(p, Punctuator::Arrow), "=" => assert_eq!(p, Punctuator::Assign), "+=" => assert_eq!(p, Punctuator::AssignAdd), "&=" => assert_eq!(p, Punctuator::AssignAnd), "&&=" => assert_eq!(p, Punctuator::AssignBoolAnd), "||=" => assert_eq!(p, Punctuator::AssignBoolOr), "??=" => assert_eq!(p, Punctuator::AssignCoalesce), "/=" => assert_eq!(p, Punctuator::AssignDiv), "<<=" => assert_eq!(p, Punctuator::AssignLeftSh), "%=" => assert_eq!(p, Punctuator::AssignMod), "*=" => assert_eq!(p, Punctuator::AssignMul), "|=" => assert_eq!(p, Punctuator::AssignOr), "**=" => assert_eq!(p, Punctuator::AssignPow), ">>=" => assert_eq!(p, Punctuator::AssignRightSh), "-=" => assert_eq!(p, Punctuator::AssignSub), ">>>=" => assert_eq!(p, Punctuator::AssignURightSh), "^=" => assert_eq!(p, Punctuator::AssignXor), "&&" => assert_eq!(p, Punctuator::BoolAnd), "||" => assert_eq!(p, Punctuator::BoolOr), "??" => assert_eq!(p, Punctuator::Coalesce), "}" => assert_eq!(p, Punctuator::CloseBlock), "]" => assert_eq!(p, Punctuator::CloseBracket), ")" => assert_eq!(p, Punctuator::CloseParen), ":" => assert_eq!(p, Punctuator::Colon), "," => assert_eq!(p, Punctuator::Comma), "--" => assert_eq!(p, Punctuator::Dec), "/" => assert_eq!(p, Punctuator::Div), "." => assert_eq!(p, Punctuator::Dot), "==" => assert_eq!(p, Punctuator::Eq), ">" => assert_eq!(p, Punctuator::GreaterThan), ">=" => assert_eq!(p, Punctuator::GreaterThanOrEq), "++" => assert_eq!(p, Punctuator::Inc), "<<" => assert_eq!(p, Punctuator::LeftSh), "<" => assert_eq!(p, Punctuator::LessThan), "<=" => assert_eq!(p, Punctuator::LessThanOrEq), "%" => assert_eq!(p, Punctuator::Mod), "*" => assert_eq!(p, Punctuator::Mul), "~" => assert_eq!(p, Punctuator::Neg), "!" => assert_eq!(p, Punctuator::Not), "!=" => assert_eq!(p, Punctuator::NotEq), "{" => assert_eq!(p, Punctuator::OpenBlock), "[" => assert_eq!(p, Punctuator::OpenBracket), "(" => assert_eq!(p, Punctuator::OpenParen), "?." => assert_eq!(p, Punctuator::Optional), "|" => assert_eq!(p, Punctuator::Or), "**" => assert_eq!(p, Punctuator::Exp), "?" => assert_eq!(p, Punctuator::Question), ">>" => assert_eq!(p, Punctuator::RightSh), ";" => assert_eq!(p, Punctuator::Semicolon), "..." => assert_eq!(p, Punctuator::Spread), "===" => assert_eq!(p, Punctuator::StrictEq), "!==" => assert_eq!(p, Punctuator::StrictNotEq), "-" => assert_eq!(p, Punctuator::Sub), ">>>" => assert_eq!(p, Punctuator::URightSh), "^" => assert_eq!(p, Punctuator::Xor), _ => unreachable!("unknown punctuator {p:?} found"), } } } #[test] fn try_into_assign_op() { for p in all_punctuators() { if p.as_assign_op().is_some() { assert!(TryInto::::try_into(p).is_ok()); } else { assert!(TryInto::::try_into(p).is_err()); } } } #[test] fn try_into_binary_op() { for p in all_punctuators() { if p.as_binary_op().is_some() { assert!(TryInto::::try_into(p).is_ok()); } else { assert!(TryInto::::try_into(p).is_err()); } } } #[test] fn display() { for p in all_punctuators() { assert_eq!(p.as_str(), p.to_string()); } } #[test] fn into_box() { for p in all_punctuators() { assert_eq!(p.as_str(), Box::::from(p).as_ref()); } } ================================================ FILE: core/ast/src/scope.rs ================================================ //! This module implements the binding scope for various AST nodes. //! //! Scopes are used to track the bindings of identifiers in the AST. use bitflags::bitflags; use boa_string::JsString; use std::{ cell::{Cell, RefCell}, fmt::Debug, rc::Rc, }; bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct BindingFlags: u8 { const MUTABLE = 1 << 0; const LEX = 1 << 1; const STRICT = 1 << 2; const ESCAPES = 1 << 3; const ACCESSED = 1 << 4; } } impl BindingFlags { fn is_mutable(self) -> bool { self.contains(BindingFlags::MUTABLE) } fn is_lex(self) -> bool { self.contains(BindingFlags::LEX) } fn is_strict(self) -> bool { self.contains(BindingFlags::STRICT) } fn escapes(self) -> bool { self.contains(BindingFlags::ESCAPES) } fn is_accessed(self) -> bool { self.contains(BindingFlags::ACCESSED) } } #[derive(Clone, Debug, PartialEq)] struct Binding { name: JsString, index: u32, flags: BindingFlags, } impl Binding { fn is_mutable(&self) -> bool { self.flags.is_mutable() } fn is_lex(&self) -> bool { self.flags.is_lex() } fn is_strict(&self) -> bool { self.flags.is_strict() } fn escapes(&self) -> bool { self.flags.escapes() } fn is_accessed(&self) -> bool { self.flags.is_accessed() } } /// A scope maps bound identifiers to their binding positions. /// /// It can be either a global scope or a function scope or a declarative scope. #[derive(Clone, PartialEq)] pub struct Scope { inner: Rc, } impl Debug for Scope { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Scope") .field("outer", &self.inner.outer) .field("index", &self.inner.index) .field("bindings", &self.inner.bindings) .field("function", &self.inner.function) .finish() } } impl Default for Scope { fn default() -> Self { Self::new_global() } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for Scope { fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { Ok(Self::new_global()) } } #[derive(Debug, PartialEq)] pub(crate) struct Inner { unique_id: u32, outer: Option, index: Cell, bindings: RefCell>, function: bool, // Has the `this` been accessed/escaped outside the function environment boundary. this_escaped: Cell, context: Rc, } impl Scope { /// Creates a new global scope. #[must_use] pub fn new_global() -> Self { Self { inner: Rc::new(Inner { unique_id: 0, outer: None, index: Cell::default(), bindings: RefCell::default(), function: true, this_escaped: Cell::new(false), context: Rc::default(), }), } } /// Creates a new scope. #[must_use] pub fn new(parent: Self, function: bool) -> Self { Self { inner: Rc::new(Inner { unique_id: parent.inner.context.next_unique_id(), index: Cell::new(parent.inner.index.get() + 1), bindings: RefCell::default(), function, this_escaped: Cell::new(false), context: parent.inner.context.clone(), outer: Some(parent), }), } } /// Checks if the scope has only local bindings. #[must_use] pub fn all_bindings_local(&self) -> bool { // if self.inner.function && self.inn self.inner .bindings .borrow() .iter() .all(|binding| !binding.escapes()) } /// Marks all bindings in this scope as escaping. pub fn escape_all_bindings(&self) { for binding in self.inner.bindings.borrow_mut().iter_mut() { binding.flags.insert(BindingFlags::ESCAPES); } } /// Has this binding escaped. #[must_use] pub fn escaped_this(&self) -> bool { self.inner.this_escaped.get() } /// Check if the scope has a lexical binding with the given name. #[must_use] pub fn has_lex_binding(&self, name: &JsString) -> bool { self.inner .bindings .borrow() .iter() .find(|b| &b.name == name) .is_some_and(Binding::is_lex) } /// Check if the scope has a binding with the given name. #[must_use] pub fn has_binding(&self, name: &JsString) -> bool { self.inner.bindings.borrow().iter().any(|b| &b.name == name) } /// Get the binding locator for a binding with the given name. /// Fall back to the global scope if the binding is not found. #[must_use] pub fn get_identifier_reference(&self, name: JsString) -> IdentifierReference { if let Some(binding) = self.inner.bindings.borrow().iter().find(|b| b.name == name) { IdentifierReference::new( BindingLocator::declarative( name, self.inner.index.get(), binding.index, self.inner.unique_id, ), binding.is_lex(), binding.escapes(), ) } else if let Some(outer) = &self.inner.outer { outer.get_identifier_reference(name) } else { IdentifierReference::new(BindingLocator::global(name), false, true) } } /// Returns the number of bindings in this scope. #[must_use] #[allow(clippy::cast_possible_truncation)] pub fn num_bindings(&self) -> u32 { self.inner.bindings.borrow().len() as u32 } /// Returns the number of bindings in this scope that are not local. #[must_use] #[allow(clippy::cast_possible_truncation)] pub fn num_bindings_non_local(&self) -> u32 { self.inner .bindings .borrow() .iter() .filter(|binding| binding.escapes()) .count() as u32 } /// Adjust the binding indices to exclude local bindings. pub(crate) fn reorder_binding_indices(&self) { let mut bindings = self.inner.bindings.borrow_mut(); let mut index = 0; for binding in bindings.iter_mut() { if !binding.escapes() { binding.index = 0; continue; } binding.index = index; index += 1; } } /// Returns the index of this scope. #[must_use] pub fn scope_index(&self) -> u32 { self.inner.index.get() } /// Set the index of this scope. pub(crate) fn set_index(&self, index: u32) { self.inner.index.set(index); } /// Check if the scope is a function scope. #[must_use] pub fn is_function(&self) -> bool { self.inner.function } /// Check if the scope is a global scope. #[must_use] pub fn is_global(&self) -> bool { self.inner.outer.is_none() } /// Check if a binding with the given name is mutable. /// /// Returns `Some(true)` for mutable bindings (`let`, `var`), /// `Some(false)` for immutable bindings (`const`), /// or `None` if the binding is not found in this or any outer scope. #[must_use] pub fn is_binding_mutable(&self, name: &JsString) -> Option { if let Some(binding) = self .inner .bindings .borrow() .iter() .find(|b| &b.name == name) { Some(binding.is_mutable()) } else if let Some(outer) = &self.inner.outer { outer.is_binding_mutable(name) } else { None } } /// Get the locator for a binding name. #[must_use] pub fn get_binding(&self, name: &JsString) -> Option { self.inner .bindings .borrow() .iter() .find(|b| &b.name == name) .map(|binding| { BindingLocator::declarative( name.clone(), self.inner.index.get(), binding.index, self.inner.unique_id, ) }) } /// Get the locator for a binding name. #[must_use] pub fn get_binding_reference(&self, name: &JsString) -> Option { self.inner .bindings .borrow() .iter() .find(|b| &b.name == name) .map(|binding| { IdentifierReference::new( BindingLocator::declarative( name.clone(), self.inner.index.get(), binding.index, self.inner.unique_id, ), binding.is_lex(), binding.escapes(), ) }) } /// Simulate a binding access. /// /// - If the binding access crosses a function border, the binding is marked as escaping. /// - If the binding access is in an eval or with scope, the binding is marked as escaping. pub fn access_binding(&self, name: &JsString, eval_or_with: bool) { let mut crossed_function_border = false; let mut current = self; loop { if let Some(binding) = current .inner .bindings .borrow_mut() .iter_mut() .find(|b| &b.name == name) { binding.flags.insert(BindingFlags::ACCESSED); if crossed_function_border || eval_or_with { binding.flags.insert(BindingFlags::ESCAPES); } return; } if let Some(outer) = ¤t.inner.outer { if current.inner.function { crossed_function_border = true; } current = outer; } else { return; } } } /// Escape enclosing function environment's `this`. pub fn escape_this_in_enclosing_function_scope(&self) { let mut current = self; let mut crossed_function_border = false; loop { if crossed_function_border && current.is_function() { current.inner.this_escaped.set(true); return; } if let Some(outer) = ¤t.inner.outer { if current.is_function() { crossed_function_border = true; } current = outer; } else { return; } } } /// Creates a mutable binding. #[must_use] #[allow(clippy::cast_possible_truncation)] pub fn create_mutable_binding(&self, name: JsString, function_scope: bool) -> BindingLocator { let mut bindings = self.inner.bindings.borrow_mut(); let binding_index = bindings.len() as u32; if let Some(binding) = bindings.iter().find(|b| b.name == name) { return BindingLocator::declarative( name, self.inner.index.get(), binding.index, self.inner.unique_id, ); } let mut flags = BindingFlags::MUTABLE; flags.set(BindingFlags::LEX, !function_scope); flags.set(BindingFlags::ESCAPES, self.is_global()); bindings.push(Binding { name: name.clone(), index: binding_index, flags, }); BindingLocator::declarative( name, self.inner.index.get(), binding_index, self.inner.unique_id, ) } /// Crate an immutable binding. #[allow(clippy::cast_possible_truncation)] pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) { let mut bindings = self.inner.bindings.borrow_mut(); if bindings.iter().any(|b| b.name == name) { return; } let binding_index = bindings.len() as u32; let mut flags = BindingFlags::LEX; flags.set(BindingFlags::STRICT, strict); flags.set(BindingFlags::ESCAPES, self.is_global()); bindings.push(Binding { name, index: binding_index, flags, }); } /// Return the binding locator for a mutable binding. /// /// # Errors /// Returns an error if the binding is not mutable or does not exist. pub fn set_mutable_binding( &self, name: JsString, ) -> Result { Ok( match self.inner.bindings.borrow().iter().find(|b| b.name == name) { Some(binding) if binding.is_mutable() => IdentifierReference::new( BindingLocator::declarative( name, self.inner.index.get(), binding.index, self.inner.unique_id, ), binding.is_lex(), binding.escapes(), ), Some(binding) if binding.is_strict() => { return Err(BindingLocatorError::MutateImmutable); } Some(_) => return Err(BindingLocatorError::Silent), None => self.inner.outer.as_ref().map_or_else( || { Ok(IdentifierReference::new( BindingLocator::global(name.clone()), false, true, )) }, |outer| outer.set_mutable_binding(name.clone()), )?, }, ) } #[cfg(feature = "annex-b")] /// Return the binding locator for a set operation on an existing var binding. /// /// # Errors /// Returns an error if the binding is not mutable or does not exist. pub fn set_mutable_binding_var( &self, name: JsString, ) -> Result { if !self.is_function() { return self.inner.outer.as_ref().map_or_else( || { Ok(IdentifierReference::new( BindingLocator::global(name.clone()), false, true, )) }, |outer| outer.set_mutable_binding_var(name.clone()), ); } Ok( match self.inner.bindings.borrow().iter().find(|b| b.name == name) { Some(binding) if binding.is_mutable() => IdentifierReference::new( BindingLocator::declarative( name, self.inner.index.get(), binding.index, self.inner.unique_id, ), binding.is_lex(), binding.escapes(), ), Some(binding) if binding.is_strict() => { return Err(BindingLocatorError::MutateImmutable); } Some(_) => return Err(BindingLocatorError::Silent), None => self.inner.outer.as_ref().map_or_else( || { Ok(IdentifierReference::new( BindingLocator::global(name.clone()), false, true, )) }, |outer| outer.set_mutable_binding_var(name.clone()), )?, }, ) } /// Gets the outer scope of this scope. #[must_use] pub fn outer(&self) -> Option<&Self> { self.inner.outer.as_ref() } /// Returns the unique ID of this scope. #[must_use] pub fn unique_id(&self) -> u32 { self.inner.unique_id } } /// Additional state that all Scopes of a single AST share for bookkeeping. #[derive(Debug, PartialEq, Default)] struct ScopeContext { /// A counter for unique IDs for Scopes generated as part of this scope tree. /// /// The value is the highest unique ID assigned so far (initialized with 0 /// which is also the unique ID of the global scope). /// /// Only used in the root scope. unique_id_ctr: Cell, } impl ScopeContext { /// Returns the next unique ID for a scope in this scope tree. fn next_unique_id(&self) -> u32 { let id = self.unique_id_ctr.get() + 1; self.unique_id_ctr.set(id); id } } /// A reference to an identifier in a scope. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct IdentifierReference { locator: BindingLocator, lexical: bool, escapes: bool, } impl IdentifierReference { /// Create a new identifier reference. pub(crate) fn new(locator: BindingLocator, lexical: bool, escapes: bool) -> Self { Self { locator, lexical, escapes, } } /// Get the binding locator for this identifier reference. #[must_use] pub fn locator(&self) -> BindingLocator { self.locator.clone() } /// Returns if the binding can be function local. #[must_use] pub fn local(&self) -> bool { self.locator.scope > 0 && !self.escapes } /// Returns if the binding is on the global object. #[must_use] pub fn is_global_object(&self) -> bool { self.locator.scope == 0 } /// Check if this identifier reference is lexical. #[must_use] pub fn is_lexical(&self) -> bool { self.lexical } } /// A binding locator contains all information about a binding that is needed to resolve it at runtime. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct BindingLocator { /// Name of the binding. name: JsString, /// Scope of the binding. /// - 0: Global object /// - 1: Global declarative scope /// - n: Stack scope at index n - 2 scope: u32, /// Index of the binding in the scope. binding_index: u32, unique_scope_id: u32, } impl BindingLocator { /// Creates a new declarative binding locator that has knows indices. pub(crate) const fn declarative( name: JsString, scope_index: u32, binding_index: u32, unique_scope_id: u32, ) -> Self { Self { name, scope: scope_index + 1, binding_index, unique_scope_id, } } /// Creates a binding locator that indicates that the binding is on the global object. pub(super) const fn global(name: JsString) -> Self { Self { name, scope: 0, binding_index: 0, unique_scope_id: 0, } } /// Returns the name of the binding. #[must_use] pub const fn name(&self) -> &JsString { &self.name } /// Returns if the binding is located on the global object. #[must_use] pub const fn is_global(&self) -> bool { self.scope == 0 } /// Returns the scope of the binding. #[must_use] pub fn scope(&self) -> BindingLocatorScope { match self.scope { 0 => BindingLocatorScope::GlobalObject, 1 => BindingLocatorScope::GlobalDeclarative, n => BindingLocatorScope::Stack(n - 2), } } /// Sets the scope of the binding. pub fn set_scope(&mut self, scope: BindingLocatorScope) { self.scope = match scope { BindingLocatorScope::GlobalObject => 0, BindingLocatorScope::GlobalDeclarative => 1, BindingLocatorScope::Stack(index) => index + 2, }; } /// Returns the binding index of the binding. #[must_use] pub const fn binding_index(&self) -> u32 { self.binding_index } /// Sets the binding index of the binding. pub fn set_binding_index(&mut self, index: u32) { self.binding_index = index; } /// Returns the unique scope ID of the binding. #[must_use] pub fn unique_scope_id(&self) -> u32 { self.unique_scope_id } } /// Action that is returned when a fallible binding operation. #[derive(Copy, Clone, Debug)] pub enum BindingLocatorError { /// Trying to mutate immutable binding, MutateImmutable, /// Indicates that any action is silently ignored. Silent, } /// The scope in which a binding is located. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BindingLocatorScope { /// The binding is located on the global object. GlobalObject, /// The binding is located in the global declarative scope. GlobalDeclarative, /// The binding is located in the scope stack at the given index. Stack(u32), } /// A collection of function scopes. #[derive(Clone, Debug, Default, PartialEq)] pub struct FunctionScopes { pub(crate) function_scope: Scope, pub(crate) parameters_eval_scope: Option, pub(crate) parameters_scope: Option, pub(crate) lexical_scope: Option, pub(crate) mapped_arguments_object: bool, pub(crate) requires_function_scope: bool, } impl FunctionScopes { /// Returns the function scope for this function. #[must_use] pub fn function_scope(&self) -> &Scope { &self.function_scope } /// Returns if the arguments object is accessed in this function. #[must_use] pub fn arguments_object_accessed(&self) -> bool { if self .function_scope .inner .bindings .borrow() .first() .filter(|b| b.name == "arguments" && b.is_accessed()) .is_some() { return true; } if let Some(scope) = &self.parameters_eval_scope && scope .inner .bindings .borrow() .first() .filter(|b| b.name == "arguments" && b.is_accessed()) .is_some() { return true; } false } /// Check if the creation of the function scope is required. #[must_use] pub fn requires_function_scope(&self) -> bool { self.requires_function_scope } /// Returns the parameters eval scope for this function. #[must_use] pub fn parameters_eval_scope(&self) -> Option<&Scope> { self.parameters_eval_scope.as_ref() } /// Returns the parameters scope for this function. #[must_use] pub fn parameters_scope(&self) -> Option<&Scope> { self.parameters_scope.as_ref() } /// Returns the lexical scope for this function. #[must_use] pub fn lexical_scope(&self) -> Option<&Scope> { self.lexical_scope.as_ref() } /// Returns the effective parameter scope for this function. #[must_use] pub fn parameter_scope(&self) -> Scope { if let Some(parameters_eval_scope) = &self.parameters_eval_scope { return parameters_eval_scope.clone(); } self.function_scope.clone() } /// Returns the effective body scope for this function. pub(crate) fn body_scope(&self) -> Scope { if let Some(lexical_scope) = &self.lexical_scope { return lexical_scope.clone(); } if let Some(parameters_scope) = &self.parameters_scope { return parameters_scope.clone(); } if let Some(parameters_eval_scope) = &self.parameters_eval_scope { return parameters_eval_scope.clone(); } self.function_scope.clone() } /// Marks all bindings in all scopes as escaping. pub(crate) fn escape_all_bindings(&self) { self.function_scope.escape_all_bindings(); if let Some(parameters_eval_scope) = &self.parameters_eval_scope { parameters_eval_scope.escape_all_bindings(); } if let Some(parameters_scope) = &self.parameters_scope { parameters_scope.escape_all_bindings(); } if let Some(lexical_scope) = &self.lexical_scope { lexical_scope.escape_all_bindings(); } } pub(crate) fn reorder_binding_indices(&self) { self.function_scope.reorder_binding_indices(); if let Some(parameters_eval_scope) = &self.parameters_eval_scope { parameters_eval_scope.reorder_binding_indices(); } if let Some(parameters_scope) = &self.parameters_scope { parameters_scope.reorder_binding_indices(); } if let Some(lexical_scope) = &self.lexical_scope { lexical_scope.reorder_binding_indices(); } } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for FunctionScopes { fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { Ok(Self { function_scope: Scope::new_global(), parameters_eval_scope: None, parameters_scope: None, lexical_scope: None, mapped_arguments_object: false, requires_function_scope: false, }) } } ================================================ FILE: core/ast/src/scope_analyzer.rs ================================================ //! This module implements the scope analysis for the AST. //! //! The scope analysis is done in two steps: //! 1. Collecting bindings: This step collects all the bindings in the AST and fills the scopes with them. //! 2. Analyzing binding escapes: This step analyzes if the bindings escape their function scopes. #[cfg(feature = "annex-b")] use crate::operations::annex_b_function_declarations_names; use crate::{ Declaration, Module, Script, StatementListItem, ToJsString, declaration::{Binding, ExportDeclaration, LexicalDeclaration, VariableList}, expression::{Identifier, literal::ObjectMethodDefinition}, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, ClassExpression, FormalParameterList, FunctionBody, FunctionDeclaration, FunctionExpression, GeneratorDeclaration, GeneratorExpression, }, operations::{ ContainsSymbol, LexicallyScopedDeclaration, VarScopedDeclaration, bound_names, contains, lexically_declared_names, lexically_scoped_declarations, var_declared_names, var_scoped_declarations, }, property::PropertyName, scope::{FunctionScopes, IdentifierReference, Scope}, statement::{ Block, Catch, ForInLoop, ForLoop, ForOfLoop, Switch, With, iteration::{ForLoopInitializer, IterableLoopInitializer}, }, visitor::{NodeRef, NodeRefMut, VisitorMut}, }; use boa_interner::{Interner, Sym}; use rustc_hash::FxHashMap; use std::ops::ControlFlow; /// Collect bindings and fill the scopes with them. /// /// # Errors /// Any break in the control flow that happened during the collection. pub(crate) fn collect_bindings<'a, N>( node: &'a mut N, strict: bool, eval: bool, scope: &Scope, interner: &Interner, ) -> Result<(), &'static str> where &'a mut N: Into>, { let mut visitor = BindingCollectorVisitor { strict, eval, in_arrow: false, scope: scope.clone(), interner, }; match visitor.visit(node) { ControlFlow::Continue(()) => Ok(()), ControlFlow::Break(reason) => Err(reason), } } /// Analyze if bindings escape their function scopes. /// /// # Errors /// Any break in the control flow that happened during the analysis. pub(crate) fn analyze_binding_escapes<'a, N>( node: &'a mut N, in_eval: bool, scope: Scope, interner: &Interner, ) -> Result<(), &'static str> where &'a mut N: Into>, { let mut visitor = BindingEscapeAnalyzer { scope, direct_eval: in_eval, with: false, interner, }; match visitor.visit(node.into()) { ControlFlow::Continue(()) => Ok(()), ControlFlow::Break(reason) => Err(reason), } } struct BindingEscapeAnalyzer<'interner> { scope: Scope, direct_eval: bool, with: bool, interner: &'interner Interner, } impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> { type BreakTy = &'static str; fn visit_identifier_mut(&mut self, node: &'ast mut Identifier) -> ControlFlow { let name = node.to_js_string(self.interner); self.scope .access_binding(&name, self.direct_eval || self.with); ControlFlow::Continue(()) } fn visit_block_mut(&mut self, node: &'ast mut Block) -> ControlFlow { let direct_eval_old = self.direct_eval; self.direct_eval = node.contains_direct_eval || self.direct_eval; if let Some(scope) = &mut node.scope { if self.direct_eval { scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, scope); } self.visit_statement_list_mut(&mut node.statements)?; if let Some(scope) = &mut node.scope { std::mem::swap(&mut self.scope, scope); scope.reorder_binding_indices(); } self.direct_eval = direct_eval_old; ControlFlow::Continue(()) } fn visit_switch_mut(&mut self, node: &'ast mut Switch) -> ControlFlow { self.visit_expression_mut(&mut node.val)?; let direct_eval_old = self.direct_eval; self.direct_eval = node.contains_direct_eval || self.direct_eval; if let Some(scope) = &mut node.scope { if self.direct_eval { scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, scope); } for case in &mut node.cases { self.visit_case_mut(case)?; } if let Some(scope) = &mut node.scope { std::mem::swap(&mut self.scope, scope); scope.reorder_binding_indices(); } self.direct_eval = direct_eval_old; ControlFlow::Continue(()) } fn visit_with_mut(&mut self, node: &'ast mut With) -> ControlFlow { let with = self.with; self.with = true; if self.direct_eval { node.scope.escape_all_bindings(); } self.visit_expression_mut(&mut node.expression)?; std::mem::swap(&mut self.scope, &mut node.scope); self.visit_statement_mut(&mut node.statement)?; std::mem::swap(&mut self.scope, &mut node.scope); node.scope.reorder_binding_indices(); self.with = with; ControlFlow::Continue(()) } fn visit_catch_mut(&mut self, node: &'ast mut Catch) -> ControlFlow { let direct_eval_old = self.direct_eval; self.direct_eval = node.contains_direct_eval || self.direct_eval; if self.direct_eval { node.scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, &mut node.scope); if let Some(binding) = &mut node.parameter { self.visit_binding_mut(binding)?; } self.visit_block_mut(&mut node.block)?; std::mem::swap(&mut self.scope, &mut node.scope); node.scope.reorder_binding_indices(); self.direct_eval = direct_eval_old; ControlFlow::Continue(()) } fn visit_for_loop_mut(&mut self, node: &'ast mut ForLoop) -> ControlFlow { let direct_eval_old = self.direct_eval; self.direct_eval = node.inner.contains_direct_eval || self.direct_eval; if let Some(ForLoopInitializer::Lexical(decl)) = &mut node.inner.init { if self.direct_eval { decl.scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, &mut decl.scope); } if let Some(init) = &mut node.inner.init { self.visit_for_loop_initializer_mut(init)?; } if let Some(condition) = &mut node.inner.condition { self.visit_expression_mut(condition)?; } if let Some(final_expr) = &mut node.inner.final_expr { self.visit_expression_mut(final_expr)?; } self.visit_statement_mut(&mut node.inner.body)?; if let Some(ForLoopInitializer::Lexical(decl)) = &mut node.inner.init { std::mem::swap(&mut self.scope, &mut decl.scope); decl.scope.reorder_binding_indices(); } self.direct_eval = direct_eval_old; ControlFlow::Continue(()) } fn visit_for_in_loop_mut(&mut self, node: &'ast mut ForInLoop) -> ControlFlow { let direct_eval_old = self.direct_eval; if let Some(scope) = &mut node.target_scope { self.direct_eval = node.target_contains_direct_eval || self.direct_eval; if self.direct_eval { scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, scope); } self.visit_expression_mut(&mut node.target)?; if let Some(scope) = &mut node.target_scope { self.direct_eval = direct_eval_old; std::mem::swap(&mut self.scope, scope); scope.reorder_binding_indices(); } if let Some(scope) = &mut node.scope { self.direct_eval = node.contains_direct_eval || self.direct_eval; if self.direct_eval { scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, scope); } self.visit_iterable_loop_initializer_mut(&mut node.initializer)?; self.visit_statement_mut(&mut node.body)?; if let Some(scope) = &mut node.scope { std::mem::swap(&mut self.scope, scope); scope.reorder_binding_indices(); } self.direct_eval = direct_eval_old; ControlFlow::Continue(()) } fn visit_for_of_loop_mut(&mut self, node: &'ast mut ForOfLoop) -> ControlFlow { let direct_eval_old = self.direct_eval; if let Some(scope) = &mut node.iterable_scope { self.direct_eval = node.iterable_contains_direct_eval || self.direct_eval; if self.direct_eval { scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, scope); } self.visit_expression_mut(&mut node.iterable)?; if let Some(scope) = &mut node.iterable_scope { self.direct_eval = direct_eval_old; std::mem::swap(&mut self.scope, scope); scope.reorder_binding_indices(); } if let Some(scope) = &mut node.scope { self.direct_eval = node.contains_direct_eval || self.direct_eval; if self.direct_eval { scope.escape_all_bindings(); } std::mem::swap(&mut self.scope, scope); } self.visit_iterable_loop_initializer_mut(&mut node.init)?; self.visit_statement_mut(&mut node.body)?; if let Some(scope) = &mut node.scope { std::mem::swap(&mut self.scope, scope); scope.reorder_binding_indices(); } self.direct_eval = direct_eval_old; ControlFlow::Continue(()) } fn visit_function_declaration_mut( &mut self, node: &'ast mut FunctionDeclaration, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_generator_declaration_mut( &mut self, node: &'ast mut GeneratorDeclaration, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_async_function_declaration_mut( &mut self, node: &'ast mut AsyncFunctionDeclaration, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_async_generator_declaration_mut( &mut self, node: &'ast mut AsyncGeneratorDeclaration, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_function_expression_mut( &mut self, node: &'ast mut FunctionExpression, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_generator_expression_mut( &mut self, node: &'ast mut GeneratorExpression, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_async_function_expression_mut( &mut self, node: &'ast mut AsyncFunctionExpression, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_async_generator_expression_mut( &mut self, node: &'ast mut AsyncGeneratorExpression, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_arrow_function_mut( &mut self, node: &'ast mut ArrowFunction, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_async_arrow_function_mut( &mut self, node: &'ast mut AsyncArrowFunction, ) -> ControlFlow { self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_class_declaration_mut( &mut self, node: &'ast mut ClassDeclaration, ) -> ControlFlow { node.name_scope.escape_all_bindings(); std::mem::swap(&mut self.scope, &mut node.name_scope); if let Some(super_ref) = &mut node.super_ref { self.visit_expression_mut(super_ref)?; } if let Some(constructor) = &mut node.constructor { self.visit_function_expression_mut(constructor)?; } for element in &mut *node.elements { self.visit_class_element_mut(element)?; } std::mem::swap(&mut self.scope, &mut node.name_scope); node.name_scope.reorder_binding_indices(); ControlFlow::Continue(()) } fn visit_class_expression_mut( &mut self, node: &'ast mut ClassExpression, ) -> ControlFlow { if let Some(name_scope) = &mut node.name_scope { if self.direct_eval { name_scope.escape_all_bindings(); } name_scope.escape_all_bindings(); std::mem::swap(&mut self.scope, name_scope); } if let Some(super_ref) = &mut node.super_ref { self.visit_expression_mut(super_ref)?; } if let Some(constructor) = &mut node.constructor { self.visit_function_expression_mut(constructor)?; } for element in &mut *node.elements { self.visit_class_element_mut(element)?; } if let Some(name_scope) = &mut node.name_scope { std::mem::swap(&mut self.scope, name_scope); name_scope.reorder_binding_indices(); } ControlFlow::Continue(()) } fn visit_class_element_mut( &mut self, node: &'ast mut ClassElement, ) -> ControlFlow { match node { ClassElement::MethodDefinition(node) => self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ), ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { self.visit_property_name_mut(&mut field.name)?; if let Some(e) = &mut field.initializer { std::mem::swap(&mut self.scope, &mut field.scope); self.visit_expression_mut(e)?; std::mem::swap(&mut self.scope, &mut field.scope); } ControlFlow::Continue(()) } ClassElement::PrivateFieldDefinition(field) | ClassElement::PrivateStaticFieldDefinition(field) => { if let Some(e) = &mut field.initializer { std::mem::swap(&mut self.scope, &mut field.scope); self.visit_expression_mut(e)?; std::mem::swap(&mut self.scope, &mut field.scope); } ControlFlow::Continue(()) } ClassElement::StaticBlock(node) => { let contains_direct_eval = contains(node.statements(), ContainsSymbol::DirectEval); self.visit_function_like( &mut FormalParameterList::default(), &mut node.body, &mut node.scopes, contains_direct_eval, ) } } } fn visit_object_method_definition_mut( &mut self, node: &'ast mut ObjectMethodDefinition, ) -> ControlFlow { self.visit_property_name_mut(&mut node.name)?; self.visit_function_like( &mut node.parameters, &mut node.body, &mut node.scopes, node.contains_direct_eval, ) } fn visit_export_declaration_mut( &mut self, node: &'ast mut ExportDeclaration, ) -> ControlFlow { match node { ExportDeclaration::ReExport { specifier, kind, .. } => { self.visit_module_specifier_mut(specifier)?; self.visit_re_export_kind_mut(kind) } ExportDeclaration::List(list) => { for item in &mut **list { self.visit_export_specifier_mut(item)?; } ControlFlow::Continue(()) } ExportDeclaration::VarStatement(var) => self.visit_var_declaration_mut(var), ExportDeclaration::Declaration(decl) => self.visit_declaration_mut(decl), ExportDeclaration::DefaultFunctionDeclaration(f) => { self.visit_function_declaration_mut(f) } ExportDeclaration::DefaultGeneratorDeclaration(g) => { self.visit_generator_declaration_mut(g) } ExportDeclaration::DefaultAsyncFunctionDeclaration(af) => { self.visit_async_function_declaration_mut(af) } ExportDeclaration::DefaultAsyncGeneratorDeclaration(ag) => { self.visit_async_generator_declaration_mut(ag) } ExportDeclaration::DefaultClassDeclaration(c) => self.visit_class_declaration_mut(c), ExportDeclaration::DefaultAssignmentExpression(expr) => { let name = Sym::DEFAULT_EXPORT.to_js_string(self.interner); drop(self.scope.create_mutable_binding(name.clone(), false)); self.scope.access_binding(&name, true); self.visit_expression_mut(expr) } } } fn visit_module_mut(&mut self, node: &'ast mut Module) -> ControlFlow { let mut scope = node.scope.clone(); scope.escape_all_bindings(); std::mem::swap(&mut self.scope, &mut scope); self.visit_module_item_list_mut(&mut node.items)?; std::mem::swap(&mut self.scope, &mut scope); scope.reorder_binding_indices(); ControlFlow::Continue(()) } } impl BindingEscapeAnalyzer<'_> { fn visit_function_like( &mut self, parameters: &mut FormalParameterList, body: &mut FunctionBody, scopes: &mut FunctionScopes, contains_direct_eval: bool, ) -> ControlFlow<&'static str> { let direct_eval_old = self.direct_eval; self.direct_eval = contains_direct_eval || self.direct_eval; if self.direct_eval { scopes.escape_all_bindings(); } let mut scope = scopes.parameter_scope(); std::mem::swap(&mut self.scope, &mut scope); self.visit_formal_parameter_list_mut(parameters)?; std::mem::swap(&mut self.scope, &mut scope); scope = scopes.body_scope(); std::mem::swap(&mut self.scope, &mut scope); self.visit_function_body_mut(body)?; std::mem::swap(&mut self.scope, &mut scope); if scopes.arguments_object_accessed() && scopes.mapped_arguments_object { let parameter_names = bound_names(parameters); for name in parameter_names { scopes .parameter_scope() .access_binding(&name.to_js_string(self.interner), true); } } scopes.reorder_binding_indices(); self.direct_eval = direct_eval_old; ControlFlow::Continue(()) } } struct BindingCollectorVisitor<'interner> { strict: bool, eval: bool, scope: Scope, in_arrow: bool, interner: &'interner Interner, } impl<'ast> VisitorMut<'ast> for BindingCollectorVisitor<'_> { type BreakTy = &'static str; fn visit_this_mut( &mut self, _node: &'ast mut crate::expression::This, ) -> ControlFlow { // NOTE: Arrow functions inherit 'this' from their enclosing scope, so we must escape it. if self.in_arrow { self.scope.escape_this_in_enclosing_function_scope(); } ControlFlow::Continue(()) } fn visit_function_declaration_mut( &mut self, node: &'ast mut FunctionDeclaration, ) -> ControlFlow { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, false, ) } fn visit_generator_declaration_mut( &mut self, node: &'ast mut GeneratorDeclaration, ) -> ControlFlow { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, false, ) } fn visit_async_function_declaration_mut( &mut self, node: &'ast mut AsyncFunctionDeclaration, ) -> ControlFlow { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, false, ) } fn visit_async_generator_declaration_mut( &mut self, node: &'ast mut AsyncGeneratorDeclaration, ) -> ControlFlow { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, false, ) } fn visit_function_expression_mut( &mut self, node: &'ast mut FunctionExpression, ) -> ControlFlow { let name = if node.has_binding_identifier { node.name() } else { None }; let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, name, &mut node.name_scope, strict, false, ) } fn visit_generator_expression_mut( &mut self, node: &'ast mut GeneratorExpression, ) -> ControlFlow { let name = if node.has_binding_identifier { node.name() } else { None }; let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, name, &mut node.name_scope, strict, false, ) } fn visit_async_function_expression_mut( &mut self, node: &'ast mut AsyncFunctionExpression, ) -> ControlFlow { let name = if node.has_binding_identifier { node.name() } else { None }; let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, name, &mut node.name_scope, strict, false, ) } fn visit_async_generator_expression_mut( &mut self, node: &'ast mut AsyncGeneratorExpression, ) -> ControlFlow { let name = if node.has_binding_identifier { node.name() } else { None }; let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, name, &mut node.name_scope, strict, false, ) } fn visit_arrow_function_mut( &mut self, node: &'ast mut ArrowFunction, ) -> ControlFlow { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, true, ) } fn visit_async_arrow_function_mut( &mut self, node: &'ast mut AsyncArrowFunction, ) -> ControlFlow { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, true, ) } fn visit_class_declaration_mut( &mut self, node: &'ast mut ClassDeclaration, ) -> ControlFlow { let mut name_scope = Scope::new(self.scope.clone(), false); let name = node.name().to_js_string(self.interner); name_scope.create_immutable_binding(name, true); std::mem::swap(&mut self.scope, &mut name_scope); if let Some(super_ref) = &mut node.super_ref { self.visit_expression_mut(super_ref)?; } if let Some(constructor) = &mut node.constructor { self.visit_function_expression_mut(constructor)?; } for element in &mut *node.elements { self.visit_class_element_mut(element)?; } std::mem::swap(&mut self.scope, &mut name_scope); node.name_scope = name_scope; ControlFlow::Continue(()) } fn visit_class_expression_mut( &mut self, node: &'ast mut ClassExpression, ) -> ControlFlow { let mut name_scope = None; if let Some(name) = node.name && node.name_scope.is_some() { let mut scope = Scope::new(self.scope.clone(), false); let name = name.to_js_string(self.interner); scope.create_immutable_binding(name, true); node.name_scope = Some(scope.clone()); std::mem::swap(&mut self.scope, &mut scope); name_scope = Some(scope); } if let Some(super_ref) = &mut node.super_ref { self.visit_expression_mut(super_ref)?; } if let Some(constructor) = &mut node.constructor { self.visit_function_expression_mut(constructor)?; } for element in &mut *node.elements { self.visit_class_element_mut(element)?; } if let Some(mut scope) = name_scope { std::mem::swap(&mut self.scope, &mut scope); } ControlFlow::Continue(()) } fn visit_class_element_mut( &mut self, node: &'ast mut ClassElement, ) -> ControlFlow { match node { ClassElement::MethodDefinition(node) => { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, false, ) } ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { self.visit_property_name_mut(&mut field.name)?; let mut scope = Scope::new(self.scope.clone(), true); std::mem::swap(&mut self.scope, &mut scope); if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } std::mem::swap(&mut self.scope, &mut scope); field.scope = scope; ControlFlow::Continue(()) } ClassElement::PrivateFieldDefinition(field) | ClassElement::PrivateStaticFieldDefinition(field) => { let mut scope = Scope::new(self.scope.clone(), true); std::mem::swap(&mut self.scope, &mut scope); if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } std::mem::swap(&mut self.scope, &mut scope); field.scope = scope; ControlFlow::Continue(()) } ClassElement::StaticBlock(node) => { let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut FormalParameterList::default(), &mut node.scopes, None, &mut None, strict, false, ) } } } fn visit_object_method_definition_mut( &mut self, node: &'ast mut ObjectMethodDefinition, ) -> ControlFlow { match &mut node.name { PropertyName::Literal(_) => {} PropertyName::Computed(name) => { self.visit_expression_mut(name)?; } } let strict = node.body.strict(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, None, &mut None, strict, false, ) } fn visit_block_mut(&mut self, node: &'ast mut Block) -> ControlFlow { let mut scope = block_declaration_instantiation(node, self.scope.clone(), self.interner); if let Some(scope) = &mut scope { std::mem::swap(&mut self.scope, scope); } self.visit_statement_list_mut(&mut node.statements)?; if let Some(scope) = &mut scope { std::mem::swap(&mut self.scope, scope); } node.scope = scope; ControlFlow::Continue(()) } fn visit_switch_mut(&mut self, node: &'ast mut Switch) -> ControlFlow { self.visit_expression_mut(&mut node.val)?; let mut scope = block_declaration_instantiation(node, self.scope.clone(), self.interner); if let Some(scope) = &mut scope { std::mem::swap(&mut self.scope, scope); } for case in &mut *node.cases { self.visit_case_mut(case)?; } if let Some(scope) = &mut scope { std::mem::swap(&mut self.scope, scope); } node.scope = scope; ControlFlow::Continue(()) } fn visit_with_mut(&mut self, node: &'ast mut With) -> ControlFlow { self.visit_expression_mut(&mut node.expression)?; let mut scope = Scope::new(self.scope.clone(), false); std::mem::swap(&mut self.scope, &mut scope); self.visit_statement_mut(&mut node.statement)?; std::mem::swap(&mut self.scope, &mut scope); node.scope = scope; ControlFlow::Continue(()) } fn visit_catch_mut(&mut self, node: &'ast mut Catch) -> ControlFlow { let mut scope = Scope::new(self.scope.clone(), false); if let Some(binding) = node.parameter() { match binding { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner); drop(scope.create_mutable_binding(ident.clone(), false)); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { let ident = ident.to_js_string(self.interner); drop(scope.create_mutable_binding(ident, false)); } } } } std::mem::swap(&mut self.scope, &mut scope); if let Some(binding) = &mut node.parameter { self.visit_binding_mut(binding)?; } self.visit_block_mut(&mut node.block)?; std::mem::swap(&mut self.scope, &mut scope); node.scope = scope; ControlFlow::Continue(()) } fn visit_for_loop_mut(&mut self, node: &'ast mut ForLoop) -> ControlFlow { let scope = match &mut node.inner.init { Some(ForLoopInitializer::Lexical(decl)) => { let mut scope = Scope::new(self.scope.clone(), false); let names = bound_names(&decl.declaration); if decl.declaration.is_const() { for name in &names { let name = name.to_js_string(self.interner); scope.create_immutable_binding(name, true); } } else { for name in &names { let name = name.to_js_string(self.interner); drop(scope.create_mutable_binding(name, false)); } } decl.scope = scope.clone(); std::mem::swap(&mut self.scope, &mut scope); Some(scope) } _ => None, }; if let Some(fli) = &mut node.inner.init { self.visit_for_loop_initializer_mut(fli)?; } if let Some(expr) = &mut node.inner.condition { self.visit_expression_mut(expr)?; } if let Some(expr) = &mut node.inner.final_expr { self.visit_expression_mut(expr)?; } self.visit_statement_mut(&mut node.inner.body)?; if let Some(mut scope) = scope { std::mem::swap(&mut self.scope, &mut scope); } ControlFlow::Continue(()) } fn visit_for_in_loop_mut(&mut self, node: &'ast mut ForInLoop) -> ControlFlow { let initializer_bound_names = match node.initializer() { IterableLoopInitializer::Let(declaration) | IterableLoopInitializer::Const(declaration) => bound_names(declaration), _ => Vec::new(), }; if initializer_bound_names.is_empty() { self.visit_expression_mut(&mut node.target)?; } else { let mut scope = Scope::new(self.scope.clone(), false); for name in &initializer_bound_names { let name = name.to_js_string(self.interner); drop(scope.create_mutable_binding(name, false)); } std::mem::swap(&mut self.scope, &mut scope); self.visit_expression_mut(&mut node.target)?; std::mem::swap(&mut self.scope, &mut scope); node.target_scope = Some(scope); } let scope = match node.initializer() { IterableLoopInitializer::Let(declaration) => { let scope = Scope::new(self.scope.clone(), false); match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner); drop(scope.create_mutable_binding(ident.clone(), false)); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { let ident = ident.to_js_string(self.interner); drop(scope.create_mutable_binding(ident, false)); } } } Some(scope) } IterableLoopInitializer::Const(declaration) => { let scope = Scope::new(self.scope.clone(), false); match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner); scope.create_immutable_binding(ident.clone(), true); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { let ident = ident.to_js_string(self.interner); scope.create_immutable_binding(ident, true); } } } Some(scope) } _ => None, }; if let Some(mut scope) = scope { std::mem::swap(&mut self.scope, &mut scope); self.visit_iterable_loop_initializer_mut(&mut node.initializer)?; self.visit_statement_mut(&mut node.body)?; std::mem::swap(&mut self.scope, &mut scope); node.scope = Some(scope); } else { self.visit_iterable_loop_initializer_mut(&mut node.initializer)?; self.visit_statement_mut(&mut node.body)?; } ControlFlow::Continue(()) } fn visit_for_of_loop_mut(&mut self, node: &'ast mut ForOfLoop) -> ControlFlow { let initializer_bound_names = match node.initializer() { IterableLoopInitializer::Let(declaration) | IterableLoopInitializer::Const(declaration) => bound_names(declaration), _ => Vec::new(), }; if initializer_bound_names.is_empty() { self.visit_expression_mut(&mut node.iterable)?; } else { let mut scope = Scope::new(self.scope.clone(), false); for name in &initializer_bound_names { let name = name.to_js_string(self.interner); drop(scope.create_mutable_binding(name, false)); } std::mem::swap(&mut self.scope, &mut scope); self.visit_expression_mut(&mut node.iterable)?; std::mem::swap(&mut self.scope, &mut scope); node.iterable_scope = Some(scope); } let scope = match node.initializer() { IterableLoopInitializer::Let(declaration) => { let scope = Scope::new(self.scope.clone(), false); match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner); drop(scope.create_mutable_binding(ident.clone(), false)); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { let ident = ident.to_js_string(self.interner); drop(scope.create_mutable_binding(ident, false)); } } } Some(scope) } IterableLoopInitializer::Const(declaration) => { let scope = Scope::new(self.scope.clone(), false); match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner); scope.create_immutable_binding(ident.clone(), true); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { let ident = ident.to_js_string(self.interner); scope.create_immutable_binding(ident, true); } } } Some(scope) } _ => None, }; if let Some(mut scope) = scope { std::mem::swap(&mut self.scope, &mut scope); self.visit_iterable_loop_initializer_mut(&mut node.init)?; self.visit_statement_mut(&mut node.body)?; std::mem::swap(&mut self.scope, &mut scope); node.scope = Some(scope); } else { self.visit_iterable_loop_initializer_mut(&mut node.init)?; self.visit_statement_mut(&mut node.body)?; } ControlFlow::Continue(()) } fn visit_module_mut(&mut self, node: &'ast mut Module) -> ControlFlow { let mut scope = Scope::new(self.scope.clone(), true); module_instantiation(node, &scope, self.interner); std::mem::swap(&mut self.scope, &mut scope); self.visit_module_item_list_mut(&mut node.items)?; std::mem::swap(&mut self.scope, &mut scope); node.scope = scope; ControlFlow::Continue(()) } fn visit_script_mut(&mut self, node: &'ast mut Script) -> ControlFlow { if self.eval { self.visit_statement_list_mut(node.statements_mut())?; } else { match global_declaration_instantiation(node, &self.scope, self.interner) { Ok(()) => { self.visit_statement_list_mut(node.statements_mut())?; } Err(e) => return ControlFlow::Break(e), } } ControlFlow::Continue(()) } } impl BindingCollectorVisitor<'_> { #[allow(clippy::too_many_arguments)] fn visit_function_like( &mut self, body: &mut FunctionBody, parameters: &mut FormalParameterList, scopes: &mut FunctionScopes, name: Option, name_scope: &mut Option, strict: bool, arrow: bool, ) -> ControlFlow<&'static str> { let strict = self.strict || strict; let old_in_arrow = self.in_arrow; self.in_arrow = arrow; let function_scope = if let Some(name) = name { let scope = Scope::new(self.scope.clone(), false); let name = name.to_js_string(self.interner); scope.create_immutable_binding(name, strict); *name_scope = Some(scope.clone()); Scope::new(scope, true) } else { Scope::new(self.scope.clone(), true) }; let function_scopes = function_declaration_instantiation( body, parameters, arrow, strict, function_scope.clone(), self.interner, ); let mut params_scope = function_scopes.parameter_scope(); let mut body_scope = function_scopes.body_scope(); std::mem::swap(&mut self.scope, &mut params_scope); self.visit_formal_parameter_list_mut(parameters)?; std::mem::swap(&mut self.scope, &mut params_scope); std::mem::swap(&mut self.scope, &mut body_scope); self.visit_function_body_mut(body)?; std::mem::swap(&mut self.scope, &mut body_scope); *scopes = function_scopes; self.in_arrow = old_in_arrow; ControlFlow::Continue(()) } } /// Optimize scope indices when scopes only contain local bindings. pub(crate) fn optimize_scope_indices<'a, N>(node: &'a mut N, scope: &Scope) where &'a mut N: Into>, { let mut visitor = ScopeIndexVisitor { index: scope.scope_index(), }; let _ = visitor.visit(node.into()); } /// Like [`optimize_scope_indices`] but for a [`FunctionExpression`] compiled /// by the `Function` constructor with `force_function_scope = true`. /// /// The `Function` constructor always pushes a function scope at runtime, so /// the optimizer must account for that even when the function scope would /// otherwise be elided. pub(crate) fn optimize_scope_indices_function_constructor( node: &mut FunctionExpression, scope: &Scope, ) { let mut visitor = ScopeIndexVisitor { index: scope.scope_index(), }; let _ = visitor.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut node.name_scope, false, // Always force the function scope for the Function constructor. true, ); } struct ScopeIndexVisitor { index: u32, } impl<'ast> VisitorMut<'ast> for ScopeIndexVisitor { type BreakTy = (); fn visit_function_declaration_mut( &mut self, node: &'ast mut FunctionDeclaration, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, false, contains_direct_eval, ) } fn visit_generator_declaration_mut( &mut self, node: &'ast mut GeneratorDeclaration, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, false, contains_direct_eval, ) } fn visit_async_function_declaration_mut( &mut self, node: &'ast mut AsyncFunctionDeclaration, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, false, contains_direct_eval, ) } fn visit_async_generator_declaration_mut( &mut self, node: &'ast mut AsyncGeneratorDeclaration, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, false, contains_direct_eval, ) } fn visit_function_expression_mut( &mut self, node: &'ast mut FunctionExpression, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut node.name_scope, false, contains_direct_eval, ) } fn visit_generator_expression_mut( &mut self, node: &'ast mut GeneratorExpression, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut node.name_scope, false, contains_direct_eval, ) } fn visit_async_function_expression_mut( &mut self, node: &'ast mut AsyncFunctionExpression, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut node.name_scope, false, contains_direct_eval, ) } fn visit_async_generator_expression_mut( &mut self, node: &'ast mut AsyncGeneratorExpression, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut node.name_scope, false, contains_direct_eval, ) } fn visit_arrow_function_mut( &mut self, node: &'ast mut ArrowFunction, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, true, contains_direct_eval, ) } fn visit_async_arrow_function_mut( &mut self, node: &'ast mut AsyncArrowFunction, ) -> ControlFlow { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, true, contains_direct_eval, ) } fn visit_class_declaration_mut( &mut self, node: &'ast mut ClassDeclaration, ) -> ControlFlow { let index = self.index; if !node.name_scope.all_bindings_local() { self.index += 1; } node.name_scope.set_index(self.index); if let Some(super_ref) = &mut node.super_ref { self.visit_expression_mut(super_ref)?; } if let Some(constructor) = &mut node.constructor { let node = constructor; self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut node.name_scope, false, true, )?; } for element in &mut *node.elements { self.visit_class_element_mut(element)?; } self.index = index; ControlFlow::Continue(()) } fn visit_class_expression_mut( &mut self, node: &'ast mut ClassExpression, ) -> ControlFlow { let index = self.index; if let Some(scope) = &node.name_scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } if let Some(super_ref) = &mut node.super_ref { self.visit_expression_mut(super_ref)?; } if let Some(constructor) = &mut node.constructor { self.visit_function_like( &mut constructor.body, &mut constructor.parameters, &mut constructor.scopes, &mut constructor.name_scope, false, true, )?; } for element in &mut *node.elements { self.visit_class_element_mut(element)?; } self.index = index; ControlFlow::Continue(()) } fn visit_class_element_mut( &mut self, node: &'ast mut ClassElement, ) -> ControlFlow { match node { ClassElement::MethodDefinition(node) => { let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, false, contains_direct_eval, ) } ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { self.visit_property_name_mut(&mut field.name)?; let index = self.index; self.index += 1; field.scope.set_index(self.index); if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } self.index = index; ControlFlow::Continue(()) } ClassElement::PrivateFieldDefinition(field) | ClassElement::PrivateStaticFieldDefinition(field) => { let index = self.index; self.index += 1; field.scope.set_index(self.index); if let Some(e) = &mut field.initializer { self.visit_expression_mut(e)?; } self.index = index; ControlFlow::Continue(()) } ClassElement::StaticBlock(node) => self.visit_function_like( &mut node.body, &mut FormalParameterList::default(), &mut node.scopes, &mut None, false, true, ), } } fn visit_object_method_definition_mut( &mut self, node: &'ast mut ObjectMethodDefinition, ) -> ControlFlow { match &mut node.name { PropertyName::Literal(_) => {} PropertyName::Computed(name) => { self.visit_expression_mut(name)?; } } let contains_direct_eval = node.contains_direct_eval(); self.visit_function_like( &mut node.body, &mut node.parameters, &mut node.scopes, &mut None, false, contains_direct_eval, ) } fn visit_block_mut(&mut self, node: &'ast mut Block) -> ControlFlow { let index = self.index; if let Some(scope) = &node.scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } self.visit_statement_list_mut(&mut node.statements)?; self.index = index; ControlFlow::Continue(()) } fn visit_switch_mut(&mut self, node: &'ast mut Switch) -> ControlFlow { let index = self.index; self.visit_expression_mut(&mut node.val)?; if let Some(scope) = &node.scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } for case in &mut *node.cases { self.visit_case_mut(case)?; } self.index = index; ControlFlow::Continue(()) } fn visit_with_mut(&mut self, node: &'ast mut With) -> ControlFlow { let index = self.index; self.visit_expression_mut(&mut node.expression)?; self.index += 1; node.scope.set_index(self.index); self.visit_statement_mut(&mut node.statement)?; self.index = index; ControlFlow::Continue(()) } fn visit_catch_mut(&mut self, node: &'ast mut Catch) -> ControlFlow { let index = self.index; if !node.scope.all_bindings_local() { self.index += 1; } node.scope.set_index(self.index); if let Some(binding) = &mut node.parameter { self.visit_binding_mut(binding)?; } self.visit_block_mut(&mut node.block)?; self.index = index; ControlFlow::Continue(()) } fn visit_for_loop_mut(&mut self, node: &'ast mut ForLoop) -> ControlFlow { let index = self.index; if let Some(ForLoopInitializer::Lexical(decl)) = &mut node.inner.init { if !decl.scope.all_bindings_local() { self.index += 1; } decl.scope.set_index(self.index); } if let Some(fli) = &mut node.inner.init { self.visit_for_loop_initializer_mut(fli)?; } if let Some(expr) = &mut node.inner.condition { self.visit_expression_mut(expr)?; } if let Some(expr) = &mut node.inner.final_expr { self.visit_expression_mut(expr)?; } self.visit_statement_mut(&mut node.inner.body)?; self.index = index; ControlFlow::Continue(()) } fn visit_for_in_loop_mut(&mut self, node: &'ast mut ForInLoop) -> ControlFlow { { let index = self.index; if let Some(scope) = &node.target_scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } self.visit_expression_mut(&mut node.target)?; self.index = index; } let index = self.index; if let Some(scope) = &node.scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } self.visit_iterable_loop_initializer_mut(&mut node.initializer)?; self.visit_statement_mut(&mut node.body)?; self.index = index; ControlFlow::Continue(()) } fn visit_for_of_loop_mut(&mut self, node: &'ast mut ForOfLoop) -> ControlFlow { { let index = self.index; if let Some(scope) = &node.iterable_scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } self.visit_expression_mut(&mut node.iterable)?; self.index = index; } let index = self.index; if let Some(scope) = &node.scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } self.visit_iterable_loop_initializer_mut(&mut node.init)?; self.visit_statement_mut(&mut node.body)?; self.index = index; ControlFlow::Continue(()) } } impl ScopeIndexVisitor { fn visit_function_like( &mut self, body: &mut FunctionBody, parameters: &mut FormalParameterList, scopes: &mut FunctionScopes, name_scope: &mut Option, arrow: bool, force_function_scope: bool, ) -> ControlFlow<()> { let index = self.index; if let Some(scope) = name_scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } if force_function_scope || !scopes.function_scope().all_bindings_local() { scopes.requires_function_scope = true; self.index += 1; } else if !arrow { assert!(scopes.function_scope().is_function()); scopes.requires_function_scope = scopes.function_scope().escaped_this() || contains(parameters, ContainsSymbol::Super) || contains(body, ContainsSymbol::Super) || contains(parameters, ContainsSymbol::NewTarget) || contains(body, ContainsSymbol::NewTarget); self.index += u32::from(scopes.requires_function_scope); } scopes.function_scope.set_index(self.index); if let Some(scope) = &scopes.parameters_eval_scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } self.visit_formal_parameter_list_mut(parameters)?; if let Some(scope) = &scopes.parameters_scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } if let Some(scope) = &scopes.lexical_scope { if !scope.all_bindings_local() { self.index += 1; } scope.set_index(self.index); } self.visit_function_body_mut(body)?; self.index = index; ControlFlow::Continue(()) } } /// `GlobalDeclarationInstantiation ( script, env )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation /// /// # Errors /// /// - If a duplicate lexical declaration is found. fn global_declaration_instantiation( script: &Script, env: &Scope, interner: &Interner, ) -> Result<(), &'static str> { // 1. Let lexNames be the LexicallyDeclaredNames of script. let lex_names = lexically_declared_names(script); // 2. Let varNames be the VarDeclaredNames of script. let var_names = var_declared_names(script); // 3. For each element name of lexNames, do for name in lex_names { let name = name.to_js_string(interner); // Note: Our implementation differs from the spec here. // a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. // b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. if env.has_binding(&name) { return Err("duplicate lexical declaration"); } } // 4. For each element name of varNames, do for name in var_names { let name = name.to_js_string(interner); // a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. if env.has_lex_binding(&name) { return Err("duplicate lexical declaration"); } } // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. // 14. Let privateEnv be null. // 15. For each element d of lexDeclarations, do for statement in &**script.statements() { // a. NOTE: Lexically declared names are only instantiated here but not initialized. // b. For each element dn of the BoundNames of d, do // i. If IsConstantDeclaration of d is true, then // 1. Perform ? env.CreateImmutableBinding(dn, true). // ii. Else, // 1. Perform ? env.CreateMutableBinding(dn, false). if let StatementListItem::Declaration(declaration) = statement { match declaration.as_ref() { Declaration::ClassDeclaration(class) => { for name in bound_names(class.as_ref()) { let name = name.to_js_string(interner); drop(env.create_mutable_binding(name, false)); } } Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); drop(env.create_mutable_binding(name, false)); } } Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); env.create_immutable_binding(name, true); } } _ => {} } } } Ok(()) } /// `BlockDeclarationInstantiation ( code, env )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-blockdeclarationinstantiation fn block_declaration_instantiation<'a, N>( block: &'a N, scope: Scope, interner: &Interner, ) -> Option where &'a N: Into>, { let scope = Scope::new(scope, false); // 1. Let declarations be the LexicallyScopedDeclarations of code. let declarations = lexically_scoped_declarations(block); // 2. Let privateEnv be the running execution context's PrivateEnvironment. // Note: Private environments are currently handled differently. // 3. For each element d of declarations, do for d in &declarations { // i. If IsConstantDeclaration of d is true, then if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(d)) = d { // a. For each element dn of the BoundNames of d, do for dn in bound_names::<'_, VariableList>(d) { // 1. Perform ! env.CreateImmutableBinding(dn, true). let dn = dn.to_js_string(interner); scope.create_immutable_binding(dn, true); } } // ii. Else, else { // a. For each element dn of the BoundNames of d, do for dn in d.bound_names() { let dn = dn.to_js_string(interner); #[cfg(not(feature = "annex-b"))] // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. drop(scope.create_mutable_binding(dn, false)); #[cfg(feature = "annex-b")] // 1. If ! env.HasBinding(dn) is false, then if !scope.has_binding(&dn) { // a. Perform ! env.CreateMutableBinding(dn, false). drop(scope.create_mutable_binding(dn, false)); } } } } if scope.num_bindings() > 0 { Some(scope) } else { None } } /// `FunctionDeclarationInstantiation ( func, argumentsList )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation fn function_declaration_instantiation( body: &FunctionBody, formals: &FormalParameterList, arrow: bool, strict: bool, function_scope: Scope, interner: &Interner, ) -> FunctionScopes { let mut scopes = FunctionScopes { function_scope, parameters_eval_scope: None, parameters_scope: None, lexical_scope: None, mapped_arguments_object: false, requires_function_scope: false, }; // 1. Let calleeContext be the running execution context. // 2. Let code be func.[[ECMAScriptCode]]. // 3. Let strict be func.[[Strict]]. // 4. Let formals be func.[[FormalParameters]]. // 5. Let parameterNames be the BoundNames of formals. let mut parameter_names = bound_names(formals); // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. // 7. Let simpleParameterList be IsSimpleParameterList of formals. // 8. Let hasParameterExpressions be ContainsExpression of formals. let has_parameter_expressions = formals.has_expressions(); // 9. Let varNames be the VarDeclaredNames of code. let var_names = var_declared_names(body); // 10. Let varDeclarations be the VarScopedDeclarations of code. let var_declarations = var_scoped_declarations(body); // 11. Let lexicalNames be the LexicallyDeclaredNames of code. let lexical_names = lexically_declared_names(body); // 12. Let functionNames be a new empty List. let mut function_names = Vec::new(); // 13. Let functionsToInitialize be a new empty List. // let mut functions_to_initialize = Vec::new(); // 14. For each element d of varDeclarations, in reverse List order, do for declaration in var_declarations.iter().rev() { // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then // a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. // a.ii. Let fn be the sole element of the BoundNames of d. let name = match declaration { VarScopedDeclaration::FunctionDeclaration(f) => f.name(), VarScopedDeclaration::GeneratorDeclaration(f) => f.name(), VarScopedDeclaration::AsyncFunctionDeclaration(f) => f.name(), VarScopedDeclaration::AsyncGeneratorDeclaration(f) => f.name(), VarScopedDeclaration::VariableDeclaration(_) => continue, }; // a.iii. If functionNames does not contain fn, then if !function_names.contains(&name.sym()) { // 1. Insert fn as the first element of functionNames. function_names.push(name.sym()); } } function_names.reverse(); // 15. Let argumentsObjectNeeded be true. let mut arguments_object_needed = true; let arguments = Sym::ARGUMENTS; // 16. If func.[[ThisMode]] is lexical, then // 17. Else if parameterNames contains "arguments", then if arrow || parameter_names.contains(&arguments) { // 16.a. NOTE: Arrow functions never have an arguments object. // 16.b. Set argumentsObjectNeeded to false. // 17.a. Set argumentsObjectNeeded to false. arguments_object_needed = false; } // 18. Else if hasParameterExpressions is false, then else if !has_parameter_expressions { //a. If functionNames contains "arguments" or lexicalNames contains "arguments", then if function_names.contains(&arguments) || lexical_names.contains(&arguments) { // i. Set argumentsObjectNeeded to false. arguments_object_needed = false; } } // 19. If strict is true or hasParameterExpressions is false, then let env = if strict || !has_parameter_expressions { // a. NOTE: Only a single Environment Record is needed for the parameters, // since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval. // b. Let env be the LexicalEnvironment of calleeContext. scopes.function_scope.clone() } // 20. Else, else { // a. NOTE: A separate Environment Record is needed to ensure that bindings created by // direct eval calls in the formal parameter list are outside the environment where parameters are declared. // b. Let calleeEnv be the LexicalEnvironment of calleeContext. // c. Let env be NewDeclarativeEnvironment(calleeEnv). // d. Assert: The VariableEnvironment of calleeContext is calleeEnv. // e. Set the LexicalEnvironment of calleeContext to env. let scope = Scope::new(scopes.function_scope.clone(), false); scopes.parameters_eval_scope = Some(scope.clone()); scope }; // 22. If argumentsObjectNeeded is true, then // // NOTE(HalidOdat): Has been moved up, so "arguments" gets registered as // the first binding in the environment with index 0. if arguments_object_needed { let arguments = arguments.to_js_string(interner); // c. If strict is true, then if strict { // i. Perform ! env.CreateImmutableBinding("arguments", false). // ii. NOTE: In strict mode code early errors prevent attempting to assign // to this binding, so its mutability is not observable. env.create_immutable_binding(arguments.clone(), false); } // d. Else, else { // i. Perform ! env.CreateMutableBinding("arguments", false). drop(env.create_mutable_binding(arguments.clone(), false)); } } // 21. For each String paramName of parameterNames, do for param_name in ¶meter_names { let param_name = param_name.to_js_string(interner); // a. Let alreadyDeclared be ! env.HasBinding(paramName). let already_declared = env.has_binding(¶m_name); // b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict // functions that do not have parameter default values or rest parameters. // c. If alreadyDeclared is false, then if !already_declared { // i. Perform ! env.CreateMutableBinding(paramName, false). drop(env.create_mutable_binding(param_name.clone(), false)); // Note: In this case the function contains a mapped arguments object. // Because we do not track (yet) if the mapped arguments object escapes the function, // we have to assume that the binding might escape trough the arguments object. if arguments_object_needed && !strict && formals.is_simple() { scopes.mapped_arguments_object = true; } // Note: These steps are not necessary in our implementation. // ii. If hasDuplicates is true, then // 1. Perform ! env.InitializeBinding(paramName, undefined). } } // 22. If argumentsObjectNeeded is true, then if arguments_object_needed { // MOVED: a-e. // // NOTE(HalidOdat): Has been moved up, see comment above. // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ». parameter_names.push(arguments); } // 23. Else, // a. Let parameterBindings be parameterNames. let parameter_bindings = parameter_names.clone(); // 27. If hasParameterExpressions is false, then // 28. Else, #[allow(unused_variables, unused_mut)] let (mut instantiated_var_names, mut var_env) = if has_parameter_expressions { // a. NOTE: A separate Environment Record is needed to ensure that closures created by // expressions in the formal parameter list do not have // visibility of declarations in the function body. // b. Let varEnv be NewDeclarativeEnvironment(env). let var_env = Scope::new(env.clone(), false); scopes.parameters_scope = Some(var_env.clone()); // c. Set the VariableEnvironment of calleeContext to varEnv. // d. Let instantiatedVarNames be a new empty List. let mut instantiated_var_names = Vec::new(); // e. For each element n of varNames, do for n in var_names { // i. If instantiatedVarNames does not contain n, then if !instantiated_var_names.contains(&n) { // 1. Append n to instantiatedVarNames. instantiated_var_names.push(n); let n_string = n.to_js_string(interner); // 2. Perform ! varEnv.CreateMutableBinding(n, false). drop(var_env.create_mutable_binding(n_string, false)); } } (instantiated_var_names, var_env) } else { // a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars. // b. Let instantiatedVarNames be a copy of the List parameterBindings. let mut instantiated_var_names = parameter_bindings; // c. For each element n of varNames, do for n in var_names { // i. If instantiatedVarNames does not contain n, then if !instantiated_var_names.contains(&n) { // 1. Append n to instantiatedVarNames. instantiated_var_names.push(n); let n = n.to_js_string(interner); // 2. Perform ! env.CreateMutableBinding(n, false). // 3. Perform ! env.InitializeBinding(n, undefined). drop(env.create_mutable_binding(n, true)); } } // d. Let varEnv be env. (instantiated_var_names, env) }; // 29. NOTE: Annex B.3.2.1 adds additional steps at this point. // 29. If strict is false, then #[cfg(feature = "annex-b")] if !strict { // a. For each FunctionDeclaration f that is directly contained in the StatementList // of a Block, CaseClause, or DefaultClause, do for f in annex_b_function_declarations_names(body) { // i. Let F be StringValue of the BindingIdentifier of f. // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F // as a BindingIdentifier would not produce any Early Errors // for func and parameterNames does not contain F, then if !lexical_names.contains(&f) && !parameter_names.contains(&f) { // 1. NOTE: A var binding for F is only instantiated here if it is neither a // VarDeclaredName, the name of a formal parameter, or another FunctionDeclaration. // 2. If initializedBindings does not contain F and F is not "arguments", then if !instantiated_var_names.contains(&f) && f != arguments { let f_string = f.to_js_string(interner); // a. Perform ! varEnv.CreateMutableBinding(F, false). // b. Perform ! varEnv.InitializeBinding(F, undefined). drop(var_env.create_mutable_binding(f_string, false)); // c. Append F to instantiatedVarNames. instantiated_var_names.push(f); } } } } // 30. If strict is false, then // 31. Else, let lex_env = if strict { // a. Let lexEnv be varEnv. var_env } else { // a. Let lexEnv be NewDeclarativeEnvironment(varEnv). // b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical // declarations so that a direct eval can determine whether any var scoped declarations // introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. // This is not needed for strict functions because a strict direct eval always // places all declarations into a new Environment Record. let lex_env = Scope::new(var_env, false); scopes.lexical_scope = Some(lex_env.clone()); lex_env }; // 32. Set the LexicalEnvironment of calleeContext to lexEnv. // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 34. For each element d of lexDeclarations, do // a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, // formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized. // b. For each element dn of the BoundNames of d, do // i. If IsConstantDeclaration of d is true, then // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). // ii. Else, // 1. Perform ! lexEnv.CreateMutableBinding(dn, false). for statement in body.statements() { if let StatementListItem::Declaration(declaration) = statement { match declaration.as_ref() { Declaration::ClassDeclaration(class) => { for name in bound_names(class.as_ref()) { let name = name.to_js_string(interner); drop(lex_env.create_mutable_binding(name, false)); } } Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); drop(lex_env.create_mutable_binding(name, false)); } } Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); lex_env.create_immutable_binding(name, true); } } _ => {} } } } // 35. Let privateEnv be the PrivateEnvironment of calleeContext. // 36. For each Parse Node f of functionsToInitialize, do if let Some(lexical_scope) = &scopes.lexical_scope && lexical_scope.num_bindings() == 0 { scopes.lexical_scope = None; } // 37. Return unused. scopes } /// Abstract operation [`InitializeEnvironment ( )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment fn module_instantiation(module: &Module, env: &Scope, interner: &Interner) { for entry in module.items().import_entries() { let local_name = entry.local_name().to_js_string(interner); env.create_immutable_binding(local_name, true); } let var_declarations = var_scoped_declarations(module); let mut declared_var_names = Vec::new(); for var in var_declarations { for name in var.bound_names() { let name = name.to_js_string(interner); if !declared_var_names.contains(&name) { drop(env.create_mutable_binding(name.clone(), false)); declared_var_names.push(name); } } } let lex_declarations = lexically_scoped_declarations(module); for declaration in lex_declarations { match declaration { LexicallyScopedDeclaration::FunctionDeclaration(f) => { let name = bound_names(f)[0].to_js_string(interner); drop(env.create_mutable_binding(name, false)); } LexicallyScopedDeclaration::GeneratorDeclaration(g) => { let name = bound_names(g)[0].to_js_string(interner); drop(env.create_mutable_binding(name, false)); } LexicallyScopedDeclaration::AsyncFunctionDeclaration(af) => { let name = bound_names(af)[0].to_js_string(interner); drop(env.create_mutable_binding(name, false)); } LexicallyScopedDeclaration::AsyncGeneratorDeclaration(ag) => { let name = bound_names(ag)[0].to_js_string(interner); drop(env.create_mutable_binding(name, false)); } LexicallyScopedDeclaration::ClassDeclaration(class) => { for name in bound_names(class) { let name = name.to_js_string(interner); drop(env.create_mutable_binding(name, false)); } } LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(c)) => { for name in bound_names(c) { let name = name.to_js_string(interner); env.create_immutable_binding(name, true); } } LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Let(l)) => { for name in bound_names(l) { let name = name.to_js_string(interner); drop(env.create_mutable_binding(name, false)); } } LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Using(u)) => { for name in bound_names(u) { let name = name.to_js_string(interner); drop(env.create_mutable_binding(name, false)); } } LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::AwaitUsing(au)) => { for name in bound_names(au) { let name = name.to_js_string(interner); drop(env.create_mutable_binding(name, false)); } } LexicallyScopedDeclaration::AssignmentExpression(expr) => { for name in bound_names(expr) { let name = name.to_js_string(interner); drop(env.create_mutable_binding(name, false)); } } } } } /// This struct isused to store bindings created during the declaration of an eval ast node. #[derive(Debug, Default)] pub struct EvalDeclarationBindings { /// New annexb function names created during the declaration of an eval ast node. pub new_annex_b_function_names: Vec, /// New function names created during the declaration of an eval ast node. pub new_function_names: FxHashMap, /// New variable names created during the declaration of an eval ast node. pub new_var_names: Vec, } /// `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation /// /// # Errors /// /// * Returns a syntax error if a duplicate lexical declaration is found. /// * Returns a syntax error if a variable declaration in an eval function already exists as a lexical variable. #[allow(clippy::missing_panics_doc)] pub(crate) fn eval_declaration_instantiation_scope( body: &Script, strict: bool, var_env: &Scope, lex_env: &Scope, #[allow(unused_variables)] annex_b_function_names: &[Sym], interner: &Interner, ) -> Result { let mut result = EvalDeclarationBindings::default(); // 2. Let varDeclarations be the VarScopedDeclarations of body. let var_declarations = var_scoped_declarations(body); // 3. If strict is false, then if !strict { // 1. Let varNames be the VarDeclaredNames of body. let var_names = var_declared_names(body); // a. If varEnv is a Global Environment Record, then if var_env.is_global() { // i. For each element name of varNames, do for name in &var_names { let name = name.to_js_string(interner); // 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. // 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration. if var_env.has_lex_binding(&name) { return Err(format!( "duplicate lexical declaration {}", name.to_std_string_escaped() )); } } } // b. Let thisEnv be lexEnv. let mut this_env = lex_env; // c. Assert: The following loop will terminate. // d. Repeat, while thisEnv is not varEnv, while this_env.scope_index() != var_env.scope_index() { // i. If thisEnv is not an Object Environment Record, then // 1. NOTE: The environment of with statements cannot contain any lexical // declaration so it doesn't need to be checked for var/let hoisting conflicts. // 2. For each element name of varNames, do for name in &var_names { let name = interner.resolve_expect(*name).utf16().into(); // a. If ! thisEnv.HasBinding(name) is true, then if this_env.has_binding(&name) { // i. Throw a SyntaxError exception. // ii. NOTE: Annex B.3.4 defines alternate semantics for the above step. return Err(format!( "variable declaration {} in eval function already exists as a lexical variable", name.to_std_string_escaped() )); } // b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration. } // ii. Set thisEnv to thisEnv.[[OuterEnv]]. if let Some(outer) = this_env.outer() { this_env = outer; } else { break; } } } // NOTE: These steps depend on the current environment state are done before bytecode compilation, // in `eval_declaration_instantiation_context`. // // SKIP: 4. Let privateIdentifiers be a new empty List. // SKIP: 5. Let pointer be privateEnv. // SKIP: 6. Repeat, while pointer is not null, // a. For each Private Name binding of pointer.[[Names]], do // i. If privateIdentifiers does not contain binding.[[Description]], // append binding.[[Description]] to privateIdentifiers. // b. Set pointer to pointer.[[OuterPrivateEnvironment]]. // SKIP: 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception. // 8. Let functionsToInitialize be a new empty List. let mut functions_to_initialize = Vec::new(); // 9. Let declaredFunctionNames be a new empty List. let mut declared_function_names = Vec::new(); // 10. For each element d of varDeclarations, in reverse List order, do for declaration in var_declarations.iter().rev() { // a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then // a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. // a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. // a.iii. Let fn be the sole element of the BoundNames of d. let name = match &declaration { VarScopedDeclaration::FunctionDeclaration(f) => f.name(), VarScopedDeclaration::GeneratorDeclaration(f) => f.name(), VarScopedDeclaration::AsyncFunctionDeclaration(f) => f.name(), VarScopedDeclaration::AsyncGeneratorDeclaration(f) => f.name(), VarScopedDeclaration::VariableDeclaration(_) => continue, }; // a.iv. If declaredFunctionNames does not contain fn, then if !declared_function_names.contains(&name.sym()) { // 1. If varEnv is a Global Environment Record, then // 2. Append fn to declaredFunctionNames. declared_function_names.push(name.sym()); // 3. Insert d as the first element of functionsToInitialize. functions_to_initialize.push(declaration.clone()); } } functions_to_initialize.reverse(); // 11. NOTE: Annex B.3.2.3 adds additional steps at this point. // 11. If strict is false, then #[cfg(feature = "annex-b")] if !strict { // NOTE: This diviates from the specification, we split the first part of defining the annex-b names // in `eval_declaration_instantiation_context`, because it depends on the context. if !var_env.is_global() { for name in annex_b_function_names { let f = name.to_js_string(interner); // i. Let bindingExists be ! varEnv.HasBinding(F). // ii. If bindingExists is false, then if !var_env.has_binding(&f) { // i. Perform ! varEnv.CreateMutableBinding(F, true). // ii. Perform ! varEnv.InitializeBinding(F, undefined). let binding = var_env.create_mutable_binding(f, true); result .new_annex_b_function_names .push(IdentifierReference::new( binding, !var_env.is_function(), true, )); } } } } // 12. Let declaredVarNames be a new empty List. let mut declared_var_names = Vec::new(); // 13. For each element d of varDeclarations, do for declaration in var_declarations { // a. If d is either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then let VarScopedDeclaration::VariableDeclaration(declaration) = declaration else { continue; }; // a.i. For each String vn of the BoundNames of d, do for name in bound_names(&declaration) { // 1. If declaredFunctionNames does not contain vn, then if !declared_function_names.contains(&name) { // a. If varEnv is a Global Environment Record, then // b. If declaredVarNames does not contain vn, then if !declared_var_names.contains(&name) { // i. Append vn to declaredVarNames. declared_var_names.push(name); } } } } // 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a // Global Environment Record and the global object is a Proxy exotic object. // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. // 16. For each element d of lexDeclarations, do for statement in &**body.statements() { // a. NOTE: Lexically declared names are only instantiated here but not initialized. // b. For each element dn of the BoundNames of d, do // i. If IsConstantDeclaration of d is true, then // 1. Perform ? lexEnv.CreateImmutableBinding(dn, true). // ii. Else, // 1. Perform ? lexEnv.CreateMutableBinding(dn, false). if let StatementListItem::Declaration(declaration) = statement { match declaration.as_ref() { Declaration::ClassDeclaration(class) => { for name in bound_names(class.as_ref()) { let name = name.to_js_string(interner); drop(lex_env.create_mutable_binding(name, false)); } } Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); drop(lex_env.create_mutable_binding(name, false)); } } Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); lex_env.create_immutable_binding(name, true); } } _ => {} } } } // 17. For each Parse Node f of functionsToInitialize, do for function in functions_to_initialize { // a. Let fn be the sole element of the BoundNames of f. let name = match &function { VarScopedDeclaration::FunctionDeclaration(f) => f.name(), VarScopedDeclaration::GeneratorDeclaration(f) => f.name(), VarScopedDeclaration::AsyncFunctionDeclaration(f) => f.name(), VarScopedDeclaration::AsyncGeneratorDeclaration(f) => f.name(), VarScopedDeclaration::VariableDeclaration(_) => { continue; } }; // c. If varEnv is a Global Environment Record, then // d. Else, if !var_env.is_global() { // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. let n = name.to_js_string(interner); // i. Let bindingExists be ! varEnv.HasBinding(fn). let binding_exists = var_env.has_binding(&n); // ii. If bindingExists is false, then // iii. Else, if binding_exists { // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). let binding = var_env.set_mutable_binding(n).expect("must not fail"); result.new_function_names.insert( name, ( IdentifierReference::new(binding.locator(), !var_env.is_function(), true), true, ), ); } else { // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. // 2. Perform ! varEnv.CreateMutableBinding(fn, true). // 3. Perform ! varEnv.InitializeBinding(fn, fo). let binding = var_env.create_mutable_binding(n, !strict); result.new_function_names.insert( name, ( IdentifierReference::new(binding, !var_env.is_function(), true), false, ), ); } } } // 18. For each String vn of declaredVarNames, do for name in declared_var_names { // a. If varEnv is a Global Environment Record, then // b. Else, if !var_env.is_global() { let name = name.to_js_string(interner); // i. Let bindingExists be ! varEnv.HasBinding(vn). let binding_exists = var_env.has_binding(&name); // ii. If bindingExists is false, then if !binding_exists { // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. // 2. Perform ! varEnv.CreateMutableBinding(vn, true). // 3. Perform ! varEnv.InitializeBinding(vn, undefined). let binding = var_env.create_mutable_binding(name, true); result.new_var_names.push(IdentifierReference::new( binding, !var_env.is_function(), true, )); } } } // 19. Return unused. Ok(result) } ================================================ FILE: core/ast/src/source.rs ================================================ use std::ops::ControlFlow; use boa_interner::{Interner, Sym, ToIndentedString}; use crate::{ ModuleItemList, StatementList, scope::Scope, scope_analyzer::{ EvalDeclarationBindings, analyze_binding_escapes, collect_bindings, eval_declaration_instantiation_scope, optimize_scope_indices, }, visitor::{VisitWith, Visitor, VisitorMut}, }; /// A Script source. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-scripts #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default)] pub struct Script { statements: StatementList, } impl Script { /// Creates a new `ScriptNode`. #[must_use] pub const fn new(statements: StatementList) -> Self { Self { statements } } /// Gets the list of statements of this `ScriptNode`. #[must_use] pub const fn statements(&self) -> &StatementList { &self.statements } /// Gets a mutable reference to the list of statements of this `ScriptNode`. pub fn statements_mut(&mut self) -> &mut StatementList { &mut self.statements } /// Gets the strict mode. #[inline] #[must_use] pub const fn strict(&self) -> bool { self.statements.strict() } /// Analyze the scope of the script. /// /// # Errors /// Any scope or binding errors that happened during the analysis. pub fn analyze_scope( &mut self, scope: &Scope, interner: &Interner, ) -> Result<(), &'static str> { collect_bindings(self, self.strict(), false, scope, interner)?; analyze_binding_escapes(self, false, scope.clone(), interner)?; optimize_scope_indices(self, scope); Ok(()) } /// Analyze the scope of the script in eval mode. /// /// # Errors /// /// Returns an error if the scope analysis fails with a syntax error. pub fn analyze_scope_eval( &mut self, strict: bool, variable_scope: &Scope, lexical_scope: &Scope, annex_b_function_names: &[Sym], interner: &Interner, ) -> Result { let bindings = eval_declaration_instantiation_scope( self, strict, variable_scope, lexical_scope, annex_b_function_names, interner, )?; if let Err(reason) = collect_bindings(self, strict, true, lexical_scope, interner) { return Err(format!("Failed to analyze scope: {reason}")); } if let Err(reason) = analyze_binding_escapes(self, true, lexical_scope.clone(), interner) { return Err(format!("Failed to analyze scope: {reason}")); } variable_scope.escape_all_bindings(); lexical_scope.escape_all_bindings(); variable_scope.reorder_binding_indices(); lexical_scope.reorder_binding_indices(); optimize_scope_indices(self, lexical_scope); Ok(bindings) } } impl VisitWith for Script { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { self.statements.visit_with(visitor) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { self.statements.visit_with_mut(visitor) } } impl ToIndentedString for Script { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { self.statements.to_indented_string(interner, indentation) } } impl PartialEq for Script { fn eq(&self, other: &Self) -> bool { self.statements == other.statements } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for Script { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let statements = StatementList::arbitrary(u)?; Ok(Self { statements }) } } /// A Module source. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-modules #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default, PartialEq)] pub struct Module { pub(crate) items: ModuleItemList, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, } impl Module { /// Creates a new `ModuleNode`. #[must_use] pub fn new(items: ModuleItemList) -> Self { Self { items, scope: Scope::default(), } } /// Gets the list of items of this `ModuleNode`. #[must_use] pub const fn items(&self) -> &ModuleItemList { &self.items } /// Gets the scope of this `ModuleNode`. #[inline] #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } /// Analyze the scope of the module. /// /// # Errors /// Any scope or binding errors that happened during the analysis. pub fn analyze_scope( &mut self, scope: &Scope, interner: &Interner, ) -> Result<(), &'static str> { collect_bindings(self, true, false, scope, interner)?; analyze_binding_escapes(self, false, scope.clone(), interner)?; optimize_scope_indices(self, &self.scope.clone()); Ok(()) } } impl VisitWith for Module { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { self.items.visit_with(visitor) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { self.items.visit_with_mut(visitor) } } ================================================ FILE: core/ast/src/source_text.rs ================================================ use crate::{LinearPosition, LinearSpan}; /// Source text. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] pub struct SourceText { source_text: Vec, } impl SourceText { /// Constructs a new, empty `SourceText` with at least the specified capacity. #[must_use] pub fn with_capacity(capacity: usize) -> Self { Self { source_text: Vec::with_capacity(capacity), } } /// Get current `LinearPosition`. #[must_use] pub fn cur_linear_position(&self) -> LinearPosition { LinearPosition::new(self.source_text.len()) } /// Get code points from `pos` to the current end. #[must_use] pub fn get_code_points_from_pos(&self, pos: LinearPosition) -> &[u16] { &self.source_text[pos.pos()..] } /// Get code points within `span`. #[must_use] pub fn get_code_points_from_span(&self, span: LinearSpan) -> &[u16] { &self.source_text[span.start().pos()..span.end().pos()] } /// Remove last code point. #[inline] pub fn remove_last_code_point(&mut self) { self.source_text.pop(); } /// Collect code point. /// /// # Panics /// /// On invalid code point. #[inline] pub fn collect_code_point(&mut self, cp: u32) { if let Ok(cu) = cp.try_into() { self.push(cu); return; } let cp = cp - 0x10000; let cu1 = (cp / 0x400 + 0xD800) .try_into() .expect("Invalid code point"); let cu2 = (cp % 0x400 + 0xDC00) .try_into() .expect("Invalid code point"); self.push(cu1); self.push(cu2); } #[inline] fn push(&mut self, cp: u16) { self.source_text.push(cp); } } const DEFAULT_CAPACITY: usize = 4 * 1024; impl Default for SourceText { fn default() -> Self { Self::with_capacity(DEFAULT_CAPACITY) } } ================================================ FILE: core/ast/src/statement/block.rs ================================================ //! Block AST node. use crate::{ Statement, StatementList, operations::{ContainsSymbol, contains}, scope::Scope, visitor::{VisitWith, Visitor, VisitorMut}, }; 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq, Default)] pub struct Block { #[cfg_attr(feature = "serde", serde(flatten))] pub(crate) statements: StatementList, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Option, } impl Block { /// Gets the list of statements and declarations in this block. #[inline] #[must_use] pub const fn statement_list(&self) -> &StatementList { &self.statements } /// Gets the scope of the block. #[inline] #[must_use] pub const fn scope(&self) -> Option<&Scope> { self.scope.as_ref() } } impl From for Block where T: Into, { fn from(list: T) -> Self { let statements = list.into(); let contains_direct_eval = contains(&statements, ContainsSymbol::DirectEval); Self { statements, scope: None, contains_direct_eval, } } } 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 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 where V: Visitor<'a>, { visitor.visit_statement_list(&self.statements) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_statement_list_mut(&mut self.statements) } } ================================================ FILE: core/ast/src/statement/if.rs ================================================ //! If statement use crate::{ expression::Expression, statement::Statement, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::{fmt::Write as _, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct If { condition: Expression, body: Box, else_node: Option>, } 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) -> 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) => { let _ = write!( buf, "{} 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 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 where V: Visitor<'a>, { visitor.visit_expression(&self.condition)?; visitor.visit_statement(&self.body)?; if let Some(stmt) = &self.else_node { visitor.visit_statement(stmt)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.condition)?; visitor.visit_statement_mut(&mut self.body)?; if let Some(stmt) = &mut self.else_node { visitor.visit_statement_mut(stmt)?; } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/statement/iteration/break.rs ================================================ use boa_interner::{Interner, Sym, ToInternedString}; use core::ops::ControlFlow; use crate::Statement; use crate::visitor::{VisitWith, Visitor, VisitorMut}; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Break { label: Option, } impl Break { /// Creates a `Break` AST node. #[must_use] pub const fn new(label: Option) -> Self { Self { label } } /// Gets the label of the break statement, if any. #[must_use] pub const fn label(&self) -> Option { 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 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 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 where V: VisitorMut<'a>, { if let Some(sym) = &mut self.label { visitor.visit_sym_mut(sym) } else { ControlFlow::Continue(()) } } } ================================================ FILE: core/ast/src/statement/iteration/continue.rs ================================================ 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Continue { label: Option, } impl Continue { /// Creates a `Continue` AST node. #[must_use] pub const fn new(label: Option) -> Self { Self { label } } /// Gets the label of this `Continue` statement. #[must_use] pub const fn label(&self) -> Option { 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 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 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 where V: VisitorMut<'a>, { if let Some(sym) = &mut self.label { visitor.visit_sym_mut(sym) } else { ControlFlow::Continue(()) } } } ================================================ FILE: core/ast/src/statement/iteration/do_while_loop.rs ================================================ use crate::{ expression::Expression, statement::Statement, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct DoWhileLoop { body: Box, 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 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 where V: Visitor<'a>, { visitor.visit_statement(&self.body)?; visitor.visit_expression(&self.condition) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_statement_mut(&mut self.body)?; visitor.visit_expression_mut(&mut self.condition) } } ================================================ FILE: core/ast/src/statement/iteration/for_in_loop.rs ================================================ use crate::operations::{ContainsSymbol, contains}; use crate::scope::Scope; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ expression::Expression, statement::{Statement, iteration::IterableLoopInitializer}, }; 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ForInLoop { pub(crate) initializer: IterableLoopInitializer, pub(crate) target: Expression, pub(crate) body: Box, pub(crate) target_contains_direct_eval: bool, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) target_scope: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Option, } impl ForInLoop { /// Creates a new `ForInLoop`. #[inline] #[must_use] pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self { let target_contains_direct_eval = contains(&target, ContainsSymbol::DirectEval); let contains_direct_eval = contains(&initializer, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { initializer, target, body: body.into(), target_contains_direct_eval, contains_direct_eval, target_scope: None, scope: None, } } /// 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 } /// Returns the target scope of the for...in loop. #[inline] #[must_use] pub const fn target_scope(&self) -> Option<&Scope> { self.target_scope.as_ref() } /// Returns the scope of the for...in loop. #[inline] #[must_use] pub const fn scope(&self) -> Option<&Scope> { self.scope.as_ref() } } 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 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 where V: Visitor<'a>, { visitor.visit_iterable_loop_initializer(&self.initializer)?; visitor.visit_expression(&self.target)?; visitor.visit_statement(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_iterable_loop_initializer_mut(&mut self.initializer)?; visitor.visit_expression_mut(&mut self.target)?; visitor.visit_statement_mut(&mut self.body) } } ================================================ FILE: core/ast/src/statement/iteration/for_loop.rs ================================================ use crate::operations::{ContainsSymbol, contains}; use crate::scope::Scope; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ Expression, declaration::{LexicalDeclaration, VarDeclaration}, statement::Statement, }; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::{fmt::Write as _, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ForLoop { #[cfg_attr(feature = "serde", serde(flatten))] pub(crate) inner: Box, } impl ForLoop { /// Creates a new for loop AST node. #[inline] #[must_use] pub fn new( init: Option, condition: Option, final_expr: Option, 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)); } let _ = write!( buf, ") {}", self.inner.body().to_indented_string(interner, indentation) ); buf } } impl From 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 where V: Visitor<'a>, { if let Some(fli) = &self.inner.init { visitor.visit_for_loop_initializer(fli)?; } if let Some(expr) = &self.inner.condition { visitor.visit_expression(expr)?; } if let Some(expr) = &self.inner.final_expr { visitor.visit_expression(expr)?; } visitor.visit_statement(&self.inner.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(fli) = &mut self.inner.init { visitor.visit_for_loop_initializer_mut(fli)?; } if let Some(expr) = &mut self.inner.condition { visitor.visit_expression_mut(expr)?; } if let Some(expr) = &mut self.inner.final_expr { 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub(crate) struct InnerForLoop { pub(crate) init: Option, pub(crate) condition: Option, pub(crate) final_expr: Option, pub(crate) body: Statement, pub(crate) contains_direct_eval: bool, } impl InnerForLoop { /// Creates a new inner for loop. #[inline] fn new( init: Option, condition: Option, final_expr: Option, body: Statement, ) -> Self { let mut contains_direct_eval = contains(&body, ContainsSymbol::DirectEval); if let Some(init) = &init { contains_direct_eval |= contains(init, ContainsSymbol::DirectEval); } if let Some(condition) = &condition { contains_direct_eval |= contains(condition, ContainsSymbol::DirectEval); } if let Some(final_expr) = &final_expr { contains_direct_eval |= contains(final_expr, ContainsSymbol::DirectEval); } Self { init, condition, final_expr, body, contains_direct_eval, } } /// 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 = "arbitrary", 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(ForLoopInitializerLexical), } /// A lexical declaration initializer for a `ForLoop`. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ForLoopInitializerLexical { pub(crate) declaration: LexicalDeclaration, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, } impl ForLoopInitializerLexical { /// Creates a new lexical declaration initializer. #[inline] #[must_use] pub fn new(declaration: LexicalDeclaration, scope: Scope) -> Self { Self { declaration, scope } } /// Returns the declaration of the lexical initializer. #[inline] #[must_use] pub const fn declaration(&self) -> &LexicalDeclaration { &self.declaration } /// Returns the scope of the lexical initializer. #[inline] #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } } 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.declaration.to_interned_string(interner), Self::Expression(expr) => expr.to_interned_string(interner), } } } impl From for ForLoopInitializer { #[inline] fn from(expr: Expression) -> Self { Self::Expression(expr) } } impl From for ForLoopInitializer { #[inline] fn from(list: LexicalDeclaration) -> Self { Self::Lexical(ForLoopInitializerLexical { declaration: list, scope: Scope::default(), }) } } impl From 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 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.declaration), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow 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(&mut ld.declaration), } } } ================================================ FILE: core/ast/src/statement/iteration/for_of_loop.rs ================================================ use crate::operations::{ContainsSymbol, contains}; use crate::scope::Scope; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ expression::Expression, statement::{Statement, iteration::IterableLoopInitializer}, }; 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ForOfLoop { pub(crate) init: IterableLoopInitializer, pub(crate) iterable: Expression, pub(crate) body: Box, r#await: bool, pub(crate) iterable_contains_direct_eval: bool, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) iterable_scope: Option, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Option, } 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 { let iterable_contains_direct_eval = contains(&iterable, ContainsSymbol::DirectEval); let contains_direct_eval = contains(&init, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { init, iterable, body: body.into(), iterable_contains_direct_eval, contains_direct_eval, r#await, iterable_scope: None, scope: None, } } /// 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 } /// Return the iterable scope of the for...of loop. #[inline] #[must_use] pub const fn iterable_scope(&self) -> Option<&Scope> { self.iterable_scope.as_ref() } /// Return the scope of the for...of loop. #[inline] #[must_use] pub const fn scope(&self) -> Option<&Scope> { self.scope.as_ref() } } 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 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 where V: Visitor<'a>, { visitor.visit_iterable_loop_initializer(&self.init)?; visitor.visit_expression(&self.iterable)?; visitor.visit_statement(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_iterable_loop_initializer_mut(&mut self.init)?; visitor.visit_expression_mut(&mut self.iterable)?; visitor.visit_statement_mut(&mut self.body) } } ================================================ FILE: core/ast/src/statement/iteration/mod.rs ================================================ //! 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, Variable}, expression::{Identifier, access::PropertyAccess}, pattern::Pattern, }; use core::ops::ControlFlow; pub use self::{ r#break::Break, r#continue::Continue, do_while_loop::DoWhileLoop, for_in_loop::ForInLoop, for_loop::{ForLoop, ForLoopInitializer, ForLoopInitializerLexical}, for_of_loop::ForOfLoop, 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 = "arbitrary", 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(Variable), /// 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.to_interned_string(interner), "var"), Self::Let(binding) => (binding.to_interned_string(interner), "let"), Self::Const(binding) => (binding.to_interned_string(interner), "const"), }; format!("{pre} {binding}") } } impl VisitWith for IterableLoopInitializer { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::Identifier(id) => visitor.visit_identifier(id), Self::Access(pa) => visitor.visit_property_access(pa), Self::Var(b) => visitor.visit_variable(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 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) => visitor.visit_variable_mut(b), Self::Let(b) | Self::Const(b) => visitor.visit_binding_mut(b), Self::Pattern(p) => visitor.visit_pattern_mut(p), } } } ================================================ FILE: core/ast/src/statement/iteration/while_loop.rs ================================================ use crate::{ expression::Expression, statement::Statement, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct WhileLoop { condition: Expression, body: Box, } 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 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 where V: Visitor<'a>, { visitor.visit_expression(&self.condition)?; visitor.visit_statement(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.condition)?; visitor.visit_statement_mut(&mut self.body) } } ================================================ FILE: core/ast/src/statement/labelled.rs ================================================ use crate::{ Statement, function::FunctionDeclaration, visitor::{VisitWith, Visitor, VisitorMut}, }; 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 [`FunctionDeclaration`]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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum LabelledItem { /// A labelled [`FunctionDeclaration`]. FunctionDeclaration(FunctionDeclaration), /// A labelled [`Statement`]. Statement(Statement), } impl LabelledItem { pub(crate) fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { match self { Self::FunctionDeclaration(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 for LabelledItem { fn from(f: FunctionDeclaration) -> Self { Self::FunctionDeclaration(f) } } impl From 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 where V: Visitor<'a>, { match self { Self::FunctionDeclaration(f) => visitor.visit_function_declaration(f), Self::Statement(s) => visitor.visit_statement(s), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::FunctionDeclaration(f) => visitor.visit_function_declaration_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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Labelled { item: Box, 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 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 where V: Visitor<'a>, { visitor.visit_labelled_item(&self.item)?; visitor.visit_sym(&self.label) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_labelled_item_mut(&mut self.item)?; visitor.visit_sym_mut(&mut self.label) } } ================================================ FILE: core/ast/src/statement/mod.rs ================================================ //! 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; mod with; pub mod iteration; pub use self::{ block::Block, r#if::If, iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop}, labelled::{Labelled, LabelledItem}, r#return::Return, switch::{Case, Switch}, throw::Throw, r#try::{Catch, ErrorHandler, Finally, Try}, with::With, }; 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 = "arbitrary", 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), /// See [`Labelled`]. Labelled(Labelled), /// See [`Throw`]. Throw(Throw), /// See [`Try`]. Try(Try), /// See [`With`]. With(With), /// A `debugger` statement. /// /// The debugger statement invokes any available debugging functionality. /// /// 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, } 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), Self::With(with) => return with.to_interned_string(interner), Self::Debugger => "debugger".to_owned(), }; 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::FunctionDeclaration(_) => 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 where V: Visitor<'a>, { match self { Self::Block(b) => visitor.visit_block(b), Self::Var(v) => visitor.visit_var_declaration(v), Self::Empty | Self::Debugger => { // 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), Self::With(with) => visitor.visit_with(with), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow 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 | Self::Debugger => { // 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), Self::With(with) => visitor.visit_with_mut(with), } } } ================================================ FILE: core/ast/src/statement/return.rs ================================================ 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Return { target: Option, } 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) -> Self { Self { target: expression } } } impl From 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 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 where V: VisitorMut<'a>, { if let Some(expr) = &mut self.target { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } } ================================================ FILE: core/ast/src/statement/switch.rs ================================================ //! Switch node. use crate::{ StatementList, expression::Expression, operations::{ContainsSymbol, contains}, scope::Scope, statement::Statement, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::{fmt::Write as _, 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Case { condition: Option, body: StatementList, } impl Case { /// Creates a regular `Case` AST node. #[inline] #[must_use] pub const fn new(condition: Expression, body: StatementList) -> Self { Self { condition: Some(condition), body, } } /// Creates a default `Case` AST node. #[inline] #[must_use] pub const fn default(body: StatementList) -> Self { Self { condition: None, body, } } /// Gets the condition of the case. /// /// If it's a regular case returns [`Some`] with the [`Expression`], /// otherwise [`None`] is returned if it's the default case. #[inline] #[must_use] pub const fn condition(&self) -> Option<&Expression> { self.condition.as_ref() } /// Gets the statement listin the body of the case. #[inline] #[must_use] pub const fn body(&self) -> &StatementList { &self.body } /// Check if the case is the `default` case. #[inline] #[must_use] pub const fn is_default(&self) -> bool { self.condition.is_none() } } impl VisitWith for Case { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(condition) = &self.condition { visitor.visit_expression(condition)?; } visitor.visit_statement_list(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(condition) = &mut self.condition { visitor.visit_expression_mut(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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Switch { pub(crate) val: Expression, pub(crate) cases: Box<[Case]>, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Option, } impl Switch { /// Creates a `Switch` AST node. #[inline] #[must_use] pub fn new(val: Expression, cases: Box<[Case]>) -> Self { let mut contains_direct_eval = false; for case in &cases { contains_direct_eval |= contains(case, ContainsSymbol::DirectEval); } Self { val, cases, contains_direct_eval, scope: None, } } /// 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 fn default(&self) -> Option<&StatementList> { for case in self.cases.as_ref() { if case.is_default() { return Some(case.body()); } } None } /// Gets the scope of the switch statement. #[inline] #[must_use] pub const fn scope(&self) -> Option<&Scope> { self.scope.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 { if let Some(condition) = e.condition() { let _ = write!( buf, "{indent} case {}:\n{}", condition.to_interned_string(interner), e.body().to_indented_string(interner, indentation + 2) ); } else { let _ = write!( buf, "{indent} default:\n{}", e.body().to_indented_string(interner, indentation + 2) ); } } let _ = write!(buf, "{indent}}}"); buf } } impl From 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 where V: Visitor<'a>, { visitor.visit_expression(&self.val)?; for case in &*self.cases { visitor.visit_case(case)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.val)?; for case in &mut *self.cases { visitor.visit_case_mut(case)?; } ControlFlow::Continue(()) } } ================================================ FILE: core/ast/src/statement/throw.rs ================================================ use crate::{ Expression, statement::Statement, visitor::{VisitWith, Visitor, VisitorMut}, }; 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 = "arbitrary", 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 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 where V: Visitor<'a>, { visitor.visit_expression(&self.target) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.target) } } ================================================ FILE: core/ast/src/statement/try.rs ================================================ //! Error handling statements use crate::operations::{ContainsSymbol, contains}; use crate::scope::Scope; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ declaration::Binding, statement::{Block, Statement}, }; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::{fmt::Write as _, 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 = "arbitrary", 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 = "arbitrary", 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 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 where V: Visitor<'a>, { visitor.visit_block(&self.block)?; if let Some(catch) = &self.catch() { visitor.visit_catch(catch)?; } if let Some(finally) = &self.finally() { visitor.visit_finally(finally)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_block_mut(&mut self.block)?; match &mut self.handler { ErrorHandler::Catch(c) => visitor.visit_catch_mut(c)?, ErrorHandler::Finally(f) => visitor.visit_finally_mut(f)?, ErrorHandler::Full(c, f) => { visitor.visit_catch_mut(c)?; visitor.visit_finally_mut(f)?; } } ControlFlow::Continue(()) } } /// Catch block. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Catch { pub(crate) parameter: Option, pub(crate) block: Block, pub(crate) contains_direct_eval: bool, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, } impl Catch { /// Creates a new catch block. #[inline] #[must_use] pub fn new(parameter: Option, block: Block) -> Self { let mut contains_direct_eval = contains(&block, ContainsSymbol::DirectEval); if let Some(param) = ¶meter { contains_direct_eval |= contains(param, ContainsSymbol::DirectEval); } Self { parameter, block, contains_direct_eval, scope: Scope::default(), } } /// 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 } /// Returns the scope of the catch block. #[inline] #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } } impl ToIndentedString for Catch { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { let mut buf = " catch".to_owned(); if let Some(param) = &self.parameter { let _ = write!(buf, "({})", param.to_interned_string(interner)); } let _ = write!( buf, " {}", self.block.to_indented_string(interner, indentation) ); buf } } impl VisitWith for Catch { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { if let Some(binding) = &self.parameter { visitor.visit_binding(binding)?; } visitor.visit_block(&self.block) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { if let Some(binding) = &mut self.parameter { 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 = "arbitrary", 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 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 where V: Visitor<'a>, { visitor.visit_block(&self.block) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_block_mut(&mut self.block) } } ================================================ FILE: core/ast/src/statement/with.rs ================================================ use crate::{ expression::Expression, scope::Scope, statement::Statement, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; /// The `with` statement extends the scope chain for a statement. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with /// [spec]: https://tc39.es/ecma262/#prod-WithStatement #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct With { pub(crate) expression: Expression, pub(crate) statement: Box, #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scope: Scope, } impl With { /// Creates a `With` AST node. #[must_use] pub fn new(expression: Expression, statement: Statement) -> Self { Self { expression, statement: Box::new(statement), scope: Scope::default(), } } /// Gets the expression value of this `With` statement. #[must_use] pub const fn expression(&self) -> &Expression { &self.expression } /// Gets the statement value of this `With` statement. #[must_use] pub const fn statement(&self) -> &Statement { &self.statement } /// Returns the scope of the `With` statement. #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } } impl From for Statement { fn from(with: With) -> Self { Self::With(with) } } impl ToIndentedString for With { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { format!( "with ({}) {}", self.expression().to_interned_string(interner), self.statement().to_indented_string(interner, indentation) ) } } impl VisitWith for With { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_expression(&self.expression)?; visitor.visit_statement(&self.statement) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_expression_mut(&mut self.expression)?; visitor.visit_statement_mut(&mut self.statement) } } ================================================ FILE: core/ast/src/statement_list.rs ================================================ //! Statement list node. use super::Declaration; use crate::{ LinearPosition, statement::Statement, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; use std::ops::Deref; /// 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 = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum StatementListItem { /// See [`Statement`]. Statement(Box), /// See [`Declaration`]. Declaration(Box), } 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 for StatementListItem { #[inline] fn from(stmt: Statement) -> Self { Self::Statement(Box::new(stmt)) } } impl From for StatementListItem { #[inline] fn from(decl: Declaration) -> Self { Self::Declaration(Box::new(decl)) } } impl VisitWith for StatementListItem { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow 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 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)] pub struct StatementList { pub(crate) statements: Box<[StatementListItem]>, linear_pos_end: LinearPosition, strict: bool, } impl PartialEq for StatementList { fn eq(&self, other: &Self) -> bool { self.statements == other.statements && self.strict == other.strict } } impl StatementList { /// Creates a new `StatementList` AST node. #[must_use] pub fn new(statements: S, linear_pos_end: LinearPosition, strict: bool) -> Self where S: Into>, { Self { statements: statements.into(), linear_pos_end, 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 } /// Get end of linear position in source code. #[inline] #[must_use] pub const fn linear_pos_end(&self) -> LinearPosition { self.linear_pos_end } } impl From<(Box<[StatementListItem]>, LinearPosition)> for StatementList { #[inline] fn from(value: (Box<[StatementListItem]>, LinearPosition)) -> Self { Self { statements: value.0, linear_pos_end: value.1, strict: false, } } } impl From<(Vec, LinearPosition)> for StatementList { #[inline] fn from(value: (Vec, LinearPosition)) -> Self { Self { statements: value.0.into(), linear_pos_end: value.1, strict: false, } } } impl Deref for StatementList { type Target = [StatementListItem]; fn deref(&self) -> &Self::Target { &self.statements } } 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 { // 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 where V: Visitor<'a>, { for statement in &*self.statements { visitor.visit_statement_list_item(statement)?; } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { for statement in &mut *self.statements { visitor.visit_statement_list_item_mut(statement)?; } ControlFlow::Continue(()) } } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for StatementList { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { Ok(Self { statements: u.arbitrary()?, linear_pos_end: LinearPosition::default(), strict: false, // disable strictness; this is *not* in source data }) } } ================================================ FILE: core/ast/src/visitor.rs ================================================ //! 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::{ Module, ModuleItem, ModuleItemList, Script, StatementList, StatementListItem, declaration::{ Binding, Declaration, ExportDeclaration, ExportSpecifier, ImportAttribute, ImportDeclaration, ImportKind, ImportSpecifier, LexicalDeclaration, ModuleSpecifier, ReExportKind, VarDeclaration, Variable, VariableList, }, expression::{ Await, Call, Expression, Identifier, ImportCall, ImportMeta, New, NewTarget, Optional, OptionalOperation, OptionalOperationKind, Parenthesized, RegExpLiteral, Spread, SuperCall, TaggedTemplate, This, Yield, access::{ PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess, SuperPropertyAccess, }, literal::{ ArrayLiteral, Literal, ObjectLiteral, ObjectMethodDefinition, PropertyDefinition, TemplateElement, TemplateLiteral, }, operator::{ Binary, BinaryInPrivate, Conditional, Unary, Update, assign::{Assign, AssignTarget}, }, }, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, ClassExpression, FormalParameter, FormalParameterList, FunctionBody, FunctionDeclaration, FunctionExpression, GeneratorDeclaration, GeneratorExpression, PrivateName, }, pattern::{ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern}, property::PropertyName, statement::{ Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Try, With, iteration::{ Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop, IterableLoopInitializer, WhileLoop, }, }, }; use boa_interner::Sym; /// 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 { 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 { 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! { Script, Module, FunctionBody, StatementList, StatementListItem, Statement, Declaration, FunctionExpression, FunctionDeclaration, GeneratorExpression, GeneratorDeclaration, AsyncFunctionExpression, AsyncFunctionDeclaration, AsyncGeneratorExpression, AsyncGeneratorDeclaration, ClassExpression, ClassDeclaration, LexicalDeclaration, Block, VarDeclaration, Expression, If, DoWhileLoop, WhileLoop, ForLoop, ForInLoop, ForOfLoop, Switch, Continue, Break, Return, Labelled, With, Throw, Try, This, NewTarget, ImportMeta, Identifier, FormalParameterList, ClassElement, PrivateName, VariableList, Variable, Binding, Pattern, Literal, RegExpLiteral, ArrayLiteral, ObjectLiteral, Spread, ArrowFunction, AsyncArrowFunction, TemplateLiteral, PropertyAccess, New, Call, SuperCall, ImportCall, Optional, TaggedTemplate, Assign, Unary, Update, Binary, BinaryInPrivate, Conditional, Await, Yield, Parenthesized, ForLoopInitializer, IterableLoopInitializer, Case, Sym, LabelledItem, Catch, Finally, FormalParameter, PropertyName, ObjectMethodDefinition, ObjectPattern, ArrayPattern, PropertyDefinition, TemplateElement, SimplePropertyAccess, PrivatePropertyAccess, SuperPropertyAccess, OptionalOperation, AssignTarget, ObjectPatternElement, ArrayPatternElement, PropertyAccessField, OptionalOperationKind, ModuleItemList, ModuleItem, ModuleSpecifier, ImportAttribute, ImportKind, ImportDeclaration, ImportSpecifier, ReExportKind, ExportDeclaration, ExportSpecifier, } /// 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_script, Script); define_visit!(visit_module, Module); define_visit!(visit_function_body, FunctionBody); 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_expression, FunctionExpression); define_visit!(visit_function_declaration, FunctionDeclaration); define_visit!(visit_generator_expression, GeneratorExpression); define_visit!(visit_generator_declaration, GeneratorDeclaration); define_visit!(visit_async_function_expression, AsyncFunctionExpression); define_visit!(visit_async_function_declaration, AsyncFunctionDeclaration); define_visit!(visit_async_generator_expression, AsyncGeneratorExpression); define_visit!(visit_async_generator_declaration, AsyncGeneratorDeclaration); define_visit!(visit_class_expression, ClassExpression); define_visit!(visit_class_declaration, ClassDeclaration); 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_with, With); define_visit!(visit_this, This); 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_reg_exp_literal, RegExpLiteral); 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_import_call, ImportCall); define_visit!(visit_optional, Optional); define_visit!(visit_tagged_template, TaggedTemplate); define_visit!(visit_assign, Assign); define_visit!(visit_unary, Unary); define_visit!(visit_update, Update); define_visit!(visit_binary, Binary); define_visit!(visit_binary_in_private, BinaryInPrivate); define_visit!(visit_conditional, Conditional); define_visit!(visit_await, Await); define_visit!(visit_yield, Yield); define_visit!(visit_parenthesized, Parenthesized); define_visit!(visit_new_target, NewTarget); define_visit!(visit_import_meta, ImportMeta); 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_object_method_definition, ObjectMethodDefinition); 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); define_visit!(visit_module_item_list, ModuleItemList); define_visit!(visit_module_item, ModuleItem); define_visit!(visit_module_specifier, ModuleSpecifier); define_visit!(visit_import_attribute, ImportAttribute); define_visit!(visit_import_kind, ImportKind); define_visit!(visit_import_declaration, ImportDeclaration); define_visit!(visit_import_specifier, ImportSpecifier); define_visit!(visit_re_export_kind, ReExportKind); define_visit!(visit_export_declaration, ExportDeclaration); define_visit!(visit_export_specifier, ExportSpecifier); /// 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>>(&mut self, node: N) -> ControlFlow { let node = node.into(); match node { NodeRef::Script(n) => self.visit_script(n), NodeRef::Module(n) => self.visit_module(n), NodeRef::FunctionBody(n) => self.visit_function_body(n), 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::FunctionExpression(n) => self.visit_function_expression(n), NodeRef::FunctionDeclaration(n) => self.visit_function_declaration(n), NodeRef::GeneratorExpression(n) => self.visit_generator_expression(n), NodeRef::GeneratorDeclaration(n) => self.visit_generator_declaration(n), NodeRef::AsyncFunctionExpression(n) => self.visit_async_function_expression(n), NodeRef::AsyncFunctionDeclaration(n) => self.visit_async_function_declaration(n), NodeRef::AsyncGeneratorExpression(n) => self.visit_async_generator_expression(n), NodeRef::AsyncGeneratorDeclaration(n) => self.visit_async_generator_declaration(n), NodeRef::ClassExpression(n) => self.visit_class_expression(n), NodeRef::ClassDeclaration(n) => self.visit_class_declaration(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::With(n) => self.visit_with(n), NodeRef::Throw(n) => self.visit_throw(n), NodeRef::Try(n) => self.visit_try(n), NodeRef::This(n) => self.visit_this(n), NodeRef::NewTarget(n) => self.visit_new_target(n), NodeRef::ImportMeta(n) => self.visit_import_meta(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::RegExpLiteral(n) => self.visit_reg_exp_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::ImportCall(n) => self.visit_import_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::Update(n) => self.visit_update(n), NodeRef::Binary(n) => self.visit_binary(n), NodeRef::BinaryInPrivate(n) => self.visit_binary_in_private(n), NodeRef::Conditional(n) => self.visit_conditional(n), NodeRef::Await(n) => self.visit_await(n), NodeRef::Yield(n) => self.visit_yield(n), NodeRef::Parenthesized(n) => self.visit_parenthesized(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::ObjectMethodDefinition(n) => self.visit_object_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), NodeRef::ModuleItemList(n) => self.visit_module_item_list(n), NodeRef::ModuleItem(n) => self.visit_module_item(n), NodeRef::ModuleSpecifier(n) => self.visit_module_specifier(n), NodeRef::ImportAttribute(n) => self.visit_import_attribute(n), NodeRef::ImportKind(n) => self.visit_import_kind(n), NodeRef::ImportDeclaration(n) => self.visit_import_declaration(n), NodeRef::ImportSpecifier(n) => self.visit_import_specifier(n), NodeRef::ReExportKind(n) => self.visit_re_export_kind(n), NodeRef::ExportDeclaration(n) => self.visit_export_declaration(n), NodeRef::ExportSpecifier(n) => self.visit_export_specifier(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_script_mut, Script); define_visit_mut!(visit_module_mut, Module); define_visit_mut!(visit_function_body_mut, FunctionBody); 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_expression_mut, FunctionExpression); define_visit_mut!(visit_function_declaration_mut, FunctionDeclaration); define_visit_mut!(visit_generator_expression_mut, GeneratorExpression); define_visit_mut!(visit_generator_declaration_mut, GeneratorDeclaration); define_visit_mut!(visit_async_function_expression_mut, AsyncFunctionExpression); define_visit_mut!( visit_async_function_declaration_mut, AsyncFunctionDeclaration ); define_visit_mut!( visit_async_generator_expression_mut, AsyncGeneratorExpression ); define_visit_mut!( visit_async_generator_declaration_mut, AsyncGeneratorDeclaration ); define_visit_mut!(visit_class_expression_mut, ClassExpression); define_visit_mut!(visit_class_declaration_mut, ClassDeclaration); 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_with_mut, With); define_visit_mut!(visit_this_mut, This); 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_reg_exp_literal_mut, RegExpLiteral); 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_import_call_mut, ImportCall); 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_update_mut, Update); define_visit_mut!(visit_binary_mut, Binary); define_visit_mut!(visit_binary_in_private_mut, BinaryInPrivate); 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_parenthesized_mut, Parenthesized); define_visit_mut!(visit_new_target_mut, NewTarget); define_visit_mut!(visit_import_meta_mut, ImportMeta); 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_object_method_definition_mut, ObjectMethodDefinition); 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); define_visit_mut!(visit_module_item_list_mut, ModuleItemList); define_visit_mut!(visit_module_item_mut, ModuleItem); define_visit_mut!(visit_module_specifier_mut, ModuleSpecifier); define_visit_mut!(visit_import_attribute_mut, ImportAttribute); define_visit_mut!(visit_import_kind_mut, ImportKind); define_visit_mut!(visit_import_declaration_mut, ImportDeclaration); define_visit_mut!(visit_import_specifier_mut, ImportSpecifier); define_visit_mut!(visit_re_export_kind_mut, ReExportKind); define_visit_mut!(visit_export_declaration_mut, ExportDeclaration); define_visit_mut!(visit_export_specifier_mut, ExportSpecifier); /// 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>>(&mut self, node: N) -> ControlFlow { let node = node.into(); match node { NodeRefMut::Script(n) => self.visit_script_mut(n), NodeRefMut::Module(n) => self.visit_module_mut(n), NodeRefMut::FunctionBody(n) => self.visit_function_body_mut(n), 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::FunctionExpression(n) => self.visit_function_expression_mut(n), NodeRefMut::FunctionDeclaration(n) => self.visit_function_declaration_mut(n), NodeRefMut::GeneratorExpression(n) => self.visit_generator_expression_mut(n), NodeRefMut::GeneratorDeclaration(n) => self.visit_generator_declaration_mut(n), NodeRefMut::AsyncFunctionExpression(n) => self.visit_async_function_expression_mut(n), NodeRefMut::AsyncFunctionDeclaration(n) => self.visit_async_function_declaration_mut(n), NodeRefMut::AsyncGeneratorExpression(n) => self.visit_async_generator_expression_mut(n), NodeRefMut::AsyncGeneratorDeclaration(n) => { self.visit_async_generator_declaration_mut(n) } NodeRefMut::ClassExpression(n) => self.visit_class_expression_mut(n), NodeRefMut::ClassDeclaration(n) => self.visit_class_declaration_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::With(n) => self.visit_with_mut(n), NodeRefMut::Throw(n) => self.visit_throw_mut(n), NodeRefMut::Try(n) => self.visit_try_mut(n), NodeRefMut::This(n) => self.visit_this_mut(n), NodeRefMut::NewTarget(n) => self.visit_new_target_mut(n), NodeRefMut::ImportMeta(n) => self.visit_import_meta_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::RegExpLiteral(n) => self.visit_reg_exp_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::ImportCall(n) => self.visit_import_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::Update(n) => self.visit_update_mut(n), NodeRefMut::Binary(n) => self.visit_binary_mut(n), NodeRefMut::BinaryInPrivate(n) => self.visit_binary_in_private_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::Parenthesized(n) => self.visit_parenthesized_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::ObjectMethodDefinition(n) => self.visit_object_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), NodeRefMut::ModuleItemList(n) => self.visit_module_item_list_mut(n), NodeRefMut::ModuleItem(n) => self.visit_module_item_mut(n), NodeRefMut::ModuleSpecifier(n) => self.visit_module_specifier_mut(n), NodeRefMut::ImportAttribute(n) => self.visit_import_attribute_mut(n), NodeRefMut::ImportKind(n) => self.visit_import_kind_mut(n), NodeRefMut::ImportDeclaration(n) => self.visit_import_declaration_mut(n), NodeRefMut::ImportSpecifier(n) => self.visit_import_specifier_mut(n), NodeRefMut::ReExportKind(n) => self.visit_re_export_kind_mut(n), NodeRefMut::ExportDeclaration(n) => self.visit_export_declaration_mut(n), NodeRefMut::ExportSpecifier(n) => self.visit_export_specifier_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 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 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 where V: Visitor<'a>, { ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { ControlFlow::Continue(()) } } ================================================ FILE: core/ast/tests/scope.rs ================================================ //! Tests for the scope analysis of the AST. #![allow(unused_crate_dependencies)] use boa_ast::{ Declaration, Expression, LinearPosition, LinearSpan, Script, Span, Statement, StatementList, StatementListItem, declaration::{LexicalDeclaration, Variable}, expression::Identifier, function::{FormalParameter, FormalParameterList, FunctionBody, FunctionDeclaration}, scope::Scope, statement::Block, }; use boa_interner::Interner; use boa_string::JsString; #[test] fn empty_script_is_ok() { let scope = &Scope::new_global(); let mut script = Script::default(); let ok = script.analyze_scope(scope, &Interner::new()).is_ok(); assert!(ok); assert_eq!(scope.num_bindings(), 0); } #[test] fn script_global_let() { let scope = Scope::new_global(); let mut interner = Interner::new(); let a = interner.get_or_intern("a"); let mut script = Script::new(StatementList::new( [Declaration::Lexical(LexicalDeclaration::Let( vec![Variable::from_identifier( Identifier::new(a, Span::new((1, 1), (1, 1))), None, )] .try_into() .unwrap(), )) .into()], LinearPosition::default(), false, )); let ok = script.analyze_scope(&scope, &interner).is_ok(); assert!(ok); assert_eq!(scope.num_bindings(), 1); let a = scope.get_binding_reference(&JsString::from("a")).unwrap(); assert!(!a.is_global_object()); assert!(a.is_lexical()); assert!(!a.local()); } #[test] fn script_global_const() { let scope = Scope::new_global(); let mut interner = Interner::new(); let a = interner.get_or_intern("a"); let mut script = Script::new(StatementList::new( [Declaration::Lexical(LexicalDeclaration::Const( vec![Variable::from_identifier( Identifier::new(a, Span::new((1, 1), (1, 1))), None, )] .try_into() .unwrap(), )) .into()], LinearPosition::default(), false, )); let ok = script.analyze_scope(&scope, &interner).is_ok(); assert!(ok); assert_eq!(scope.num_bindings(), 1); let a = scope.get_binding_reference(&JsString::from("a")).unwrap(); assert!(!a.is_global_object()); assert!(a.is_lexical()); assert!(!a.local()); } #[test] fn script_block_let() { let scope = Scope::new_global(); let mut interner = Interner::new(); let a = interner.get_or_intern("a"); let mut script = Script::new(StatementList::new( [Statement::Block(Block::from(( vec![ Declaration::Lexical(LexicalDeclaration::Let( vec![Variable::from_identifier( Identifier::new(a, Span::new((1, 1), (1, 1))), None, )] .try_into() .unwrap(), )) .into(), ], LinearPosition::default(), ))) .into()], LinearPosition::default(), false, )); let ok = script.analyze_scope(&scope, &interner).is_ok(); assert!(ok); assert_eq!(scope.num_bindings(), 0); let StatementListItem::Statement(statement) = script.statements().first().unwrap() else { panic!("Expected a block statement"); }; let Statement::Block(block) = statement.as_ref() else { panic!("Expected a block statement"); }; let scope = block.scope().unwrap(); assert_eq!(scope.num_bindings(), 1); let a = scope.get_binding_reference(&JsString::from("a")).unwrap(); assert!(!a.is_global_object()); assert!(a.is_lexical()); assert!(a.local()); } #[test] fn script_function_mapped_arguments_not_accessed() { let scope = Scope::new_global(); let mut interner = Interner::new(); let f = interner.get_or_intern("f"); let a = interner.get_or_intern("a"); let mut script = Script::new(StatementList::new( [Declaration::FunctionDeclaration(FunctionDeclaration::new( Identifier::new(f, Span::new((1, 1), (1, 1))), FormalParameterList::from_parameters(vec![FormalParameter::new( Variable::from_identifier(Identifier::new(a, Span::new((1, 1), (1, 1))), None), false, )]), FunctionBody::new( StatementList::new( [Declaration::Lexical(LexicalDeclaration::Let( vec![Variable::from_identifier( Identifier::new(a, Span::new((1, 1), (1, 1))), None, )] .try_into() .unwrap(), )) .into()], LinearPosition::default(), false, ), Span::new((1, 1), (1, 1)), ), LinearSpan::default(), )) .into()], LinearPosition::default(), false, )); let ok = script.analyze_scope(&scope, &interner).is_ok(); assert!(ok); assert_eq!(scope.num_bindings(), 0); let StatementListItem::Declaration(declaration) = script.statements().first().unwrap() else { panic!("Expected a block statement"); }; let Declaration::FunctionDeclaration(f) = declaration.as_ref() else { panic!("Expected a block statement"); }; assert_eq!(f.scopes().function_scope().num_bindings(), 2); assert_eq!(f.scopes().parameters_eval_scope(), None); assert_eq!(f.scopes().parameters_scope(), None); assert_eq!(f.scopes().lexical_scope().unwrap().num_bindings(), 1); let arguments = f .scopes() .function_scope() .get_binding_reference(&JsString::from("arguments")) .unwrap(); assert!(!f.scopes().arguments_object_accessed()); assert!(!arguments.is_global_object()); assert!(arguments.is_lexical()); assert!(arguments.local()); let a = f .scopes() .function_scope() .get_binding_reference(&JsString::from("a")) .unwrap(); assert!(!a.is_global_object()); assert!(a.is_lexical()); assert!(a.local()); let a = f .scopes() .lexical_scope() .unwrap() .get_binding_reference(&JsString::from("a")) .unwrap(); assert!(!a.is_global_object()); assert!(a.is_lexical()); assert!(a.local()); } #[test] fn script_function_mapped_arguments_accessed() { let scope = Scope::new_global(); let mut interner = Interner::new(); let f = interner.get_or_intern("f"); let a = interner.get_or_intern("a"); let arguments = interner.get_or_intern("arguments"); let mut script = Script::new(StatementList::new( [Declaration::FunctionDeclaration(FunctionDeclaration::new( Identifier::new(f, Span::new((1, 1), (1, 1))), FormalParameterList::from_parameters(vec![FormalParameter::new( Variable::from_identifier(Identifier::new(a, Span::new((1, 1), (1, 1))), None), false, )]), FunctionBody::new( StatementList::new( [ Declaration::Lexical(LexicalDeclaration::Let( vec![Variable::from_identifier( Identifier::new(a, Span::new((1, 1), (1, 1))), None, )] .try_into() .unwrap(), )) .into(), Statement::Expression(Expression::Identifier(Identifier::new( arguments, Span::new((1, 1), (1, 1)), ))) .into(), ], LinearPosition::default(), false, ), Span::new((1, 1), (1, 1)), ), LinearSpan::default(), )) .into()], LinearPosition::default(), false, )); let ok = script.analyze_scope(&scope, &interner).is_ok(); assert!(ok); assert_eq!(scope.num_bindings(), 0); let StatementListItem::Declaration(declaration) = script.statements().first().unwrap() else { panic!("Expected a block statement"); }; let Declaration::FunctionDeclaration(f) = declaration.as_ref() else { panic!("Expected a block statement"); }; assert!(f.scopes().arguments_object_accessed()); assert_eq!(f.scopes().function_scope().num_bindings(), 2); assert_eq!(f.scopes().parameters_eval_scope(), None); assert_eq!(f.scopes().parameters_scope(), None); assert_eq!(f.scopes().lexical_scope().unwrap().num_bindings(), 1); let arguments = f .scopes() .function_scope() .get_binding_reference(&JsString::from("arguments")) .unwrap(); assert!(!arguments.is_global_object()); assert!(arguments.is_lexical()); assert!(arguments.local()); let a = f .scopes() .function_scope() .get_binding_reference(&JsString::from("a")) .unwrap(); assert!(!a.is_global_object()); assert!(a.is_lexical()); assert!(!a.local()); let a = f .scopes() .lexical_scope() .unwrap() .get_binding_reference(&JsString::from("a")) .unwrap(); assert!(!a.is_global_object()); assert!(a.is_lexical()); assert!(a.local()); } #[test] fn multiple_scopes_with_same_parent_have_distinct_ids() { let global = Scope::new_global(); let child1 = Scope::new(global.clone(), false); let child2 = Scope::new(global.clone(), false); let child3 = Scope::new(child1.clone(), false); let child4 = Scope::new(child1.clone(), false); // Check uniqueness by inserting in a set and asserting that there are // 5 elements numbered from 0 to 4. let mut ids = [global, child1, child2, child3, child4] .into_iter() .map(|scope| scope.unique_id()) .collect::>(); ids.sort_unstable(); assert_eq!(ids, vec![0, 1, 2, 3, 4]); } #[test] fn can_correlate_binding_to_scope() { // GIVEN let global = Scope::new_global(); let child1 = Scope::new(global.clone(), false); let child2 = Scope::new(child1.clone(), false); let _unused = child1.create_mutable_binding("x".into(), false); // WHEN let binding = child2.get_identifier_reference("x".into()); // THEN assert_eq!(binding.locator().unique_scope_id(), child1.unique_id()); } ================================================ FILE: core/engine/ABOUT.md ================================================ # 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_cli`**][cli] - Boa's CLI && REPL implementation - [**`boa_ast`**][ast] - Boa's ECMAScript Abstract Syntax Tree. - [**`boa_engine`**][engine] - Boa's implementation of ECMAScript builtin objects and execution. - [**`boa_gc`**][gc] - Boa's garbage collector. - [**`boa_icu_provider`**][icu] - Boa's ICU4X data provider. - [**`boa_interner`**][interner] - Boa's string interner. - [**`boa_macros`**][macros] - Boa's macros. - [**`boa_parser`**][parser] - Boa's lexer and parser. - [**`boa_runtime`**][runtime] - Boa's `WebAPI` features. - [**`boa_string`**][string] - Boa's ECMAScript string implementation. - [**`boa_wintertc`**][wintertc] - Boa's `WinterTC` (TC55) Minimum Common Web API implementation. - [**`tag_ptr`**][tag_ptr] - Utility library that enables a pointer to be associated with a tag of type `usize`. - [**`small_btree`**][small_btree] - Utility library that adds the `SmallBTreeMap` data structure. [boa-conformance]: https://boajs.dev/conformance [boa-web]: https://boajs.dev/ [boa-playground]: https://boajs.dev/playground [ast]: https://docs.rs/boa_ast/latest/boa_ast/index.html [engine]: https://docs.rs/boa_engine/latest/boa_engine/index.html [gc]: https://docs.rs/boa_gc/latest/boa_gc/index.html [interner]: https://docs.rs/boa_interner/latest/boa_interner/index.html [parser]: https://docs.rs/boa_parser/latest/boa_parser/index.html [icu]: https://docs.rs/boa_icu_provider/latest/boa_icu_provider/index.html [runtime]: https://docs.rs/boa_runtime/latest/boa_runtime/index.html [string]: https://docs.rs/boa_string/latest/boa_string/index.html [wintertc]: https://docs.rs/boa_wintertc/latest/boa_wintertc/index.html [tag_ptr]: https://docs.rs/tag_ptr/latest/tag_ptr/index.html [small_btree]: https://docs.rs/small_btree/latest/small_btree/index.html [macros]: https://docs.rs/boa_macros/latest/boa_macros/index.html [cli]: https://crates.io/crates/boa_cli ================================================ FILE: core/engine/Cargo.toml ================================================ [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] default = ["float16", "xsum", "temporal"] embedded_lz4 = ["boa_macros/embedded_lz4", "lz4_flex"] # Replace the NaN-boxing implementation of `JsValueInner` with an # enum-based implementation. This implementation is less performant # but compatible with more platforms. # If you encounter the "assertion left == right failed: Pointer is # not 4-bits aligned or over 51-bits." error, try this feature. # For more details, see https://github.com/boa-dev/boa/issues/4275 # Disabled by default. jsvalue-enum = [] deser = ["boa_interner/serde", "boa_ast/serde"] either = ["dep:either", "boa_gc/either"] # Enables the `Intl` builtin object and bundles a default ICU4X data provider. # Prefer this over `intl` if you just want to enable `Intl` without dealing with the # generation of ICU4X data. intl_bundled = ["intl", "dep:boa_icu_provider"] # Enables Boa's `Intl` builtin implementation. # Prefer this over `intl_bundled` if you want to reduce the size of the final binary # by providing a smaller ICU4X data provider. intl = [ "boa_gc/icu", "icu_normalizer/serde", "dep:icu_locale", "dep:icu_datetime", "dep:icu_time", "dep:icu_plurals", "dep:icu_provider", "dep:icu_calendar", "dep:icu_collator", "dep:icu_casemap", "dep:icu_list", "dep:icu_segmenter", "dep:icu_decimal", "dep:writeable", "dep:sys-locale", "dep:yoke", "dep:zerofrom", "dep:fixed_decimal", "dep:tinystr", "dep:timezone_provider", "timezone_provider/experimental_tzif", ] fuzz = ["boa_ast/arbitrary", "boa_interner/arbitrary"] # Enable Boa's VM instruction flowgraph generator. flowgraph = [] # Enable Boa's VM instruction tracing. trace = ["js"] # Enable Boa's additional ECMAScript features for web browsers. annex-b = ["boa_ast/annex-b", "boa_parser/annex-b"] # Enable Boa's Temporal proposal implementation temporal = ["dep:icu_calendar", "dep:temporal_rs", "dep:timezone_provider", "timezone_provider/experimental_tzif"] #Enable access to host system timezone system-time-zone = ["dep:iana-time-zone"] # Enable experimental features, like Stage 3 proposals. experimental = [] # Enable binding to JS APIs for system related utilities. js = ["dep:web-time", "dep:getrandom", "getrandom/wasm_js", "time/wasm-bindgen"] # Enable support for Float16 typed arrays float16 = ["dep:float16"] # Enable support for `Math.sumPrecise` xsum = ["dep:xsum"] # Native Backtraces native-backtrace = [] [dependencies] tag_ptr.workspace = true boa_interner.workspace = true boa_gc = { workspace = true, features = ["thin-vec", "boa_string", "arrayvec"] } boa_macros.workspace = true boa_ast.workspace = true boa_parser.workspace = true boa_string.workspace = true cow-utils.workspace = true futures-lite.workspace = true float16 = { version = "0.1", optional = true } lz4_flex = { workspace = true, optional = true } xsum = { version = "0.1.6", optional = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true rand.workspace = true num-traits.workspace = true regress.workspace = true rustc-hash = { workspace = true, features = ["std"] } num-bigint = { workspace = true, features = ["serde"] } num-integer.workspace = true bitflags.workspace = true indexmap = { workspace = true, features = ["std"] } ryu-js.workspace = true fast-float2.workspace = true tap.workspace = true small_btree.workspace = true paste.workspace = true thiserror.workspace = true dashmap.workspace = true num_enum.workspace = true thin-vec.workspace = true itertools = { workspace = true, default-features = false, features = ["use_alloc"] } icu_normalizer = { workspace = true, features = [ "compiled_data", "utf16_iter", ] } portable-atomic.workspace = true bytemuck = { workspace = true, features = ["derive"] } arrayvec.workspace = true intrusive-collections.workspace = true cfg-if.workspace = true time.workspace = true hashbrown.workspace = true either = { workspace = true, optional = true } static_assertions.workspace = true aligned-vec.workspace = true dynify = { workspace = true, features = ["macros"] } futures-concurrency.workspace = true oneshot = { workspace = true, features = ["async"] } async-channel.workspace = true # intl deps boa_icu_provider = { workspace = true, features = ["std"], optional = true } sys-locale = { workspace = true, optional = true } icu_provider = { workspace = true, optional = true } icu_locale = { workspace = true, default-features = false, features = [ "serde", ], optional = true } icu_datetime = { workspace = true, default-features = false, features = [ "serde", ], optional = true } icu_time = { workspace = true, default-features = false, features = [ "serde", ], optional = true } icu_calendar = { workspace = true, default-features = false, optional = true } icu_collator = { workspace = true, default-features = false, features = [ "serde", ], optional = true } icu_plurals = { workspace = true, default-features = false, features = [ "serde", "experimental", ], optional = true } icu_list = { workspace = true, default-features = false, features = [ "serde", "alloc", ], optional = true } icu_casemap = { workspace = true, default-features = false, features = [ "serde", ], optional = true } icu_segmenter = { workspace = true, default-features = false, features = [ "auto", "serde", ], optional = true } icu_decimal = { workspace = true, default-features = false, features = [ "serde", ], optional = true } writeable = { workspace = true, optional = true } yoke = { workspace = true, optional = true } zerofrom = { workspace = true, optional = true } fixed_decimal = { workspace = true, features = [ "ryu", ], optional = true } tinystr = { workspace = true, optional = true } # temporal deps temporal_rs = { workspace = true, optional = true } timezone_provider = { workspace = true, optional = true } iana-time-zone = { workspace = true, optional = true } [target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] web-time = { workspace = true, optional = true } # NOTE: This enables the wasm_js required for rand to work on wasm getrandom = { workspace = true, features = ["wasm_js"], optional = true } [dev-dependencies] criterion.workspace = true float-cmp.workspace = true indoc.workspace = true textwrap.workspace = true test-case.workspace = true husky-rs.workspace = true [target.x86_64-unknown-linux-gnu.dev-dependencies] jemallocator.workspace = true [lib] crate-type = ["cdylib", "lib"] name = "boa_engine" bench = false [[bench]] name = "full" harness = false [lints] workspace = true [package.metadata.docs.rs] all-features = true ================================================ FILE: core/engine/benches/README.md ================================================ # 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. ================================================ FILE: core/engine/benches/bench_scripts/arithmetic_operations.js ================================================ ((2 + 2) ** 3 / 100 - 5 ** 3 * -1000) ** 2 + 100 - 8; ================================================ FILE: core/engine/benches/bench_scripts/array_access.js ================================================ (function () { let testArr = [1, 2, 3, 4, 5]; let res = testArr[2]; return res; })(); ================================================ FILE: core/engine/benches/bench_scripts/array_create.js ================================================ (function () { let testArr = []; for (let a = 0; a <= 500; a++) { testArr[a] = "p" + a; } return testArr; })(); ================================================ FILE: core/engine/benches/bench_scripts/array_pop.js ================================================ (function () { let testArray = [ 83, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, ]; while (testArray.length > 0) { testArray.pop(); } return testArray; })(); ================================================ FILE: core/engine/benches/bench_scripts/boolean_object_access.js ================================================ new Boolean( !new Boolean( new Boolean( !new Boolean(false).valueOf() && new Boolean(true).valueOf(), ).valueOf(), ).valueOf(), ).valueOf(); ================================================ FILE: core/engine/benches/bench_scripts/clean_js.js ================================================ !function () { var M = new Array(); for (i = 0; i < 100; i++) { M.push(Math.floor(Math.random() * 100)); } var test = []; for (i = 0; i < 100; i++) { if (M[i] > 50) { test.push(M[i]); } } test.forEach(elem => { 0 }); }(); ================================================ FILE: core/engine/benches/bench_scripts/fibonacci.js ================================================ (function () { let num = 12; function fib(n) { if (n <= 1) return 1; return fib(n - 1) + fib(n - 2); } return fib(num); })(); ================================================ FILE: core/engine/benches/bench_scripts/for_loop.js ================================================ (function () { let b = "hello"; for (let a = 10; a < 100; a += 5) { if (a < 50) { b += "world"; } } return b; })(); ================================================ FILE: core/engine/benches/bench_scripts/mini_js.js ================================================ !function(){var r=new Array();for(i=0;i<100;i++)r.push(Math.floor(100*Math.random()));var a=[];for(i=0;i<100;i++)r[i]>50&&a.push(r[i]);a.forEach(i=>{0})}(); ================================================ FILE: core/engine/benches/bench_scripts/number_object_access.js ================================================ new Number( new Number( new Number(new Number(100).valueOf() - 10.5).valueOf() + 100, ).valueOf() * 1.6, ); ================================================ FILE: core/engine/benches/bench_scripts/object_creation.js ================================================ (function () { let test = { my_prop: "hello", another: 65, }; return test; })(); ================================================ FILE: core/engine/benches/bench_scripts/object_prop_access_const.js ================================================ (function () { let test = { my_prop: "hello", another: 65, }; return test.my_prop; })(); ================================================ FILE: core/engine/benches/bench_scripts/object_prop_access_dyn.js ================================================ (function () { let test = { my_prop: "hello", another: 65, }; return test["my" + "_prop"]; })(); ================================================ FILE: core/engine/benches/bench_scripts/regexp.js ================================================ (function () { let regExp = new RegExp("hello", "i"); return regExp.test("Hello World"); })(); ================================================ FILE: core/engine/benches/bench_scripts/regexp_creation.js ================================================ (function () { let regExp = new RegExp("hello", "i"); return regExp; })(); ================================================ FILE: core/engine/benches/bench_scripts/regexp_literal.js ================================================ (function () { let regExp = /hello/i; return regExp.test("Hello World"); })(); ================================================ FILE: core/engine/benches/bench_scripts/regexp_literal_creation.js ================================================ (function () { let regExp = /hello/i; return regExp; })(); ================================================ FILE: core/engine/benches/bench_scripts/string_code_point_sum.js ================================================ (() => { let sum = ""; let string = "Hello, world!!!"; for (let i = 0; i < string.length; ++i) { sum += string.charCodeAt(i).toString(16); } return sum; })(); ================================================ FILE: core/engine/benches/bench_scripts/string_compare.js ================================================ (function () { var a = "hello"; var b = "world"; var c = a == b; var d = b; var e = d == b; })(); ================================================ FILE: core/engine/benches/bench_scripts/string_concat.js ================================================ (function () { var a = "hello"; var b = "world"; var c = a + b; })(); ================================================ FILE: core/engine/benches/bench_scripts/string_copy.js ================================================ (function () { var a = "hello"; var b = a; })(); ================================================ FILE: core/engine/benches/bench_scripts/string_object_access.js ================================================ new String( new String( new String( new String("Hello").valueOf() + new String(", world").valueOf(), ).valueOf() + "!", ).valueOf(), ).valueOf(); ================================================ FILE: core/engine/benches/bench_scripts/symbol_creation.js ================================================ (function () { return Symbol(); })(); ================================================ FILE: core/engine/benches/full.rs ================================================ #![allow(unused_crate_dependencies, missing_docs)] //! Benchmarks of the whole execution engine in Boa. use boa_engine::{ Context, Source, context::DefaultHooks, object::shape::RootShape, optimizer::OptimizerOptions, realm::Realm, script::Script, }; use criterion::{Criterion, criterion_group, criterion_main}; use std::hint::black_box; #[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; fn create_realm(c: &mut Criterion) { c.bench_function("Create Realm", move |b| { let root_shape = RootShape::default(); b.iter(|| Realm::create(&DefaultHooks, &root_shape)); }); } macro_rules! full_benchmarks { ($({$id:literal, $name:ident}),*) => { fn bench_parser(c: &mut Criterion) { $( { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); let mut context = Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); c.bench_function(concat!($id, " (Parser)"), move |b| { b.iter(|| { Script::parse( black_box(Source::from_bytes(CODE)), None, &mut context, ).unwrap() }) }); } )* } fn bench_compile(c: &mut Criterion) { $( { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); let context = &mut Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); let script = Script::parse( black_box(Source::from_bytes(CODE)), None, context, ).unwrap(); c.bench_function(concat!($id, " (Compiler)"), move |b| { b.iter(|| { script.codeblock(context).unwrap() }) }); } )* } fn bench_execution(c: &mut Criterion) { $( { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); let context = &mut Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); let script = Script::parse( black_box(Source::from_bytes(CODE)), None, context, ).unwrap(); script.codeblock(context).unwrap(); c.bench_function(concat!($id, " (Execution)"), move |b| { b.iter(|| { script.evaluate(context).unwrap(); }) }); } )* } }; } full_benchmarks!( {"String Code Point Sum", string_code_point_sum}, {"Symbols", symbol_creation}, {"For loop", for_loop}, {"Fibonacci", fibonacci}, {"Object Creation", object_creation}, {"Static Object Property Access", object_prop_access_const}, {"Dynamic Object Property Access", object_prop_access_dyn}, {"RegExp Literal Creation", regexp_literal_creation}, {"RegExp Creation", regexp_creation}, {"RegExp Literal", regexp_literal}, {"RegExp", regexp}, {"Array access", array_access}, {"Array creation", array_create}, {"Array pop", array_pop}, {"String concatenation", string_concat}, {"String comparison", string_compare}, {"String copy", string_copy}, {"Number Object Access", number_object_access}, {"Boolean Object Access", boolean_object_access}, {"String Object Access", string_object_access}, {"Arithmetic operations", arithmetic_operations}, {"Clean js", clean_js}, {"Mini js", mini_js} ); criterion_group!( benches, create_realm, bench_parser, bench_compile, bench_execution, ); criterion_main!(benches); ================================================ FILE: core/engine/src/bigint.rs ================================================ //! Boa's implementation of ECMAScript's bigint primitive type. use crate::{JsData, JsResult, JsString, builtins::Number, error::JsNativeError}; use boa_gc::{Finalize, Trace}; use num_bigint::Sign; use num_integer::Integer; use num_traits::{FromPrimitive, One, ToPrimitive, Zero, pow::Pow}; use std::{ fmt::{self, Display}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, ptr::NonNull, rc::Rc, }; /// The raw bigint type. pub type RawBigInt = num_bigint::BigInt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; /// JavaScript bigint primitive rust type. #[allow( clippy::unsafe_derive_deserialize, reason = "unsafe methods do not add invariants that need to be held" )] #[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Trace, Finalize, JsData)] // Safety: `JsBigInt` doesn't contain any traceable types. #[boa_gc(unsafe_empty_trace)] pub struct JsBigInt { inner: Rc, } impl JsBigInt { /// Create a new [`JsBigInt`]. #[must_use] pub fn new>(value: T) -> Self { value.into() } /// Create a [`JsBigInt`] with value `0`. #[inline] #[must_use] pub fn zero() -> Self { Self { inner: Rc::new(RawBigInt::zero()), } } /// Check if is zero. #[inline] #[must_use] pub fn is_zero(&self) -> bool { self.inner.is_zero() } /// Create a [`JsBigInt`] with value `1`. #[inline] #[must_use] pub fn one() -> Self { Self { inner: Rc::new(RawBigInt::one()), } } /// Check if is one. #[inline] #[must_use] pub fn is_one(&self) -> bool { self.inner.is_one() } /// Convert bigint to string with radix. #[inline] #[must_use] pub fn to_string_radix(&self, radix: u32) -> String { self.inner.to_str_radix(radix) } /// Converts the `BigInt` to a f64 type. /// /// Returns `f64::INFINITY` if the `BigInt` is too big. #[inline] #[must_use] pub fn to_f64(&self) -> f64 { self.inner.to_f64().unwrap_or(f64::INFINITY) } /// Converts the `BigInt` to a i128 type. /// /// Returns `i128::MAX` if the `BigInt` is too big. #[inline] #[must_use] pub fn to_i128(&self) -> i128 { self.inner.to_i128().unwrap_or(i128::MAX) } /// Converts a string to a `BigInt` with the specified radix. #[inline] #[must_use] pub fn from_string_radix(buf: &str, radix: u32) -> Option { Some(Self { inner: Rc::new(RawBigInt::parse_bytes(buf.as_bytes(), radix)?), }) } /// Abstract operation `StringToBigInt ( str )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-stringtobigint pub(crate) fn from_js_string(string: &JsString) -> Option { // 1. Let text be ! StringToCodePoints(str). // 2. Let literal be ParseText(text, StringIntegerLiteral). // 3. If literal is a List of errors, return undefined. // 4. Let mv be the MV of literal. // 5. Assert: mv is an integer. // 6. Return ℤ(mv). JsBigInt::from_string(string.to_std_string().ok().as_ref()?) } /// This function takes a string and converts it to `BigInt` type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-stringtobigint #[inline] #[must_use] pub fn from_string(mut string: &str) -> Option { string = string.trim(); if string.is_empty() { return Some(Self::zero()); } let mut radix = 10; if string.starts_with("0b") || string.starts_with("0B") { radix = 2; string = &string[2..]; } else if string.starts_with("0x") || string.starts_with("0X") { radix = 16; string = &string[2..]; } else if string.starts_with("0o") || string.starts_with("0O") { radix = 8; string = &string[2..]; } Self::from_string_radix(string, radix) } /// Checks for `SameValueZero` equality. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-equal #[inline] #[must_use] pub fn same_value_zero(x: &Self, y: &Self) -> bool { // Return BigInt::equal(x, y) Self::equal(x, y) } /// Checks for `SameValue` equality. /// /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValue #[inline] #[must_use] pub fn same_value(x: &Self, y: &Self) -> bool { // Return BigInt::equal(x, y) Self::equal(x, y) } /// Checks for mathematical equality. /// /// The abstract operation `BigInt::equal` takes arguments x (a `BigInt`) and y (a `BigInt`). /// It returns `true` if x and y have the same mathematical integer value and false otherwise. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValueZero #[inline] #[must_use] pub fn equal(x: &Self, y: &Self) -> bool { x == y } /// Returns `x` to the power `y`. #[inline] pub fn pow(x: &Self, y: &Self) -> JsResult { let y = y .inner .to_biguint() .ok_or_else(|| JsNativeError::range().with_message("BigInt negative exponent"))?; let num_bits = (x.inner.bits() as f64 * y.to_f64().expect("Unable to convert from BigUInt to f64")) .floor() + 1f64; if num_bits > 1_000_000_000f64 { return Err(JsNativeError::range() .with_message("Maximum BigInt size exceeded") .into()); } Ok(Self::new(x.inner.as_ref().clone().pow(y))) } /// Performs the `>>` operation. #[inline] pub fn shift_right(x: &Self, y: &Self) -> JsResult { match y.inner.to_i32() { Some(n) if n > 0 => Ok(Self::new(x.inner.as_ref().clone().shr(n as usize))), Some(n) => Ok(Self::new(x.inner.as_ref().clone().shl(n.unsigned_abs()))), // y doesn't fit in i32. // // Best-effort safeguard: while the spec doesn't explicitly mandate a // result for implementation-limited shift amounts, the mathematical // definition of BigInt right shift (`floor(x / 2^y)`) from // // implies that for very large positive y the result converges to // 0n (x >= 0) or -1n (x < 0). V8 and SpiderMonkey agree. None => match (x.inner.sign(), y.inner.sign()) { // x >> (large positive): all bits are shifted out. (Sign::Minus, Sign::Plus) => Ok(Self::new(RawBigInt::from(-1))), (_, Sign::Plus) => Ok(Self::zero()), // x >> (large negative) is equivalent to x << (large positive), which overflows. (_, _) => Err(JsNativeError::range() .with_message("Maximum BigInt size exceeded") .into()), }, } } /// Performs the `<<` operation. #[inline] pub fn shift_left(x: &Self, y: &Self) -> JsResult { match y.inner.to_i32() { Some(n) if n > 0 => Ok(Self::new(x.inner.as_ref().clone().shl(n as usize))), Some(n) => Ok(Self::new(x.inner.as_ref().clone().shr(n.unsigned_abs()))), // y doesn't fit in i32. // // Best-effort safeguard: symmetric to shift_right above. // See . None => match (x.inner.sign(), y.inner.sign()) { // x << (large negative) is equivalent to x >> (large positive): all bits shifted out. (Sign::Minus, Sign::Minus) => Ok(Self::new(RawBigInt::from(-1))), (_, Sign::Minus) => Ok(Self::zero()), // x << (large positive) overflows. (_, _) => Err(JsNativeError::range() .with_message("Maximum BigInt size exceeded") .into()), }, } } /// Floored integer modulo. /// /// # Examples /// ``` /// # use num_integer::Integer; /// assert_eq!((8).mod_floor(&3), 2); /// assert_eq!((8).mod_floor(&-3), -1); /// ``` #[inline] #[must_use] pub fn mod_floor(x: &Self, y: &Self) -> Self { Self::new(x.inner.mod_floor(&y.inner)) } /// Performs the `+` operation. #[inline] #[must_use] pub fn add(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().add(y.inner.as_ref())) } /// Performs the `-` operation. #[inline] #[must_use] pub fn sub(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().sub(y.inner.as_ref())) } /// Performs the `*` operation. #[inline] #[must_use] pub fn mul(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().mul(y.inner.as_ref())) } /// Performs the `/` operation. #[inline] #[must_use] pub fn div(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().div(y.inner.as_ref())) } /// Performs the `%` operation. #[inline] #[must_use] pub fn rem(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().rem(y.inner.as_ref())) } /// Performs the `&` operation. #[inline] #[must_use] pub fn bitand(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().bitand(y.inner.as_ref())) } /// Performs the `|` operation. #[inline] #[must_use] pub fn bitor(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().bitor(y.inner.as_ref())) } /// Performs the `^` operation. #[inline] #[must_use] pub fn bitxor(x: &Self, y: &Self) -> Self { Self::new(x.inner.as_ref().clone().bitxor(y.inner.as_ref())) } /// Performs the unary `-` operation. #[inline] #[must_use] pub fn neg(x: &Self) -> Self { Self::new(x.as_inner().neg()) } /// Performs the unary `!` operation. #[inline] #[must_use] pub fn not(x: &Self) -> Self { Self::new(!x.as_inner()) } /// Returns a reference to the raw inner value. #[inline] #[must_use] pub fn as_inner(&self) -> &RawBigInt { &self.inner } /// Consumes the [`JsBigInt`], returning a pointer to [`RawBigInt`]. /// /// To avoid a memory leak the pointer must be converted back to a `JsBigInt` using /// [`JsBigInt::from_raw`]. #[inline] #[must_use] #[allow(unused, reason = "only used in nan-boxed implementation of JsValue")] pub(crate) fn into_raw(self) -> NonNull { // SAFETY: `Rc::into_raw` must always return a non-null pointer. unsafe { NonNull::new_unchecked(Rc::into_raw(self.inner).cast_mut()) } } /// Constructs a `JsBigInt` from a pointer to [`RawBigInt`]. /// /// The raw pointer must have been previously returned by a call to /// [`JsBigInt::into_raw`]. /// /// # Safety /// /// This function is unsafe because improper use may lead to memory unsafety, /// even if the returned `JsBigInt` is never accessed. #[inline] #[must_use] #[allow(unused, reason = "only used in nan-boxed implementation of JsValue")] pub(crate) unsafe fn from_raw(ptr: *const RawBigInt) -> Self { Self { // SAFETY: the validity of `ptr` is guaranteed by the caller. inner: unsafe { Rc::from_raw(ptr) }, } } } impl Display for JsBigInt { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self.inner, f) } } impl From for JsBigInt { #[inline] fn from(value: RawBigInt) -> Self { Self { inner: Rc::new(value), } } } impl From> for JsBigInt { #[inline] fn from(value: Box) -> Self { Self { inner: value.into(), } } } impl From for JsBigInt { #[inline] fn from(value: i8) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: u8) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: i16) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: u16) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: i32) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: u32) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: i64) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: u64) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: i128) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: u128) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: isize) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } impl From for JsBigInt { #[inline] fn from(value: usize) -> Self { Self { inner: Rc::new(RawBigInt::from(value)), } } } /// The error indicates that the conversion from [`f64`] to [`JsBigInt`] failed. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct TryFromF64Error; impl Display for TryFromF64Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Could not convert f64 value to a BigInt type") } } impl TryFrom for JsBigInt { type Error = TryFromF64Error; #[inline] fn try_from(n: f64) -> Result { // If the truncated version of the number is not the // same as the non-truncated version then the floating-point // number contains a fractional part. if !Number::equal(n.trunc(), n) { return Err(TryFromF64Error); } RawBigInt::from_f64(n).map_or(Err(TryFromF64Error), |bigint| Ok(Self::new(bigint))) } } impl PartialEq for JsBigInt { #[inline] fn eq(&self, other: &i32) -> bool { self.inner.as_ref() == &RawBigInt::from(*other) } } impl PartialEq for i32 { #[inline] fn eq(&self, other: &JsBigInt) -> bool { &RawBigInt::from(*self) == other.inner.as_ref() } } impl PartialEq for JsBigInt { #[inline] fn eq(&self, other: &f64) -> bool { other.fract().is_zero() && RawBigInt::from_f64(*other).is_some_and(|bigint| self.inner.as_ref() == &bigint) } } impl PartialEq for f64 { #[inline] fn eq(&self, other: &JsBigInt) -> bool { self.fract().is_zero() && RawBigInt::from_f64(*self).is_some_and(|bigint| other.inner.as_ref() == &bigint) } } ================================================ FILE: core/engine/src/builtins/array/array_iterator.rs ================================================ //! This module implements the `ArrayIterator` object. //! //! More information: //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects use crate::{ Context, JsData, JsResult, builtins::{ Array, BuiltInBuilder, IntrinsicObject, JsValue, iterable::create_iter_result_object, typed_array::TypedArray, }, context::intrinsics::Intrinsics, error::JsNativeError, js_string, object::JsObject, property::{Attribute, PropertyNameKind}, realm::Realm, symbol::JsSymbol, }; use boa_gc::{Finalize, Trace}; /// The Array Iterator object represents an iteration over an array. It implements the iterator protocol. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects #[derive(Debug, Clone, Finalize, Trace, JsData)] pub(crate) struct ArrayIterator { array: JsObject, next_index: u64, #[unsafe_ignore_trace] kind: PropertyNameKind, done: bool, } impl IntrinsicObject for ArrayIterator { fn init(realm: &Realm) { BuiltInBuilder::with_intrinsic::(realm) .prototype( realm .intrinsics() .objects() .iterator_prototypes() .iterator(), ) .static_method(Self::next, js_string!("next"), 0) .static_property( JsSymbol::to_string_tag(), js_string!("Array Iterator"), Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().iterator_prototypes().array() } } impl ArrayIterator { fn new(array: JsObject, kind: PropertyNameKind) -> Self { Self { array, kind, next_index: 0, done: false, } } /// `CreateArrayIterator( array, kind )` /// /// Creates a new iterator over the given array. /// /// More information: /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator pub(crate) fn create_array_iterator( array: JsObject, kind: PropertyNameKind, context: &Context, ) -> JsValue { let array_iterator = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().objects().iterator_prototypes().array(), Self::new(array, kind), ); array_iterator.into() } /// %ArrayIteratorPrototype%.next( ) /// /// Gets the next result in the array. /// /// More information: /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let object = this.as_object(); let mut array_iterator = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; let index = array_iterator.next_index; if array_iterator.done { return Ok(create_iter_result_object( JsValue::undefined(), true, context, )); } let len = if let Some(f) = array_iterator.array.downcast_ref::() { let buf = f.viewed_array_buffer().as_buffer(); let Some(buf) = buf .bytes(std::sync::atomic::Ordering::SeqCst) .filter(|buf| !f.is_out_of_bounds(buf.len())) else { return Err(JsNativeError::typ() .with_message("Cannot get value from out of bounds typed array") .into()); }; f.array_length(buf.len()) } else { array_iterator.array.length_of_array_like(context)? }; if index >= len { array_iterator.done = true; return Ok(create_iter_result_object( JsValue::undefined(), true, context, )); } array_iterator.next_index = index + 1; match array_iterator.kind { PropertyNameKind::Key => Ok(create_iter_result_object(index.into(), false, context)), PropertyNameKind::Value => { let element_value = array_iterator.array.get(index, context)?; Ok(create_iter_result_object(element_value, false, context)) } PropertyNameKind::KeyAndValue => { let element_value = array_iterator.array.get(index, context)?; let result = Array::create_array_from_list([index.into(), element_value], context); Ok(create_iter_result_object(result.into(), false, context)) } } } } ================================================ FILE: core/engine/src/builtins/array/from_async.rs ================================================ use boa_gc::{Finalize, Trace}; use super::Array; use crate::builtins::AsyncFromSyncIterator; use crate::builtins::iterable::IteratorRecord; use crate::builtins::promise::ResolvingFunctions; use crate::native_function::{CoroutineState, NativeCoroutine}; use crate::object::{JsFunction, JsPromise}; use crate::{ Context, JsArgs, JsError, JsExpect, JsNativeError, JsObject, JsResult, JsSymbol, JsValue, js_string, }; use std::cell::Cell; impl Array { /// [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`][spec] /// /// The `Array.fromAsync()` static method creates a new, /// shallow-copied Array instance from a list or iterator of Promise-like values. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/proposal-array-from-async/#sec-array.fromAsync #[allow(clippy::unnecessary_wraps)] pub(crate) fn from_async( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let C be the this value. // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). let (promise, resolvers) = JsPromise::new_pending(context); let async_items = args.get_or_undefined(0); let mapfn = args.get_or_undefined(1); let this_arg = args.get_or_undefined(2).clone(); // 3. Let fromAsyncClosure be a new Abstract Closure with no parameters that captures C, mapfn, and thisArg and // performs the following steps when called: // 4. Perform AsyncFunctionStart(promiseCapability, fromAsyncClosure). // NOTE: We avoid putting more state onto the coroutines by preprocessing all we can before allocating // the coroutines. let result: JsResult<()> = (|| { // a. If mapfn is undefined, let mapping be false. let mapfn = if mapfn.is_undefined() { None } else { // b. Else, // i. If IsCallable(mapfn) is false, throw a TypeError exception. let Some(callable) = mapfn.as_callable() else { return Err(JsNativeError::typ() .with_message("Array.fromAsync: mapping function must be callable") .into()); }; // ii. Let mapping be true. Some(JsFunction::from_object_unchecked(callable)) }; // c. Let usingAsyncIterator be ? GetMethod(asyncItems, @@asyncIterator). // d. If usingAsyncIterator is undefined, then // i. Let usingSyncIterator be ? GetMethod(asyncItems, @@iterator). // e. Let iteratorRecord be undefined. // f. If usingAsyncIterator is not undefined, then let iterator_record = if let Some(method) = async_items.get_method(JsSymbol::async_iterator(), context)? { // i. Set iteratorRecord to ? GetIterator(asyncItems, async, usingAsyncIterator). async_items.get_iterator_from_method(&method, context)? } // g. Else if usingSyncIterator is not undefined, then else if let Some(method) = async_items.get_method(JsSymbol::iterator(), context)? { // i. Set iteratorRecord to ? CreateAsyncFromSyncIterator(GetIterator(asyncItems, sync, usingSyncIterator)). AsyncFromSyncIterator::create( async_items.get_iterator_from_method(&method, context)?, context, ) } // i. Else, else { // i. NOTE: asyncItems is neither an AsyncIterable nor an Iterable so assume it is an array-like object. // ii. Let arrayLike be ! ToObject(asyncItems). let array_like = async_items.to_object(context)?; // iii. Let len be ? LengthOfArrayLike(arrayLike). let len = array_like.length_of_array_like(context)?; // iv. If IsConstructor(C) is true, then let a = if let Some(c) = this.as_constructor() { // 1. Let A be ? Construct(C, « 𝔽(len) »). c.construct(&[len.into()], None, context)? } // v. Else, else { // 1. Let A be ? ArrayCreate(len). Array::array_create(len, None, context)? }; let coroutine_state = ( GlobalState { mapfn, this_arg, resolvers: resolvers.clone(), }, Cell::new(Some(ArrayLikeStateMachine::LoopStart { array_like, a, len, // iii. Let k be 0. k: 0, })), ); // Try to run the coroutine once to see if it finishes early. // This avoids allocating a new coroutine that will immediately finish. // Spec continues on `from_array_like`... if let CoroutineState::Yielded(value) = from_array_like(Ok(JsValue::undefined()), &coroutine_state, context)? { // Coroutine yielded. We need to allocate it for a future execution. JsPromise::resolve(value, context)?.await_native( NativeCoroutine::from_copy_closure_with_captures( from_array_like, coroutine_state, ), context, ); } return Ok(()); }; // h. If iteratorRecord is not undefined, then // i. If IsConstructor(C) is true, then let a = if let Some(c) = this.as_constructor() { // 1. Let A be ? Construct(C). c.construct(&[], None, context)? } // ii. Else, else { // 1. Let A be ! ArrayCreate(0). Array::array_create(0, None, context)? }; let coroutine_state = ( GlobalState { mapfn, this_arg, resolvers: resolvers.clone(), }, Cell::new(Some(AsyncIteratorStateMachine::LoopStart { // vi. Let k be 0. k: 0, a, iterator_record, })), ); // Try to run the coroutine once to see if it finishes early. // This avoids allocating a new coroutine that will immediately finish. // Spec continues on `from_async_iterator`... if let CoroutineState::Yielded(value) = from_async_iterator(Ok(JsValue::undefined()), &coroutine_state, context)? { JsPromise::resolve(value, context)?.await_native( NativeCoroutine::from_copy_closure_with_captures( from_async_iterator, coroutine_state, ), context, ); } Ok(()) })(); // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start // -> // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) // https://tc39.es/ecma262/#sec-asyncblockstart // i. Assert: result is a throw completion. if let Err(err) = result { // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). resolvers .reject .call(&JsValue::undefined(), &[err.into_opaque(context)?], context) .js_expect("resolving functions cannot fail")?; } // 5. Return promiseCapability.[[Promise]]. Ok(promise.into()) } } #[derive(Trace, Finalize)] struct GlobalState { mapfn: Option, this_arg: JsValue, resolvers: ResolvingFunctions, } #[derive(Trace, Finalize)] #[boa_gc(unsafe_no_drop)] enum AsyncIteratorStateMachine { LoopStart { a: JsObject, k: u64, iterator_record: IteratorRecord, }, LoopContinue { a: JsObject, k: u64, iterator_record: IteratorRecord, }, LoopEnd { a: JsObject, k: u64, iterator_record: IteratorRecord, mapped_value: Option>, }, AsyncIteratorCloseStart { err: JsError, iterator: JsObject, }, AsyncIteratorCloseEnd { err: JsError, }, } /// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`](https://tc39.es/proposal-array-from-async/#sec-array.fromAsync). fn from_async_iterator( mut result: JsResult, (global_state, state_machine): &(GlobalState, Cell>), context: &mut Context, ) -> JsResult { let result = (|| { let Some(mut sm) = state_machine.take() else { return Ok(CoroutineState::Done); }; // iv. Repeat, loop { match sm { AsyncIteratorStateMachine::LoopStart { a, k, iterator_record, } => { // Inverted conditional makes for a simpler code. if k < 2u64.pow(53) - 1 { // 2. Let Pk be ! ToString(𝔽(k)). // 3. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). let next_result = iterator_record.next_method().call( &iterator_record.iterator().clone().into(), &[], context, )?; state_machine.set(Some(AsyncIteratorStateMachine::LoopContinue { a, k, iterator_record, })); // 4. Set nextResult to ? Await(nextResult). return Ok(CoroutineState::Yielded(next_result)); } // 1. If k ≥ 2**53 - 1, then // a. Let error be ThrowCompletion(a newly created TypeError object). // b. Return ? AsyncIteratorClose(iteratorRecord, error). sm = AsyncIteratorStateMachine::AsyncIteratorCloseStart { err: JsNativeError::typ() .with_message( "Array.fromAsync: \ reached the maximum number of elements in an array \ (2^53 - 1)", ) .into(), iterator: iterator_record.iterator().clone(), }; } AsyncIteratorStateMachine::LoopContinue { a, k, mut iterator_record, } => { // `result` is `Await(nextResult)`. let result = std::mem::replace(&mut result, Ok(JsValue::undefined())); // 5. If nextResult is not an Object, throw a TypeError exception. // Implicit on the call to `update_result`. iterator_record.update_result(result?, context)?; // 6. Let done be ? IteratorComplete(nextResult). // 7. If done is true, if iterator_record.done() { // a. Perform ? Set(A, "length", 𝔽(k), true). a.set(js_string!("length"), k, true, context)?; // b. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }. // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start // -> // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) // https://tc39.es/ecma262/#sec-asyncblockstart // g. Else if result is a return completion, then // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). global_state .resolvers .resolve .call(&JsValue::undefined(), &[a.into()], context) .js_expect("resolving functions cannot fail")?; return Ok(CoroutineState::Done); } // 8. Let nextValue be ? IteratorValue(nextResult). let next_value = iterator_record.value(context)?; // 9. If mapping is true, then if let Some(mapfn) = &global_state.mapfn { // a. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). // b. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). // https://tc39.es/proposal-array-from-async/#sec-ifabruptcloseasynciterator let mapped_value = match mapfn.call( &global_state.this_arg, &[next_value, k.into()], context, ) { // 1. If value is an abrupt completion, then Err(err) => { // a. Perform ? AsyncIteratorClose(iteratorRecord, value). // b. Return value. sm = AsyncIteratorStateMachine::AsyncIteratorCloseStart { err, iterator: iterator_record.iterator().clone(), }; continue; } // 2. Else if value is a Completion Record, set value to value.[[Value]]. Ok(value) => value, }; state_machine.set(Some(AsyncIteratorStateMachine::LoopEnd { a, k, iterator_record, mapped_value: None, })); // c. Set mappedValue to Await(mappedValue). return Ok(CoroutineState::Yielded(mapped_value)); } sm = AsyncIteratorStateMachine::LoopEnd { a, k, iterator_record, // 10. Else, let mappedValue be nextValue. mapped_value: Some(Ok(next_value)), } } AsyncIteratorStateMachine::LoopEnd { a, k, iterator_record, mapped_value, } => { // Either awaited `mappedValue` or directly set `mappedValue` to `nextValue`. let result = std::mem::replace(&mut result, Ok(JsValue::undefined())); // d. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). // https://tc39.es/proposal-array-from-async/#sec-ifabruptcloseasynciterator let mapped_value = match mapped_value.unwrap_or(result) { // 1. If value is an abrupt completion, then Err(err) => { // a. Perform ? AsyncIteratorClose(iteratorRecord, value). // b. Return value. sm = AsyncIteratorStateMachine::AsyncIteratorCloseStart { err, iterator: iterator_record.iterator().clone(), }; continue; } // 2. Else if value is a Completion Record, set value to value.[[Value]]. Ok(value) => value, }; // 11. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue). sm = if let Err(err) = a.create_data_property_or_throw(k, mapped_value, context) { // 12. If defineStatus is an abrupt completion, return ? AsyncIteratorClose(iteratorRecord, defineStatus). AsyncIteratorStateMachine::AsyncIteratorCloseStart { err, iterator: iterator_record.iterator().clone(), } } else { AsyncIteratorStateMachine::LoopStart { a, // 13. Set k to k + 1. k: k + 1, iterator_record, } }; } // AsyncIteratorClose ( iteratorRecord, completion ) // https://tc39.es/ecma262/#sec-asynciteratorclose // Simplified for only error completions. AsyncIteratorStateMachine::AsyncIteratorCloseStart { err, iterator } => { // 1. Assert: iteratorRecord.[[Iterator]] is an Object. // 2. Let iterator be iteratorRecord.[[Iterator]]. // 3. Let innerResult be Completion(GetMethod(iterator, "return")). // 4. If innerResult is a normal completion, then // a. Let return be innerResult.[[Value]]. // b. If return is undefined, return ? completion. // c. Set innerResult to Completion(Call(return, iterator)). // d. If innerResult is a normal completion, set innerResult to Completion(Await(innerResult.[[Value]])). // 5. If completion is a throw completion, return ? completion. let Ok(Some(ret)) = iterator.get_method(js_string!("return"), context) else { return Err(err); }; let Ok(value) = ret.call(&iterator.into(), &[], context) else { return Err(err); }; state_machine.set(Some(AsyncIteratorStateMachine::AsyncIteratorCloseEnd { err, })); return Ok(CoroutineState::Yielded(value)); } AsyncIteratorStateMachine::AsyncIteratorCloseEnd { err } => { // Awaited `innerResult.[[Value]]`. // Only need to return the original error. return Err(err); } } } })(); // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start // -> // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) // https://tc39.es/ecma262/#sec-asyncblockstart match result { Ok(cont) => Ok(cont), // i. Assert: result is a throw completion. Err(err) => { // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). global_state .resolvers .reject .call(&JsValue::undefined(), &[err.into_opaque(context)?], context) .js_expect("resolving functions cannot fail")?; Ok(CoroutineState::Done) } } } #[derive(Trace, Finalize)] #[boa_gc(unsafe_no_drop)] #[allow(clippy::enum_variant_names)] enum ArrayLikeStateMachine { LoopStart { array_like: JsObject, a: JsObject, len: u64, k: u64, }, LoopContinue { array_like: JsObject, a: JsObject, len: u64, k: u64, }, LoopEnd { array_like: JsObject, a: JsObject, len: u64, k: u64, mapped_value: Option, }, } /// Part of [`Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )`](https://tc39.es/proposal-array-from-async/#sec-array.fromAsync). fn from_array_like( mut result: JsResult, (global_state, state_machine): &(GlobalState, Cell>), context: &mut Context, ) -> JsResult { let result: JsResult<_> = (|| { let Some(mut sm) = state_machine.take() else { return Ok(CoroutineState::Done); }; loop { match sm { ArrayLikeStateMachine::LoopStart { array_like, a, len, k, } => { // vii. Repeat, while k < len, if k >= len { // viii. Perform ? Set(A, "length", 𝔽(len), true). a.set(js_string!("length"), len, true, context)?; // ix. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }. // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start // -> // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) // https://tc39.es/ecma262/#sec-asyncblockstart // g. Else if result is a return completion, then // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). global_state .resolvers .resolve .call(&JsValue::undefined(), &[a.into()], context) .js_expect("resolving functions cannot fail")?; return Ok(CoroutineState::Done); } // 1. Let Pk be ! ToString(𝔽(k)). // 2. Let kValue be ? Get(arrayLike, Pk). let k_value = array_like.get(k, context)?; state_machine.set(Some(ArrayLikeStateMachine::LoopContinue { array_like, a, len, k, })); // 3. Set kValue to ? Await(kValue). return Ok(CoroutineState::Yielded(k_value)); } ArrayLikeStateMachine::LoopContinue { array_like, a, len, k, } => { // Awaited kValue let k_value = std::mem::replace(&mut result, Ok(JsValue::undefined()))?; // 4. If mapping is true, then if let Some(mapfn) = &global_state.mapfn { // a. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). let mapped_value = mapfn.call(&global_state.this_arg, &[k_value, k.into()], context)?; state_machine.set(Some(ArrayLikeStateMachine::LoopEnd { array_like, a, len, k, mapped_value: None, })); // b. Set mappedValue to ? Await(mappedValue). return Ok(CoroutineState::Yielded(mapped_value)); } // 5. Else, let mappedValue be kValue. sm = ArrayLikeStateMachine::LoopEnd { array_like, a, len, k, mapped_value: Some(k_value), } } ArrayLikeStateMachine::LoopEnd { array_like, a, len, k, mapped_value, } => { // Either awaited `mappedValue` or directly set this from `kValue`. let result = std::mem::replace(&mut result, Ok(JsValue::undefined()))?; let mapped_value = mapped_value.unwrap_or(result); // 6. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). a.create_data_property_or_throw(k, mapped_value, context)?; // 7. Set k to k + 1. sm = ArrayLikeStateMachine::LoopStart { array_like, a, len, k: k + 1, } } } } })(); // AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start // -> // AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) // https://tc39.es/ecma262/#sec-asyncblockstart match result { Ok(cont) => Ok(cont), // i. Assert: result is a throw completion. Err(err) => { // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). global_state .resolvers .reject .call(&JsValue::undefined(), &[err.into_opaque(context)?], context) .js_expect("resolving functions cannot fail")?; Ok(CoroutineState::Done) } } } ================================================ FILE: core/engine/src/builtins/array/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `Array` object. //! //! The ECMAScript `Array` class is a global object that is used in the construction of arrays; which are high-level, list-like objects. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-array-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array use boa_gc::{Finalize, Trace}; use thin_vec::ThinVec; use crate::{ Context, JsArgs, JsExpect, JsResult, JsString, builtins::{BuiltInObject, Number, iterable::if_abrupt_close_iterator}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, object::{ CONSTRUCTOR, IndexedProperties, JsData, JsObject, internal_methods::{ InternalMethodPropertyContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, get_prototype_from_constructor, ordinary_define_own_property, ordinary_get_own_property, }, }, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::{IntegerOrInfinity, JsValue}, }; use std::cmp::{Ordering, min}; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; mod array_iterator; use crate::value::JsVariant; pub(crate) use array_iterator::ArrayIterator; #[cfg(feature = "experimental")] mod from_async; #[cfg(test)] mod tests; /// Direction for `find_via_predicate` #[derive(Clone, Copy, Eq, PartialEq)] pub(crate) enum Direction { Ascending, Descending, } /// JavaScript `Array` built-in implementation. #[derive(Debug, Clone, Copy, Trace, Finalize)] #[boa_gc(empty_trace)] pub(crate) struct Array; /// Definitions of the internal object methods for array exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { __define_own_property__: array_exotic_define_own_property, ..ORDINARY_INTERNAL_METHODS }; impl JsData for Array { fn internal_methods(&self) -> &'static InternalObjectMethods { &ARRAY_EXOTIC_INTERNAL_METHODS } } impl IntrinsicObject for Array { fn init(realm: &Realm) { let symbol_iterator = JsSymbol::iterator(); let symbol_unscopables = JsSymbol::unscopables(); let get_species = BuiltInBuilder::callable(realm, Self::get_species) .name(js_string!("get [Symbol.species]")) .build(); let values_function = BuiltInBuilder::callable_with_object( realm, realm.intrinsics().objects().array_prototype_values().into(), Self::values, ) .name(js_string!("values")) .build(); let to_string_function = BuiltInBuilder::callable_with_object( realm, realm .intrinsics() .objects() .array_prototype_to_string() .into(), Self::to_string, ) .name(js_string!("toString")) .build(); let unscopables_object = Self::unscopables_object(); let builder = BuiltInBuilder::from_standard_constructor::(realm) // Static Methods .static_method(Self::from, js_string!("from"), 1) .static_method(Self::is_array, js_string!("isArray"), 1) .static_method(Self::of, js_string!("of"), 0) .static_accessor( JsSymbol::species(), Some(get_species), None, Attribute::CONFIGURABLE, ) .property( StaticJsStrings::LENGTH, 0, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ) .method(Self::at, js_string!("at"), 1) .method(Self::concat, js_string!("concat"), 1) .method(Self::copy_within, js_string!("copyWithin"), 2) .method(Self::entries, js_string!("entries"), 0) .method(Self::every, js_string!("every"), 1) .method(Self::fill, js_string!("fill"), 1) .method(Self::filter, js_string!("filter"), 1) .method(Self::find, js_string!("find"), 1) .method(Self::find_index, js_string!("findIndex"), 1) .method(Self::find_last, js_string!("findLast"), 1) .method(Self::find_last_index, js_string!("findLastIndex"), 1) .method(Self::flat, js_string!("flat"), 0) .method(Self::flat_map, js_string!("flatMap"), 1) .method(Self::for_each, js_string!("forEach"), 1) .method(Self::includes_value, js_string!("includes"), 1) .method(Self::index_of, js_string!("indexOf"), 1) .method(Self::join, js_string!("join"), 1) .method(Self::keys, js_string!("keys"), 0) .method(Self::last_index_of, js_string!("lastIndexOf"), 1) .method(Self::map, js_string!("map"), 1) .method(Self::pop, js_string!("pop"), 0) .method(Self::push, js_string!("push"), 1) .method(Self::reduce, js_string!("reduce"), 1) .method(Self::reduce_right, js_string!("reduceRight"), 1) .method(Self::reverse, js_string!("reverse"), 0) .method(Self::shift, js_string!("shift"), 0) .method(Self::slice, js_string!("slice"), 2) .method(Self::some, js_string!("some"), 1) .method(Self::sort, js_string!("sort"), 1) .method(Self::splice, js_string!("splice"), 2) .method(Self::to_locale_string, js_string!("toLocaleString"), 0) .method(Self::to_reversed, js_string!("toReversed"), 0) .method(Self::to_sorted, js_string!("toSorted"), 1) .method(Self::to_spliced, js_string!("toSpliced"), 2) .method(Self::unshift, js_string!("unshift"), 1) .method(Self::with, js_string!("with"), 2) .property( js_string!("toString"), to_string_function, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("values"), values_function.clone(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( symbol_iterator, values_function, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( symbol_unscopables, unscopables_object, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); #[cfg(feature = "experimental")] let builder = builder.static_method(Self::from_async, js_string!("fromAsync"), 1); builder.build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for Array { const NAME: JsString = StaticJsStrings::ARRAY; } impl BuiltInConstructor for Array { const PROTOTYPE_STORAGE_SLOTS: usize = 41; const CONSTRUCTOR_STORAGE_SLOTS: usize = 6; const CONSTRUCTOR_ARGUMENTS: usize = 1; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::array; fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. let new_target = &if new_target.is_undefined() { context .active_function_object() .unwrap_or_else(|| context.intrinsics().constructors().array().constructor()) .into() } else { new_target.clone() }; // 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%"). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::array, context)?; // 3. Let numberOfArgs be the number of elements in values. let number_of_args = args.len(); // 4. If numberOfArgs = 0, then if number_of_args == 0 { // 4.a. Return ! ArrayCreate(0, proto). Ok(Self::array_create(0, Some(prototype), context)?.into()) // 5. Else if numberOfArgs = 1, then } else if number_of_args == 1 { // a. Let len be values[0]. let len = &args[0]; // b. Let array be ! ArrayCreate(0, proto). let array = Self::array_create(0, Some(prototype), context)?; // c. If Type(len) is not Number, then #[allow(clippy::if_not_else)] let int_len = if !len.is_number() { // i. Perform ! CreateDataPropertyOrThrow(array, "0", len). array .create_data_property_or_throw(0, len.clone(), context) .js_expect("this CreateDataPropertyOrThrow call must not fail")?; // ii. Let intLen be 1𝔽. 1 // d. Else, } else { // i. Let intLen be ! ToUint32(len). let int_len = len .to_u32(context) .js_expect("this ToUint32 call must not fail")?; // ii. If SameValueZero(intLen, len) is false, throw a RangeError exception. if !JsValue::same_value_zero(&int_len.into(), len) { return Err(JsNativeError::range() .with_message("invalid array length") .into()); } int_len }; // e. Perform ! Set(array, "length", intLen, true). array .set(StaticJsStrings::LENGTH, int_len, true, context) .js_expect("this Set call must not fail")?; // f. Return array. Ok(array.into()) // 6. Else, } else { // 6.a. Assert: numberOfArgs ≥ 2. debug_assert!(number_of_args >= 2); // b. Let array be ? ArrayCreate(numberOfArgs, proto). let array = Self::array_create(number_of_args as u64, Some(prototype), context)?; // c. Let k be 0. // d. Repeat, while k < numberOfArgs, // i. Let Pk be ! ToString(𝔽(k)). // ii. Let itemK be values[k]. // iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK). // iv. Set k to k + 1. array .borrow_mut() .properties_mut() .override_indexed_properties(args.iter().cloned().collect()); // e. Assert: The mathematical value of array's "length" property is numberOfArgs. // f. Return array. Ok(array.into()) } } } impl Array { /// Optimized helper function, that sets the length of the array. fn set_length(o: &JsObject, len: u64, context: &mut Context) -> JsResult<()> { if o.is_array() && len < (2u64.pow(32) - 1) { let mut borrowed_object = o.borrow_mut(); if borrowed_object.properties().shape.to_addr_usize() == context .intrinsics() .templates() .array() .shape() .to_addr_usize() { // NOTE: The "length" property is the first element. borrowed_object.properties_mut().storage[0] = JsValue::new(len); return Ok(()); } } o.set(StaticJsStrings::LENGTH, len, true, context)?; Ok(()) } /// Utility for constructing `Array` objects. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arraycreate pub(crate) fn array_create( length: u64, prototype: Option, context: &mut Context, ) -> JsResult { // 1. If length > 2^32 - 1, throw a RangeError exception. if length > 2u64.pow(32) - 1 { return Err(JsNativeError::range() .with_message("array exceeded max size") .into()); } // Fast path: if prototype.is_none() { return Ok(context .intrinsics() .templates() .array() .create(Array, vec![JsValue::new(length)])); } // 7. Return A. // 2. If proto is not present, set proto to %Array.prototype%. // 3. Let A be ! MakeBasicObject(« [[Prototype]], [[Extensible]] »). // 4. Set A.[[Prototype]] to proto. // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1. let prototype = prototype.unwrap_or_else(|| context.intrinsics().constructors().array().prototype()); // Fast path: if context .intrinsics() .templates() .array() .has_prototype(&prototype) { return Ok(context .intrinsics() .templates() .array() .create(Array, vec![JsValue::new(length)])); } let array = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, Array) .upcast(); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). ordinary_define_own_property( &array, &StaticJsStrings::LENGTH.into(), PropertyDescriptor::builder() .value(length) .writable(true) .enumerable(false) .configurable(false) .build(), &mut InternalMethodPropertyContext::new(context), )?; Ok(array) } /// Utility for constructing `Array` objects from an iterator of `JsValue`s. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createarrayfromlist pub(crate) fn create_array_from_list(elements: I, context: &Context) -> JsObject where I: IntoIterator, { // 1. Assert: elements is a List whose elements are all ECMAScript language values. // 2. Let array be ! ArrayCreate(0). // 3. Let n be 0. // 4. For each element e of elements, do // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(n)), e). // b. Set n to n + 1. // 5. Return array. // NOTE: This deviates from the spec, but it should have the same behaviour. let elements: ThinVec<_> = elements.into_iter().collect(); let length = elements.len(); context .intrinsics() .templates() .array() .create_with_indexed_properties( Array, vec![JsValue::new(length)], IndexedProperties::from_dense_js_value(elements), ) } /// Utility function for concatenating array objects. /// /// Returns a Boolean valued property that if `true` indicates that /// an object should be flattened to its array elements /// by `Array.prototype.concat`. fn is_concat_spreadable(o: &JsValue, context: &mut Context) -> JsResult { // 1. If Type(O) is not Object, return false. let Some(o) = o.as_object() else { return Ok(false); }; // 2. Let spreadable be ? Get(O, @@isConcatSpreadable). let spreadable = o.get(JsSymbol::is_concat_spreadable(), context)?; // 3. If spreadable is not undefined, return ! ToBoolean(spreadable). if !spreadable.is_undefined() { return Ok(spreadable.to_boolean()); } // 4. Return ? IsArray(O). o.is_array_abstract() } /// `get Array [ @@species ]` /// /// The `Array [ @@species ]` accessor property returns the Array constructor. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-get-array-@@species /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@species #[allow(clippy::unnecessary_wraps)] fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Return the this value. Ok(this.clone()) } /// Utility function used to specify the creation of a new Array object using a constructor /// function that is derived from `original_array`. /// /// see: pub(crate) fn array_species_create( original_array: &JsObject, length: u64, context: &mut Context, ) -> JsResult { // 1. Let isArray be ? IsArray(originalArray). // 2. If isArray is false, return ? ArrayCreate(length). if !original_array.is_array_abstract()? { return Self::array_create(length, None, context); } // 3. Let C be ? Get(originalArray, "constructor"). let c = original_array.get(CONSTRUCTOR, context)?; // 4. If IsConstructor(C) is true, then if let Some(c) = c.as_constructor() { // a. Let thisRealm be the current Realm Record. let this_realm = context.realm().clone(); // b. Let realmC be ? GetFunctionRealm(C). let realm_c = c.get_function_realm(context)?; // c. If thisRealm and realmC are not the same Realm Record, then if this_realm != realm_c && c == realm_c.intrinsics().constructors().array().constructor() { // i. If SameValue(C, realmC.[[Intrinsics]].[[%Array%]]) is true, set C to undefined. // Note: fast path to step 6. return Self::array_create(length, None, context); } } // 5. If Type(C) is Object, then let c = if let Some(c) = c.as_object() { // 5.a. Set C to ? Get(C, @@species). let c = c.get(JsSymbol::species(), context)?; // 5.b. If C is null, set C to undefined. if c.is_null_or_undefined() { JsValue::undefined() } else { c } } else { c }; // 6. If C is undefined, return ? ArrayCreate(length). if c.is_undefined() { return Self::array_create(length, None, context); } if let Some(c) = c.as_constructor() { // 8. Return ? Construct(C, « 𝔽(length) »). return c.construct(&[JsValue::new(length)], Some(&c), context); } // 7. If IsConstructor(C) is false, throw a TypeError exception. Err(JsNativeError::typ() .with_message("Symbol.species must be a constructor") .into()) } /// `Array.from(arrayLike)` /// /// The `Array.from()` static method creates a new, /// shallow-copied Array instance from an array-like or iterable object. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.from /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from pub(crate) fn from( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let items = args.get_or_undefined(0); let mapfn = args.get_or_undefined(1); let this_arg = args.get_or_undefined(2); // 2. If mapfn is undefined, let mapping be false // 3. Else, // a. If IsCallable(mapfn) is false, throw a TypeError exception. // b. Let mapping be true. let mapping = match mapfn.variant() { JsVariant::Undefined => None, JsVariant::Object(o) if o.is_callable() => Some(o), _ => { return Err(JsNativeError::typ() .with_message(format!("`{}` is not callable", mapfn.type_of())) .into()); } }; // 4. Let usingIterator be ? GetMethod(items, @@iterator). let using_iterator = items.get_method(JsSymbol::iterator(), context)?; let Some(using_iterator) = using_iterator else { // 6. NOTE: items is not an Iterable so assume it is an array-like object. // 7. Let arrayLike be ! ToObject(items). let array_like = items .to_object(context) .js_expect("should not fail according to spec")?; // 8. Let len be ? LengthOfArrayLike(arrayLike). let len = array_like.length_of_array_like(context)?; // 9. If IsConstructor(C) is true, then // a. Let A be ? Construct(C, « 𝔽(len) »). // 10. Else, // a. Let A be ? ArrayCreate(len). let a = match this.as_constructor() { Some(constructor) => constructor.construct(&[len.into()], None, context)?, _ => Self::array_create(len, None, context)?, }; // 11. Let k be 0. // 12. Repeat, while k < len, // ... // f. Set k to k + 1. for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ? Get(arrayLike, Pk). let k_value = array_like.get(k, context)?; let mapped_value = if let Some(ref mapfn) = mapping { // c. If mapping is true, then // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). mapfn.call(this_arg, &[k_value, k.into()], context)? } else { // d. Else, let mappedValue be kValue. k_value }; // e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). a.create_data_property_or_throw(k, mapped_value, context)?; } // 13. Perform ? Set(A, "length", 𝔽(len), true). a.set(StaticJsStrings::LENGTH, len, true, context)?; // 14. Return A. return Ok(a.into()); }; // 5. If usingIterator is not undefined, then // a. If IsConstructor(C) is true, then // i. Let A be ? Construct(C). // b. Else, // i. Let A be ? ArrayCreate(0en). let a = match this.as_constructor() { Some(constructor) => constructor.construct(&[], None, context)?, _ => Self::array_create(0, None, context)?, }; // c. Let iteratorRecord be ? GetIteratorFromMethod(items, usingIterator). let mut iterator_record = items.get_iterator_from_method(&using_iterator, context)?; // d. Let k be 0. // e. Repeat, // i. If k ≥ 2^53 - 1 (MAX_SAFE_INTEGER), then // ... // ix. Set k to k + 1. for k in 0..9_007_199_254_740_991_u64 { // iii. Let next be ? IteratorStepValue(iteratorRecord). let Some(next) = iterator_record.step_value(context)? else { // iv. If next is done, then // 1. Perform ? Set(A, "length", 𝔽(k), true). a.set(StaticJsStrings::LENGTH, k, true, context)?; // 2. Return A. return Ok(a.into()); }; // v. If mapping is true, then let mapped_value = if let Some(ref mapfn) = mapping { // 1. Let mappedValue be Completion(Call(mapper, thisArg, « next, 𝔽(k) »)). let mapped_value = mapfn.call(this_arg, &[next, k.into()], context); // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord). if_abrupt_close_iterator!(mapped_value, iterator_record, context) } else { // vi. Else, // 1. Let mappedValue be next. next }; // vii. Let defineStatus be Completion(CreateDataPropertyOrThrow(A, Pk, mappedValue)). let define_status = a.create_data_property_or_throw(k, mapped_value, context); // viii. IfAbruptCloseIterator(defineStatus, iteratorRecord). if_abrupt_close_iterator!(define_status, iterator_record, context); } // NOTE: The loop above has to return before it reaches iteration limit, // which is why it's safe to have this as the fallback return // // 1. Let error be ThrowCompletion(a newly created TypeError object). let error = Err(JsNativeError::typ() .with_message("Invalid array length") .into()); // 2. Return ? IteratorClose(iteratorRecord, error). iterator_record.close(error, context) } /// `Array.isArray( arg )` /// /// The isArray function takes one argument arg, and returns the Boolean value true /// if the argument is an object whose class internal property is "Array"; otherwise it returns false. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.isarray /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray pub(crate) fn is_array( _: &JsValue, args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Return ? IsArray(arg). args.get_or_undefined(0).is_array().map(Into::into) } /// `Array.of(...items)` /// /// The Array.of method creates a new Array instance from a variable number of arguments, /// regardless of the number or type of arguments. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.of /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of pub(crate) fn of(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let len be the number of elements in items. // 2. Let lenNumber be 𝔽(len). let len = args.len(); // 3. Let C be the this value. // 4. If IsConstructor(C) is true, then // a. Let A be ? Construct(C, « lenNumber »). // 5. Else, // a. Let A be ? ArrayCreate(len). let a = match this.as_constructor() { Some(constructor) => constructor.construct(&[len.into()], None, context)?, _ => Self::array_create(len as u64, None, context)?, }; // 6. Let k be 0. // 7. Repeat, while k < len, for (k, value) in args.iter().enumerate() { // a. Let kValue be items[k]. // b. Let Pk be ! ToString(𝔽(k)). // c. Perform ? CreateDataPropertyOrThrow(A, Pk, kValue). a.create_data_property_or_throw(k, value.clone(), context)?; // d. Set k to k + 1. } // 8. Perform ? Set(A, "length", lenNumber, true). Self::set_length(&a, len as u64, context)?; // 9. Return A. Ok(a.into()) } ///'Array.prototype.at(index)' /// /// The `at()` method takes an integer value and returns the item at that /// index, allowing for positive and negative integers. Negative integers /// count back from the last item in the array. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.at /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { //1. let O be ? ToObject(this value) let obj = this.to_object(context)?; //2. let len be ? LengthOfArrayLike(O) let len = obj.length_of_array_like(context)? as i64; //3. let relativeIndex be ? ToIntegerOrInfinity(index) let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?; let k = match relative_index { //4. if relativeIndex >= 0, then let k be relativeIndex //check if positive and if below length of array IntegerOrInfinity::Integer(i) if i >= 0 && i < len => i, //5. Else, let k be len + relativeIndex //integer should be negative, so abs() and check if less than or equal to length of array IntegerOrInfinity::Integer(i) if i < 0 && i.abs() <= len => len + i, //handle most likely impossible case of //IntegerOrInfinity::NegativeInfinity || IntegerOrInfinity::PositiveInfinity //by returning undefined _ => return Ok(JsValue::undefined()), }; //6. if k < 0 or k >= len, //handled by the above match guards //7. Return ? Get(O, !ToString(𝔽(k))) obj.get(k, context) } /// `Array.prototype.concat(...arguments)` /// /// When the concat method is called with zero or more arguments, it returns an /// array containing the array elements of the object followed by the array /// elements of each argument in order. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat pub(crate) fn concat( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let obj = this.to_object(context)?; // 2. Let A be ? ArraySpeciesCreate(O, 0). let arr = Self::array_species_create(&obj, 0, context)?; // 3. Let n be 0. let mut n = 0; // 4. Prepend O to items. // 5. For each element E of items, do for item in std::iter::once(&JsValue::new(obj)).chain(args.iter()) { // a. Let spreadable be ? IsConcatSpreadable(E). let spreadable = Self::is_concat_spreadable(item, context)?; // b. If spreadable is true, then if spreadable { // item is guaranteed to be an object since is_concat_spreadable checks it, // so we can call `.unwrap()` let item = item.as_object().js_expect("guaranteed to be an object")?; // i. Let k be 0. // ii. Let len be ? LengthOfArrayLike(E). let len = item.length_of_array_like(context)?; // iii. If n + len > 2^53 - 1, throw a TypeError exception. if n + len > Number::MAX_SAFE_INTEGER as u64 { return Err(JsNativeError::typ() .with_message( "length + number of arguments exceeds the max safe integer limit", ) .into()); } // iv. Repeat, while k < len, for k in 0..len { // 1. Let P be ! ToString(𝔽(k)). // 2. Let exists be ? HasProperty(E, P). // 3. If exists is true, then // 3.a. Let subElement be ? Get(E, P). if let Some(sub_element) = item.try_get(k, context)? { // b. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), subElement). arr.create_data_property_or_throw(n, sub_element, context)?; } // 4. Set n to n + 1. n += 1; // 5. Set k to k + 1. } } // c. Else, else { // i. NOTE: E is added as a single item rather than spread. // ii. If n ≥ 2^53 - 1, throw a TypeError exception. if n >= Number::MAX_SAFE_INTEGER as u64 { return Err(JsNativeError::typ() .with_message("length exceeds the max safe integer limit") .into()); } // iii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), E). arr.create_data_property_or_throw(n, item.clone(), context)?; // iv. Set n to n + 1. n += 1; } } // 6. Perform ? Set(A, "length", 𝔽(n), true). Self::set_length(&arr, n, context)?; // 7. Return A. Ok(JsValue::new(arr)) } /// `Array.prototype.push( ...items )` /// /// The arguments are appended to the end of the array, in the order in which /// they appear. The new length of the array is returned as the result of the /// call. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push pub(crate) fn push( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let mut len = o.length_of_array_like(context)?; // 3. Let argCount be the number of elements in items. let arg_count = args.len() as u64; // 4. If len + argCount > 2^53 - 1, throw a TypeError exception. if len + arg_count > 2u64.pow(53) - 1 { return Err(JsNativeError::typ() .with_message( "the length + the number of arguments exceed the maximum safe integer limit", ) .into()); } // 5. For each element E of items, do for element in args.iter().cloned() { // a. Perform ? Set(O, ! ToString(𝔽(len)), E, true). o.set(len, element, true, context)?; // b. Set len to len + 1. len += 1; } // 6. Perform ? Set(O, "length", 𝔽(len), true). Self::set_length(&o, len, context)?; // 7. Return 𝔽(len). Ok(len.into()) } /// `Array.prototype.pop()` /// /// The last element of the array is removed from the array and returned. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop pub(crate) fn pop(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If len = 0, then if len == 0 { // a. Perform ? Set(O, "length", +0𝔽, true). Self::set_length(&o, 0, context)?; // b. Return undefined. Ok(JsValue::undefined()) // 4. Else, } else { // a. Assert: len > 0. // b. Let newLen be 𝔽(len - 1). let new_len = len - 1; // c. Let index be ! ToString(newLen). let index = new_len; // d. Let element be ? Get(O, index). let element = o.get(index, context)?; // e. Perform ? DeletePropertyOrThrow(O, index). o.delete_property_or_throw(index, context)?; // f. Perform ? Set(O, "length", newLen, true). Self::set_length(&o, new_len, context)?; // g. Return element. Ok(element) } } /// `Array.prototype.forEach( callbackFn [ , thisArg ] )` /// /// This method executes the provided callback function for each element in the array. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach pub(crate) fn for_each( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ().with_message("Array.prototype.forEach: invalid callback function") })?; // 4. Let k be 0. // 5. Repeat, while k < len, for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). let pk = k; // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(k_value) = o.try_get(pk, context)? { // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let this_arg = args.get_or_undefined(1); callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?; } // d. Set k to k + 1. } // 6. Return undefined. Ok(JsValue::undefined()) } /// `Array.prototype.join( separator )` /// /// The elements of the array are converted to Strings, and these Strings are /// then concatenated, separated by occurrences of the separator. If no /// separator is provided, a single comma is used as the separator. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join pub(crate) fn join( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If separator is undefined, let sep be the single-element String ",". // 4. Else, let sep be ? ToString(separator). let separator = args.get_or_undefined(0); let separator = if separator.is_undefined() { js_string!(",") } else { separator.to_string(context)? }; // 5. Let R be the empty String. let mut r = Vec::with_capacity(len as usize + len.saturating_sub(1) as usize); // 6. Let k be 0. // 7. Repeat, while k < len, for k in 0..len { // a. If k > 0, set R to the string-concatenation of R and sep. if k > 0 { r.push(separator.clone()); } // b. Let element be ? Get(O, ! ToString(𝔽(k))). let element = o.get(k, context)?; // c. If element is undefined, null or the array itself, let next be the empty String; otherwise, let next be ? ToString(element). let next = if element.is_null_or_undefined() || &element == this { js_string!() } else { element.to_string(context)? }; // d. Set R to the string-concatenation of R and next. r.push(next.clone()); // e. Set k to k + 1. } // 8. Return R. Ok(js_string!(&r[..]).into()) } /// `Array.prototype.toString( separator )` /// /// The toString function is intentionally generic; it does not require that /// its this value be an Array object. Therefore it can be transferred to /// other kinds of objects for use as a method. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let array be ? ToObject(this value). let array = this.to_object(context)?; // 2. Let func be ? Get(array, "join"). let func = array.get(js_string!("join"), context)?; // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%. // 4. Return ? Call(func, array). if let Some(func) = func.as_callable() { func.call(&array.into(), &[], context) } else { crate::builtins::object::OrdinaryObject::to_string(&array.into(), &[], context) } } /// `Array.prototype.reverse()` /// /// The elements of the array are rearranged so as to reverse their order. /// The object is returned as the result of the call. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse #[allow(clippy::else_if_without_else)] pub(crate) fn reverse( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let middle be floor(len / 2). let middle = len / 2; // 4. Let lower be 0. let mut lower = 0; // 5. Repeat, while lower ≠ middle, while lower != middle { // a. Let upper be len - lower - 1. let upper = len - lower - 1; // Skipped: b. Let upperP be ! ToString(𝔽(upper)). // Skipped: c. Let lowerP be ! ToString(𝔽(lower)). // d. Let lowerExists be ? HasProperty(O, lowerP). // e. If lowerExists is true, then // e.i. Let lowerValue be ? Get(O, lowerP). let lower_value = o.try_get(lower, context)?; // f. Let upperExists be ? HasProperty(O, upperP). // g. If upperExists is true, then // g.i. Let upperValue be ? Get(O, upperP). let upper_value = o.try_get(upper, context)?; match (lower_value, upper_value) { // h. If lowerExists is true and upperExists is true, then (Some(lower_value), Some(upper_value)) => { // i. Perform ? Set(O, lowerP, upperValue, true). o.set(lower, upper_value, true, context)?; // ii. Perform ? Set(O, upperP, lowerValue, true). o.set(upper, lower_value, true, context)?; } // i. Else if lowerExists is false and upperExists is true, then (None, Some(upper_value)) => { // i. Perform ? Set(O, lowerP, upperValue, true). o.set(lower, upper_value, true, context)?; // ii. Perform ? DeletePropertyOrThrow(O, upperP). o.delete_property_or_throw(upper, context)?; } // j. Else if lowerExists is true and upperExists is false, then (Some(lower_value), None) => { // i. Perform ? DeletePropertyOrThrow(O, lowerP). o.delete_property_or_throw(lower, context)?; // ii. Perform ? Set(O, upperP, lowerValue, true). o.set(upper, lower_value, true, context)?; } // k. Else, (None, None) => { // i. Assert: lowerExists and upperExists are both false. // ii. No action is required. } } // l. Set lower to lower + 1. lower += 1; } // 6. Return O. Ok(o.into()) } /// [`Array.prototype.toReversed()`][spec] /// /// Reverses this array, returning the result into a copy of the array. /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.toreversed pub(crate) fn to_reversed( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let A be ? ArrayCreate(len). let a = Array::array_create(len, None, context)?; // 4. Let k be 0. // 5. Repeat, while k < len, for i in 0..len { // a. Let from be ! ToString(𝔽(len - k - 1)). let from = len - i - 1; // b. Let Pk be ! ToString(𝔽(k)). // c. Let fromValue be ? Get(O, from). let from_value = o.get(from, context)?; // d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). a.create_data_property_or_throw(i, from_value, context) .js_expect("cannot fail per the spec")?; // e. Set k to k + 1. } // 6. Return A. Ok(a.into()) } /// `Array.prototype.shift()` /// /// The first element of the array is removed from the array and returned. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift pub(crate) fn shift(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If len = 0, then if len == 0 { // a. Perform ? Set(O, "length", +0𝔽, true). Self::set_length(&o, 0, context)?; // b. Return undefined. return Ok(JsValue::undefined()); } // Small optimization for arrays using dense properties // TODO: this optimization could be generalized to many other objects with // slot-based dense property maps. if o.is_array() { let mut o_borrow = o.borrow_mut(); if let IndexedProperties::DenseI32(dense) = &mut o_borrow.properties_mut().indexed_properties && len <= dense.len() as u64 { let v = dense.remove(0); drop(o_borrow); Self::set_length(&o, len - 1, context)?; return Ok(v.into()); } if let IndexedProperties::DenseF64(dense) = &mut o_borrow.properties_mut().indexed_properties && len <= dense.len() as u64 { let v = dense.remove(0); drop(o_borrow); Self::set_length(&o, len - 1, context)?; return Ok(v.into()); } if let Some(dense) = o_borrow.properties_mut().dense_indexed_properties_mut() && len <= dense.len() as u64 { let v = dense.remove(0); drop(o_borrow); Self::set_length(&o, len - 1, context)?; return Ok(v); } } // 4. Let first be ? Get(O, "0"). let first = o.get(0, context)?; // 5. Let k be 1. // 6. Repeat, while k < len, for k in 1..len { // a. Let from be ! ToString(𝔽(k)). let from = k; // b. Let to be ! ToString(𝔽(k - 1)). let to = k - 1; // c. Let fromPresent be ? HasProperty(O, from). // d. If fromPresent is true, then // d.i. Let fromVal be ? Get(O, from). if let Some(from_val) = o.try_get(from, context)? { // ii. Perform ? Set(O, to, fromVal, true). o.set(to, from_val, true, context)?; // e. Else, } else { // i. Assert: fromPresent is false. // ii. Perform ? DeletePropertyOrThrow(O, to). o.delete_property_or_throw(to, context)?; } // f. Set k to k + 1. } // 7. Perform ? DeletePropertyOrThrow(O, ! ToString(𝔽(len - 1))). o.delete_property_or_throw(len - 1, context)?; // 8. Perform ? Set(O, "length", 𝔽(len - 1), true). Self::set_length(&o, len - 1, context)?; // 9. Return first. Ok(first) } /// `Array.prototype.unshift( ...items )` /// /// The arguments are prepended to the start of the array, such that their order /// within the array is the same as the order in which they appear in the /// argument list. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift pub(crate) fn unshift( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let argCount be the number of elements in items. let arg_count = args.len() as u64; // 4. If argCount > 0, then if arg_count > 0 { // a. If len + argCount > 2^53 - 1, throw a TypeError exception. if len + arg_count > 2u64.pow(53) - 1 { return Err(JsNativeError::typ() .with_message("length + number of arguments exceeds the max safe integer limit") .into()); } // b. Let k be len. let mut k = len; // c. Repeat, while k > 0, while k > 0 { // i. Let from be ! ToString(𝔽(k - 1)). let from = k - 1; // ii. Let to be ! ToString(𝔽(k + argCount - 1)). let to = k + arg_count - 1; // iii. Let fromPresent be ? HasProperty(O, from). // iv. If fromPresent is true, then // iv.1. Let fromValue be ? Get(O, from). if let Some(from_value) = o.try_get(from, context)? { // 2. Perform ? Set(O, to, fromValue, true). o.set(to, from_value, true, context)?; // v. Else, } else { // 1. Assert: fromPresent is false. // 2. Perform ? DeletePropertyOrThrow(O, to). o.delete_property_or_throw(to, context)?; } // vi. Set k to k - 1. k -= 1; } // d. Let j be +0𝔽. // e. For each element E of items, do for (j, e) in args.iter().enumerate() { // i. Perform ? Set(O, ! ToString(j), E, true). o.set(j, e.clone(), true, context)?; // ii. Set j to j + 1𝔽. } } // 5. Perform ? Set(O, "length", 𝔽(len + argCount), true). Self::set_length(&o, len + arg_count, context)?; // 6. Return 𝔽(len + argCount). Ok((len + arg_count).into()) } /// `Array.prototype.every( callback, [ thisArg ] )` /// /// The every method executes the provided callback function once for each /// element present in the array until it finds the one where callback returns /// a falsy value. It returns `false` if it finds such element, otherwise it /// returns `true`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every pub(crate) fn every( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ().with_message("Array.prototype.every: callback is not callable") })?; let this_arg = args.get_or_undefined(1); // 4. Let k be 0. // 5. Repeat, while k < len, for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(k_value) = o.try_get(k, context)? { // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let test_result = callback .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is false, return false. if !test_result { return Ok(JsValue::new(false)); } } // d. Set k to k + 1. } // 6. Return true. Ok(JsValue::new(true)) } /// `Array.prototype.map( callback, [ thisArg ] )` /// /// For each element in the array the callback function is called, and a new /// array is constructed from the return values of these calls. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map pub(crate) fn map( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ().with_message("Array.prototype.map: Callbackfn is not callable") })?; // 4. Let A be ? ArraySpeciesCreate(O, len). let a = Self::array_species_create(&o, len, context)?; let this_arg = args.get_or_undefined(1); // 5. Let k be 0. // 6. Repeat, while k < len, for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let k_present be ? HasProperty(O, Pk). // c. If k_present is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(k_value) = o.try_get(k, context)? { // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?; // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). a.create_data_property_or_throw(k, mapped_value, context)?; } // d. Set k to k + 1. } // 7. Return A. Ok(a.into()) } /// `Array.prototype.indexOf( searchElement[, fromIndex ] )` /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf pub(crate) fn index_of( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)? as i64; // 3. If len is 0, return -1𝔽. if len == 0 { return Ok(JsValue::new(-1)); } // 4. Let n be ? ToIntegerOrInfinity(fromIndex). let n = args .get(1) .cloned() .unwrap_or_default() .to_integer_or_infinity(context)?; // 5. Assert: If fromIndex is undefined, then n is 0. let n = match n { // 6. If n is +∞, return -1𝔽. IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(-1)), // 7. Else if n is -∞, set n to 0. IntegerOrInfinity::NegativeInfinity => 0, IntegerOrInfinity::Integer(value) => value, }; // 8. If n ≥ 0, then let mut k; if n >= 0 { // a. Let k be n. k = n; // 9. Else, } else { // a. Let k be len + n. k = len + n; // b. If k < 0, set k to 0. if k < 0 { k = 0; } } let search_element = args.get_or_undefined(0); // 10. Repeat, while k < len, while k < len { // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))). // b. If kPresent is true, then // b.i. Let elementK be ? Get(O, ! ToString(𝔽(k))). if let Some(element_k) = o.try_get(k, context)? { // ii. Let same be IsStrictlyEqual(searchElement, elementK). // iii. If same is true, return 𝔽(k). if search_element.strict_equals(&element_k) { return Ok(JsValue::new(k)); } } // c. Set k to k + 1. k += 1; } // 11. Return -1𝔽. Ok(JsValue::new(-1)) } /// `Array.prototype.lastIndexOf( searchElement[, fromIndex ] )` /// /// /// `lastIndexOf` compares searchElement to the elements of the array in descending order /// using the Strict Equality Comparison algorithm, and if found at one or more indices, /// returns the largest such index; otherwise, -1 is returned. /// /// The optional second argument fromIndex defaults to the array's length minus one /// (i.e. the whole array is searched). If it is greater than or equal to the length of the array, /// the whole array will be searched. If it is negative, it is used as the offset from the end /// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf pub(crate) fn last_index_of( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)? as i64; // 3. If len is 0, return -1𝔽. if len == 0 { return Ok(JsValue::new(-1)); } // 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. let n = if let Some(from_index) = args.get(1) { from_index.to_integer_or_infinity(context)? } else { IntegerOrInfinity::Integer(len - 1) }; let mut k = match n { // 5. If n is -∞, return -1𝔽. IntegerOrInfinity::NegativeInfinity => return Ok(JsValue::new(-1)), // 6. If n ≥ 0, then // a. Let k be min(n, len - 1). IntegerOrInfinity::Integer(n) if n >= 0 => min(n, len - 1), IntegerOrInfinity::PositiveInfinity => len - 1, // 7. Else, // a. Let k be len + n. IntegerOrInfinity::Integer(n) => len + n, }; let search_element = args.get_or_undefined(0); // 8. Repeat, while k ≥ 0, while k >= 0 { // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))). // b. If kPresent is true, then // b.i. Let elementK be ? Get(O, ! ToString(𝔽(k))). if let Some(element_k) = o.try_get(k, context)? { // ii. Let same be IsStrictlyEqual(searchElement, elementK). // iii. If same is true, return 𝔽(k). if JsValue::strict_equals(search_element, &element_k) { return Ok(JsValue::new(k)); } } // c. Set k to k - 1. k -= 1; } // 9. Return -1𝔽. Ok(JsValue::new(-1)) } /// `Array.prototype.find( callback, [thisArg] )` /// /// The find method executes the callback function once for each index of the array /// until the callback returns a truthy value. If so, find immediately returns the value /// of that element. Otherwise, find returns undefined. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find pub(crate) fn find( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 3. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). let (_, value) = find_via_predicate( &o, len, Direction::Ascending, predicate, this_arg, context, "Array.prototype.find", )?; // 4. Return findRec.[[Value]]. Ok(value) } /// `Array.prototype.findIndex( predicate [ , thisArg ] )` /// /// This method executes the provided predicate function for each element of the array. /// If the predicate function returns `true` for an element, this method returns the index of the element. /// If all elements return `false`, the value `-1` is returned. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex pub(crate) fn find_index( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 3. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). let (index, _) = find_via_predicate( &o, len, Direction::Ascending, predicate, this_arg, context, "Array.prototype.findIndex", )?; // 4. Return findRec.[[Index]]. Ok(index) } /// `Array.prototype.findLast( predicate, [thisArg] )` /// /// findLast calls predicate once for each element of the array, in descending order, /// until it finds one where predicate returns true. If such an element is found, findLast /// immediately returns that element value. Otherwise, findLast returns undefined. /// /// More information: /// - [ECMAScript proposal][spec] /// /// [spec]: https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlast pub(crate) fn find_last( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 3. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). let (_, value) = find_via_predicate( &o, len, Direction::Descending, predicate, this_arg, context, "Array.prototype.findLast", )?; // 4. Return findRec.[[Value]]. Ok(value) } /// `Array.prototype.findLastIndex( predicate [ , thisArg ] )` /// /// `findLastIndex` calls predicate once for each element of the array, in descending order, /// until it finds one where predicate returns true. If such an element is found, `findLastIndex` /// immediately returns the index of that element value. Otherwise, `findLastIndex` returns -1. /// /// More information: /// - [ECMAScript proposal][spec] /// /// [spec]: https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlastindex pub(crate) fn find_last_index( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 3. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). let (index, _) = find_via_predicate( &o, len, Direction::Descending, predicate, this_arg, context, "Array.prototype.findLastIndex", )?; // 4. Return findRec.[[Index]]. Ok(index) } /// `Array.prototype.flat( [depth] )` /// /// This method creates a new array with all sub-array elements concatenated into it /// recursively up to the specified depth. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat pub(crate) fn flat( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ToObject(this value) let o = this.to_object(context)?; // 2. Let sourceLen be LengthOfArrayLike(O) let source_len = o.length_of_array_like(context)?; // 3. Let depthNum be 1 // 4. If depth is not undefined, then set depthNum to IntegerOrInfinity(depth) let depth = args.get_or_undefined(0); let depth_num = if depth.is_undefined() { 1 } else { // a. Set depthNum to ? ToIntegerOrInfinity(depth). // b. If depthNum < 0, set depthNum to 0. match depth.to_integer_or_infinity(context)? { IntegerOrInfinity::Integer(value) if value >= 0 => value as u64, IntegerOrInfinity::PositiveInfinity => u64::MAX, _ => 0, } }; // 5. Let A be ArraySpeciesCreate(O, 0) let a = Self::array_species_create(&o, 0, context)?; // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum) Self::flatten_into_array( &a, &o, source_len, 0, depth_num, None, &JsValue::undefined(), context, )?; Ok(a.into()) } /// `Array.prototype.flatMap( callback, [ thisArg ] )` /// /// This method returns a new array formed by applying a given callback function to /// each element of the array, and then flattening the result by one level. It is /// identical to a `map()` followed by a `flat()` of depth 1, but slightly more /// efficient than calling those two methods separately. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flatMap /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap pub(crate) fn flat_map( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ToObject(this value) let o = this.to_object(context)?; // 2. Let sourceLen be LengthOfArrayLike(O) let source_len = o.length_of_array_like(context)?; // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. let mapper_function = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ().with_message("flatMap mapper function is not callable") })?; // 4. Let A be ? ArraySpeciesCreate(O, 0). let a = Self::array_species_create(&o, 0, context)?; // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg). Self::flatten_into_array( &a, &o, source_len, 0, 1, Some(&mapper_function), args.get_or_undefined(1), context, )?; // 6. Return A Ok(a.into()) } /// Abstract method `FlattenIntoArray`. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-flattenintoarray #[allow(clippy::too_many_arguments)] fn flatten_into_array( target: &JsObject, source: &JsObject, source_len: u64, start: u64, depth: u64, mapper_function: Option<&JsObject>, this_arg: &JsValue, context: &mut Context, ) -> JsResult { // 1. Assert target is Object // 2. Assert source is Object // 3. Assert if mapper_function is present, then: // - IsCallable(mapper_function) is true // - thisArg is present // - depth is 1 // 4. Let targetIndex be start let mut target_index = start; // 5. Let sourceIndex be 0 let mut source_index = 0; // 6. Repeat, while R(sourceIndex) < sourceLen while source_index < source_len { // a. Let P be ToString(sourceIndex) let p = source_index; // b. Let exists be ? HasProperty(source, P). // c. If exists is true, then // c.i. Let element be Get(source, P) if let Some(mut element) = source.try_get(p, context)? { // ii. If mapperFunction is present, then if let Some(mapper_function) = mapper_function { // 1. Set element to ? Call(mapperFunction, thisArg, <>) element = mapper_function.call( this_arg, &[element, source_index.into(), source.clone().into()], context, )?; } // iii. Let shouldFlatten be false // iv. If depth > 0, then let should_flatten = if depth > 0 { // 1. Set shouldFlatten to ? IsArray(element). element.is_array()? } else { false }; // v. If shouldFlatten is true if should_flatten { // For `should_flatten` to be true, element must be an object. let element = element.as_object().js_expect("must be an object")?; // 1. If depth is +Infinity let newDepth be +Infinity let new_depth = if depth == u64::MAX { u64::MAX // 2. Else, let newDepth be depth - 1 } else { depth - 1 }; // 3. Let elementLen be ? LengthOfArrayLike(element) let element_len = element.length_of_array_like(context)?; // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth) target_index = Self::flatten_into_array( target, &element, element_len, target_index, new_depth, None, &JsValue::undefined(), context, )?; // vi. Else } else { // 1. If targetIndex >= 2^53 - 1, throw a TypeError exception if target_index >= Number::MAX_SAFE_INTEGER as u64 { return Err(JsNativeError::typ() .with_message("Target index exceeded max safe integer value") .into()); } // 2. Perform ? CreateDataPropertyOrThrow(target, targetIndex, element) target.create_data_property_or_throw(target_index, element, context)?; // 3. Set targetIndex to targetIndex + 1 target_index += 1; } } // d. Set sourceIndex to sourceIndex + 1 source_index += 1; } // 7. Return targetIndex Ok(target_index) } /// `Array.prototype.fill( value[, start[, end]] )` /// /// The method fills (modifies) all the elements of an array from start index (default 0) /// to an end index (default array length) with a static value. It returns the modified array. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill pub(crate) fn fill( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let relativeStart be ? ToIntegerOrInfinity(start). // 4. If relativeStart is -∞, let k be 0. // 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0). // 6. Else, let k be min(relativeStart, len). let mut k = Self::get_relative_start(context, args.get_or_undefined(1), len)?; // 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). // 8. If relativeEnd is -∞, let final be 0. // 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). // 10. Else, let final be min(relativeEnd, len). let final_ = Self::get_relative_end(context, args.get_or_undefined(2), len)?; let value = args.get_or_undefined(0); // 11. Repeat, while k < final, while k < final_ { // a. Let Pk be ! ToString(𝔽(k)). let pk = k; // b. Perform ? Set(O, Pk, value, true). o.set(pk, value.clone(), true, context)?; // c. Set k to k + 1. k += 1; } // 12. Return O. Ok(o.into()) } /// `Array.prototype.includes( valueToFind [, fromIndex] )` /// /// Determines whether an array includes a certain value among its entries, returning `true` or `false` as appropriate. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes pub(crate) fn includes_value( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)? as i64; // 3. If len is 0, return false. if len == 0 { return Ok(JsValue::new(false)); } // 4. Let n be ? ToIntegerOrInfinity(fromIndex). let n = args .get(1) .cloned() .unwrap_or_default() .to_integer_or_infinity(context)?; // 5. Assert: If fromIndex is undefined, then n is 0. // 6. If n is +∞, return false. // 7. Else if n is -∞, set n to 0. let n = match n { IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(false)), IntegerOrInfinity::NegativeInfinity => 0, IntegerOrInfinity::Integer(value) => value, }; // 8. If n ≥ 0, then let mut k; if n >= 0 { // a. Let k be n. k = n; // 9. Else, } else { // a. Let k be len + n. k = len + n; // b. If k < 0, set k to 0. if k < 0 { k = 0; } } let search_element = args.get_or_undefined(0); // 10. Repeat, while k < len, while k < len { // a. Let elementK be ? Get(O, ! ToString(𝔽(k))). let element_k = o.get(k, context)?; // b. If SameValueZero(searchElement, elementK) is true, return true. if JsValue::same_value_zero(search_element, &element_k) { return Ok(JsValue::new(true)); } // c. Set k to k + 1. k += 1; } // 11. Return false. Ok(JsValue::new(false)) } /// `Array.prototype.slice( [begin[, end]] )` /// /// The slice method takes two arguments, start and end, and returns an array containing the /// elements of the array from element start up to, but not including, element end (or through the /// end of the array if end is undefined). If start is negative, it is treated as length + start /// where length is the length of the array. If end is negative, it is treated as length + end where /// length is the length of the array. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice pub(crate) fn slice( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let relativeStart be ? ToIntegerOrInfinity(start). // 4. If relativeStart is -∞, let k be 0. // 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0). // 6. Else, let k be min(relativeStart, len). let mut k = Self::get_relative_start(context, args.get_or_undefined(0), len)?; // 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). // 8. If relativeEnd is -∞, let final be 0. // 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). // 10. Else, let final be min(relativeEnd, len). let final_ = Self::get_relative_end(context, args.get_or_undefined(1), len)?; // 11. Let count be max(final - k, 0). let count = final_.saturating_sub(k); // 12. Let A be ? ArraySpeciesCreate(O, count). let a = Self::array_species_create(&o, count, context)?; // 13. Let n be 0. let mut n: u64 = 0; // 14. Repeat, while k < final, while k < final_ { // a. Let Pk be ! ToString(𝔽(k)). let pk = k; // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(k_value) = o.try_get(pk, context)? { // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), kValue). a.create_data_property_or_throw(n, k_value, context)?; } // d. Set k to k + 1. k += 1; // e. Set n to n + 1. n += 1; } // 15. Perform ? Set(A, "length", 𝔽(n), true). Self::set_length(&a, n, context)?; // 16. Return A. Ok(a.into()) } /// [`Array.prototype.toLocaleString ( [ locales [ , options ] ] )`][spec]. /// /// Returns a string representing the elements of the array. The elements are converted to /// strings using their `toLocaleString` methods and these strings are separated by a /// locale-specific string (such as a comma ","). /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sup-array.prototype.tolocalestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toLocaleString pub(crate) fn to_locale_string( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let array be ? ToObject(this value). let array = this.to_object(context)?; // 2. Let len be ? ToLength(? Get(array, "length")). let len = array.length_of_array_like(context)?; let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); // 3. Let separator be the implementation-defined list-separator String value appropriate for the host environment's current locale (such as ", "). let separator = { #[cfg(feature = "intl")] { use crate::builtins::intl::locale::default_locale; use icu_list::{ ListFormatter, ListFormatterPreferences, options::ListFormatterOptions, }; let locale = default_locale(context.intl_provider().locale_canonicalizer()?); let preferences = ListFormatterPreferences::from(&locale); let formatter = ListFormatter::try_new_unit_with_buffer_provider( context.intl_provider().erased_provider(), preferences, ListFormatterOptions::default(), ) .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; // Ask ICU for the list pattern literal by formatting two empty elements. // For many locales this yields ", ", but it may differ. js_string!( formatter.format_to_string(std::iter::once("").chain(std::iter::once(""))) ) } #[cfg(not(feature = "intl"))] { js_string!(", ") } }; // 4. Let R be the empty String. let mut r = Vec::with_capacity(len as usize + len.saturating_sub(1) as usize); // 5. Let k be 0. // 6. Repeat, while k < len, for k in 0..len { // a. If k > 0, then if k > 0 { // i. Set R to the string-concatenation of R and separator. r.extend(separator.iter()); } // b. Let nextElement be ? Get(array, ! ToString(k)). let next = array.get(k, context)?; // c. If nextElement is not undefined or null, then if !next.is_null_or_undefined() { // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)). let s = next .invoke( js_string!("toLocaleString"), &[locales.clone(), options.clone()], context, )? .to_string(context)?; // ii. Set R to the string-concatenation of R and S. r.extend(s.iter()); } // d. Increase k by 1. } // 7. Return R. Ok(js_string!(&r[..]).into()) } /// Gets the delete count of a splice operation. fn get_delete_count( len: u64, actual_start: u64, start: Option<&JsValue>, delete_count: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 8. If start is not present, then let actual_delete_count = if start.is_none() { // a. Let actualDeleteCount be 0. 0 } // 10. Else, else if let Some(delete_count) = delete_count { // a. Let dc be ? ToIntegerOrInfinity(deleteCount). let dc = delete_count.to_integer_or_infinity(context)?; // b. Let actualDeleteCount be the result of clamping dc between 0 and len - actualStart. let max = len - actual_start; match dc { IntegerOrInfinity::Integer(i) => u64::try_from(i) .unwrap_or_default() .clamp(0, len - actual_start), IntegerOrInfinity::PositiveInfinity => max, IntegerOrInfinity::NegativeInfinity => 0, } } // 9. Else if deleteCount is not present, then else { // a. Let actualDeleteCount be len - actualStart. len - actual_start }; Ok(actual_delete_count) } /// `Array.prototype.splice ( start, [deleteCount[, ...items]] )` /// /// Splices an array by following /// The deleteCount elements of the array starting at integer index start are replaced by the elements of items. /// An Array object containing the deleted elements (if any) is returned. pub(crate) fn splice( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let start = args.first(); let delete_count = args.get(1); let items = args.get(2..).unwrap_or_default(); Self::splice_internal(this, start, delete_count, items, context) } pub(crate) fn splice_internal( this: &JsValue, start: Option<&JsValue>, delete_count: Option<&JsValue>, items: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let relativeStart be ? ToIntegerOrInfinity(start). // 4. If relativeStart = -∞, let actualStart be 0. // 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0). // 6. Else, let actualStart be min(relativeStart, len). let actual_start = Self::get_relative_start(context, start.unwrap_or(&JsValue::undefined()), len)?; // 7. Let itemCount be the number of elements in items. let item_count = items.len() as u64; let actual_delete_count = Self::get_delete_count(len, actual_start, start, delete_count, context)?; // If len + itemCount - actualDeleteCount > 2**53 - 1, throw a TypeError exception. if len + item_count - actual_delete_count > Number::MAX_SAFE_INTEGER as u64 { return Err(JsNativeError::typ() .with_message("Target splice exceeded max safe integer value") .into()); } // 12. Let A be ? ArraySpeciesCreate(O, actualDeleteCount). let arr = Self::array_species_create(&o, actual_delete_count, context)?; // 13. Let k be 0. // 14. Repeat, while k < actualDeleteCount, for k in 0..actual_delete_count { // a. Let from be ! ToString(𝔽(actualStart + k)). // b. If ? HasProperty(O, from) is true, then // b.i. Let fromValue be ? Get(O, from). if let Some(from_value) = o.try_get(actual_start + k, context)? { // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(k)), fromValue). arr.create_data_property_or_throw(k, from_value, context)?; } // c. Set k to k + 1. } // 15. Perform ? Set(A, "length", 𝔽(actualDeleteCount), true). Self::set_length(&arr, actual_delete_count, context)?; let item_count = items.len() as u64; match item_count.cmp(&actual_delete_count) { Ordering::Equal => {} // 16. If itemCount < actualDeleteCount, then Ordering::Less => { // a. Set k to actualStart. // b. Repeat, while k < (len - actualDeleteCount), for k in actual_start..(len - actual_delete_count) { // i. Let from be ! ToString(𝔽(k + actualDeleteCount)). let from = k + actual_delete_count; // ii. Let to be ! ToString(𝔽(k + itemCount)). let to = k + item_count; // iii. If ? HasProperty(O, from) is true, then // iii.1. Let fromValue be ? Get(O, from). if let Some(from_value) = o.try_get(from, context)? { // 2. Perform ? Set(O, to, fromValue, true). o.set(to, from_value, true, context)?; } else { // iv. Else, // 1. Perform ? DeletePropertyOrThrow(O, to). o.delete_property_or_throw(to, context)?; } // v. Set k to k + 1. } // c. Set k to len. // d. Repeat, while k > (len - actualDeleteCount + itemCount), for k in ((len - actual_delete_count + item_count)..len).rev() { // i. Perform ? DeletePropertyOrThrow(O, ! ToString(𝔽(k - 1))). o.delete_property_or_throw(k, context)?; // ii. Set k to k - 1. } } // 17. Else if itemCount > actualDeleteCount, then Ordering::Greater => { // a. Set k to (len - actualDeleteCount). // b. Repeat, while k > actualStart, for k in (actual_start..len - actual_delete_count).rev() { // i. Let from be ! ToString(𝔽(k + actualDeleteCount - 1)). let from = k + actual_delete_count; // ii. Let to be ! ToString(𝔽(k + itemCount - 1)). let to = k + item_count; // iii. If ? HasProperty(O, from) is true, then // iii.1. Let fromValue be ? Get(O, from). if let Some(from_value) = o.try_get(from, context)? { // 2. Perform ? Set(O, to, fromValue, true). o.set(to, from_value, true, context)?; } // iv. Else, else { // 1. Perform ? DeletePropertyOrThrow(O, to). o.delete_property_or_throw(to, context)?; } // v. Set k to k - 1. } } } // 18. Set k to actualStart. // 19. For each element E of items, do for (i, item) in items.iter().enumerate() { // a. Perform ? Set(O, ! ToString(𝔽(k)), E, true). // b. Set k to k + 1. o.set(actual_start + i as u64, item.clone(), true, context)?; } // 20. Perform ? Set(O, "length", 𝔽(len - actualDeleteCount + itemCount), true). Self::set_length(&o, len - actual_delete_count + item_count, context)?; // 21. Return A. Ok(JsValue::from(arr)) } /// [`Array.prototype.toSpliced ( start, skipCount, ...items )`][spec] /// /// Splices the target array, returning the result as a new array. /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tospliced fn to_spliced(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; let start = args.first(); let skip_count = args.get(1); let items = args.get(2..).unwrap_or_default(); // 3. Let relativeStart be ? ToIntegerOrInfinity(start). // 4. If relativeStart is -∞, let actualStart be 0. // 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0). // 6. Else, let actualStart be min(relativeStart, len). let actual_start = Self::get_relative_start(context, start.unwrap_or(&JsValue::undefined()), len)?; // 7. Let insertCount be the number of elements in items. let insert_count = items.len() as u64; let actual_skip_count = Self::get_delete_count(len, actual_start, start, skip_count, context)?; // 11. Let newLen be len + insertCount - actualSkipCount. let new_len = len + insert_count - actual_skip_count; // 12. If newLen > 2**53 - 1, throw a TypeError exception. if new_len > Number::MAX_SAFE_INTEGER as u64 { return Err(JsNativeError::typ() .with_message("Target splice exceeded max safe integer value") .into()); } // 13. Let A be ? ArrayCreate(newLen). let arr = Array::array_create(new_len, None, context)?; // 14. Let i be 0. let mut i = 0; // 16. Repeat, while i < actualStart, while i < actual_start { // a. Let Pi be ! ToString(𝔽(i)). // b. Let iValue be ? Get(O, Pi). let value = o.get(i, context)?; // c. Perform ! CreateDataPropertyOrThrow(A, Pi, iValue). arr.create_data_property_or_throw(i, value, context) .js_expect("cannot fail for a newly created array")?; // d. Set i to i + 1. i += 1; } // 17. For each element E of items, do for item in items.iter().cloned() { // a. Let Pi be ! ToString(𝔽(i)). // b. Perform ! CreateDataPropertyOrThrow(A, Pi, E). arr.create_data_property_or_throw(i, item, context) .js_expect("cannot fail for a newly created array")?; // c. Set i to i + 1. i += 1; } // 15. Let r be actualStart + actualSkipCount. let mut r = actual_start + actual_skip_count; // 18. Repeat, while i < newLen, while i < new_len { // a. Let Pi be ! ToString(𝔽(i)). // b. Let from be ! ToString(𝔽(r)). // c. Let fromValue be ? Get(O, from). let from_value = o.get(r, context)?; // d. Perform ! CreateDataPropertyOrThrow(A, Pi, fromValue). arr.create_data_property_or_throw(i, from_value, context) .js_expect("cannot fail for a newly created array")?; // e. Set i to i + 1. i += 1; // f. Set r to r + 1. r += 1; } // 19. Return A. Ok(arr.into()) } /// `Array.prototype.filter( callback, [ thisArg ] )` /// /// For each element in the array the callback function is called, and a new /// array is constructed for every value whose callback returned a truthy value. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter pub(crate) fn filter( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let length = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ().with_message("Array.prototype.filter: `callback` must be callable") })?; let this_arg = args.get_or_undefined(1); // 4. Let A be ? ArraySpeciesCreate(O, 0). let a = Self::array_species_create(&o, 0, context)?; // 5. Let k be 0. // 6. Let to be 0. let mut to = 0u32; // 7. Repeat, while k < len, for idx in 0..length { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(element) = o.try_get(idx, context)? { let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())]; // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let selected = callback.call(this_arg, &args, context)?.to_boolean(); // iii. If selected is true, then if selected { // 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(to)), kValue). a.create_data_property_or_throw(to, element, context)?; // 2. Set to to to + 1. to += 1; } } } // 8. Return A. Ok(a.into()) } /// Array.prototype.some ( callbackfn [ , thisArg ] ) /// /// The some method tests whether at least one element in the array passes /// the test implemented by the provided callback function. It returns a Boolean value, /// true if the callback function returns a truthy value for at least one element /// in the array. Otherwise, false. /// /// Caution: Calling this method on an empty array returns false for any condition! /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some pub(crate) fn some( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ().with_message("Array.prototype.some: callback is not callable") })?; // 4. Let k be 0. // 5. Repeat, while k < len, for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(k_value) = o.try_get(k, context)? { // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let this_arg = args.get_or_undefined(1); let test_result = callback .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is true, return true. if test_result { return Ok(JsValue::new(true)); } } // d. Set k to k + 1. } // 6. Return false. Ok(JsValue::new(false)) } /// [`SortIndexedProperties ( obj, len, SortCompare, holes )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-sortindexedproperties pub(crate) fn sort_indexed_properties( obj: &JsObject, len: u64, sort_compare: F, skip_holes: bool, context: &mut Context, ) -> JsResult> where F: Fn(&JsValue, &JsValue, &mut Context) -> JsResult, { // 1. Let items be a new empty List. // doesn't matter if it clamps since it's just a best-effort optimization let mut items = Vec::with_capacity(len as usize); // 2. Let k be 0. // 3. Repeat, while k < len, for i in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. If holes is skip-holes, then let read = if skip_holes { // i. Let kRead be ? HasProperty(obj, Pk). obj.has_property(i, context)? } // c. Else, else { // i. Assert: holes is read-through-holes. // ii. Let kRead be true. true }; // d. If kRead is true, then if read { // i. Let kValue be ? Get(obj, Pk). // ii. Append kValue to items. items.push(obj.get(i, context)?); } // e. Set k to k + 1. } // 4. Sort items using an implementation-defined sequence of calls to SortCompare. If any such call returns an abrupt completion, stop before performing any further calls to SortCompare and return that Completion Record. let mut sort_err = Ok(()); items.sort_by(|x, y| { if sort_err.is_ok() { sort_compare(x, y, context).unwrap_or_else(|err| { sort_err = Err(err); Ordering::Equal }) } else { Ordering::Equal } }); sort_err?; // 5. Return items. Ok(items) } /// Array.prototype.sort ( comparefn ) /// /// The sort method sorts the elements of an array in place and returns the sorted array. /// The default sort order is ascending, built upon converting the elements into strings, /// then comparing their sequences of UTF-16 code units values. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.sort /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort pub(crate) fn sort( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. let comparefn = match args.get_or_undefined(0).variant() { JsVariant::Object(obj) if obj.is_callable() => Some(obj), JsVariant::Undefined => None, _ => { return Err(JsNativeError::typ() .with_message("The comparison function must be either a function or undefined") .into()); } }; // 2. Let obj be ? ToObject(this value). let obj = this.to_object(context)?; // 3. Let len be ? LengthOfArrayLike(obj). let len = obj.length_of_array_like(context)?; // 4. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: let sort_compare = |x: &JsValue, y: &JsValue, context: &mut Context| -> JsResult { // a. Return ? CompareArrayElements(x, y, comparefn). compare_array_elements(x, y, comparefn.as_ref(), context) }; // 5. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, skip-holes). let sorted = Self::sort_indexed_properties(&obj, len, sort_compare, true, context)?; let sorted_len = sorted.len() as u64; // 6. Let itemCount be the number of elements in sortedList. // 7. Let j be 0. // 8. Repeat, while j < itemCount, for (j, item) in sorted.into_iter().enumerate() { // a. Perform ? Set(obj, ! ToString(𝔽(j)), sortedList[j], true). obj.set(j, item, true, context)?; // b. Set j to j + 1. } // 9. NOTE: The call to SortIndexedProperties in step 5 uses skip-holes. The remaining indices // are deleted to preserve the number of holes that were detected and excluded from the sort. // 10. Repeat, while j < len, for j in sorted_len..len { // a. Perform ? DeletePropertyOrThrow(obj, ! ToString(𝔽(j))). obj.delete_property_or_throw(j, context)?; // b. Set j to j + 1. } // 11. Return obj. Ok(obj.into()) } /// [`Array.prototype.toSorted ( comparefn )`][spec] /// /// Orders the target array, returning the result in a new array. /// /// [spec]: https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.tosorted pub(crate) fn to_sorted( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. let comparefn = match args.get_or_undefined(0).variant() { JsVariant::Object(obj) if obj.is_callable() => Some(obj), JsVariant::Undefined => None, _ => { return Err(JsNativeError::typ() .with_message("The comparison function must be either a function or undefined") .into()); } }; // 2. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 3. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 4. Let A be ? ArrayCreate(len). let arr = Array::array_create(len, None, context)?; // 5. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: let sort_compare = |x: &JsValue, y: &JsValue, context: &mut Context| -> JsResult { // a. Return ? CompareArrayElements(x, y, comparefn). compare_array_elements(x, y, comparefn.as_ref(), context) }; // 6. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, read-through-holes). let sorted = Self::sort_indexed_properties(&o, len, sort_compare, false, context)?; // 7. Let j be 0. // 8. Repeat, while j < len, for (i, item) in sorted.into_iter().enumerate() { // a. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(j)), sortedList[j]). arr.create_data_property_or_throw(i, item, context) .js_expect("cannot fail for a newly created array")?; // b. Set j to j + 1. } // 9. Return A. Ok(arr.into()) } /// `Array.prototype.reduce( callbackFn [ , initialValue ] )` /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduce /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce pub(crate) fn reduce( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ() .with_message("Array.prototype.reduce: callback function is not callable") })?; // 4. If len = 0 and initialValue is not present, throw a TypeError exception. if len == 0 && args.get(1).is_none() { return Err(JsNativeError::typ() .with_message( "Array.prototype.reduce: called on an empty array and with no initial value", ) .into()); } // 5. Let k be 0. let mut k = 0; // 6. Let accumulator be undefined. let mut accumulator = JsValue::undefined(); // 7. If initialValue is present, then if let Some(initial_value) = args.get(1) { // a. Set accumulator to initialValue. accumulator = initial_value.clone(); // 8. Else, } else { // a. Let kPresent be false. let mut k_present = false; // b. Repeat, while kPresent is false and k < len, while !k_present && k < len { // i. Let Pk be ! ToString(𝔽(k)). let pk = k; // ii. Set kPresent to ? HasProperty(O, Pk). // iii. If kPresent is true, then // iii.1. Set accumulator to ? Get(O, Pk). if let Some(v) = o.try_get(pk, context)? { accumulator = v; k_present = true; } else { k_present = false; } // iv. Set k to k + 1. k += 1; } // c. If kPresent is false, throw a TypeError exception. if !k_present { return Err(JsNativeError::typ().with_message( "Array.prototype.reduce: called on an empty array and with no initial value", ).into()); } } // 9. Repeat, while k < len, while k < len { // a. Let Pk be ! ToString(𝔽(k)). let pk = k; // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(k_value) = o.try_get(pk, context)? { // ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). accumulator = callback.call( &JsValue::undefined(), &[accumulator, k_value, k.into(), o.clone().into()], context, )?; } // d. Set k to k + 1. k += 1; } // 10. Return accumulator. Ok(accumulator) } /// `Array.prototype.reduceRight( callbackFn [ , initialValue ] )` /// /// The reduceRight method traverses right to left starting from the last defined value in the array, /// accumulating a value using a given callback function. It returns the accumulated value. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduceright /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight pub(crate) fn reduce_right( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { JsNativeError::typ() .with_message("Array.prototype.reduceRight: callback function is not callable") })?; // 4. If len is 0 and initialValue is not present, throw a TypeError exception. if len == 0 && args.get(1).is_none() { return Err(JsNativeError::typ().with_message( "Array.prototype.reduceRight: called on an empty array and with no initial value", ).into()); } // 5. Let k be len - 1. let mut k = len as i64 - 1; // 6. Let accumulator be undefined. let mut accumulator = JsValue::undefined(); // 7. If initialValue is present, then if let Some(initial_value) = args.get(1) { // a. Set accumulator to initialValue. accumulator = initial_value.clone(); // 8. Else, } else { // a. Let kPresent be false. let mut k_present = false; // b. Repeat, while kPresent is false and k ≥ 0, while !k_present && k >= 0 { // i. Let Pk be ! ToString(𝔽(k)). let pk = k; // ii. Set kPresent to ? HasProperty(O, Pk). // iii. If kPresent is true, then // iii.1. Set accumulator to ? Get(O, Pk). if let Some(v) = o.try_get(pk, context)? { k_present = true; accumulator = v; } else { k_present = false; } // iv. Set k to k - 1. k -= 1; } // c. If kPresent is false, throw a TypeError exception. if !k_present { return Err(JsNativeError::typ().with_message( "Array.prototype.reduceRight: called on an empty array and with no initial value", ).into()); } } // 9. Repeat, while k ≥ 0, while k >= 0 { // a. Let Pk be ! ToString(𝔽(k)). let pk = k; // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // c.i. Let kValue be ? Get(O, Pk). if let Some(k_value) = o.try_get(pk, context)? { // ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). accumulator = callback.call( &JsValue::undefined(), &[accumulator.clone(), k_value, k.into(), o.clone().into()], context, )?; } // d. Set k to k - 1. k -= 1; } // 10. Return accumulator. Ok(accumulator) } /// `Array.prototype.copyWithin ( target, start [ , end ] )` /// /// The `copyWithin()` method shallow copies part of an array to another location /// in the same array and returns it without modifying its length. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.copywithin /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin pub(crate) fn copy_within( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let relativeTarget be ? ToIntegerOrInfinity(target). // 4. If relativeTarget is -∞, let to be 0. // 5. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0). // 6. Else, let to be min(relativeTarget, len). let mut to = Self::get_relative_start(context, args.get_or_undefined(0), len)? as i64; // 7. Let relativeStart be ? ToIntegerOrInfinity(start). // 8. If relativeStart is -∞, let from be 0. // 9. Else if relativeStart < 0, let from be max(len + relativeStart, 0). // 10. Else, let from be min(relativeStart, len). let mut from = Self::get_relative_start(context, args.get_or_undefined(1), len)? as i64; // 11. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). // 12. If relativeEnd is -∞, let final be 0. // 13. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). // 14. Else, let final be min(relativeEnd, len). let final_ = Self::get_relative_end(context, args.get_or_undefined(2), len)? as i64; // 15. Let count be min(final - from, len - to). let mut count = min(final_ - from, len as i64 - to); // 16. If from < to and to < from + count, then let direction = if from < to && to < from + count { // b. Set from to from + count - 1. from = from + count - 1; // c. Set to to to + count - 1. to = to + count - 1; // a. Let direction be -1. -1 // 17. Else, } else { // a. Let direction be 1. 1 }; // 18. Repeat, while count > 0, while count > 0 { // a. Let fromKey be ! ToString(𝔽(from)). let from_key = from; // b. Let toKey be ! ToString(𝔽(to)). let to_key = to; // c. Let fromPresent be ? HasProperty(O, fromKey). // d. If fromPresent is true, then // d.i. Let fromVal be ? Get(O, fromKey). if let Some(from_val) = o.try_get(from_key, context)? { // ii. Perform ? Set(O, toKey, fromVal, true). o.set(to_key, from_val, true, context)?; // e. Else, } else { // i. Assert: fromPresent is false. // ii. Perform ? DeletePropertyOrThrow(O, toKey). o.delete_property_or_throw(to_key, context)?; } // f. Set from to from + direction. from += direction; // g. Set to to to + direction. to += direction; // h. Set count to count - 1. count -= 1; } // 19. Return O. Ok(o.into()) } /// `Array.prototype.values( )` /// /// The values method returns an iterable that iterates over the values in the array. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values pub(crate) fn values( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Return CreateArrayIterator(O, value). Ok(ArrayIterator::create_array_iterator( o, PropertyNameKind::Value, context, )) } /// `Array.prototype.keys( )` /// /// The keys method returns an iterable that iterates over the indexes in the array. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.keys /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Return CreateArrayIterator(O, key). Ok(ArrayIterator::create_array_iterator( o, PropertyNameKind::Key, context, )) } /// `Array.prototype.entries( )` /// /// The entries method returns an iterable that iterates over the key-value pairs in the array. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.entries /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values pub(crate) fn entries( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Return CreateArrayIterator(O, key+value). Ok(ArrayIterator::create_array_iterator( o, PropertyNameKind::KeyAndValue, context, )) } /// [`Array.prototype.with ( index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.with pub(crate) fn with( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). let IntegerOrInfinity::Integer(relative_index) = args.get_or_undefined(0).to_integer_or_infinity(context)? else { return Err(JsNativeError::range() .with_message("invalid integer index for TypedArray operation") .into()); }; let value = args.get_or_undefined(1); // 4. If relativeIndex ≥ 0, let actualIndex be relativeIndex. let actual_index = u64::try_from(relative_index) // should succeed if `relative_index >= 0` .ok() // 5. Else, let actualIndex be len + relativeIndex. .or_else(|| len.checked_add_signed(relative_index)) .filter(|&rel| rel < len) .ok_or_else(|| { // 6. If actualIndex ≥ len or actualIndex < 0, throw a RangeError exception. JsNativeError::range() .with_message("invalid integer index for TypedArray operation") })?; // 7. Let A be ? ArrayCreate(len). let new_array = Array::array_create(len, None, context)?; // 8. Let k be 0. // 9. Repeat, while k < len, for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). let from_value = if k == actual_index { // b. If k is actualIndex, let fromValue be value. value.clone() } else { // c. Else, let fromValue be ? Get(O, Pk). o.get(k, context)? }; // d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). new_array .create_data_property_or_throw(k, from_value, context) .js_expect("cannot fail for a newly created array")?; // e. Set k to k + 1. } // 10. Return A. Ok(new_array.into()) } /// Represents the algorithm to calculate `relativeStart` (or `k`) in array functions. pub(super) fn get_relative_start( context: &mut Context, arg: &JsValue, len: u64, ) -> JsResult { // 1. Let relativeStart be ? ToIntegerOrInfinity(start). let relative_start = arg.to_integer_or_infinity(context)?; let start = match relative_start { // 2. If relativeStart is -∞, let k be 0. IntegerOrInfinity::NegativeInfinity => 0, // 3. Else if relativeStart < 0, let k be max(len + relativeStart, 0). IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), // 4. Else, let k be min(relativeStart, len). IntegerOrInfinity::Integer(i) => min(i as u64, len), // Special case - positive infinity. `len` is always smaller than +inf, thus from (4) IntegerOrInfinity::PositiveInfinity => len, }; Ok(start) } /// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions. pub(super) fn get_relative_end( context: &mut Context, value: &JsValue, len: u64, ) -> JsResult { // 1. If end is undefined, let relativeEnd be len [and return it] if value.is_undefined() { Ok(len) } else { // 1. cont, else let relativeEnd be ? ToIntegerOrInfinity(end). let relative_end = value.to_integer_or_infinity(context)?; let end = match relative_end { // 2. If relativeEnd is -∞, let final be 0. IntegerOrInfinity::NegativeInfinity => 0, // 3. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), // 4. Else, let final be min(relativeEnd, len). // Both `as` casts are safe as both variables are non-negative IntegerOrInfinity::Integer(i) => min(i as u64, len), // Special case - positive infinity. `len` is always smaller than +inf, thus from (4) IntegerOrInfinity::PositiveInfinity => len, }; Ok(end) } } /// `Array.prototype [ @@unscopables ]` /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype-@@unscopables /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables pub(crate) fn unscopables_object() -> JsObject { // 1. Let unscopableList be OrdinaryObjectCreate(null). let unscopable_list = JsObject::with_null_proto(); let true_prop = PropertyDescriptor::builder() .value(true) .writable(true) .enumerable(true) .configurable(true); { let mut obj = unscopable_list.borrow_mut(); // 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true). obj.insert(js_string!("at"), true_prop.clone()); // 3. Perform ! CreateDataPropertyOrThrow(unscopableList, "copyWithin", true). obj.insert(js_string!("copyWithin"), true_prop.clone()); // 4. Perform ! CreateDataPropertyOrThrow(unscopableList, "entries", true). obj.insert(js_string!("entries"), true_prop.clone()); // 5. Perform ! CreateDataPropertyOrThrow(unscopableList, "fill", true). obj.insert(js_string!("fill"), true_prop.clone()); // 6. Perform ! CreateDataPropertyOrThrow(unscopableList, "find", true). obj.insert(js_string!("find"), true_prop.clone()); // 7. Perform ! CreateDataPropertyOrThrow(unscopableList, "findIndex", true). obj.insert(js_string!("findIndex"), true_prop.clone()); // 8. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLast", true). obj.insert(js_string!("findLast"), true_prop.clone()); // 9. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLastIndex", true). obj.insert(js_string!("findLastIndex"), true_prop.clone()); // 10. Perform ! CreateDataPropertyOrThrow(unscopableList, "flat", true). obj.insert(js_string!("flat"), true_prop.clone()); // 11. Perform ! CreateDataPropertyOrThrow(unscopableList, "flatMap", true). obj.insert(js_string!("flatMap"), true_prop.clone()); // 12. Perform ! CreateDataPropertyOrThrow(unscopableList, "includes", true). obj.insert(js_string!("includes"), true_prop.clone()); // 13. Perform ! CreateDataPropertyOrThrow(unscopableList, "keys", true). obj.insert(js_string!("keys"), true_prop.clone()); // 14. Perform ! CreateDataPropertyOrThrow(unscopableList, "toReversed", true). obj.insert(js_string!("toReversed"), true_prop.clone()); // 15. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSorted", true). obj.insert(js_string!("toSorted"), true_prop.clone()); // 16. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSpliced", true). obj.insert(js_string!("toSpliced"), true_prop.clone()); // 17. Perform ! CreateDataPropertyOrThrow(unscopableList, "values", true). obj.insert(js_string!("values"), true_prop); } // 13. Return unscopableList. unscopable_list } } /// [`CompareArrayElements ( x, y, comparefn )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-comparearrayelements fn compare_array_elements( x: &JsValue, y: &JsValue, comparefn: Option<&JsObject>, context: &mut Context, ) -> JsResult { match (x.is_undefined(), y.is_undefined()) { // 1. If x and y are both undefined, return +0𝔽. (true, true) => return Ok(Ordering::Equal), // 2. If x is undefined, return 1𝔽. (true, false) => return Ok(Ordering::Greater), // 3. If y is undefined, return -1𝔽. (false, true) => return Ok(Ordering::Less), _ => {} } // 4. If comparefn is not undefined, then if let Some(cmp) = comparefn { let args = [x.clone(), y.clone()]; // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). let v = cmp .call(&JsValue::undefined(), &args, context)? .to_number(context)?; // b. If v is NaN, return +0𝔽. // c. Return v. return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal)); } // 5. Let xString be ? ToString(x). let x_str = x.to_string(context)?; // 6. Let yString be ? ToString(y). let y_str = y.to_string(context)?; // 7. Let xSmaller be ! IsLessThan(xString, yString, true). // 8. If xSmaller is true, return -1𝔽. // 9. Let ySmaller be ! IsLessThan(yString, xString, true). // 10. If ySmaller is true, return 1𝔽. // 11. Return +0𝔽. // NOTE: skipped IsLessThan because it just makes a lexicographic comparison // when x and y are strings Ok(x_str.cmp(&y_str)) } /// `FindViaPredicate ( O, len, direction, predicate, thisArg )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-findviapredicate pub(crate) fn find_via_predicate( o: &JsObject, len: u64, direction: Direction, predicate: &JsValue, this_arg: &JsValue, context: &mut Context, caller_name: &str, ) -> JsResult<(JsValue, JsValue)> { // 1. If IsCallable(predicate) is false, throw a TypeError exception. let predicate = predicate.as_callable().ok_or_else(|| { JsNativeError::typ().with_message(format!("{caller_name}: predicate is not callable")) })?; let indices = match direction { // 2. If direction is ascending, then // a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in ascending order. Direction::Ascending => itertools::Either::Left(0..len), // 3. Else, // a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in descending order. Direction::Descending => itertools::Either::Right((0..len).rev()), }; // 4. For each integer k of indices, do for k in indices { // a. Let Pk be ! ToString(𝔽(k)). let pk = k; // b. NOTE: If O is a TypedArray, the following invocation of Get will return a normal completion. // c. Let kValue be ? Get(O, Pk). let k_value = o.get(pk, context)?; // d. Let testResult be ? Call(predicate, thisArg, « kValue, 𝔽(k), O »). let test_result = predicate .call( this_arg, &[k_value.clone(), k.into(), o.clone().into()], context, )? .to_boolean(); if test_result { // e. If ToBoolean(testResult) is true, return the Record { [[Index]]: 𝔽(k), [[Value]]: kValue }. return Ok((JsValue::new(k), k_value)); } } // 5. Return the Record { [[Index]]: -1𝔽, [[Value]]: undefined } Ok((JsValue::new(-1), JsValue::undefined())) } /// Define an own property for an array exotic object. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc fn array_exotic_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult { // 1. Assert: IsPropertyKey(P) is true. match key { // 2. If P is "length", then PropertyKey::String(s) if s == &StaticJsStrings::LENGTH => { // a. Return ? ArraySetLength(A, Desc). array_set_length(obj, desc, context) } // 3. Else if P is an array index, then PropertyKey::Index(index) => { let index = index.get(); let new_len = index + 1; // Optimization: If the shape of the object is the array template shape, // we know the position of the "length" property. if u64::from(new_len) < (2u64.pow(32) - 1) { let borrowed_object = obj.borrow(); if borrowed_object.properties().shape.to_addr_usize() == context .intrinsics() .templates() .array() .shape() .to_addr_usize() { let old_len = borrowed_object.properties().storage[0].clone(); drop(borrowed_object); let old_len = old_len.to_u32(context)?; if new_len >= old_len { if ordinary_define_own_property(obj, key, desc, context)? { let mut borrowed_object = obj.borrow_mut(); borrowed_object.properties_mut().storage[0] = JsValue::new(new_len); return Ok(true); } return Ok(false); } } } // a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). let old_len_desc = ordinary_get_own_property(obj, &StaticJsStrings::LENGTH.into(), context)? .expect("the property descriptor must exist"); // b. Assert: ! IsDataDescriptor(oldLenDesc) is true. debug_assert!(old_len_desc.is_data_descriptor()); // c. Assert: oldLenDesc.[[Configurable]] is false. debug_assert!(!old_len_desc.expect_configurable()); // d. Let oldLen be oldLenDesc.[[Value]]. // e. Assert: oldLen is a non-negative integral Number. // f. Let index be ! ToUint32(P). let old_len = old_len_desc .expect_value() .to_u32(context) .js_expect("this ToUint32 call must not fail")?; // g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false. if index >= old_len && !old_len_desc.expect_writable() { return Ok(false); } // h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc). if ordinary_define_own_property(obj, key, desc, context)? { // j. If index ≥ oldLen, then if index >= old_len { // i. Set oldLenDesc.[[Value]] to index + 1𝔽. let old_len_desc = PropertyDescriptor::builder() .value(new_len) .maybe_writable(old_len_desc.writable()) .maybe_enumerable(old_len_desc.enumerable()) .maybe_configurable(old_len_desc.configurable()); // ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc). let succeeded = ordinary_define_own_property( obj, &StaticJsStrings::LENGTH.into(), old_len_desc.into(), context, )?; // iii. Assert: succeeded is true. debug_assert!(succeeded); } // k. Return true. Ok(true) } else { // i. If succeeded is false, return false. Ok(false) } } // 4. Return OrdinaryDefineOwnProperty(A, P, Desc). _ => ordinary_define_own_property(obj, key, desc, context), } } /// Abstract operation `ArraySetLength ( A, Desc )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arraysetlength fn array_set_length( obj: &JsObject, desc: PropertyDescriptor, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult { // 1. If Desc.[[Value]] is absent, then let Some(new_len_val) = desc.value() else { // a. Return OrdinaryDefineOwnProperty(A, "length", Desc). return ordinary_define_own_property(obj, &StaticJsStrings::LENGTH.into(), desc, context); }; // 3. Let newLen be ? ToUint32(Desc.[[Value]]). let new_len = new_len_val.to_u32(context)?; // 4. Let numberLen be ? ToNumber(Desc.[[Value]]). let number_len = new_len_val.to_number(context)?; // 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception. #[allow(clippy::float_cmp)] if f64::from(new_len) != number_len { return Err(JsNativeError::range() .with_message("bad length for array") .into()); } // 2. Let newLenDesc be a copy of Desc. // 6. Set newLenDesc.[[Value]] to newLen. let mut new_len_desc = PropertyDescriptor::builder() .value(new_len) .maybe_writable(desc.writable()) .maybe_enumerable(desc.enumerable()) .maybe_configurable(desc.configurable()); // 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). let old_len_desc = ordinary_get_own_property(obj, &StaticJsStrings::LENGTH.into(), context)? .expect("the property descriptor must exist"); // 8. Assert: ! IsDataDescriptor(oldLenDesc) is true. debug_assert!(old_len_desc.is_data_descriptor()); // 9. Assert: oldLenDesc.[[Configurable]] is false. debug_assert!(!old_len_desc.expect_configurable()); // 10. Let oldLen be oldLenDesc.[[Value]]. let old_len = old_len_desc.expect_value(); // 11. If newLen ≥ oldLen, then if new_len >= old_len.to_u32(context)? { // a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc). return ordinary_define_own_property( obj, &StaticJsStrings::LENGTH.into(), new_len_desc.build(), context, ); } // 12. If oldLenDesc.[[Writable]] is false, return false. if !old_len_desc.expect_writable() { return Ok(false); } // 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true. let new_writable = if new_len_desc.inner().writable().unwrap_or(true) { true } // 14. Else, else { // a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any // elements cannot be deleted. // c. Set newLenDesc.[[Writable]] to true. new_len_desc = new_len_desc.writable(true); // b. Let newWritable be false. false }; // 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc). // 16. If succeeded is false, return false. if !ordinary_define_own_property( obj, &StaticJsStrings::LENGTH.into(), new_len_desc.clone().build(), context, ) .js_expect("this OrdinaryDefineOwnProperty call must not fail")? { return Ok(false); } // 17. For each own property key P of A that is an array index, whose numeric value is // greater than or equal to newLen, in descending numeric index order, do let ordered_keys = { let mut keys: Vec<_> = obj .borrow() .properties .index_property_keys() .filter(|idx| new_len <= *idx && *idx < u32::MAX) .collect(); keys.sort_unstable_by(|x, y| y.cmp(x)); keys }; for index in ordered_keys { // a. Let deleteSucceeded be ! A.[[Delete]](P). // b. If deleteSucceeded is false, then if !obj.__delete__(&index.into(), context)? { // i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽. new_len_desc = new_len_desc.value(index + 1); // ii. If newWritable is false, set newLenDesc.[[Writable]] to false. if !new_writable { new_len_desc = new_len_desc.writable(false); } // iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc). ordinary_define_own_property( obj, &StaticJsStrings::LENGTH.into(), new_len_desc.build(), context, ) .js_expect("this OrdinaryDefineOwnProperty call must not fail")?; // iv. Return false. return Ok(false); } } // 18. If newWritable is false, then if !new_writable { // a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length", // PropertyDescriptor { [[Writable]]: false }). let succeeded = ordinary_define_own_property( obj, &StaticJsStrings::LENGTH.into(), PropertyDescriptor::builder().writable(false).build(), context, ) .js_expect("this OrdinaryDefineOwnProperty call must not fail")?; // b. Assert: succeeded is true. debug_assert!(succeeded); } // 19. Return true. Ok(true) } ================================================ FILE: core/engine/src/builtins/array/tests.rs ================================================ use super::Array; use crate::{ Context, JsNativeErrorKind, JsValue, TestAction, builtins::Number, js_string, run_test_actions, }; use boa_macros::js_str; use indoc::indoc; #[test] fn is_array() { run_test_actions([ TestAction::assert("Array.isArray([])"), TestAction::assert("Array.isArray(new Array())"), TestAction::assert("Array.isArray(['a', 'b', 'c'])"), TestAction::assert("Array.isArray([1, 2, 3])"), TestAction::assert("!Array.isArray({})"), TestAction::assert("Array.isArray(new Array)"), TestAction::assert("!Array.isArray()"), TestAction::assert("!Array.isArray({ constructor: Array })"), TestAction::assert( "!Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })", ), TestAction::assert("!Array.isArray(17)"), TestAction::assert("!Array.isArray({ __proto__: Array.prototype })"), TestAction::assert("!Array.isArray({ length: 0 })"), ]); } #[test] fn of() { run_test_actions([ TestAction::run_harness(), TestAction::assert("arrayEquals(Array.of(1, 2, 3), [1, 2, 3])"), TestAction::assert(indoc! {r#" arrayEquals( Array.of(1, 'a', [], undefined, null), [1, 'a', [], undefined, null] ) "#}), TestAction::assert("arrayEquals(Array.of(), [])"), TestAction::run("let a = Array.of.call(Date, 'a', undefined, 3);"), TestAction::assert("a instanceof Date"), TestAction::assert_eq("a[0]", js_str!("a")), TestAction::assert_eq("a[1]", JsValue::undefined()), TestAction::assert_eq("a[2]", 3), TestAction::assert_eq("a.length", 3), ]); } #[test] fn concat() { run_test_actions([ TestAction::run_harness(), // Empty ++ Empty TestAction::assert("arrayEquals([].concat([]), [])"), // Empty ++ NonEmpty TestAction::assert("arrayEquals([].concat([1]), [1])"), // NonEmpty ++ Empty TestAction::assert("arrayEquals([1].concat([]), [1])"), // NonEmpty ++ NonEmpty TestAction::assert("arrayEquals([1].concat([1]), [1, 1])"), ]); } #[test] fn copy_within() { run_test_actions([ TestAction::run_harness(), TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(-2), [1,2,3,1,2])"), TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(0, 3), [4,5,3,4,5])"), TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(0, 3, 4), [4,2,3,4,5])"), TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(-2, -3, -1), [1,2,3,3,4])"), ]); } #[test] fn join() { run_test_actions([ TestAction::assert_eq("[].join('.')", js_string!()), TestAction::assert_eq("['a'].join('.')", js_str!("a")), TestAction::assert_eq("['a', 'b', 'c'].join('.')", js_str!("a.b.c")), TestAction::assert_eq("let a=[];a[0]=a;a[1]=a;a[2]=a;a.join()", js_str!(",,")), ]); } #[test] fn to_string() { run_test_actions([ TestAction::assert_eq("[].toString()", js_string!()), TestAction::assert_eq("['a'].toString()", js_str!("a")), TestAction::assert_eq("['a', 'b', 'c'].toString()", js_str!("a,b,c")), ]); } #[test] fn every() { // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every run_test_actions([ TestAction::run(indoc! {r#" function appendingCallback(elem,index,arr) { arr.push('new'); return elem !== "new"; } function deletingCallback(elem,index,arr) { arr.pop() return elem < 3; } "#}), TestAction::assert("[11, 23, 45].every(e => e > 10)"), TestAction::assert("[].every(e => e < 10)"), TestAction::assert("![11, 23, 45].every(e => e < 10)"), TestAction::assert("[1,2,3,4].every(appendingCallback)"), TestAction::assert("[1,2,3,4].every(deletingCallback)"), ]); } #[test] fn find() { run_test_actions([TestAction::assert_eq( "['a', 'b', 'c'].find(e => e == 'a')", js_str!("a"), )]); } #[test] fn find_index() { run_test_actions([ TestAction::assert_eq("[1, 2, 3].findIndex(e => e == 2)", 1), TestAction::assert_eq("[].findIndex(e => e == 2)", -1), TestAction::assert_eq("[4, 5, 6].findIndex(e => e == 2)", -1), ]); } #[test] fn flat() { run_test_actions([ TestAction::run_harness(), TestAction::assert("arrayEquals( [[]].flat(), [] )"), TestAction::assert(indoc! {r#" arrayEquals( ['a', ['b', 'c']].flat(), ['a', 'b', 'c'] ) "#}), TestAction::assert(indoc! {r#" arrayEquals( ['a', ['b', ['c'], 'd']].flat(2), ['a', 'b', 'c', 'd'] ) "#}), TestAction::assert("arrayEquals( [[[[[['a']]]]]].flat(Infinity), ['a'] )"), ]); } #[test] fn flat_map() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" arrayEquals( [1, 2, 3].flatMap(i => [i * 2]), [2, 4, 6] ) "#}), TestAction::assert(indoc! {r#" arrayEquals( ["it's Sunny", "in Cali"].flatMap(x => x.split(" ")), ["it's", "Sunny", "in", "Cali"] ) "#}), ]); } #[test] fn flat_map_with_hole() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" var arr = [0, 1, 2]; delete arr[1]; arrayEquals( arr.flatMap(i => [i * 2]), [0, 4] ) "#}), ]); } #[test] fn flat_map_not_callable() { run_test_actions([TestAction::assert_native_error( indoc! {r#" var array = [1,2,3]; array.flatMap("not a function"); "#}, JsNativeErrorKind::Type, "flatMap mapper function is not callable", )]); } #[test] fn push() { run_test_actions([ TestAction::run("var arr = [1, 2];"), TestAction::assert_eq("arr.push()", 2), TestAction::assert_eq("arr.push(3, 4)", 4), TestAction::assert_eq("arr[2]", 3), TestAction::assert_eq("arr[3]", 4), ]); } #[test] fn pop() { run_test_actions([ TestAction::run_harness(), TestAction::run(indoc! {r#" var one = [1]; var many = [1, 2, 3, 4]; "#}), TestAction::assert_eq("[].pop()", JsValue::undefined()), TestAction::assert_eq("one.pop()", 1), TestAction::assert("arrayEquals(one, [])"), TestAction::assert_eq("many.pop()", 4), TestAction::assert("arrayEquals(many, [1, 2, 3])"), ]); } #[test] fn shift() { run_test_actions([ TestAction::run_harness(), TestAction::run(indoc! {r#" var one = [1]; var many = [1, 2, 3, 4]; "#}), TestAction::assert_eq("[].shift()", JsValue::undefined()), TestAction::assert_eq("one.shift()", 1), TestAction::assert("arrayEquals(one, [])"), TestAction::assert_eq("many.shift()", 1), TestAction::assert("arrayEquals(many, [2, 3, 4])"), ]); } #[test] fn unshift() { run_test_actions([ TestAction::run_harness(), TestAction::run("var arr = [3, 4];"), TestAction::assert_eq("arr.unshift()", 2), TestAction::assert_eq("arr.unshift(1, 2)", 4), TestAction::assert("arrayEquals(arr, [1, 2, 3, 4])"), ]); } #[test] fn reverse() { run_test_actions([ TestAction::run_harness(), TestAction::run("var arr = [1, 2];"), TestAction::assert("arrayEquals(arr.reverse(), [2, 1])"), TestAction::assert("arrayEquals(arr, [2, 1])"), ]); } #[test] fn index_of() { run_test_actions([ TestAction::run(indoc! {r#" var one = ["a"]; var many = ["a", "b", "c"]; var duplicates = ["a", "b", "c", "a", "b"]; "#}), // Empty TestAction::assert_eq("[].indexOf('a')", -1), // One TestAction::assert_eq("one.indexOf('a')", 0), // Missing from one TestAction::assert_eq("one.indexOf('b')", -1), // First in many TestAction::assert_eq("many.indexOf('a')", 0), // Second in many TestAction::assert_eq("many.indexOf('b')", 1), // First in duplicates TestAction::assert_eq("duplicates.indexOf('a')", 0), // Second in duplicates TestAction::assert_eq("duplicates.indexOf('b')", 1), // Positive fromIndex greater than array length TestAction::assert_eq("one.indexOf('a', 2)", -1), // Positive fromIndex missed match TestAction::assert_eq("many.indexOf('a', 1)", -1), // Positive fromIndex matched TestAction::assert_eq("many.indexOf('b', 1)", 1), // Positive fromIndex with duplicates TestAction::assert_eq("duplicates.indexOf('a', 1)", 3), // Negative fromIndex greater than array length TestAction::assert_eq("one.indexOf('a', -2)", 0), // Negative fromIndex missed match TestAction::assert_eq("many.indexOf('b', -1)", -1), // Negative fromIndex matched TestAction::assert_eq("many.indexOf('c', -1)", 2), // Negative fromIndex with duplicates TestAction::assert_eq("duplicates.indexOf('b', -2)", 4), ]); } #[test] fn last_index_of() { run_test_actions([ TestAction::run(indoc! {r#" var one = ["a"]; var many = ["a", "b", "c"]; var duplicates = ["a", "b", "c", "a", "b"]; "#}), // Empty TestAction::assert_eq("[].lastIndexOf('a')", -1), // One TestAction::assert_eq("one.lastIndexOf('a')", 0), // Missing from one TestAction::assert_eq("one.lastIndexOf('b')", -1), // First in many TestAction::assert_eq("many.lastIndexOf('a')", 0), // Second in many TestAction::assert_eq("many.lastIndexOf('b')", 1), // 4th in duplicates TestAction::assert_eq("duplicates.lastIndexOf('a')", 3), // 5th in duplicates TestAction::assert_eq("duplicates.lastIndexOf('b')", 4), // Positive fromIndex greater than array length TestAction::assert_eq("one.lastIndexOf('a', 2)", 0), // Positive fromIndex missed match TestAction::assert_eq("many.lastIndexOf('c', 1)", -1), // Positive fromIndex matched TestAction::assert_eq("many.lastIndexOf('b', 1)", 1), // Positive fromIndex with duplicates TestAction::assert_eq("duplicates.lastIndexOf('a', 1)", 0), // Negative fromIndex greater than array length TestAction::assert_eq("one.lastIndexOf('a', -2)", -1), // Negative fromIndex missed match TestAction::assert_eq("many.lastIndexOf('c', -2)", -1), // Negative fromIndex matched TestAction::assert_eq("many.lastIndexOf('c', -1)", 2), // Negative fromIndex with duplicates TestAction::assert_eq("duplicates.lastIndexOf('b', -2)", 1), ]); } #[test] fn fill_obj_ref() { run_test_actions([ TestAction::run(indoc! {r#" let obj = {}; let a = new Array(3).fill(obj); obj.hi = 'hi' "#}), TestAction::assert_eq("a[2].hi", js_str!("hi")), ]); } #[test] fn fill() { run_test_actions([ TestAction::run_harness(), TestAction::run("var a = [1, 2, 3];"), TestAction::assert("arrayEquals(a.fill(4), [4, 4, 4])"), // make sure the array is modified TestAction::assert("arrayEquals(a, [4, 4, 4])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, '1'), [1, 4, 4])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, 1, 2), [1, 4, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, 1, 1), [1, 2, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, 3, 3), [1, 2, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, -3, -2), [4, 2, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, NaN, NaN), [1, 2, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, 3, 5), [1, 2, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, '1.2', '2.5'), [1, 4, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, 'str'), [4, 4, 4])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, 'str', 'str'), [1, 2, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, undefined, null), [1, 2, 3])"), TestAction::assert("arrayEquals([1, 2, 3].fill(4, undefined, undefined), [4, 4, 4])"), TestAction::assert("arrayEquals([1, 2, 3].fill(), [undefined, undefined, undefined])"), ]); } #[test] fn includes_value() { run_test_actions([ TestAction::run(indoc! {r#" var one = ["a"]; var many = ["a", "b", "c"]; var duplicates = ["a", "b", "c", "a", "b"]; "#}), // Empty TestAction::assert("![].includes('a')"), // One TestAction::assert("one.includes('a')"), // Missing from one TestAction::assert("!one.includes('b')"), // In many TestAction::assert("many.includes('b')"), // Missing from many TestAction::assert("!many.includes('d')"), // In duplicates TestAction::assert("duplicates.includes('a')"), // Missing from duplicates TestAction::assert("!duplicates.includes('d')"), ]); } #[test] fn map() { run_test_actions([ TestAction::run_harness(), TestAction::run(indoc! {r#" var one = ["x"]; var many = ["x", "y", "z"]; "#}), // Empty TestAction::assert("arrayEquals([].map(v => v + '_'), [])"), // One TestAction::assert("arrayEquals(one.map(v => '_' + v), ['_x'])"), // Many TestAction::assert(indoc! {r#" arrayEquals( many.map(v => '_' + v + '_'), ['_x_', '_y_', '_z_'] ) "#}), // assert the old arrays have not been modified TestAction::assert("arrayEquals(one, ['x'])"), TestAction::assert("arrayEquals(many, ['x', 'y', 'z'])"), // One but it uses `this` inside the callback TestAction::assert(indoc! {r#" var _this = { answer: 42 }; function callback() { return 'The answer to life is: ' + this.answer; } arrayEquals( one.map(callback, _this), ['The answer to life is: 42'] ) "#}), ]); } #[test] fn slice() { run_test_actions([ TestAction::run_harness(), TestAction::assert("arrayEquals([].slice(), [])"), TestAction::assert("arrayEquals(['a'].slice(), ['a'])"), TestAction::assert(indoc! {r#" arrayEquals( ["a", "b", "c", "d"].slice(1), ["b", "c", "d"] ) "#}), TestAction::assert(indoc! {r#" arrayEquals( ["a", "b", "c", "d"].slice(2, 3), ["c"] ) "#}), TestAction::assert(indoc! {r#" arrayEquals( ["a", "b", "c", "d"].slice(7), [] ) "#}), ]); } #[test] fn for_each() { run_test_actions([ TestAction::run(indoc! {r#" var sum = 0; var indexSum = 0; var listLengthSum = 0; function callingCallback(item, index, list) { sum += item; indexSum += index; listLengthSum += list.length; } [2, 3, 4, 5].forEach(callingCallback); "#}), TestAction::assert_eq("sum", 14), TestAction::assert_eq("indexSum", 6), TestAction::assert_eq("listLengthSum", 16), ]); } #[test] fn for_each_push_value() { run_test_actions([ TestAction::run_harness(), TestAction::run(indoc! {r#" var a = [1, 2, 3, 4]; a.forEach((item, index, list) => list.push(item * 2)); "#}), TestAction::assert(indoc! {r#" arrayEquals( a, [1, 2, 3, 4, 2, 4, 6, 8] ) "#}), ]); } #[test] fn filter() { run_test_actions([ TestAction::run_harness(), TestAction::run("var empty = [], one = ['1'], many = ['1', '0', '1'];"), // Empty TestAction::assert(indoc! {r#" arrayEquals( empty.filter(v => v === "1"), [] ) "#}), // One filtered on "1" TestAction::assert(indoc! {r#" arrayEquals( one.filter(v => v === "1"), ["1"] ) "#}), // One filtered on "0" TestAction::assert(indoc! {r#" arrayEquals( one.filter(v => v === "0"), [] ) "#}), // Many filtered on "1" TestAction::assert(indoc! {r#" arrayEquals( many.filter(v => v === "1"), ["1", "1"] ) "#}), // Many filtered on "0" TestAction::assert(indoc! {r#" arrayEquals( many.filter(v => v === "0"), ["0"] ) "#}), // assert the old arrays have not been modified TestAction::assert("arrayEquals(one, ['1'])"), TestAction::assert("arrayEquals(many, ['1', '0', '1'])"), ]); } #[test] fn some() { run_test_actions([ TestAction::run_harness(), TestAction::run("var array = [11, 23, 45];"), TestAction::assert("!array.some(e => e < 10)"), TestAction::assert("![].some(e => e < 10)"), TestAction::assert("array.some(e => e > 10)"), TestAction::assert(indoc! {r#" // Cases where callback mutates the array. var appendArray = [1,2,3,4]; function appendingCallback(elem, index, arr) { arr.push('new'); return elem !== "new"; } appendArray.some(appendingCallback) "#}), TestAction::assert(indoc! {r#" arrayEquals( appendArray, [1, 2, 3, 4, "new"] ) "#}), TestAction::assert(indoc! {r#" var delArray = [1,2,3,4]; function deletingCallback(elem,index,arr) { arr.pop() return elem < 3; } delArray.some(deletingCallback) "#}), TestAction::assert(indoc! {r#" arrayEquals( delArray, [1, 2, 3] ) "#}), ]); } #[test] fn reduce() { run_test_actions([ TestAction::run(indoc! {r#" var arr = [1, 2, 3, 4]; function add(acc, x) { return acc + x; } function addIdx(acc, _, idx) { return acc + idx; } function addLen(acc, _x, _idx, arr) { return acc + arr.length; } function addResize(acc, x, idx, arr) { if(idx == 0) { arr.length = 3; } return acc + x; } var delArray = [1, 2, 3, 4, 5]; delete delArray[0]; delete delArray[1]; delete delArray[3]; "#}), // empty array TestAction::assert_eq("[].reduce(add, 0)", 0), // simple with initial value TestAction::assert_eq("arr.reduce(add, 0)", 10), // without initial value TestAction::assert_eq("arr.reduce(add)", 10), // with some items missing TestAction::assert_eq("delArray.reduce(add, 0)", 8), // with index TestAction::assert_eq("arr.reduce(addIdx, 0)", 6), // with array TestAction::assert_eq("arr.reduce(addLen, 0)", 16), // resizing the array as reduce progresses TestAction::assert_eq("arr.reduce(addResize, 0)", 6), // Empty array TestAction::assert_native_error( "[].reduce((acc, x) => acc + x);", JsNativeErrorKind::Type, "Array.prototype.reduce: called on an empty array and with no initial value", ), // Array with no defined elements TestAction::assert_native_error( indoc! {r#" var deleteArr = [0, 1]; delete deleteArr[0]; delete deleteArr[1]; deleteArr.reduce((acc, x) => acc + x); "#}, JsNativeErrorKind::Type, "Array.prototype.reduce: called on an empty array and with no initial value", ), // No callback TestAction::assert_native_error( indoc! {r#" var someArr = [0, 1]; someArr.reduce(''); "#}, JsNativeErrorKind::Type, "Array.prototype.reduce: callback function is not callable", ), ]); } #[test] fn reduce_right() { run_test_actions([ TestAction::run(indoc! {r#" var arr = [1, 2, 3, 4]; function sub(acc, x) { return acc - x; } function subIdx(acc, _, idx) { return acc - idx; } function subLen(acc, _x, _idx, arr) { return acc - arr.length; } function subResize(acc, x, idx, arr) { if(idx == arr.length - 1) { arr.length = 1; } return acc - x; } function subResize0(acc, x, idx, arr) { if(idx == arr.length - 2) { arr.length = 0; } return acc - x; } var delArray = [1, 2, 3, 4, 5]; delete delArray[0]; delete delArray[1]; delete delArray[3]; "#}), // empty array TestAction::assert_eq("[].reduceRight(sub, 0)", 0), // simple with initial value TestAction::assert_eq("arr.reduceRight(sub, 0)", -10), // without initial value TestAction::assert_eq("arr.reduceRight(sub)", -2), // with some items missing TestAction::assert_eq("delArray.reduceRight(sub, 0)", -8), // with index TestAction::assert_eq("arr.reduceRight(subIdx)", 1), // with array TestAction::assert_eq("arr.reduceRight(subLen)", -8), // resizing the array as reduce progresses TestAction::assert_eq("arr.reduceRight(subResize, 0)", -5), // reset array TestAction::run("arr = [1, 2, 3, 4];"), // resizing the array to 0 as reduce progresses TestAction::assert_eq("arr.reduceRight(subResize0, 0)", -7), // Empty array TestAction::assert_native_error( "[].reduceRight((acc, x) => acc + x);", JsNativeErrorKind::Type, "Array.prototype.reduceRight: called on an empty array and with no initial value", ), // Array with no defined elements TestAction::assert_native_error( indoc! {r#" var deleteArr = [0, 1]; delete deleteArr[0]; delete deleteArr[1]; deleteArr.reduceRight((acc, x) => acc + x); "#}, JsNativeErrorKind::Type, "Array.prototype.reduceRight: called on an empty array and with no initial value", ), // No callback TestAction::assert_native_error( indoc! {r#" var otherArr = [0, 1]; otherArr.reduceRight(""); "#}, JsNativeErrorKind::Type, "Array.prototype.reduceRight: callback function is not callable", ), ]); } #[test] fn call_array_constructor_with_one_argument() { run_test_actions([ TestAction::run_harness(), TestAction::assert("arrayEquals(new Array(0), [])"), TestAction::assert("arrayEquals(new Array(5), [,,,,,])"), TestAction::assert("arrayEquals(new Array('Hello, world!'), ['Hello, world!'])"), ]); } #[test] fn array_values_simple() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" arrayEquals( Array.from([1, 2, 3].values()), [1, 2, 3] ) "#}), ]); } #[test] fn array_keys_simple() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" arrayEquals( Array.from([1, 2, 3].keys()), [0, 1, 2] ) "#}), ]); } #[test] fn array_entries_simple() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" arrayEquals( Array.from([1, 2, 3].entries()), [ [0, 1], [1, 2], [2, 3] ] ) "#}), ]); } #[test] fn array_values_empty() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" arrayEquals( Array.from([].values()), [] ) "#}), ]); } #[test] fn array_values_sparse() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" var array = Array(); array[3] = 5; arrayEquals( Array.from(array.values()), [undefined, undefined, undefined, 5] ) "#}), ]); } #[test] fn array_symbol_iterator() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" arrayEquals( Array.from([1, 2, 3][Symbol.iterator]()), [1, 2, 3] ) "#}), ]); } #[test] fn array_values_symbol_iterator() { run_test_actions([TestAction::assert(indoc! {r#" var iterator = [1, 2, 3].values(); iterator === iterator[Symbol.iterator](); "#})]); } #[test] fn array_spread_arrays() { run_test_actions([ TestAction::run_harness(), TestAction::assert(indoc! {r#" arrayEquals( [1, ...[2, 3]], [1, 2, 3] ) "#}), ]); } #[test] fn array_spread_non_iterable() { run_test_actions([TestAction::assert_native_error( "const array2 = [...5];", JsNativeErrorKind::Type, "value with type `number` is not iterable", )]); } #[test] fn get_relative_start() { #[track_caller] fn assert(context: &mut Context, arg: Option<&JsValue>, len: u64, expected: u64) { const UNDEFINED: &JsValue = &JsValue::undefined(); let arg = arg.unwrap_or(UNDEFINED); assert_eq!( Array::get_relative_start(context, arg, len).unwrap(), expected ); } run_test_actions([TestAction::inspect_context(|ctx| { assert(ctx, None, 10, 0); assert(ctx, Some(&JsValue::undefined()), 10, 0); assert(ctx, Some(&JsValue::new(f64::NEG_INFINITY)), 10, 0); assert(ctx, Some(&JsValue::new(f64::INFINITY)), 10, 10); assert(ctx, Some(&JsValue::new(-1)), 10, 9); assert(ctx, Some(&JsValue::new(1)), 10, 1); assert(ctx, Some(&JsValue::new(-11)), 10, 0); assert(ctx, Some(&JsValue::new(11)), 10, 10); assert(ctx, Some(&JsValue::new(f64::MIN)), 10, 0); assert(ctx, Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), 10, 0); assert(ctx, Some(&JsValue::new(f64::MAX)), 10, 10); // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) assert(ctx, Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), 10, 10); })]); } #[test] fn get_relative_end() { #[track_caller] fn assert(context: &mut Context, arg: Option<&JsValue>, len: u64, expected: u64) { const UNDEFINED: &JsValue = &JsValue::undefined(); let arg = arg.unwrap_or(UNDEFINED); assert_eq!( Array::get_relative_end(context, arg, len).unwrap(), expected ); } run_test_actions([TestAction::inspect_context(|ctx| { assert(ctx, None, 10, 10); assert(ctx, Some(&JsValue::undefined()), 10, 10); assert(ctx, Some(&JsValue::new(f64::NEG_INFINITY)), 10, 0); assert(ctx, Some(&JsValue::new(f64::INFINITY)), 10, 10); assert(ctx, Some(&JsValue::new(-1)), 10, 9); assert(ctx, Some(&JsValue::new(1)), 10, 1); assert(ctx, Some(&JsValue::new(-11)), 10, 0); assert(ctx, Some(&JsValue::new(11)), 10, 10); assert(ctx, Some(&JsValue::new(f64::MIN)), 10, 0); assert(ctx, Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), 10, 0); assert(ctx, Some(&JsValue::new(f64::MAX)), 10, 10); // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) assert(ctx, Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), 10, 10); })]); } #[test] fn array_length_is_not_enumerable() { run_test_actions([TestAction::assert( "!Object.getOwnPropertyDescriptor([], 'length').enumerable", )]); } #[test] fn array_sort() { run_test_actions([ TestAction::run_harness(), TestAction::run("let arr = ['80', '9', '700', 40, 1, 5, 200];"), TestAction::assert(indoc! {r#" arrayEquals( arr.sort(), [1, 200, 40, 5, "700", "80", "9"] ) "#}), TestAction::assert(indoc! {r#" arrayEquals( arr.sort((a, b) => a - b), [1, 5, "9", 40, "80", 200, "700"] ) "#}), ]); } #[test] fn array_of_neg_zero() { run_test_actions([ TestAction::run("let arr = [-0, -0, -0, -0];"), // Assert the parity of all items of the list. TestAction::assert("arr.every(x => (1/x) === -Infinity)"), ]); } ================================================ FILE: core/engine/src/builtins/array_buffer/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `ArrayBuffer` and `SharedArrayBuffer` objects //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-arraybuffer-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer #![deny(unsafe_op_in_unsafe_fn)] #![deny(clippy::undocumented_unsafe_blocks)] pub(crate) mod shared; pub(crate) mod utils; #[cfg(test)] mod tests; use std::ops::{Deref, DerefMut}; use aligned_vec::{ABox, AVec, ConstAlign}; pub use shared::SharedArrayBuffer; use std::sync::atomic::Ordering; use crate::{ Context, JsArgs, JsData, JsResult, JsString, JsValue, builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, object::{JsObject, internal_methods::get_prototype_from_constructor}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, }; use boa_gc::{Finalize, GcRef, GcRefMut, Trace}; use self::utils::{SliceRef, SliceRefMut}; use super::{ Array, BuiltInBuilder, BuiltInConstructor, DataView, IntrinsicObject, typed_array::TypedArray, }; /// `Vec`, but aligned to a 64-bit memory address. pub type AlignedVec = AVec>; pub(crate) type AlignedBox = ABox>; #[derive(Debug, Clone, Copy)] pub(crate) enum BufferRef { Buffer(B), SharedBuffer(S), } impl BufferRef where B: Deref, S: Deref, { /// Gets the inner data of the buffer. pub(crate) fn bytes(&self, ordering: Ordering) -> Option> { match self { Self::Buffer(buf) => buf.deref().bytes().map(SliceRef::Slice), Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().bytes(ordering))), } } /// Gets the inner data of the buffer without accessing the current atomic length. /// /// Returns `None` if the buffer is detached or if the provided `len` is bigger than /// the allocated buffer. #[track_caller] pub(crate) fn bytes_with_len(&self, len: usize) -> Option> { match self { Self::Buffer(buf) => buf.deref().bytes_with_len(len).map(SliceRef::Slice), Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().bytes_with_len(len))), } } pub(crate) fn is_fixed_len(&self) -> bool { match self { Self::Buffer(buf) => buf.is_fixed_len(), Self::SharedBuffer(buf) => buf.is_fixed_len(), } } } #[derive(Debug)] pub(crate) enum BufferRefMut { Buffer(B), SharedBuffer(S), } impl BufferRefMut where B: DerefMut, S: DerefMut, { pub(crate) fn bytes(&mut self, ordering: Ordering) -> Option> { match self { Self::Buffer(buf) => buf.deref_mut().bytes_mut().map(SliceRefMut::Slice), Self::SharedBuffer(buf) => { Some(SliceRefMut::AtomicSlice(buf.deref_mut().bytes(ordering))) } } } /// Gets the mutable inner data of the buffer without accessing the current atomic length. /// /// Returns `None` if the buffer is detached or if the provided `len` is bigger than /// the allocated buffer. pub(crate) fn bytes_with_len(&mut self, len: usize) -> Option> { match self { Self::Buffer(buf) => buf .deref_mut() .bytes_with_len_mut(len) .map(SliceRefMut::Slice), Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice( buf.deref_mut().bytes_with_len(len), )), } } } /// A `JsObject` containing a bytes buffer as its inner data. #[derive(Debug, Clone, Trace, Finalize)] #[boa_gc(unsafe_no_drop)] pub(crate) enum BufferObject { Buffer(JsObject), SharedBuffer(JsObject), } impl From for JsObject { fn from(value: BufferObject) -> Self { match value { BufferObject::Buffer(buf) => buf.upcast(), BufferObject::SharedBuffer(buf) => buf.upcast(), } } } impl From for JsValue { fn from(value: BufferObject) -> Self { JsValue::from(JsObject::from(value)) } } impl BufferObject { /// Gets the buffer data of the object. #[inline] #[must_use] pub(crate) fn as_buffer( &self, ) -> BufferRef, GcRef<'_, SharedArrayBuffer>> { match self { Self::Buffer(buf) => BufferRef::Buffer(GcRef::map(buf.borrow(), |o| o.data())), Self::SharedBuffer(buf) => { BufferRef::SharedBuffer(GcRef::map(buf.borrow(), |o| o.data())) } } } /// Gets the mutable buffer data of the object #[inline] #[track_caller] pub(crate) fn as_buffer_mut( &self, ) -> BufferRefMut, GcRefMut<'_, SharedArrayBuffer>> { match self { Self::Buffer(buf) => { BufferRefMut::Buffer(GcRefMut::map(buf.borrow_mut(), |o| o.data_mut())) } Self::SharedBuffer(buf) => { BufferRefMut::SharedBuffer(GcRefMut::map(buf.borrow_mut(), |o| o.data_mut())) } } } /// Returns `true` if the buffer objects point to the same buffer. #[inline] #[track_caller] pub(crate) fn equals(lhs: &Self, rhs: &Self) -> bool { match (lhs, rhs) { (BufferObject::Buffer(lhs), BufferObject::Buffer(rhs)) => JsObject::equals(lhs, rhs), (BufferObject::SharedBuffer(lhs), BufferObject::SharedBuffer(rhs)) => { if JsObject::equals(lhs, rhs) { return true; } let lhs = lhs.borrow(); let rhs = rhs.borrow(); std::ptr::eq(lhs.data().as_ptr(), rhs.data().as_ptr()) } _ => false, } } } /// The internal representation of an `ArrayBuffer` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct ArrayBuffer { /// The `[[ArrayBufferData]]` internal slot. #[unsafe_ignore_trace] data: Option>, /// The `[[ArrayBufferMaxByteLength]]` internal slot. max_byte_len: Option, /// The `[[ArrayBufferDetachKey]]` internal slot. detach_key: JsValue, } impl ArrayBuffer { pub(crate) fn from_data(data: AlignedVec, detach_key: JsValue) -> Self { Self { data: Some(data), max_byte_len: None, detach_key, } } pub(crate) fn len(&self) -> usize { self.data.as_ref().map_or(0, AlignedVec::len) } pub(crate) fn bytes(&self) -> Option<&[u8]> { self.data.as_deref() } pub(crate) fn bytes_mut(&mut self) -> Option<&mut [u8]> { self.data.as_deref_mut() } pub(crate) fn vec_mut(&mut self) -> Option<&mut AlignedVec> { self.data.as_mut() } /// Sets the maximum byte length of the buffer, returning the previous value if present. pub(crate) fn set_max_byte_length(&mut self, max_byte_len: u64) -> Option { self.max_byte_len.replace(max_byte_len) } /// Gets the inner bytes of the buffer without accessing the current atomic length. #[track_caller] pub(crate) fn bytes_with_len(&self, len: usize) -> Option<&[u8]> { if let Some(s) = self.data.as_deref() { Some(&s[..len]) } else { None } } /// Gets the mutable inner bytes of the buffer without accessing the current atomic length. #[track_caller] pub(crate) fn bytes_with_len_mut(&mut self, len: usize) -> Option<&mut [u8]> { if let Some(s) = self.data.as_deref_mut() { Some(&mut s[..len]) } else { None } } /// Gets the underlying vector for this buffer. #[must_use] pub fn data(&self) -> Option<&[u8]> { self.data.as_deref() } /// Resizes the buffer to the new size, clamped to the maximum byte length if present. pub fn resize(&mut self, new_byte_length: u64) -> JsResult<()> { let Some(max_byte_len) = self.max_byte_len else { return Err(JsNativeError::typ() .with_message("ArrayBuffer.resize: cannot resize a fixed-length buffer") .into()); }; let Some(buf) = self.vec_mut() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer.resize: cannot resize a detached buffer") .into()); }; if new_byte_length > max_byte_len { return Err(JsNativeError::range() .with_message( "ArrayBuffer.resize: new byte length exceeds buffer's maximum byte length", ) .into()); } buf.resize(new_byte_length as usize, 0); Ok(()) } /// Detaches the inner data of this `ArrayBuffer`, returning the original buffer if still /// present. /// /// # Errors /// /// Throws an error if the provided detach key is invalid. pub fn detach(&mut self, key: &JsValue) -> JsResult>> { if !JsValue::same_value(&self.detach_key, key) { return Err(JsNativeError::typ() .with_message("Cannot detach array buffer with different key") .into()); } Ok(self.data.take()) } /// `IsDetachedBuffer ( arrayBuffer )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer pub(crate) const fn is_detached(&self) -> bool { // 1. If arrayBuffer.[[ArrayBufferData]] is null, return true. // 2. Return false. self.data.is_none() } pub(crate) fn is_fixed_len(&self) -> bool { self.max_byte_len.is_none() } } impl IntrinsicObject for ArrayBuffer { fn init(realm: &Realm) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let get_species = BuiltInBuilder::callable(realm, Self::get_species) .name(js_string!("get [Symbol.species]")) .build(); let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) .name(js_string!("get byteLength")) .build(); let get_resizable = BuiltInBuilder::callable(realm, Self::get_resizable) .name(js_string!("get resizable")) .build(); let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length) .name(js_string!("get maxByteLength")) .build(); #[cfg(feature = "experimental")] let get_detached = BuiltInBuilder::callable(realm, Self::get_detached) .name(js_string!("get detached")) .build(); let builder = BuiltInBuilder::from_standard_constructor::(realm) .static_accessor( JsSymbol::species(), Some(get_species), None, Attribute::CONFIGURABLE, ) .static_method(Self::is_view, js_string!("isView"), 1) .accessor( js_string!("byteLength"), Some(get_byte_length), None, flag_attributes, ) .accessor( js_string!("resizable"), Some(get_resizable), None, flag_attributes, ) .accessor( js_string!("maxByteLength"), Some(get_max_byte_length), None, flag_attributes, ) .method(Self::js_resize, js_string!("resize"), 1) .method(Self::slice, js_string!("slice"), 2) .property( JsSymbol::to_string_tag(), Self::NAME, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); #[cfg(feature = "experimental")] let builder = builder .accessor( js_string!("detached"), Some(get_detached), None, flag_attributes, ) .method(Self::transfer::, js_string!("transfer"), 0) .method( Self::transfer::, js_string!("transferToFixedLength"), 0, ); builder.build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for ArrayBuffer { const NAME: JsString = StaticJsStrings::ARRAY_BUFFER; } impl BuiltInConstructor for ArrayBuffer { const PROTOTYPE_STORAGE_SLOTS: usize = 13; const CONSTRUCTOR_STORAGE_SLOTS: usize = 3; const CONSTRUCTOR_ARGUMENTS: usize = 1; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::array_buffer; /// `ArrayBuffer ( length )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer-length fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { return Err(JsNativeError::typ() .with_message("ArrayBuffer.constructor called with undefined new target") .into()); } // 2. Let byteLength be ? ToIndex(length). let byte_len = args.get_or_undefined(0).to_index(context)?; // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options). let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; // 4. Return ? AllocateArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). Ok(Self::allocate(new_target, byte_len, max_byte_len, context)? .upcast() .into()) } } impl ArrayBuffer { /// `ArrayBuffer.isView ( arg )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.isview #[allow(clippy::unnecessary_wraps)] fn is_view(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult { // 1. If Type(arg) is not Object, return false. // 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true. // 3. Return false. Ok(args .get_or_undefined(0) .as_object() .is_some_and(|obj| obj.is::() || obj.is::()) .into()) } /// `get ArrayBuffer [ @@species ]` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species #[allow(clippy::unnecessary_wraps)] fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Return the this value. Ok(this.clone()) } /// `get ArrayBuffer.prototype.byteLength` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength pub(crate) fn get_byte_length( this: &JsValue, _args: &[JsValue], _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let object = this.as_object(); let buf = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("get ArrayBuffer.prototype.byteLength called with invalid `this`") })?; // 4. If IsDetachedBuffer(O) is true, return +0𝔽. // 5. Let length be O.[[ArrayBufferByteLength]]. // 6. Return 𝔽(length). Ok(buf.len().into()) } /// [`get ArrayBuffer.prototype.maxByteLength`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength pub(crate) fn get_max_byte_length( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let object = this.as_object(); let buf = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message( "get ArrayBuffer.prototype.maxByteLength called with invalid `this`", ) })?; // 4. If IsDetachedBuffer(O) is true, return +0𝔽. let Some(data) = buf.bytes() else { return Ok(JsValue::from(0)); }; // 5. If IsFixedLengthArrayBuffer(O) is true, then // a. Let length be O.[[ArrayBufferByteLength]]. // 6. Else, // a. Let length be O.[[ArrayBufferMaxByteLength]]. // 7. Return 𝔽(length). Ok(buf.max_byte_len.unwrap_or(data.len() as u64).into()) } /// [`get ArrayBuffer.prototype.resizable`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable pub(crate) fn get_resizable( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let object = this.as_object(); let buf = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("get ArrayBuffer.prototype.resizable called with invalid `this`") })?; // 4. If IsFixedLengthArrayBuffer(O) is false, return true; otherwise return false. Ok(JsValue::from(!buf.is_fixed_len())) } /// [`get ArrayBuffer.prototype.detached`][spec]. /// /// [spec]: https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached #[cfg(feature = "experimental")] fn get_detached( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let object = this.as_object(); let buf = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("get ArrayBuffer.prototype.detached called with invalid `this`") })?; // 4. Return IsDetachedBuffer(O). Ok(buf.is_detached().into()) } /// [`ArrayBuffer.prototype.resize ( newLength )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize pub(crate) fn js_resize( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let buf = this .as_object() .and_then(|o| o.clone().downcast::().ok()) .ok_or_else(|| { JsNativeError::typ() .with_message("ArrayBuffer.prototype.resize called with invalid `this`") })?; // 4. Let newByteLength be ? ToIndex(newLength). let new_byte_length = args.get_or_undefined(0).to_index(context)?; // These steps are performed in the `Self::resize` method. // 5. If IsDetachedBuffer(O) is true, throw a TypeError exception. // 6. If newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception. // TODO: 7. Let hostHandled be ? HostResizeArrayBuffer(O, newByteLength). // 8. If hostHandled is handled, return undefined. // Used in engines to handle Wasm buffers in a special way, but we don't // have a Wasm interpreter in place yet. // 9. Let oldBlock be O.[[ArrayBufferData]]. // 10. Let newBlock be ? CreateByteDataBlock(newByteLength). // 11. Let copyLength be min(newByteLength, O.[[ArrayBufferByteLength]]). // 12. Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength). // 13. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. // Implementations may implement this method as in-place growth or shrinkage. // 14. Set O.[[ArrayBufferData]] to newBlock. // 15. Set O.[[ArrayBufferByteLength]] to newByteLength. buf.borrow_mut().data_mut().resize(new_byte_length)?; // 16. Return undefined. Ok(JsValue::undefined()) } /// `ArrayBuffer.prototype.slice ( start, end )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let buf = this .as_object() .and_then(|o| o.clone().downcast::().ok()) .ok_or_else(|| { JsNativeError::typ() .with_message("ArrayBuffer.slice called with invalid `this` value") })?; let len = { let buf = buf.borrow(); // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. if buf.data().is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer.slice called with detached buffer") .into()); } // 5. Let len be O.[[ArrayBufferByteLength]]. buf.data().len() as u64 }; // 6. Let relativeStart be ? ToIntegerOrInfinity(start). // 7. If relativeStart = -∞, let first be 0. // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). // 9. Else, let first be min(relativeStart, len). let first = Array::get_relative_start(context, args.get_or_undefined(0), len)?; // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). // 11. If relativeEnd = -∞, let final be 0. // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). // 13. Else, let final be min(relativeEnd, len). let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len)?; // 14. Let newLen be max(final - first, 0). let new_len = final_.saturating_sub(first); // 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). let ctor = buf .clone() .upcast() .species_constructor(StandardConstructors::array_buffer, context)?; // 16. Let new be ? Construct(ctor, « 𝔽(newLen) »). let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. let Ok(new) = new.downcast::() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer constructor returned invalid object") .into()); }; // 20. If SameValue(new, O) is true, throw a TypeError exception. if JsObject::equals(&buf, &new) { return Err(JsNativeError::typ() .with_message("new ArrayBuffer is the same as this ArrayBuffer") .into()); } { // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. // 25. Let toBuf be new.[[ArrayBufferData]]. let mut new = new.borrow_mut(); let Some(to_buf) = new.data_mut().bytes_mut() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer constructor returned detached ArrayBuffer") .into()); }; // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. if (to_buf.len() as u64) < new_len { return Err(JsNativeError::typ() .with_message("new ArrayBuffer length too small") .into()); } // 22. NOTE: Side-effects of the above steps may have detached O. // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. // 24. Let fromBuf be O.[[ArrayBufferData]]. let buf = buf.borrow(); let Some(from_buf) = buf.data().bytes() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer detached while ArrayBuffer.slice was running") .into()); }; // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). let first = first as usize; let new_len = new_len as usize; to_buf[..new_len].copy_from_slice(&from_buf[first..first + new_len]); } // 27. Return new. Ok(new.upcast().into()) } /// [`ArrayBuffer.prototype.transfer ( [ newLength ] )`][transfer] and /// [`ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )`][transferFL] /// /// [transfer]: https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer /// [transferFL]: https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength #[cfg(feature = "experimental")] fn transfer( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Return ? ArrayBufferCopyAndDetach(O, newLength, preserve-resizability). // Abstract operation `ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability )` // https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffercopyanddetach let new_length = args.get_or_undefined(0); // 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]). // 2. If IsSharedArrayBuffer(arrayBuffer) is true, throw a TypeError exception. let buf = this .as_object() .and_then(|o| o.clone().downcast::().ok()) .ok_or_else(|| { JsNativeError::typ().with_message(if TO_FIXED_LENGTH { "ArrayBuffer.prototype.transferToFixedLength called with invalid `this`" } else { "ArrayBuffer.prototype.transfer called with invalid `this`" }) })?; // 3. If newLength is undefined, then let new_len = if new_length.is_undefined() { // a. Let newByteLength be arrayBuffer.[[ArrayBufferByteLength]]. buf.borrow().data().len() as u64 } else { // 4. Else, // a. Let newByteLength be ? ToIndex(newLength). new_length.to_index(context)? }; // 5. If IsDetachedBuffer(arrayBuffer) is true, throw a TypeError exception. let Some(mut bytes) = buf.borrow_mut().data_mut().data.take() else { return Err(JsNativeError::typ() .with_message("cannot transfer a detached buffer") .into()); }; // 6. If preserveResizability is preserve-resizability and IsResizableArrayBuffer(arrayBuffer) // is true, then // a. Let newMaxByteLength be arrayBuffer.[[ArrayBufferMaxByteLength]]. // 7. Else, // a. Let newMaxByteLength be empty. let new_max_len = buf .borrow() .data() .max_byte_len .filter(|_| !TO_FIXED_LENGTH); // 8. If arrayBuffer.[[ArrayBufferDetachKey]] is not undefined, throw a TypeError exception. if !buf.borrow().data().detach_key.is_undefined() { buf.borrow_mut().data_mut().data = Some(bytes); return Err(JsNativeError::typ() .with_message("cannot transfer a buffer with a detach key") .into()); } // Effectively, the next steps only create a new object for the same vec, so we can skip all // those steps and just make a single check + trigger the realloc. // 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, newMaxByteLength). // 10. Let copyLength be min(newByteLength, arrayBuffer.[[ArrayBufferByteLength]]). // 11. Let fromBlock be arrayBuffer.[[ArrayBufferData]]. // 12. Let toBlock be newBuffer.[[ArrayBufferData]]. // 13. Perform CopyDataBlockBytes(toBlock, 0, fromBlock, 0, copyLength). // 14. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are // observable. Implementations may implement this method as a zero-copy move or a realloc. // 15. Perform ! DetachArrayBuffer(arrayBuffer). // 16. Return newBuffer. if let Some(new_max_len) = new_max_len { if new_len > new_max_len { buf.borrow_mut().data_mut().data = Some(bytes); return Err(JsNativeError::range() .with_message("`length` cannot be bigger than `maxByteLength`") .into()); } // Should only truncate without reallocating. bytes.resize(new_len as usize, 0); } else { bytes.resize(new_len as usize, 0); // Realloc the vec to fit onto the new exact length. bytes.shrink_to_fit(); } let prototype = context .intrinsics() .constructors() .array_buffer() .prototype(); Ok(JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, ArrayBuffer { data: Some(bytes), max_byte_len: new_max_len, detach_key: JsValue::undefined(), }, ) .into()) } /// `AllocateArrayBuffer ( constructor, byteLength )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-allocatearraybuffer pub(crate) fn allocate( constructor: &JsValue, byte_len: u64, max_byte_len: Option, context: &mut Context, ) -> JsResult> { // 1. Let slots be « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] ». // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingResizableBuffer be true; otherwise let allocatingResizableBuffer be false. // 3. If allocatingResizableBuffer is true, then // a. If byteLength > maxByteLength, throw a RangeError exception. // b. Append [[ArrayBufferMaxByteLength]] to slots. if let Some(max_byte_len) = max_byte_len && byte_len > max_byte_len { return Err(JsNativeError::range() .with_message("`length` cannot be bigger than `maxByteLength`") .into()); } // 4. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", slots). let prototype = get_prototype_from_constructor( constructor, StandardConstructors::array_buffer, context, )?; // 5. Let block be ? CreateByteDataBlock(byteLength). // Preemptively allocate for `max_byte_len` if possible. // a. If it is not possible to create a Data Block block consisting of maxByteLength bytes, throw a RangeError exception. // b. NOTE: Resizable ArrayBuffers are designed to be implementable with in-place growth. Implementations may // throw if, for example, virtual memory cannot be reserved up front. let block = create_byte_data_block(byte_len, max_byte_len, context)?; let obj = JsObject::new( context.root_shape(), prototype, Self { // 6. Set obj.[[ArrayBufferData]] to block. // 7. Set obj.[[ArrayBufferByteLength]] to byteLength. data: Some(block), // 8. If allocatingResizableBuffer is true, then // c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. max_byte_len, detach_key: JsValue::undefined(), }, ); // 9. Return obj. Ok(obj) } } /// Abstract operation [`GetArrayBufferMaxByteLengthOption ( options )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption fn get_max_byte_len(options: &JsValue, context: &mut Context) -> JsResult> { // 1. If options is not an Object, return empty. let Some(options) = options.as_object() else { return Ok(None); }; // 2. Let maxByteLength be ? Get(options, "maxByteLength"). let max_byte_len = options.get(js_string!("maxByteLength"), context)?; // 3. If maxByteLength is undefined, return empty. if max_byte_len.is_undefined() { return Ok(None); } // 4. Return ? ToIndex(maxByteLength). max_byte_len.to_index(context).map(Some) } /// `CreateByteDataBlock ( size )` abstract operation. /// /// The abstract operation `CreateByteDataBlock` takes argument `size` (a non-negative /// integer). For more information, check the [spec][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock pub(crate) fn create_byte_data_block( size: u64, max_buffer_size: Option, context: &mut Context, ) -> JsResult> { let alloc_size = max_buffer_size.unwrap_or(size); assert!(size <= alloc_size); if alloc_size > context.host_hooks().max_buffer_size(context) { return Err(JsNativeError::range() .with_message("cannot allocate a buffer that exceeds the maximum buffer size") .into()); } // 1. Let db be a new Data Block value consisting of size bytes. If it is impossible to // create such a Data Block, throw a RangeError exception. let alloc_size = alloc_size.try_into().map_err(|e| { JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) })?; let mut data_block = AlignedVec::::new(64); data_block.try_reserve_exact(alloc_size).map_err(|e| { let message = match e { aligned_vec::TryReserveError::CapacityOverflow => { format!("capacity overflow for size {size} while allocating data block") } aligned_vec::TryReserveError::AllocError { layout } => { format!("invalid layout {layout:?} while allocating data block") } }; JsNativeError::range().with_message(message) })?; // since size <= alloc_size, then `size` must also fit inside a `usize`. let size = size as usize; // 2. Set all of the bytes of db to 0. data_block.resize(size, 0); // 3. Return db. Ok(data_block) } ================================================ FILE: core/engine/src/builtins/array_buffer/shared.rs ================================================ use std::{ ptr, sync::{Arc, atomic::Ordering}, }; use portable_atomic::{AtomicU8, AtomicUsize}; use boa_gc::{Finalize, Trace}; use crate::{ Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, builtins::{ Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, array_buffer::{AlignedBox, AlignedVec}, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{get_max_byte_len, utils::copy_shared_to_shared}; /// The internal representation of a `SharedArrayBuffer` object. /// /// This struct implements `Send` and `Sync`, meaning it can be shared between threads /// running different JS code at the same time. #[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct SharedArrayBuffer { // Shared buffers cannot be detached. #[unsafe_ignore_trace] data: Arc, } #[derive(Debug)] struct Inner { // Technically we should have an `[[ArrayBufferData]]` internal slot, // `[[ArrayBufferByteLengthData]]` and `[[ArrayBufferMaxByteLength]]` slots for growable arrays // or `[[ArrayBufferByteLength]]` for fixed arrays, but we can save some work // by just using this representation instead. // // The maximum buffer length is represented by `buffer.len()`, and `current_len` has the current // buffer length, or `None` if this is a fixed buffer; in this case, `buffer.len()` will be // the true length of the buffer. buffer: AlignedBox<[AtomicU8]>, current_len: Option, } impl Default for Inner { fn default() -> Self { Self { buffer: AlignedVec::new(0).into_boxed_slice(), current_len: None, } } } impl SharedArrayBuffer { /// Creates a `SharedArrayBuffer` with an empty buffer. #[must_use] pub fn empty() -> Self { Self { data: Arc::default(), } } /// Gets the length of this `SharedArrayBuffer`. pub(crate) fn len(&self, ordering: Ordering) -> usize { self.data .current_len .as_ref() .map_or_else(|| self.data.buffer.len(), |len| len.load(ordering)) } /// Gets the inner bytes of this `SharedArrayBuffer`. pub(crate) fn bytes(&self, ordering: Ordering) -> &[AtomicU8] { &self.data.buffer[..self.len(ordering)] } /// Gets the inner data of the buffer without accessing the current atomic length. #[track_caller] pub(crate) fn bytes_with_len(&self, len: usize) -> &[AtomicU8] { &self.data.buffer[..len] } /// Gets a pointer to the internal shared buffer. pub(crate) fn as_ptr(&self) -> *const AtomicU8 { (*self.data.buffer).as_ptr() } pub(crate) fn is_fixed_len(&self) -> bool { self.data.current_len.is_none() } } impl IntrinsicObject for SharedArrayBuffer { fn init(realm: &Realm) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let get_species = BuiltInBuilder::callable(realm, Self::get_species) .name(js_string!("get [Symbol.species]")) .build(); let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) .name(js_string!("get byteLength")) .build(); let get_growable = BuiltInBuilder::callable(realm, Self::get_growable) .name(js_string!("get growable")) .build(); let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length) .name(js_string!("get maxByteLength")) .build(); BuiltInBuilder::from_standard_constructor::(realm) .static_accessor( JsSymbol::species(), Some(get_species), None, Attribute::CONFIGURABLE, ) .accessor( js_string!("byteLength"), Some(get_byte_length), None, flag_attributes, ) .accessor( js_string!("growable"), Some(get_growable), None, flag_attributes, ) .accessor( js_string!("maxByteLength"), Some(get_max_byte_length), None, flag_attributes, ) .method(Self::slice, js_string!("slice"), 2) .method(Self::grow, js_string!("grow"), 1) .property( JsSymbol::to_string_tag(), Self::NAME, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for SharedArrayBuffer { const NAME: JsString = StaticJsStrings::SHARED_ARRAY_BUFFER; } impl BuiltInConstructor for SharedArrayBuffer { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 9; const CONSTRUCTOR_STORAGE_SLOTS: usize = 2; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::shared_array_buffer; /// `25.1.3.1 SharedArrayBuffer ( length [ , options ] )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-constructor fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { return Err(JsNativeError::typ() .with_message("ArrayBuffer.constructor called with undefined new target") .into()); } // 2. Let byteLength be ? ToIndex(length). let byte_len = args.get_or_undefined(0).to_index(context)?; // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options). let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; // 4. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). Ok(Self::allocate(new_target, byte_len, max_byte_len, context)? .upcast() .into()) } } impl SharedArrayBuffer { /// `get SharedArrayBuffer [ @@species ]` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-@@species #[allow(clippy::unnecessary_wraps)] fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Return the this value. Ok(this.clone()) } /// `get SharedArrayBuffer.prototype.byteLength` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength pub(crate) fn get_byte_length( this: &JsValue, _args: &[JsValue], _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let object = this.as_object(); let buf = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("SharedArrayBuffer.byteLength called with invalid value") })?; // 4. Let length be ArrayBufferByteLength(O, seq-cst). let len = buf.bytes(Ordering::SeqCst).len() as u64; // 5. Return 𝔽(length). Ok(len.into()) } /// [`get SharedArrayBuffer.prototype.growable`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.growable pub(crate) fn get_growable( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. let object = this.as_object(); let buf = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("get SharedArrayBuffer.growable called with invalid `this`") })?; // 4. If IsFixedLengthArrayBuffer(O) is false, return true; otherwise return false. Ok(JsValue::from(!buf.is_fixed_len())) } /// [`get SharedArrayBuffer.prototype.maxByteLength`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.maxbytelength pub(crate) fn get_max_byte_length( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. let object = this.as_object(); let buf = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("get SharedArrayBuffer.maxByteLength called with invalid value") })?; // 4. If IsFixedLengthArrayBuffer(O) is true, then // a. Let length be O.[[ArrayBufferByteLength]]. // 5. Else, // a. Let length be O.[[ArrayBufferMaxByteLength]]. // 6. Return 𝔽(length). Ok(buf.data.buffer.len().into()) } /// [`SharedArrayBuffer.prototype.grow ( newLength )`][spec]. /// /// [spec]: https://tc39.es/ecma262/sec-sharedarraybuffer.prototype.grow pub(crate) fn grow( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. let Some(buf) = this .as_object() .and_then(|o| o.clone().downcast::().ok()) else { return Err(JsNativeError::typ() .with_message("SharedArrayBuffer.grow called with non-object value") .into()); }; // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]). if buf.borrow().data().is_fixed_len() { return Err(JsNativeError::typ() .with_message("SharedArrayBuffer.grow: cannot grow a fixed-length buffer") .into()); } // 4. Let newByteLength be ? ToIndex(newLength). let new_byte_len = args.get_or_undefined(0).to_index(context)?; // TODO: 5. Let hostHandled be ? HostGrowSharedArrayBuffer(O, newByteLength). // 6. If hostHandled is handled, return undefined. // Used in engines to handle Wasm buffers in a special way, but we don't // have a Wasm interpreter in place yet. // 7. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. // 8. Let byteLengthBlock be O.[[ArrayBufferByteLengthData]]. // 9. Let currentByteLengthRawBytes be GetRawBytesFromSharedBlock(byteLengthBlock, 0, biguint64, true, seq-cst). // 10. Let newByteLengthRawBytes be NumericToRawBytes(biguint64, ℤ(newByteLength), isLittleEndian). let buf = buf.borrow(); let buf = &buf.data(); // d. If newByteLength < currentByteLength or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception. // Extracting this condition outside the CAS since throwing early doesn't affect the correct // behaviour of the loop. if new_byte_len > buf.data.buffer.len() as u64 { return Err(JsNativeError::range() .with_message( "SharedArrayBuffer.grow: new length cannot be bigger than `maxByteLength`", ) .into()); } let new_byte_len = new_byte_len as usize; // If we used let-else above to avoid the expect, we would carry a borrow through the `to_index` // call, which could mutably borrow. Another alternative would be to clone the whole // `SharedArrayBuffer`, but it's better to avoid contention with the counter in the `Arc` pointer. let atomic_len = buf .data .current_len .as_ref() .expect("already checked that the buffer is not fixed-length"); // 11. Repeat, // a. NOTE: This is a compare-and-exchange loop to ensure that parallel, racing grows of the same buffer are // totally ordered, are not lost, and do not silently do nothing. The loop exits if it was able to attempt // to grow uncontended. // b. Let currentByteLength be ℝ(RawBytesToNumeric(biguint64, currentByteLengthRawBytes, isLittleEndian)). // c. If newByteLength = currentByteLength, return undefined. // d. If newByteLength < currentByteLength or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a // RangeError exception. // e. Let byteLengthDelta be newByteLength - currentByteLength. // f. If it is impossible to create a new Shared Data Block value consisting of byteLengthDelta bytes, throw // a RangeError exception. // g. NOTE: No new Shared Data Block is constructed and used here. The observable behaviour of growable // SharedArrayBuffers is specified by allocating a max-sized Shared Data Block at construction time, and // this step captures the requirement that implementations that run out of memory must throw a RangeError. // h. Let readByteLengthRawBytes be AtomicCompareExchangeInSharedBlock(byteLengthBlock, 0, 8, // currentByteLengthRawBytes, newByteLengthRawBytes). // i. If ByteListEqual(readByteLengthRawBytes, currentByteLengthRawBytes) is true, return undefined. // j. Set currentByteLengthRawBytes to readByteLengthRawBytes. // We require SEQ-CST operations because readers of the buffer also use SEQ-CST operations. atomic_len .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev_byte_len| { (prev_byte_len <= new_byte_len).then_some(new_byte_len) }) .map_err(|_| { JsNativeError::range() .with_message("SharedArrayBuffer.grow: failed to grow buffer to new length") })?; Ok(JsValue::undefined()) } /// `SharedArrayBuffer.prototype.slice ( start, end )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. let buf = this .as_object() .and_then(|o| o.clone().downcast::().ok()) .ok_or_else(|| { JsNativeError::typ() .with_message("SharedArrayBuffer.slice called with invalid `this` value") })?; // 4. Let len be ArrayBufferByteLength(O, seq-cst). let len = buf.borrow().data().len(Ordering::SeqCst); // 5. Let relativeStart be ? ToIntegerOrInfinity(start). // 6. If relativeStart = -∞, let first be 0. // 7. Else if relativeStart < 0, let first be max(len + relativeStart, 0). // 8. Else, let first be min(relativeStart, len). let first = Array::get_relative_start(context, args.get_or_undefined(0), len as u64)?; // 9. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). // 10. If relativeEnd = -∞, let final be 0. // 11. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). // 12. Else, let final be min(relativeEnd, len). let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len as u64)?; // 13. Let newLen be max(final - first, 0). let new_len = final_.saturating_sub(first); // 14. Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%). let ctor = buf .clone() .upcast() .species_constructor(StandardConstructors::shared_array_buffer, context)?; // 15. Let new be ? Construct(ctor, « 𝔽(newLen) »). let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; { let buf = buf.borrow(); let buf = &buf.data(); // 16. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). // 17. If IsSharedArrayBuffer(new) is false, throw a TypeError exception. let new = new.downcast_ref::().ok_or_else(|| { JsNativeError::typ() .with_message("SharedArrayBuffer constructor returned invalid object") })?; // 18. If new.[[ArrayBufferData]] is O.[[ArrayBufferData]], throw a TypeError exception. if ptr::eq(buf.as_ptr(), new.as_ptr()) { return Err(JsNativeError::typ() .with_message("cannot reuse the same SharedArrayBuffer for a slice operation") .into()); } // 19. If ArrayBufferByteLength(new, seq-cst) < newLen, throw a TypeError exception. if (new.len(Ordering::SeqCst) as u64) < new_len { return Err(JsNativeError::typ() .with_message("invalid size of constructed SharedArrayBuffer") .into()); } let first = first as usize; let new_len = new_len as usize; // 20. Let fromBuf be O.[[ArrayBufferData]]. let from_buf = &buf.bytes_with_len(len)[first..]; // 21. Let toBuf be new.[[ArrayBufferData]]. let to_buf = new; // Sanity check to ensure there is enough space inside `from_buf` for // `new_len` elements. debug_assert!(from_buf.len() >= new_len); // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). // SAFETY: `get_slice_range` will always return indices that are in-bounds. // This also means that the newly created buffer will have at least `new_len` elements // to write to. unsafe { copy_shared_to_shared(from_buf.as_ptr(), to_buf.as_ptr(), new_len) } } // 23. Return new. Ok(new.into()) } /// `AllocateSharedArrayBuffer ( constructor, byteLength [ , maxByteLength ] )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-allocatesharedarraybuffer pub(crate) fn allocate( constructor: &JsValue, byte_len: u64, max_byte_len: Option, context: &mut Context, ) -> JsResult> { // 1. Let slots be « [[ArrayBufferData]] ». // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingGrowableBuffer // be true; otherwise let allocatingGrowableBuffer be false. // 3. If allocatingGrowableBuffer is true, then // a. If byteLength > maxByteLength, throw a RangeError exception. // b. Append [[ArrayBufferByteLengthData]] and [[ArrayBufferMaxByteLength]] to slots. // 4. Else, // a. Append [[ArrayBufferByteLength]] to slots. if let Some(max_byte_len) = max_byte_len && byte_len > max_byte_len { return Err(JsNativeError::range() .with_message("`length` cannot be bigger than `maxByteLength`") .into()); } // 5. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", slots). let prototype = get_prototype_from_constructor( constructor, StandardConstructors::shared_array_buffer, context, )?; // 6. If allocatingGrowableBuffer is true, let allocLength be maxByteLength; // otherwise let allocLength be byteLength. let alloc_len = max_byte_len.unwrap_or(byte_len); // 7. Let block be ? CreateSharedByteDataBlock(allocLength). // 8. Set obj.[[ArrayBufferData]] to block. let block = create_shared_byte_data_block(alloc_len, context)?; // 9. If allocatingGrowableBuffer is true, then // `byte_len` must fit inside an `usize` thanks to the checks inside // `create_shared_byte_data_block`. // a. Assert: byteLength ≤ maxByteLength. // b. Let byteLengthBlock be ? CreateSharedByteDataBlock(8). // c. Perform SetValueInBuffer(byteLengthBlock, 0, biguint64, ℤ(byteLength), true, seq-cst). // d. Set obj.[[ArrayBufferByteLengthData]] to byteLengthBlock. // e. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. let current_len = max_byte_len.map(|_| AtomicUsize::new(byte_len as usize)); // 10. Else, // a. Set obj.[[ArrayBufferByteLength]] to byteLength. let obj = JsObject::new( context.root_shape(), prototype, Self { data: Arc::new(Inner { buffer: block, current_len, }), }, ); // 11. Return obj. Ok(obj) } } /// [`CreateSharedByteDataBlock ( size )`][spec] abstract operation. /// /// Creates a new `Arc>` that can be used as a backing buffer for a [`SharedArrayBuffer`]. /// /// For more information, check the [spec][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-createsharedbytedatablock pub(crate) fn create_shared_byte_data_block( size: u64, context: &mut Context, ) -> JsResult> { if size > context.host_hooks().max_buffer_size(context) { return Err(JsNativeError::range() .with_message( "cannot allocate a buffer that exceeds the maximum buffer size".to_string(), ) .into()); } // 1. Let db be a new Shared Data Block value consisting of size bytes. If it is impossible to // create such a Shared Data Block, throw a RangeError exception. let size = size.try_into().map_err(|e| { JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) })?; if size == 0 { // Must ensure we don't allocate a zero-sized buffer. return Ok(AlignedVec::new(0).into_boxed_slice()); } // 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. // 3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose // [[AgentSignifier]] is AgentSignifier(). // 4. Let zero be « 0 ». // 5. For each index i of db, do // a. Append WriteSharedMemory { [[Order]]: init, [[NoTear]]: true, [[Block]]: db, // [[ByteIndex]]: i, [[ElementSize]]: 1, [[Payload]]: zero } to eventsRecord.[[EventList]]. // 6. Return db. let mut buf = AlignedVec::::new(0); buf.try_reserve_exact(size).map_err(|e| { let message = match e { aligned_vec::TryReserveError::CapacityOverflow => { format!("capacity overflow for size {size} while allocating data block") } aligned_vec::TryReserveError::AllocError { layout } => { format!("invalid layout {layout:?} while allocating data block") } }; JsNativeError::range().with_message(message) })?; buf.resize(size, 0); buf.shrink_to_fit(); let (data, align, len, _) = buf.into_raw_parts(); // 3. Return db. // SAFETY: `[u8]` must be transparently castable to `[AtomicU8]`. Ok(unsafe { AlignedBox::from_raw_parts(align, ptr::slice_from_raw_parts_mut(data.cast(), len)) }) } ================================================ FILE: core/engine/src/builtins/array_buffer/tests.rs ================================================ use crate::object::JsArrayBuffer; use crate::{TestAction, run_test_actions}; #[test] fn create_byte_data_block() { run_test_actions([TestAction::inspect_context(|context| { // Sunny day assert!(super::create_byte_data_block(100, None, context).is_ok()); // Rainy day assert!(super::create_byte_data_block(u64::MAX, None, context).is_err()); })]); } #[test] fn create_shared_byte_data_block() { run_test_actions([TestAction::inspect_context(|context| { // Sunny day assert!(super::shared::create_shared_byte_data_block(100, context).is_ok()); // Rainy day assert!(super::shared::create_shared_byte_data_block(u64::MAX, context).is_err()); })]); } #[test] fn resize() { run_test_actions([TestAction::inspect_context(|context| { let data_block = super::create_byte_data_block(100, None, context).unwrap(); let js_arr = JsArrayBuffer::from_byte_block(data_block, context) .unwrap() .with_max_byte_length(100); let mut arr = js_arr.borrow_mut(); // Sunny day assert_eq!(arr.data_mut().resize(50), Ok(())); // Rainy day assert!(arr.data_mut().resize(u64::MAX).is_err()); })]); } #[test] fn get_values() { run_test_actions([ TestAction::run( r#" var buffer = new ArrayBuffer(12); var sample = new DataView(buffer, 0); sample.setUint8(0, 127); sample.setUint8(1, 255); sample.setUint8(2, 255); sample.setUint8(3, 255); sample.setUint8(4, 128); sample.setUint8(5, 0); sample.setUint8(6, 0); sample.setUint8(7, 0); sample.setUint8(8, 1); sample.setUint8(9, 0); sample.setUint8(10, 0); sample.setUint8(11, 0); "#, ), TestAction::assert("sample.getUint32(0, false) == 2147483647"), TestAction::assert("sample.getUint32(1, false) == 4294967168"), TestAction::assert("sample.getUint32(2, false) == 4294934528"), TestAction::assert("sample.getUint32(3, false) == 4286578688"), TestAction::assert("sample.getUint32(4, false) == 2147483648"), TestAction::assert("sample.getUint32(5, false) == 1"), TestAction::assert("sample.getUint32(6, false) == 256"), TestAction::assert("sample.getUint32(7, false) == 65536"), TestAction::assert("sample.getUint32(8, false) == 16777216"), TestAction::assert("sample.getUint32(0, true) == 4294967167"), TestAction::assert("sample.getUint32(1, true) == 2164260863"), TestAction::assert("sample.getUint32(2, true) == 8454143"), TestAction::assert("sample.getUint32(3, true) == 33023"), TestAction::assert("sample.getUint32(4, true) == 128"), TestAction::assert("sample.getUint32(5, true) == 16777216"), TestAction::assert("sample.getUint32(6, true) == 65536"), TestAction::assert("sample.getUint32(7, true) == 256"), TestAction::assert("sample.getUint32(8, true) == 1"), ]); } #[test] fn sort() { run_test_actions([ TestAction::run( r#" // This cmp function is needed as the harness does not support TypedArray comparison. function cmp(a, b) { return a.length === b.length && a.every((v, i) => v === b[i]); } var TypedArrayCtor = [ Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, ]; var descending = TypedArrayCtor.map((ctor) => new ctor([4, 3, 2, 1]).sort()); var mixed = TypedArrayCtor.map((ctor) => new ctor([3, 4, 1, 2]).sort()); var repeating = TypedArrayCtor.map((ctor) => new ctor([0, 1, 1, 2, 3, 3, 4]).sort()); "#, ), // Descending TestAction::assert("cmp(descending[0], [1, 2, 3, 4])"), TestAction::assert("cmp(descending[1], [1, 2, 3, 4])"), TestAction::assert("cmp(descending[2], [1, 2, 3, 4])"), TestAction::assert("cmp(descending[3], [1, 2, 3, 4])"), TestAction::assert("cmp(descending[4], [1, 2, 3, 4])"), TestAction::assert("cmp(descending[5], [1, 2, 3, 4])"), TestAction::assert("cmp(descending[6], [1, 2, 3, 4])"), TestAction::assert("cmp(descending[7], [1, 2, 3, 4])"), // Mixed TestAction::assert("cmp(mixed[0], [1, 2, 3, 4])"), TestAction::assert("cmp(mixed[1], [1, 2, 3, 4])"), TestAction::assert("cmp(mixed[2], [1, 2, 3, 4])"), TestAction::assert("cmp(mixed[3], [1, 2, 3, 4])"), TestAction::assert("cmp(mixed[4], [1, 2, 3, 4])"), TestAction::assert("cmp(mixed[5], [1, 2, 3, 4])"), TestAction::assert("cmp(mixed[6], [1, 2, 3, 4])"), TestAction::assert("cmp(mixed[7], [1, 2, 3, 4])"), // Repeating TestAction::assert("cmp(repeating[0], [0, 1, 1, 2, 3, 3, 4])"), TestAction::assert("cmp(repeating[1], [0, 1, 1, 2, 3, 3, 4])"), TestAction::assert("cmp(repeating[2], [0, 1, 1, 2, 3, 3, 4])"), TestAction::assert("cmp(repeating[3], [0, 1, 1, 2, 3, 3, 4])"), TestAction::assert("cmp(repeating[4], [0, 1, 1, 2, 3, 3, 4])"), TestAction::assert("cmp(repeating[5], [0, 1, 1, 2, 3, 3, 4])"), TestAction::assert("cmp(repeating[6], [0, 1, 1, 2, 3, 3, 4])"), TestAction::assert("cmp(repeating[7], [0, 1, 1, 2, 3, 3, 4])"), ]); } #[test] fn sort_negative_zero() { run_test_actions([ TestAction::run( r#" // This cmp function is needed as the harness does not support TypedArray comparison. function cmp(a, b) { return a.length === b.length && a.every((v, i) => v === b[i]); } var TypedArrayCtor = [Float32Array, Float64Array]; var negativeZero = TypedArrayCtor.map((ctor) => new ctor([1, 0, -0, 2]).sort()); var infinities = TypedArrayCtor.map((ctor) => new ctor([3, 4, Infinity, -Infinity, 1, 2]).sort()); "#, ), TestAction::assert("cmp(negativeZero[0], [-0, 0, 1, 2])"), TestAction::assert("cmp(negativeZero[1], [-0, 0, 1, 2])"), TestAction::assert("cmp(infinities[0], [-Infinity, 1, 2, 3, 4, Infinity])"), TestAction::assert("cmp(infinities[1], [-Infinity, 1, 2, 3, 4, Infinity])"), ]); } /// Tests `SharedArrayBuffer.prototype.slice` which triggers `copy_shared_to_shared` /// (the `batched_atomic_copy_forward` path). #[test] fn shared_array_buffer_slice() { run_test_actions([ TestAction::run( r#" var sab = new SharedArrayBuffer(16); var view = new Uint8Array(sab); for (var i = 0; i < 16; i++) view[i] = i + 1; var sliced = sab.slice(0); var result = new Uint8Array(sliced); "#, ), // Verify all 16 bytes copied correctly (exercises u64 batch + head/tail) TestAction::assert("result[0] === 1"), TestAction::assert("result[7] === 8"), TestAction::assert("result[15] === 16"), TestAction::assert("result.length === 16"), ]); } /// Tests `SharedArrayBuffer.prototype.slice` with a partial range and odd sizes /// to exercise alignment edge cases in the batched copy. #[test] fn shared_array_buffer_slice_partial() { run_test_actions([ TestAction::run( r#" var sab = new SharedArrayBuffer(20); var view = new Uint8Array(sab); for (var i = 0; i < 20; i++) view[i] = i * 3; // Slice with odd offset and size to hit unaligned head/tail var sliced = sab.slice(3, 14); var result = new Uint8Array(sliced); "#, ), TestAction::assert("result.length === 11"), TestAction::assert("result[0] === 9"), TestAction::assert("result[10] === 39"), ]); } /// Tests TypedArray.set from a SharedArrayBuffer-backed array to a regular /// ArrayBuffer-backed array, triggering `batched_copy_atomic_to_bytes`. #[test] fn shared_to_regular_typed_array_set() { run_test_actions([ TestAction::run( r#" var sab = new SharedArrayBuffer(16); var src = new Uint8Array(sab); for (var i = 0; i < 16; i++) src[i] = 100 + i; var ab = new ArrayBuffer(16); var dest = new Uint8Array(ab); dest.set(src); "#, ), TestAction::assert("dest[0] === 100"), TestAction::assert("dest[7] === 107"), TestAction::assert("dest[15] === 115"), ]); } /// Tests TypedArray.set from a regular ArrayBuffer-backed array to a /// SharedArrayBuffer-backed array, triggering `batched_copy_bytes_to_atomic`. #[test] fn regular_to_shared_typed_array_set() { run_test_actions([ TestAction::run( r#" var ab = new ArrayBuffer(16); var src = new Uint8Array(ab); for (var i = 0; i < 16; i++) src[i] = 200 + i; var sab = new SharedArrayBuffer(16); var dest = new Uint8Array(sab); dest.set(src); "#, ), TestAction::assert("dest[0] === 200"), TestAction::assert("dest[7] === 207"), TestAction::assert("dest[15] === 215"), ]); } /// Tests forward `copyWithin` on a SharedArrayBuffer-backed typed array, /// triggering `copy_shared_to_shared` via `memmove`. #[test] fn shared_typed_array_copy_within() { run_test_actions([ TestAction::run( r#" var sab = new SharedArrayBuffer(16); var arr = new Uint8Array(sab); for (var i = 0; i < 16; i++) arr[i] = i + 1; // Forward copy: copies bytes 4..12 to offset 0 arr.copyWithin(0, 4, 12); "#, ), TestAction::assert("arr[0] === 5"), TestAction::assert("arr[7] === 12"), TestAction::assert("arr[8] === 9"), ]); } /// Tests backward `copyWithin` on a SharedArrayBuffer-backed typed array, /// triggering `copy_shared_to_shared_backwards` when source and dest overlap. #[test] fn shared_typed_array_copy_within_backward() { run_test_actions([ TestAction::run( r#" var sab = new SharedArrayBuffer(16); var arr = new Uint8Array(sab); for (var i = 0; i < 16; i++) arr[i] = i + 1; // Backward copy: copies bytes 0..8 to offset 4 (overlapping) arr.copyWithin(4, 0, 8); "#, ), TestAction::assert("arr[0] === 1"), TestAction::assert("arr[3] === 4"), TestAction::assert("arr[4] === 1"), TestAction::assert("arr[11] === 8"), TestAction::assert("arr[12] === 13"), ]); } /// Tests zero-length slice to exercise the `count == 0` early return. #[test] fn shared_array_buffer_slice_empty() { run_test_actions([ TestAction::run( r#" var sab = new SharedArrayBuffer(16); var view = new Uint8Array(sab); for (var i = 0; i < 16; i++) view[i] = i + 1; var sliced = sab.slice(5, 5); var result = new Uint8Array(sliced); "#, ), TestAction::assert("result.length === 0"), ]); } ================================================ FILE: core/engine/src/builtins/array_buffer/utils.rs ================================================ use std::{ptr, slice::SliceIndex, sync::atomic::Ordering}; use portable_atomic::{AtomicU8, AtomicU64}; use crate::{ Context, JsResult, builtins::typed_array::{ClampedU8, Element, TypedArrayElement, TypedArrayKind}, object::JsObject, }; use super::ArrayBuffer; #[derive(Clone, Copy)] pub(crate) enum BytesConstPtr { Bytes(*const u8), AtomicBytes(*const AtomicU8), } impl BytesConstPtr { /// Offsets this const pointer by a positive amount. pub(crate) unsafe fn add(self, count: usize) -> Self { // SAFETY: the operation is guaranteed to be safe by the caller. unsafe { match self { Self::Bytes(p) => Self::Bytes(p.add(count)), Self::AtomicBytes(p) => Self::AtomicBytes(p.add(count)), } } } } #[derive(Clone, Copy)] pub(crate) enum BytesMutPtr { Bytes(*mut u8), AtomicBytes(*const AtomicU8), } impl BytesMutPtr { /// Offsets this mut pointer by a positive amount. pub(crate) unsafe fn add(self, count: usize) -> Self { // SAFETY: the operation is guaranteed to be safe by the caller. unsafe { match self { Self::Bytes(p) => Self::Bytes(p.add(count)), Self::AtomicBytes(p) => Self::AtomicBytes(p.add(count)), } } } } #[derive(Debug, Clone, Copy)] pub(crate) enum SliceRef<'a> { Slice(&'a [u8]), AtomicSlice(&'a [AtomicU8]), } impl SliceRef<'_> { /// Gets the byte length of this `SliceRef`. pub(crate) fn len(&self) -> usize { match self { Self::Slice(buf) => buf.len(), Self::AtomicSlice(buf) => buf.len(), } } /// Gets a subslice of this `SliceRef`. pub(crate) fn subslice(&self, index: I) -> SliceRef<'_> where I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>, { match self { Self::Slice(buffer) => SliceRef::Slice(buffer.get(index).expect("index out of bounds")), Self::AtomicSlice(buffer) => { SliceRef::AtomicSlice(buffer.get(index).expect("index out of bounds")) } } } /// Copies the slice into a new `Vec`. For `AtomicSlice`, each byte is loaded with `SeqCst` ordering. #[must_use] pub(crate) fn to_vec(self) -> Vec { match self { Self::Slice(s) => s.to_vec(), Self::AtomicSlice(s) => { let count = s.len(); let mut target = Vec::with_capacity(count); // SAFETY: we only copy `count` bytes, which should be in bounds // for both the target buffer and the source atomic slice. // `target` also has enough capacity for `count` elements and // all its elements are initialized by `memcpy`. unsafe { memcpy( BytesConstPtr::AtomicBytes(s.as_ptr()), BytesMutPtr::Bytes(target.as_mut_ptr()), count, ); target.set_len(s.len()); } target } } } /// Gets the starting address of this `SliceRef`. #[cfg(debug_assertions)] pub(crate) fn addr(&self) -> usize { match self { Self::Slice(buf) => buf.as_ptr().addr(), Self::AtomicSlice(buf) => buf.as_ptr().addr(), } } /// Gets a pointer to the underlying slice. pub(crate) fn as_ptr(&self) -> BytesConstPtr { match self { SliceRef::Slice(s) => BytesConstPtr::Bytes(s.as_ptr()), SliceRef::AtomicSlice(s) => BytesConstPtr::AtomicBytes(s.as_ptr()), } } /// [`GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )`][spec] /// /// The start offset is determined by the input buffer instead of a `byteIndex` parameter. /// /// # Safety /// /// - There must be enough bytes in `buffer` to read an element from an array with type `TypedArrayKind`. /// - `buffer` must be aligned to the alignment of said element. /// /// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer pub(crate) unsafe fn get_value( &self, kind: TypedArrayKind, order: Ordering, ) -> TypedArrayElement { unsafe fn read_elem(buffer: SliceRef<'_>, order: Ordering) -> T { // // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. #[cfg(debug_assertions)] { assert!(buffer.len() >= size_of::()); assert_eq!(buffer.addr() % align_of::(), 0); } // 3. Let block be arrayBuffer.[[ArrayBufferData]]. // 4. Let elementSize be the Element Size value specified in Table 70 for Element Type type. // 5. If IsSharedArrayBuffer(arrayBuffer) is true, then // a. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. // b. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier(). // c. If isTypedArray is true and IsNoTearConfiguration(type, order) is true, let noTear be true; otherwise let noTear be false. // d. Let rawValue be a List of length elementSize whose elements are nondeterministically chosen byte values. // e. NOTE: In implementations, rawValue is the result of a non-atomic or atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency. // f. Let readEvent be ReadSharedMemory { [[Order]]: order, [[NoTear]]: noTear, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize }. // g. Append readEvent to eventsRecord.[[EventList]]. // h. Append Chosen Value Record { [[Event]]: readEvent, [[ChosenValue]]: rawValue } to execution.[[ChosenValues]]. // 6. Else, // a. Let rawValue be a List whose elements are bytes from block at indices in the interval from byteIndex (inclusive) to byteIndex + elementSize (exclusive). // 7. Assert: The number of elements in rawValue is elementSize. // 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. // 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian). // SAFETY: The invariants of this operation are ensured by the caller. unsafe { T::read(buffer).load(order) } } let buffer = *self; // SAFETY: The invariants of this operation are ensured by the caller. unsafe { match kind { TypedArrayKind::Int8 => read_elem::(buffer, order).into(), TypedArrayKind::Uint8 => read_elem::(buffer, order).into(), TypedArrayKind::Uint8Clamped => read_elem::(buffer, order).into(), TypedArrayKind::Int16 => read_elem::(buffer, order).into(), TypedArrayKind::Uint16 => read_elem::(buffer, order).into(), TypedArrayKind::Int32 => read_elem::(buffer, order).into(), TypedArrayKind::Uint32 => read_elem::(buffer, order).into(), TypedArrayKind::BigInt64 => read_elem::(buffer, order).into(), TypedArrayKind::BigUint64 => read_elem::(buffer, order).into(), #[cfg(feature = "float16")] TypedArrayKind::Float16 => { read_elem::(buffer, order).into() } TypedArrayKind::Float32 => read_elem::(buffer, order).into(), TypedArrayKind::Float64 => read_elem::(buffer, order).into(), } } } /// [`CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer pub(crate) fn clone(&self, context: &mut Context) -> JsResult> { // 1. Assert: IsDetachedBuffer(srcBuffer) is false. // 2. Let targetBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, srcLength). let target_buffer = ArrayBuffer::allocate( &context .realm() .intrinsics() .constructors() .array_buffer() .constructor() .into(), self.len() as u64, None, context, )?; // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. { let mut target_buffer = target_buffer.borrow_mut(); let target_block = target_buffer .data_mut() .bytes_mut() .expect("ArrayBuffer cannot be detached here"); // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). // SAFETY: Both buffers are of the same length, `buffer.len()`, which makes this operation // safe. unsafe { memcpy( self.as_ptr(), BytesMutPtr::Bytes(target_block.as_mut_ptr()), self.len(), ); } } // 6. Return targetBuffer. Ok(target_buffer) } } impl<'a> From<&'a [u8]> for SliceRef<'a> { fn from(value: &'a [u8]) -> Self { Self::Slice(value) } } impl<'a> From<&'a [AtomicU8]> for SliceRef<'a> { fn from(value: &'a [AtomicU8]) -> Self { Self::AtomicSlice(value) } } #[derive(Debug)] pub(crate) enum SliceRefMut<'a> { Slice(&'a mut [u8]), AtomicSlice(&'a [AtomicU8]), } impl SliceRefMut<'_> { /// Gets the byte length of this `SliceRefMut`. pub(crate) fn len(&self) -> usize { match self { Self::Slice(buf) => buf.len(), Self::AtomicSlice(buf) => buf.len(), } } /// Gets a subslice of this `SliceRefMut`. #[expect(unused, reason = "could still be useful in the future")] pub(crate) fn subslice(&self, index: I) -> SliceRef<'_> where I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>, { match self { Self::Slice(buffer) => SliceRef::Slice(buffer.get(index).expect("index out of bounds")), Self::AtomicSlice(buffer) => { SliceRef::AtomicSlice(buffer.get(index).expect("index out of bounds")) } } } /// Gets a mutable subslice of this `SliceRefMut`. pub(crate) fn subslice_mut(&mut self, index: I) -> SliceRefMut<'_> where I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>, { match self { Self::Slice(buffer) => { SliceRefMut::Slice(buffer.get_mut(index).expect("index out of bounds")) } Self::AtomicSlice(buffer) => { SliceRefMut::AtomicSlice(buffer.get(index).expect("index out of bounds")) } } } /// Gets the starting address of this `SliceRefMut`. #[cfg(debug_assertions)] pub(crate) fn addr(&self) -> usize { match self { Self::Slice(buf) => buf.as_ptr().addr(), Self::AtomicSlice(buf) => buf.as_ptr().addr(), } } /// Gets a pointer to the underlying slice. pub(crate) fn as_ptr(&mut self) -> BytesMutPtr { match self { Self::Slice(s) => BytesMutPtr::Bytes(s.as_mut_ptr()), Self::AtomicSlice(s) => BytesMutPtr::AtomicBytes(s.as_ptr()), } } /// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )` /// /// The start offset is determined by the input buffer instead of a `byteIndex` parameter. /// /// # Safety /// /// - There must be enough bytes in `buffer` to write the `TypedArrayElement`. /// - `buffer` must be aligned to the alignment of the `TypedArrayElement`. /// /// # Panics /// /// - Panics if the type of `value` is not equal to the content of `kind`. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer pub(crate) unsafe fn set_value(&mut self, value: TypedArrayElement, order: Ordering) { unsafe fn write_elem(buffer: SliceRefMut<'_>, value: T, order: Ordering) { // // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number. #[cfg(debug_assertions)] { assert!(buffer.len() >= size_of::()); assert_eq!(buffer.addr() % align_of::(), 0); } // 4. Let block be arrayBuffer.[[ArrayBufferData]]. // 5. Let elementSize be the Element Size value specified in Table 70 for Element Type type. // 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. // 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian). // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then // a. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. // b. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier(). // c. If isTypedArray is true and IsNoTearConfiguration(type, order) is true, let noTear be true; otherwise let noTear be false. // d. Append WriteSharedMemory { [[Order]]: order, [[NoTear]]: noTear, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize, [[Payload]]: rawBytes } to eventsRecord.[[EventList]]. // 9. Else, // a. Store the individual bytes of rawBytes into block, starting at block[byteIndex]. // 10. Return unused. // SAFETY: The invariants of this operation are ensured by the caller. unsafe { T::read_mut(buffer).store(value, order); } } // Have to rebind in order to remove the outer `&mut` ref. let buffer = match self { SliceRefMut::Slice(buf) => SliceRefMut::Slice(buf), SliceRefMut::AtomicSlice(buf) => SliceRefMut::AtomicSlice(buf), }; // SAFETY: The invariants of this operation are ensured by the caller. unsafe { match value { TypedArrayElement::Int8(e) => write_elem(buffer, e, order), TypedArrayElement::Uint8(e) => write_elem(buffer, e, order), TypedArrayElement::Uint8Clamped(e) => write_elem(buffer, e, order), TypedArrayElement::Int16(e) => write_elem(buffer, e, order), TypedArrayElement::Uint16(e) => write_elem(buffer, e, order), TypedArrayElement::Int32(e) => write_elem(buffer, e, order), TypedArrayElement::Uint32(e) => write_elem(buffer, e, order), TypedArrayElement::BigInt64(e) => write_elem(buffer, e, order), TypedArrayElement::BigUint64(e) => write_elem(buffer, e, order), #[cfg(feature = "float16")] TypedArrayElement::Float16(e) => write_elem(buffer, e, order), TypedArrayElement::Float32(e) => write_elem(buffer, e, order), TypedArrayElement::Float64(e) => write_elem(buffer, e, order), } } } } impl<'a> From<&'a mut [u8]> for SliceRefMut<'a> { fn from(value: &'a mut [u8]) -> Self { Self::Slice(value) } } impl<'a> From<&'a [AtomicU8]> for SliceRefMut<'a> { fn from(value: &'a [AtomicU8]) -> Self { Self::AtomicSlice(value) } } const BATCH_SIZE: usize = size_of::(); /// Given a pointer address and total byte count, computes the number of /// head bytes needed to reach 8-byte alignment, the number of aligned /// 8-byte chunks, and the number of remaining tail bytes. fn compute_batch_offsets(ptr_addr: usize, count: usize) -> (usize, usize, usize) { let misalign = ptr_addr % BATCH_SIZE; let head = if misalign == 0 { 0 } else { (BATCH_SIZE - misalign).min(count) }; let remaining = count - head; let chunks = remaining / BATCH_SIZE; let tail = remaining % BATCH_SIZE; (head, chunks, tail) } /// Copies `count` bytes forward from `src` to `dest` using `AtomicU64` for aligned /// 8-byte chunks when both pointers share the same misalignment, falling back to /// byte-by-byte `AtomicU8` copies otherwise. /// /// # Safety /// /// - `src` must be valid for `count` reads of `AtomicU8`. /// - `dest` must be valid for `count` writes of `AtomicU8`. /// - The memory regions must not overlap. unsafe fn batched_atomic_copy_forward(src: *const AtomicU8, dest: *const AtomicU8, count: usize) { if count == 0 { return; } // Batch only if both pointers have the same misalignment, so that aligning // one automatically aligns the other. Otherwise, fall back to byte-by-byte. if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE { // SAFETY: ensured by the caller. unsafe { for i in 0..count { (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } return; } let (head, chunks, tail) = compute_batch_offsets(dest as usize, count); if chunks == 0 { // SAFETY: ensured by the caller. unsafe { for i in 0..count { (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } return; } // Phase 1: Copy unaligned head bytes until both pointers are 8-byte aligned. // SAFETY: ensured by the caller — both pointers are valid for `count` bytes. unsafe { for i in 0..head { (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } // Verify both pointers are now aligned after Phase 1. #[cfg(debug_assertions)] { debug_assert_eq!((dest as usize + head) % BATCH_SIZE, 0); debug_assert_eq!((src as usize + head) % BATCH_SIZE, 0); } // Phase 2: Copy aligned 8-byte chunks using AtomicU64 load/store. // SAFETY: Both `src + head` and `dest + head` are now 8-byte aligned // (same misalignment guaranteed, Phase 1 aligned dest, so src is also aligned). // `AtomicU8` is `#[repr(transparent)]` over `u8`, so casting to `AtomicU64` // is valid when properly aligned. #[allow(clippy::cast_ptr_alignment)] unsafe { let src_u64 = src.add(head).cast::(); let dest_u64 = dest.add(head).cast::(); for i in 0..chunks { (*dest_u64.add(i)).store((*src_u64.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } // Phase 3: Copy remaining tail bytes. let tail_start = head + chunks * BATCH_SIZE; // SAFETY: ensured by the caller — both pointers are valid for `count` bytes. unsafe { for i in 0..tail { (*dest.add(tail_start + i)).store( (*src.add(tail_start + i)).load(Ordering::Relaxed), Ordering::Relaxed, ); } } } /// Copies `count` bytes backward from `src` to `dest` using `AtomicU64` for aligned /// 8-byte chunks when both pointers share the same misalignment, falling back to /// byte-by-byte `AtomicU8` copies otherwise. /// /// # Safety /// /// - `src` must be valid for `count` reads of `AtomicU8`. /// - `dest` must be valid for `count` writes of `AtomicU8`. unsafe fn batched_atomic_copy_backward(src: *const AtomicU8, dest: *const AtomicU8, count: usize) { if count == 0 { return; } // Batch only if both pointers have the same misalignment. if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE { // SAFETY: ensured by the caller. unsafe { for i in (0..count).rev() { (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } return; } let (head, chunks, tail) = compute_batch_offsets(dest as usize, count); if chunks == 0 { // SAFETY: ensured by the caller. unsafe { for i in (0..count).rev() { (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } return; } let tail_start = head + chunks * BATCH_SIZE; // Phase 1: Copy tail bytes backwards. // SAFETY: ensured by the caller — both pointers are valid for `count` bytes. unsafe { for i in (0..tail).rev() { (*dest.add(tail_start + i)).store( (*src.add(tail_start + i)).load(Ordering::Relaxed), Ordering::Relaxed, ); } } // Verify both pointers are aligned after peeling tail bytes. #[cfg(debug_assertions)] { debug_assert_eq!((dest as usize + tail_start) % BATCH_SIZE, 0); debug_assert_eq!((src as usize + tail_start) % BATCH_SIZE, 0); } // Phase 2: Copy aligned 8-byte chunks backwards. // SAFETY: Both `src + head` and `dest + head` are 8-byte aligned // (same misalignment guaranteed, Phase 1 peeled tail bytes). #[allow(clippy::cast_ptr_alignment)] unsafe { let src_u64 = src.add(head).cast::(); let dest_u64 = dest.add(head).cast::(); for i in (0..chunks).rev() { (*dest_u64.add(i)).store((*src_u64.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } // Phase 3: Copy remaining head bytes backwards. // SAFETY: ensured by the caller. unsafe { for i in (0..head).rev() { (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } } /// Copies `count` bytes from non-atomic `src` to atomic `dest` using `u64`/`AtomicU64` /// for aligned 8-byte chunks when both pointers share the same misalignment. /// /// # Safety /// /// - `src` must be valid for `count` reads. /// - `dest` must be valid for `count` writes of `AtomicU8`. /// - The memory regions must not overlap. unsafe fn batched_copy_bytes_to_atomic(src: *const u8, dest: *const AtomicU8, count: usize) { if count == 0 { return; } // Batch only if both pointers have the same misalignment. if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE { // SAFETY: ensured by the caller. unsafe { for i in 0..count { (*dest.add(i)).store(*src.add(i), Ordering::Relaxed); } } return; } let (head, chunks, tail) = compute_batch_offsets(dest as usize, count); if chunks == 0 { // SAFETY: ensured by the caller. unsafe { for i in 0..count { (*dest.add(i)).store(*src.add(i), Ordering::Relaxed); } } return; } // Phase 1: Head bytes until both pointers are 8-byte aligned. // SAFETY: ensured by the caller. unsafe { for i in 0..head { (*dest.add(i)).store(*src.add(i), Ordering::Relaxed); } } // Verify both pointers are now aligned after Phase 1. #[cfg(debug_assertions)] { debug_assert_eq!((dest as usize + head) % BATCH_SIZE, 0); debug_assert_eq!((src as usize + head) % BATCH_SIZE, 0); } // Phase 2: Aligned 8-byte chunks. // SAFETY: Both `src + head` and `dest + head` are 8-byte aligned. #[allow(clippy::cast_ptr_alignment)] unsafe { let src_u64 = src.add(head).cast::(); let dest_u64 = dest.add(head).cast::(); for i in 0..chunks { (*dest_u64.add(i)).store(ptr::read(src_u64.add(i)), Ordering::Relaxed); } } // Phase 3: Tail bytes. let tail_start = head + chunks * BATCH_SIZE; // SAFETY: ensured by the caller. unsafe { for i in 0..tail { (*dest.add(tail_start + i)).store(*src.add(tail_start + i), Ordering::Relaxed); } } } /// Copies `count` bytes from atomic `src` to non-atomic `dest` using `AtomicU64`/`u64` /// for aligned 8-byte chunks when both pointers share the same misalignment. /// /// # Safety /// /// - `src` must be valid for `count` reads of `AtomicU8`. /// - `dest` must be valid for `count` writes. /// - The memory regions must not overlap. unsafe fn batched_copy_atomic_to_bytes(src: *const AtomicU8, dest: *mut u8, count: usize) { if count == 0 { return; } // Batch only if both pointers have the same misalignment. if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE { // SAFETY: ensured by the caller. unsafe { for i in 0..count { *dest.add(i) = (*src.add(i)).load(Ordering::Relaxed); } } return; } let (head, chunks, tail) = compute_batch_offsets(src as usize, count); if chunks == 0 { // SAFETY: ensured by the caller. unsafe { for i in 0..count { *dest.add(i) = (*src.add(i)).load(Ordering::Relaxed); } } return; } // Phase 1: Head bytes until both pointers are 8-byte aligned. // SAFETY: ensured by the caller. unsafe { for i in 0..head { *dest.add(i) = (*src.add(i)).load(Ordering::Relaxed); } } // Verify both pointers are now aligned after Phase 1. #[cfg(debug_assertions)] { debug_assert_eq!((src as usize + head) % BATCH_SIZE, 0); debug_assert_eq!((dest as usize + head) % BATCH_SIZE, 0); } // Phase 2: Aligned 8-byte chunks. // SAFETY: Both `src + head` and `dest + head` are 8-byte aligned. #[allow(clippy::cast_ptr_alignment)] unsafe { let src_u64 = src.add(head).cast::(); let dest_u64 = dest.add(head).cast::(); for i in 0..chunks { ptr::write(dest_u64.add(i), (*src_u64.add(i)).load(Ordering::Relaxed)); } } // Phase 3: Tail bytes. let tail_start = head + chunks * BATCH_SIZE; // SAFETY: ensured by the caller. unsafe { for i in 0..tail { *dest.add(tail_start + i) = (*src.add(tail_start + i)).load(Ordering::Relaxed); } } } /// Copies `count` bytes from `src` into `dest` using atomic relaxed loads and stores. /// /// Uses `AtomicU64` for aligned 8-byte chunks and falls back to `AtomicU8` for /// unaligned head/tail bytes. /// /// # Safety /// /// - Both `src` and `dest` must have at least `count` bytes to read and write, /// respectively. pub(super) unsafe fn copy_shared_to_shared( src: *const AtomicU8, dest: *const AtomicU8, count: usize, ) { // SAFETY: The invariants of this operation are ensured by the caller. unsafe { batched_atomic_copy_forward(src, dest, count) } } /// Copies `count` bytes backwards from `src` into `dest` using atomic relaxed loads and stores. /// /// Uses `AtomicU64` for aligned 8-byte chunks and falls back to `AtomicU8` for /// unaligned head/tail bytes. /// /// # Safety /// /// - Both `src` and `dest` must have at least `count` bytes to read and write, /// respectively. unsafe fn copy_shared_to_shared_backwards( src: *const AtomicU8, dest: *const AtomicU8, count: usize, ) { // SAFETY: The invariants of this operation are ensured by the caller. unsafe { batched_atomic_copy_backward(src, dest, count) } } /// Copies `count` bytes from the buffer `src` into the buffer `dest`, using the atomic ordering /// `Ordering::Relaxed` if any of the buffers are atomic. /// /// # Safety /// /// - Both `src` and `dest` must have at least `count` bytes to read and write, respectively. /// - The region of memory referenced by `src` must not overlap with the region of memory /// referenced by `dest`. pub(crate) unsafe fn memcpy(src: BytesConstPtr, dest: BytesMutPtr, count: usize) { match (src, dest) { // SAFETY: The invariants of this operation are ensured by the caller of the function. (BytesConstPtr::Bytes(src), BytesMutPtr::Bytes(dest)) => unsafe { ptr::copy_nonoverlapping(src, dest, count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. (BytesConstPtr::Bytes(src), BytesMutPtr::AtomicBytes(dest)) => unsafe { batched_copy_bytes_to_atomic(src, dest, count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. (BytesConstPtr::AtomicBytes(src), BytesMutPtr::Bytes(dest)) => unsafe { batched_copy_atomic_to_bytes(src, dest, count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. (BytesConstPtr::AtomicBytes(src), BytesMutPtr::AtomicBytes(dest)) => unsafe { copy_shared_to_shared(src, dest, count); }, } } /// Copies `count` bytes from the position `from` to the position `to` in `buffer`, but always /// copying from left to right. /// /// /// # Safety /// /// - `ptr` must be valid from the offset `ptr + from` for `count` reads of bytes. /// - `ptr` must be valid from the offset `ptr + to` for `count` writes of bytes. // This looks like a worse version of `memmove`... and it is exactly that... // but it's the correct behaviour for a weird usage of `%TypedArray%.prototype.slice` so ¯\_(ツ)_/¯. // Obviously don't use this if you need to implement something that requires a "proper" memmove. pub(crate) unsafe fn memmove_naive(ptr: BytesMutPtr, from: usize, to: usize, count: usize) { match ptr { // SAFETY: The invariants of this operation are ensured by the caller of the function. BytesMutPtr::Bytes(ptr) => unsafe { for i in 0..count { ptr::copy(ptr.add(from + i), ptr.add(to + i), 1); } }, // SAFETY: The invariants of this operation are ensured by the caller of the function. BytesMutPtr::AtomicBytes(ptr) => unsafe { let src = ptr.add(from); let dest = ptr.add(to); copy_shared_to_shared(src, dest, count); }, } } /// Copies `count` bytes from the position `from` to the position `to` in `buffer`. /// /// # Safety /// /// - `ptr` must be valid from the offset `ptr + from` for `count` reads of bytes. /// - `ptr` must be valid from the offset `ptr + to` for `count` writes of bytes. pub(crate) unsafe fn memmove(ptr: BytesMutPtr, from: usize, to: usize, count: usize) { match ptr { // SAFETY: The invariants of this operation are ensured by the caller of the function. BytesMutPtr::Bytes(ptr) => unsafe { let src = ptr.add(from); let dest = ptr.add(to); ptr::copy(src, dest, count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. BytesMutPtr::AtomicBytes(ptr) => unsafe { let src = ptr.add(from); let dest = ptr.add(to); // Let's draw a simple array. // // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | // // Now let's define `from`, `to` and `count` such that the below condition is satisfied. // `from = 0` // `to = 2` // `count = 4` // // We can now imagine that the array is pointed to by our indices: // // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | // ^ ^ // from to // // If we start copying bytes until `from + 2 = to`, we can see that the new array would be: // // | 0 | 1 | 0 | 1 | 0 | 5 | 6 | 7 | 8 | // ^ ^ // from + 2 to + 2 // // However, we've lost the data that was in the index 2! If this process // continues, this'll give the incorrect result: // // | 0 | 1 | 0 | 1 | 0 | 1 | 6 | 7 | 8 | // // To solve this, we just need to copy backwards to ensure we never override data that // we need in next iterations: // // | 0 | 1 | 2 | 3 | 4 | 3 | 6 | 7 | 8 | // ^ ^ // from to // // | 0 | 1 | 2 | 3 | 2 | 3 | 6 | 7 | 8 | // ^ ^ // from to // // | 0 | 1 | 0 | 1 | 2 | 3 | 6 | 7 | 8 | // ^ ^ // from to if src < dest { copy_shared_to_shared_backwards(src, dest, count); } else { copy_shared_to_shared(src, dest, count); } }, } } #[cfg(test)] mod tests_miri { use super::*; use portable_atomic::AtomicU8; use std::sync::atomic::Ordering; /// Tests `batched_atomic_copy_forward` with misaligned pointers /// (different misalignment) to exercise the byte-by-byte fallback. #[test] fn batched_forward_misaligned_fallback() { let src_data: Vec = (0..32).map(|i| AtomicU8::new(i as u8)).collect(); let dest_data: Vec = (0..32).map(|_| AtomicU8::new(0)).collect(); // SAFETY: Vec has 32 elements, offset 1 and 2 are within bounds. let src = unsafe { src_data.as_ptr().add(1) }; // SAFETY: Vec has 32 elements, offset 2 is within bounds. let dest = unsafe { dest_data.as_ptr().add(2) }; let count = 20; // SAFETY: Both pointers are valid for 20 reads/writes (32 - max_offset = 30 >= 20). unsafe { batched_atomic_copy_forward(src, dest, count) }; for i in 0..count { // SAFETY: `src` is valid for `count` reads starting from its base. let expected = unsafe { (*src.add(i)).load(Ordering::Relaxed) }; // SAFETY: `dest` is valid for `count` reads starting from its base. let actual = unsafe { (*dest.add(i)).load(Ordering::Relaxed) }; assert_eq!(actual, expected, "mismatch at index {i}"); } } /// Tests `batched_atomic_copy_backward` with misaligned pointers. #[test] fn batched_backward_misaligned_fallback() { let src_data: Vec = (0..32).map(|i| AtomicU8::new(i as u8)).collect(); let dest_data: Vec = (0..32).map(|_| AtomicU8::new(0)).collect(); // SAFETY: Vec has 32 elements, offset 1 is within bounds. let src = unsafe { src_data.as_ptr().add(1) }; // SAFETY: Vec has 32 elements, offset 2 is within bounds. let dest = unsafe { dest_data.as_ptr().add(2) }; let count = 20; // SAFETY: Both pointers are valid for 20 reads/writes (32 - max_offset = 30 >= 20). unsafe { batched_atomic_copy_backward(src, dest, count) }; for i in 0..count { // SAFETY: `src` is valid for `count` reads starting from its base. let expected = unsafe { (*src.add(i)).load(Ordering::Relaxed) }; // SAFETY: `dest` is valid for `count` reads starting from its base. let actual = unsafe { (*dest.add(i)).load(Ordering::Relaxed) }; assert_eq!(actual, expected, "mismatch at index {i}"); } } /// Tests `batched_copy_bytes_to_atomic` with misaligned pointers. #[test] fn batched_bytes_to_atomic_misaligned_fallback() { let src_data: Vec = (0..32).map(|i| i as u8).collect(); let dest_data: Vec = (0..32).map(|_| AtomicU8::new(0)).collect(); // SAFETY: Vec has 32 elements, offset 1 is within bounds. let src = unsafe { src_data.as_ptr().add(1) }; // SAFETY: Vec has 32 elements, offset 2 is within bounds. let dest = unsafe { dest_data.as_ptr().add(2) }; let count = 20; // SAFETY: Both pointers are valid for 20 reads/writes, regions do not overlap. unsafe { batched_copy_bytes_to_atomic(src, dest, count) }; for i in 0..count { // SAFETY: `src` is valid for `count` reads starting from its base. let expected = unsafe { *src.add(i) }; // SAFETY: `dest` is valid for `count` reads starting from its base. let actual = unsafe { (*dest.add(i)).load(Ordering::Relaxed) }; assert_eq!(actual, expected, "mismatch at index {i}"); } } /// Tests `batched_copy_atomic_to_bytes` with misaligned pointers. #[test] fn batched_atomic_to_bytes_misaligned_fallback() { let src_data: Vec = (0..32).map(|i| AtomicU8::new(i as u8)).collect(); let mut dest_data: Vec = vec![0u8; 32]; // SAFETY: Vec has 32 elements, offset 1 is within bounds. let src = unsafe { src_data.as_ptr().add(1) }; // SAFETY: Vec has 32 elements, offset 2 is within bounds. let dest = unsafe { dest_data.as_mut_ptr().add(2) }; let count = 20; // SAFETY: Both pointers are valid for 20 reads/writes, regions do not overlap. unsafe { batched_copy_atomic_to_bytes(src, dest, count) }; for i in 0..count { // SAFETY: `src` is valid for `count` reads starting from its base. let expected = unsafe { (*src.add(i)).load(Ordering::Relaxed) }; // SAFETY: `dest` is valid for `count` reads starting from its base. let actual = unsafe { *dest.add(i) }; assert_eq!(actual, expected, "mismatch at index {i}"); } } } ================================================ FILE: core/engine/src/builtins/async_function/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `AsyncFunction` object. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-async-function-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction use crate::{ Context, JsResult, JsString, JsValue, builtins::{BuiltInObject, function::BuiltInFunctionObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, }; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; /// The internal representation of an `AsyncFunction` object. #[derive(Debug, Clone, Copy)] pub struct AsyncFunction; impl IntrinsicObject for AsyncFunction { fn init(realm: &Realm) { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().function().constructor()) .inherits(Some( realm.intrinsics().constructors().function().prototype(), )) .property( JsSymbol::to_string_tag(), Self::NAME, Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> crate::object::JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for AsyncFunction { const NAME: JsString = StaticJsStrings::ASYNC_FUNCTION; } impl BuiltInConstructor for AsyncFunction { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 1; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::async_function; /// `AsyncFunction ( p1, p2, … , pn, body )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-async-function-constructor-arguments fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let active_function = context.active_function_object().unwrap_or_else(|| { context .intrinsics() .constructors() .async_function() .constructor() }); BuiltInFunctionObject::create_dynamic_function( active_function, new_target, args, true, false, context, ) .map(Into::into) } } ================================================ FILE: core/engine/src/builtins/async_generator/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `AsyncGenerator` object. //! //! More information: //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-objects use crate::{ Context, JsArgs, JsData, JsError, JsExpect, JsResult, JsString, builtins::{ Promise, generator::GeneratorContext, iterable::create_iter_result_object, promise::{PromiseCapability, if_abrupt_reject_promise}, }, context::intrinsics::Intrinsics, error::JsNativeError, js_string, native_function::NativeFunction, object::{CONSTRUCTOR, FunctionObjectBuilder, JsObject}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::JsValue, vm::{CompletionRecord, GeneratorResumeKind}, }; use boa_gc::{Finalize, Trace}; use std::collections::VecDeque; use super::{BuiltInBuilder, IntrinsicObject}; /// Indicates the state of an async generator. #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum AsyncGeneratorState { SuspendedStart, SuspendedYield, Executing, DrainingQueue, Completed, } /// `AsyncGeneratorRequest Records` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorrequest-records #[derive(Debug, Clone, Finalize, Trace)] pub(crate) struct AsyncGeneratorRequest { /// The `[[Completion]]` slot. pub(crate) completion: CompletionRecord, /// The `[[Capability]]` slot. capability: PromiseCapability, } /// The internal representation of an `AsyncGenerator` object. #[derive(Debug, Finalize, Trace, JsData)] pub struct AsyncGenerator { /// The `[[AsyncGeneratorState]]` internal slot. #[unsafe_ignore_trace] pub(crate) state: AsyncGeneratorState, /// The `[[AsyncGeneratorContext]]` internal slot. pub(crate) context: Option, /// The `[[AsyncGeneratorQueue]]` internal slot. pub(crate) queue: VecDeque, } impl IntrinsicObject for AsyncGenerator { fn init(realm: &Realm) { BuiltInBuilder::with_intrinsic::(realm) .prototype( realm .intrinsics() .objects() .iterator_prototypes() .async_iterator(), ) .static_method(Self::next, js_string!("next"), 1) .static_method(Self::r#return, js_string!("return"), 1) .static_method(Self::throw, js_string!("throw"), 1) .static_property( JsSymbol::to_string_tag(), Self::NAME, Attribute::CONFIGURABLE, ) .static_property( CONSTRUCTOR, realm .intrinsics() .constructors() .async_generator_function() .prototype(), Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().async_generator() } } impl AsyncGenerator { const NAME: JsString = StaticJsStrings::ASYNC_GENERATOR; /// `AsyncGenerator.prototype.next ( value )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next pub(crate) fn next( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let generator be the this value. let generator = this; // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). let promise_capability = PromiseCapability::new( &context.intrinsics().constructors().promise().constructor(), context, ) .js_expect("cannot fail with promise constructor")?; // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). let result: JsResult<_> = generator.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); let generator = if_abrupt_reject_promise!(result, promise_capability, context); let result: JsResult<_> = generator.clone().downcast::().map_err(|_| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); let generator = if_abrupt_reject_promise!(result, promise_capability, context); // 5. Let state be generator.[[AsyncGeneratorState]]. let state = generator.borrow().data().state; // 6. If state is completed, then if state == AsyncGeneratorState::Completed { // a. Let iteratorResult be CreateIterResultObject(undefined, true). let iterator_result = create_iter_result_object(JsValue::undefined(), true, context); // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). promise_capability.resolve().call( &JsValue::undefined(), &[iterator_result], context, )?; // c. Return promiseCapability.[[Promise]]. return Ok(promise_capability.promise().clone().into()); } // 7. Let completion be NormalCompletion(value). let completion = CompletionRecord::Normal(args.get_or_undefined(0).clone()); // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). Self::enqueue(&generator, completion.clone(), promise_capability.clone()); // 9. If state is either suspendedStart or suspendedYield, then if state == AsyncGeneratorState::SuspendedStart || state == AsyncGeneratorState::SuspendedYield { // a. Perform AsyncGeneratorResume(generator, completion). Self::resume(&generator, completion, context)?; } // 11. Return promiseCapability.[[Promise]]. Ok(promise_capability.promise().clone().into()) } /// `AsyncGenerator.prototype.return ( value )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return pub(crate) fn r#return( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let generator be the this value. let generator = this; // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). let promise_capability = PromiseCapability::new( &context.intrinsics().constructors().promise().constructor(), context, ) .js_expect("cannot fail with promise constructor")?; // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). let result: JsResult<_> = generator.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); let generator_object = if_abrupt_reject_promise!(result, promise_capability, context); let result: JsResult<_> = generator_object.clone().downcast::().map_err(|_| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); let generator = if_abrupt_reject_promise!(result, promise_capability, context); // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. let return_value = args.get_or_undefined(0).clone(); let completion = CompletionRecord::Return(return_value.clone()); // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). Self::enqueue(&generator, completion.clone(), promise_capability.clone()); // 7. Let state be generator.[[AsyncGeneratorState]]. let state = generator.borrow().data().state; // 8. If state is either suspended-start or completed, then if state == AsyncGeneratorState::SuspendedStart || state == AsyncGeneratorState::Completed { // a. Set generator.[[AsyncGeneratorState]] to draining-queue. generator.borrow_mut().data_mut().state = AsyncGeneratorState::DrainingQueue; // b. Perform ! AsyncGeneratorAwaitReturn(generator). Self::await_return(&generator, return_value, context)?; } // 9. Else if state is suspended-yield, then else if state == AsyncGeneratorState::SuspendedYield { // a. Perform AsyncGeneratorResume(generator, completion). Self::resume(&generator, completion, context)?; } // 10. Else, // a. Assert: state is either executing or draining-queue. // 11. Return promiseCapability.[[Promise]]. Ok(promise_capability.promise().clone().into()) } /// `AsyncGenerator.prototype.throw ( exception )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw pub(crate) fn throw( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let generator be the this value. let generator = this; // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). let promise_capability = PromiseCapability::new( &context.intrinsics().constructors().promise().constructor(), context, ) .js_expect("cannot fail with promise constructor")?; // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). let result: JsResult<_> = generator.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); let generator_object = if_abrupt_reject_promise!(result, promise_capability, context); let result: JsResult<_> = generator_object.clone().downcast::().map_err(|_| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); let generator = if_abrupt_reject_promise!(result, promise_capability, context); let mut r#gen = generator.borrow_mut(); // 5. Let state be generator.[[AsyncGeneratorState]]. let mut state = r#gen.data().state; // 6. If state is suspendedStart, then if state == AsyncGeneratorState::SuspendedStart { // a. Set generator.[[AsyncGeneratorState]] to completed. r#gen.data_mut().state = AsyncGeneratorState::Completed; r#gen.data_mut().context = None; // b. Set state to completed. state = AsyncGeneratorState::Completed; } drop(r#gen); // 7. If state is completed, then if state == AsyncGeneratorState::Completed { // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »). promise_capability.reject().call( &JsValue::undefined(), &[args.get_or_undefined(0).clone()], context, )?; // b. Return promiseCapability.[[Promise]]. return Ok(promise_capability.promise().clone().into()); } // 8. Let completion be ThrowCompletion(exception). let completion = CompletionRecord::Throw(JsError::from_opaque(args.get_or_undefined(0).clone())); // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). Self::enqueue(&generator, completion.clone(), promise_capability.clone()); // 10. If state is suspended-yield, then if state == AsyncGeneratorState::SuspendedYield { // a. Perform AsyncGeneratorResume(generator, completion). Self::resume(&generator, completion, context)?; } // 11. Else, // a. Assert: state is either executing or draining-queue. // 12. Return promiseCapability.[[Promise]]. Ok(promise_capability.promise().clone().into()) } /// `AsyncGeneratorEnqueue ( generator, completion, promiseCapability )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue pub(crate) fn enqueue( generator: &JsObject, completion: CompletionRecord, promise_capability: PromiseCapability, ) { let mut r#gen = generator.borrow_mut(); // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }. let request = AsyncGeneratorRequest { completion, capability: promise_capability, }; // 2. Append request to the end of generator.[[AsyncGeneratorQueue]]. r#gen.data_mut().queue.push_back(request); } /// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )` /// /// More information: /// - [ECMAScript reference][spec] /// /// # Errors /// /// Returns `EngineError::Panic` if the async generator request queue of `generator` is empty. /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep pub(crate) fn complete_step( generator: &JsObject, completion: JsResult, done: bool, realm: Option, context: &mut Context, ) -> JsResult<()> { // 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty. // 2. Let next be the first element of generator.[[AsyncGeneratorQueue]]. // 3. Remove the first element from generator.[[AsyncGeneratorQueue]]. let next = generator .borrow_mut() .data_mut() .queue .pop_front() .js_expect("1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.")?; // 4. Let promiseCapability be next.[[Capability]]. let promise_capability = &next.capability; // 5. Let value be completion.[[Value]]. match completion { // 6. If completion is a throw completion, then Err(e) => { // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). promise_capability.reject().call( &JsValue::undefined(), &[e.into_opaque(context)?], context, )?; } // 7. Else, Ok(value) => { // a. Assert: completion is a normal completion. // b. If realm is present, then let iterator_result = if let Some(realm) = realm { // i. Let oldRealm be the running execution context's Realm. // ii. Set the running execution context's Realm to realm. let old_realm = context.enter_realm(realm); // iii. Let iteratorResult be CreateIteratorResultObject(value, done). let iterator_result = create_iter_result_object(value, done, context); // iv. Set the running execution context's Realm to oldRealm. context.enter_realm(old_realm); iterator_result } else { // c. Else, // i. Let iteratorResult be CreateIteratorResultObject(value, done). create_iter_result_object(value, done, context) }; // d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). promise_capability.resolve().call( &JsValue::undefined(), &[iterator_result], context, )?; } } // 8. Return unused. Ok(()) } /// `AsyncGeneratorResume ( generator, completion )` /// /// More information: /// - [ECMAScript reference][spec] /// /// # Errors /// /// Returns `EngineError::Panic` if `generator` is neither in the `SuspendedStart` nor in the `SuspendedYield` states. /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume pub(crate) fn resume( generator: &JsObject, completion: CompletionRecord, context: &mut Context, ) -> JsResult<()> { // 1. Assert: generator.[[AsyncGeneratorState]] is either suspended-start or suspended-yield. assert!(matches!( generator.borrow().data().state, AsyncGeneratorState::SuspendedStart | AsyncGeneratorState::SuspendedYield )); // 2. Let genContext be generator.[[AsyncGeneratorContext]]. let mut generator_context = generator .borrow_mut() .data_mut() .context .take() .js_expect("generator context cannot be empty here")?; // 5. Set generator.[[AsyncGeneratorState]] to executing. generator.borrow_mut().data_mut().state = AsyncGeneratorState::Executing; let (value, resume_kind) = match completion { CompletionRecord::Normal(val) => (val, GeneratorResumeKind::Normal), CompletionRecord::Return(val) => (val, GeneratorResumeKind::Return), CompletionRecord::Throw(err) => (err.into_opaque(context)?, GeneratorResumeKind::Throw), }; // 3. Let callerContext be the running execution context. // 4. Suspend callerContext. // 6. Push genContext onto the execution context stack; genContext is now the running execution context. let result = generator_context.resume(Some(value), resume_kind, context); // 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation. generator.borrow_mut().data_mut().context = Some(generator_context); // 8. Assert: result is never an abrupt completion. assert!(!result.is_throw_completion()); // 9. Assert: When we return here, genContext has already been removed from the execution context stack and // callerContext is the currently running execution context. // 10. Return unused. Ok(()) } /// `AsyncGeneratorAwaitReturn ( generator )` /// /// More information: /// - [ECMAScript reference][spec] /// /// # Errors /// /// Returns `EngineError::Panic` if `generator` is not in the `DrainingQueue` state. /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn pub(crate) fn await_return( generator: &JsObject, value: JsValue, context: &mut Context, ) -> JsResult<()> { // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue. assert_eq!( generator.borrow().data().state, AsyncGeneratorState::DrainingQueue ); // 2. Let queue be generator.[[AsyncGeneratorQueue]]. // 3. Assert: queue is not empty. // 4. Let next be the first element of queue. // 5. Let completion be Completion(next.[[Completion]]). // 6. Assert: completion is a return completion. // 7. Let promiseCompletion be Completion(PromiseResolve(%Promise%, completion.[[Value]])). let promise_completion = Promise::promise_resolve( &context.intrinsics().constructors().promise().constructor(), value, context, ); let promise = match promise_completion { Ok(value) => value .downcast::() .ok() .js_expect("%Promise% constructor must always return a Promise object")?, // 8. If promiseCompletion is an abrupt completion, then Err(e) => { // a. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true). Self::complete_step(generator, Err(e), true, None, context)?; // b. Perform AsyncGeneratorDrainQueue(generator). Self::drain_queue(generator, context)?; // c. Return unused. return Ok(()); } }; // 9. Assert: promiseCompletion is a normal completion. // 10. Let promise be promiseCompletion.[[Value]]. // 11. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called: // 12. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). let on_fulfilled = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. assert_eq!( generator.borrow().data().state, AsyncGeneratorState::DrainingQueue ); // b. Let result be NormalCompletion(value). let result = Ok(args.get_or_undefined(0).clone()); // c. Perform AsyncGeneratorCompleteStep(generator, result, true). Self::complete_step(generator, result, true, None, context)?; // d. Perform AsyncGeneratorDrainQueue(generator). Self::drain_queue(generator, context)?; // e. Return undefined. Ok(JsValue::undefined()) }, generator.clone(), ), ) .name(js_string!()) .length(1) .build(); // 13. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called: // 14. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). let on_rejected = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. assert_eq!( generator.borrow().data().state, AsyncGeneratorState::DrainingQueue ); // b. Let result be ThrowCompletion(reason). let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); // c. Perform AsyncGeneratorCompleteStep(generator, result, true). Self::complete_step(generator, result, true, None, context)?; // d. Perform AsyncGeneratorDrainQueue(generator). Self::drain_queue(generator, context)?; // e. Return undefined. Ok(JsValue::undefined()) }, generator.clone(), ), ) .name(js_string!()) .length(1) .build(); // 15. Perform PerformPromiseThen(promise, onFulfilled, onRejected). Promise::perform_promise_then( &promise, Some(on_fulfilled), Some(on_rejected), None, context, ); // 16. Return unused. Ok(()) } /// `AsyncGeneratorDrainQueue ( generator )` /// /// More information: /// - [ECMAScript reference][spec] /// /// # Errors /// /// Returns `EngineError::Panic` if `generator` is not in the `DrainingQueue` state. /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue pub(crate) fn drain_queue( generator: &JsObject, context: &mut Context, ) -> JsResult<()> { // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue. assert_eq!( generator.borrow().data().state, AsyncGeneratorState::DrainingQueue ); // 2. Let queue be generator.[[AsyncGeneratorQueue]]. // 3. If queue is empty, then if generator.borrow().data().queue.is_empty() { // a. Set generator.[[AsyncGeneratorState]] to completed. generator.borrow_mut().data_mut().state = AsyncGeneratorState::Completed; generator.borrow_mut().data_mut().context = None; // b. Return unused. return Ok(()); } // 4. Let done be false. // 5. Repeat, while done is false, loop { // a. Let next be the first element of queue. let next = generator .borrow() .data() .queue .front() .js_expect("must have entry")? .completion .clone(); // b. Let completion be Completion(next.[[Completion]]). match next { // c. If completion is a return completion, then CompletionRecord::Return(val) => { // i. Perform AsyncGeneratorAwaitReturn(generator). Self::await_return(generator, val, context)?; // ii. Set done to true. break; } // d. Else, completion => { // i. If completion is a normal completion, then // 1. Set completion to NormalCompletion(undefined). let completion = completion.consume().map(|_| JsValue::undefined()); // ii. Perform AsyncGeneratorCompleteStep(generator, completion, true). Self::complete_step(generator, completion, true, None, context)?; // iii. If queue is empty, then if generator.borrow().data().queue.is_empty() { // 1. Set generator.[[AsyncGeneratorState]] to completed. generator.borrow_mut().data_mut().state = AsyncGeneratorState::Completed; generator.borrow_mut().data_mut().context = None; // 2. Set done to true. break; } } } } // 6. Return unused. Ok(()) } } ================================================ FILE: core/engine/src/builtins/async_generator_function/mod.rs ================================================ //! Boa's implementation of ECMAScript's `AsyncGeneratorFunction` object. //! //! More information: //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction-objects use crate::{ Context, JsResult, JsString, builtins::{BuiltInObject, function::BuiltInFunctionObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, object::{JsObject, PROTOTYPE}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::JsValue, }; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; /// The internal representation of an `AsyncGeneratorFunction` object. #[derive(Debug, Clone, Copy)] pub struct AsyncGeneratorFunction; impl IntrinsicObject for AsyncGeneratorFunction { fn init(realm: &Realm) { BuiltInBuilder::from_standard_constructor::(realm) .inherits(Some( realm.intrinsics().constructors().function().prototype(), )) .constructor_attributes(Attribute::CONFIGURABLE) .property( PROTOTYPE, realm.intrinsics().objects().async_generator(), Attribute::CONFIGURABLE, ) .property( JsSymbol::to_string_tag(), Self::NAME, Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for AsyncGeneratorFunction { const NAME: JsString = StaticJsStrings::ASYNC_GENERATOR_FUNCTION; } impl BuiltInConstructor for AsyncGeneratorFunction { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::async_generator_function; /// `AsyncGeneratorFunction ( p1, p2, … , pn, body )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let active_function = context.active_function_object().unwrap_or_else(|| { context .intrinsics() .constructors() .generator_function() .constructor() }); BuiltInFunctionObject::create_dynamic_function( active_function, new_target, args, true, true, context, ) .map(Into::into) } } ================================================ FILE: core/engine/src/builtins/atomics/futex.rs ================================================ // TODO: track https://github.com/rust-lang/rfcs/pull/3467 to see if we can use `UnsafeAliased` instead // of raw pointers. // A bit of context about how exactly this thing works. // // `Atomics.wait/notify` is basically an emulation of the "futex" syscall, which internally uses // a wait queue attached to a certain memory address, where processes and threads can manipulate // it to synchronize between them. // More information: https://en.wikipedia.org/wiki/Futex // // Our emulation of the API is composed by three components: // // - `FutexWaiters`, which is a map of addresses to the corresponding wait queue for that address. // Internally uses intrusive linked lists to avoid allocating when adding a new waiter, which // reduces the time spent by a thread in the critical section. // // - `FutexWaiter`, which contains all the data necessary to be able to wake a waiter from another // thread. If `FutexWaiter` is part of a linked list, it has not been woken up. Otherwise, // it has been woken up by a call to `notify`. This is checked after waking up to see // if the waiter was indeed woken up or if it just sporadically woke up, which can happen // while waiting on a `CondVar`. // // - `CRITICAL_SECTION`, a global static that must be locked before registering or notifying any // waiter. This guarantees that only one agent can write to the wait queues at any point in time. // // We can emulate a typical execution using the API for demonstration purposes. // At the start of the program, we initially have an empty map of wait queues. We represent this // graphically as: // // Address │ // │ // ────────────┼──────────────────────────────────────────────────────────────────── // │ // │ // │ // │ // │ // // Each row here will represent an address and the corresponding wait queue for that address. // // Let's suppose that "Thread 2" wants to wait on the address 50. After locking the global mutex, // it first creates a new instante of a `FutexWaiter` and passes a pointer to it to the // `FutexWaiters::add_waiter`: // // Address │ // │ // ────────────┼────────────────────────────────────────────────────────────────────── // │ // │ ┌───────────────┐ // │ ┌─►│ │ // │ │ │ Thread 2 │ // │ │ │ FutexWaiter │ // 50 ├────┘ │ │ // │ │ │ // │ │ cond_var │ // │ │ │ // │ │ │ // │ └───────────────┘ // │ // // Immediately after this, "Thread 2" calls `cond_var.wait`, unlocks the global mutex and sleeps // until it is notified again (ignoring the spurious wakeups, those are handled in an infinite loop // anyways). // // Now, let's suppose that `Thread 1` has now acquired the lock and now wants to also // wait on the address `50`. Doing the same procedure as "Thread 2", our map now looks like: // // Address │ // │ // ────────────┼────────────────────────────────────────────────────────────────────── // │ // │ ┌───────────────┐ ┌───────────────┐ // │ ┌─►│ ├───────►│ │ // │ │ │ Thread 2 │ │ Thread 1 │ // │ │ │ FutexWaiter │ │ FutexWaiter │ // 50 ├────┘ │ │ │ │ // │ │ │ │ │ // │ │ cond_var │ │ cond_var │ // │ │ │◄───────┤ │ // │ │ │ │ │ // │ └───────────────┘ └───────────────┘ // │ // // Note how the head of our list contains the first waiter which was registered, and the // tail of our list is our most recent waiter. // // After "Thread 1" sleeps, "Thread 3" has the opportunity to lock the global mutex. // In this case, "Thread 3" will notify one waiter of the address 50 using the `cond_var` inside // `FutexWaiter`, and will also remove it from the linked list. In this case // the notified thread is "Thread 2": // // Address │ // │ // ────────────┼────────────────────────────────────────────────────────────────────── // │ // │ ┌────────────────┐ ┌────────────────┐ // │ │ │ ┌──►│ │ // │ │ Thread 2 │ │ │ Thread 1 │ // │ │ FutexWaiter │ │ │ FutexWaiter │ // 50 ├───┐ │ │ │ │ │ // │ │ │ │ │ │ │ // │ │ │ cond_var │ │ │ cond_var │ // │ │ │ │ │ │ │ // │ │ │ │ │ │ │ // │ │ └────────────────┘ │ └────────────────┘ // │ │ │ // │ └────────────────────────┘ // │ // // Then, when the lock is released and "Thread 2" has woken up, it tries to lock the global mutex // again, checking if it is still part of the linked list, or manually remove itself from the queue // if that's not the case. // In this case "Thread 2" has already been notified, which doesn't require any other handling, so it just // removes the `FutexWaiter` from its stack and returns `AtomicsWaitResult::Ok`. // // Address │ // │ // ────────────┼────────────────────────────────────────────────────────────────────── // │ // │ ┌────────────────┐ // │ ┌──────────────────────────►│ │ // │ │ │ Thread 1 │ // │ │ │ FutexWaiter │ // 50 ├────┘ │ │ // │ │ │ // │ │ cond_var │ // │ │ waiting: true │ // │ │ │ // │ └────────────────┘ // │ // │ // │ // // In a future point in time, "Thread 1" will be notified, which will proceed with the // exact same steps as "Thread 2", emptying the wait queue and finishing the execution of our // program. #![deny(unsafe_op_in_unsafe_fn)] #![deny(clippy::undocumented_unsafe_blocks)] #![allow(clippy::expl_impl_clone_on_copy)] use std::{ cell::Cell, fmt, ptr, sync::{Arc, atomic::Ordering}, }; use crate::{ Context, JsNativeError, JsResult, JsValue, builtins::{ array_buffer::{SharedArrayBuffer, utils::SliceRef}, promise::ResolvingFunctions, typed_array::Element, }, job::{NativeAsyncJob, TimeoutJob}, js_string, sys::time::{Duration, Instant}, }; use std::sync::{Condvar, Mutex, MutexGuard}; use boa_string::JsString; use intrusive_collections::{LinkedList, LinkedListLink, UnsafeRef, intrusive_adapter}; use small_btree::{Entry, SmallBTreeMap}; /// The result of the [`wait`] and [`wait_async`] functions. #[derive(Debug, Clone, Copy)] pub(super) enum AtomicsWaitResult { NotEqual, TimedOut, Ok, } impl AtomicsWaitResult { pub(super) fn to_js_string(self) -> JsString { match self { AtomicsWaitResult::NotEqual => js_string!("not-equal"), AtomicsWaitResult::TimedOut => js_string!("timed-out"), AtomicsWaitResult::Ok => js_string!("ok"), } } } /// Data used by async waiters. #[derive(Debug)] struct AsyncWaiterData { // Only here to ensure the buffer does not get collected before all its // waiters. _buffer: SharedArrayBuffer, // Channel to signals the waiter that it has been timed out or notified. sender: oneshot::Sender, } /// A waiter for a memory address. struct FutexWaiter { link: LinkedListLink, cond_var: Condvar, addr: usize, async_data: Cell>, } impl fmt::Debug for FutexWaiter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FutexWaiter") .field("link", &self.link) .field("cond_var", &self.cond_var) .field("addr", &self.addr) .finish_non_exhaustive() } } intrusive_adapter!(FutexWaiterAdapter = UnsafeRef: FutexWaiter { link => LinkedListLink }); impl FutexWaiter { /// Creates a new `FutexWaiter` that will block the current thread while waiting. fn new_sync(addr: usize) -> Self { Self { link: LinkedListLink::new(), cond_var: Condvar::new(), addr, async_data: Cell::new(None), } } /// Creates a new `FutexWaiter` that will NOT block the current thread while waiting. #[allow( clippy::arc_with_non_send_sync, reason = "across threads we only access the fields that are `Sync`" )] fn new_async(data: AsyncWaiterData, addr: usize) -> Arc { Arc::new(Self { link: LinkedListLink::new(), cond_var: Condvar::new(), addr, async_data: Cell::new(Some(data)), }) } } /// List of memory addresses and its corresponding list of waiters for that address. struct FutexWaiters { waiters: SmallBTreeMap, 16>, } // SAFETY: `FutexWaiters` is not constructable outside its `get` method, and it's only exposed by // a global lock, meaning the inner data of `FutexWaiters` (which includes non-Send pointers) // can only be accessed by a single thread at once. unsafe impl Send for FutexWaiters {} impl FutexWaiters { /// Gets the map of all shared data addresses and its corresponding list of agents waiting on that location. fn get() -> JsResult> { static CRITICAL_SECTION: Mutex = Mutex::new(FutexWaiters { waiters: SmallBTreeMap::new(), }); CRITICAL_SECTION.lock().map_err(|_| { JsNativeError::typ() .with_message("failed to synchronize with the agent cluster") .into() }) } /// # Safety /// /// - `node` must NOT be linked to an existing waiter list. /// - `node` must always reference a valid instance of `FutexWaiter` until `node` is /// removed from its linked list. This can happen by either `remove_waiter` or `notify_many`. unsafe fn add_waiter(&mut self, node: &FutexWaiter) { // SAFETY: `node` must point to a valid instance. let node = unsafe { UnsafeRef::from_raw(ptr::from_ref(node)) }; self.waiters .entry(node.addr) .or_insert_with(|| LinkedList::new(FutexWaiterAdapter::new())) .push_back(node); } /// # Safety /// /// - `node` must NOT be linked to an existing waiter list. unsafe fn add_async_waiter(&mut self, node: Arc) { // SAFETY: `node` is not linked by the guarantees of the caller. let node = unsafe { UnsafeRef::from_raw(Arc::into_raw(node)) }; self.waiters .entry(node.addr) .or_insert_with(|| LinkedList::new(FutexWaiterAdapter::new())) .push_back(node); } /// Notifies at most `max_count` waiters that are waiting on the address `addr`, and /// returns the number of waiters that were notified. /// /// Equivalent to [`RemoveWaiters`][remove] and [`NotifyWaiter`][notify], but in a single operation. /// /// [remove]: https://tc39.es/ecma262/#sec-removewaiters /// [notify]: https://tc39.es/ecma262/#sec-notifywaiter fn notify_many(&mut self, addr: usize, max_count: u64) -> u64 { let Entry::Occupied(mut wl) = self.waiters.entry(addr) else { return 0; }; for i in 0..max_count { let Some(elem) = wl.get_mut().pop_front() else { wl.remove(); return i; }; elem.cond_var.notify_one(); if let Some(async_data) = elem.async_data.take() { // SAFETY: All entries on the waiter list must be valid, and all async entries // must come from an Arc. unsafe { Arc::from_raw(UnsafeRef::into_raw(elem)) }; // - If the async waiter is still awaiting, this should never fail. // - If the async waiter was dropped, the channel is now closed. // We don't need to handle the error since this can only happen // if the async waiter task was dropped, and at that point the // message won't even matter. drop(async_data.sender.send(AtomicsWaitResult::Ok)); } } if wl.get().is_empty() { wl.remove(); } max_count } /// # Safety /// /// - `node` must point to a valid instance of `FutexWaiter`. /// - `node` must be inside the wait list associated with `node.addr`. #[track_caller] pub(super) unsafe fn remove_waiter(&mut self, node: &FutexWaiter) { debug_assert!(node.link.is_linked()); let Entry::Occupied(mut wl) = self.waiters.entry(node.addr) else { panic!("node was not a valid `FutexWaiter`"); }; // SAFETY: `node` must be inside the wait list associated with `node.addr`. let node = unsafe { let Some(node) = wl .get_mut() .cursor_mut_from_ptr(ptr::from_ref(node)) .remove() else { panic!("node was not a valid `FutexWaiter`") }; node }; if let Some(async_data) = node.async_data.take() { // SAFETY: all async entries must be managed by an Arc. unsafe { Arc::from_raw(UnsafeRef::into_raw(node)); } // - If the async waiter is still awaiting, this should never fail. // - If the async waiter was dropped, the channel is now closed. // We don't need to handle the error since this can only happen // if the async waiter task was dropped, and at that point the // message won't even matter. drop(async_data.sender.send(AtomicsWaitResult::TimedOut)); } if wl.get().is_empty() { wl.remove(); } } } /// Adds this agent to the wait queue for the address pointed to by `buffer[offset..]`. /// /// # Safety /// /// - `addr` must be a multiple of `std::mem::size_of::()`. /// - `buffer` must contain at least `std::mem::size_of::()` bytes to read starting from `usize`. // our implementation guarantees that `SharedArrayBuffer` is always aligned to `u64` at minimum. pub(super) unsafe fn wait( buffer: &SharedArrayBuffer, buf_len: usize, offset: usize, check: E, timeout: Option, ) -> JsResult { // 11. Let block be buffer.[[ArrayBufferData]]. // 12. Let offset be typedArray.[[ByteOffset]]. // 13. Let byteIndexInBuffer be (i × 4) + offset. let buffer = &buffer.bytes_with_len(buf_len)[offset..]; // 14. Let WL be GetWaiterList(block, indexedPosition). // 18. Perform EnterCriticalSection(WL). let mut waiters = FutexWaiters::get()?; // 13. Let elementType be TypedArrayElementType(typedArray). // 14. Let w be GetValueFromBuffer(buffer, indexedPosition, elementType, true, SeqCst). // SAFETY: The safety of this operation is guaranteed by the caller. let value = unsafe { E::read(SliceRef::AtomicSlice(buffer)).load(Ordering::SeqCst) }; // 20. If v ≠ w, then // a. Perform LeaveCriticalSection(WL). // b. If mode is sync, return "not-equal". if check != value { return Ok(AtomicsWaitResult::NotEqual); } // 23. Let now be the time value (UTC) identifying the current time. // 24. Let additionalTimeout be an implementation-defined non-negative mathematical value. // 25. Let timeoutTime be ℝ(now) + t + additionalTimeout. // 26. NOTE: When t is +∞, timeoutTime is also +∞. let timeout_time = timeout.map(|to| (Instant::now(), to)); // 27. Let waiterRecord be a new Waiter Record { [[AgentSignifier]]: thisAgent, [[PromiseCapability]]: promiseCapability, [[TimeoutTime]]: timeoutTime, [[Result]]: "ok" }. // ensure we can have aliased pointers to the waiter in a sound way. let waiter = FutexWaiter::new_sync(buffer.as_ptr().addr()); // 28. Perform AddWaiter(WL, waiterRecord). // SAFETY: waiter is valid and we call `remove_waiter` below. unsafe { waiters.add_waiter(&waiter); } // 18. Let notified be SuspendAgent(WL, W, t). // `SuspendAgent(WL, W, t)` // https://tc39.es/ecma262/#sec-suspendthisagent // In a couple of places here we early return without removing the waiter from // the waiters list. This could seem unsound, but in reality all our early // returns are because the mutex is poisoned, and if that's the case then // no other thread can read the pointer to the waiter, so we can return safely. let result = loop { if !waiter.link.is_linked() { break AtomicsWaitResult::Ok; } if let Some((start, timeout)) = timeout_time { let Some(remaining) = timeout.checked_sub(start.elapsed()) else { break AtomicsWaitResult::TimedOut; }; // This doesn't use `wait_timeout_while` because it has to mutably borrow `waiter`, // which is a big nono since we have pointers to that location while the borrow is // active. waiters = waiter .cond_var .wait_timeout(waiters, remaining) .map_err(|_| { JsNativeError::typ() .with_message("failed to synchronize with the agent cluster") })? .0; } else { waiters = waiter.cond_var.wait(waiters).map_err(|_| { JsNativeError::typ().with_message("failed to synchronize with the agent cluster") })?; } }; // 19. If notified is true, then // a. Assert: W is not on the list of waiters in WL. // 20. Else, // a. Perform RemoveWaiter(WL, W). if waiter.link.is_linked() { // SAFETY: waiter is valid and contained in its waiter list if it is still linked. unsafe { waiters.remove_waiter(&waiter); } } // 21. Perform LeaveCriticalSection(WL). drop(waiters); // 22. If notified is true, return "ok". // 23. Return "timed-out". Ok(result) } /// Adds this agent to the wait queue for the address pointed to by `buffer[offset..]`, /// without blocking the execution thread. /// /// # Safety /// /// - `addr` must be a multiple of `std::mem::size_of::()`. /// - `buffer` must contain at least `std::mem::size_of::()` bytes to read starting from `usize`. // our implementation guarantees that `SharedArrayBuffer` is always aligned to `u64` at minimum. pub(super) unsafe fn wait_async( buffer: &SharedArrayBuffer, buf_len: usize, offset: usize, check: E, timeout: Option, functions: ResolvingFunctions, context: &mut Context, ) -> JsResult { // 11. Let block be buffer.[[ArrayBufferData]]. // 12. Let offset be typedArray.[[ByteOffset]]. // 13. Let byteIndexInBuffer be (i × 4) + offset. let buf = &buffer.bytes_with_len(buf_len)[offset..]; // 14. Let WL be GetWaiterList(block, indexedPosition). // 17. Perform EnterCriticalSection(WL). let mut waiters = FutexWaiters::get()?; // 18. Let elementType be TypedArrayElementType(typedArray). // 19. Let w be GetValueFromBuffer(buffer, indexedPosition, elementType, true, SeqCst). // SAFETY: The safety of this operation is guaranteed by the caller. let value = unsafe { E::read(SliceRef::AtomicSlice(buf)).load(Ordering::SeqCst) }; // 20. If v ≠ w, then // a. Perform LeaveCriticalSection(WL). // b. If mode is sync, return "not-equal". if check != value { return Ok(AtomicsWaitResult::NotEqual); } // 21. If t = 0 and mode is async, then // a. NOTE: There is no special handling of synchronous immediate timeouts. Asynchronous immediate // timeouts have special handling in order to fail fast and avoid unnecessary Promise jobs. // b. Perform LeaveCriticalSection(WL). if let Some(timeout) = &timeout && timeout.is_zero() { return Ok(AtomicsWaitResult::TimedOut); } // 23. Let now be the time value (UTC) identifying the current time. // 24. Let additionalTimeout be an implementation-defined non-negative mathematical value. // 25. Let timeoutTime be ℝ(now) + t + additionalTimeout. // 26. NOTE: When t is +∞, timeoutTime is also +∞. let (sender, receiver) = oneshot::async_channel(); // 27. Let waiterRecord be a new Waiter Record { [[AgentSignifier]]: thisAgent, // [[PromiseCapability]]: promiseCapability, [[TimeoutTime]]: timeoutTime, [[Result]]: "ok"}. // ensure we can have aliased pointers to the waiter in a sound way. let waiter = FutexWaiter::new_async( AsyncWaiterData { _buffer: buffer.clone(), sender, }, buf.as_ptr().addr(), ); let weak_waiter = Arc::downgrade(&waiter); // 28. Perform AddWaiter(WL, waiterRecord). // SAFETY: `waiter` is pinned to the heap, so it must be valid. unsafe { waiters.add_async_waiter(waiter.clone()); } // 30. Else if timeoutTime is finite, then // a. Perform EnqueueAtomicsWaitAsyncTimeoutJob(WL, waiterRecord). let timeout_cancel = if let Some(timeout) = timeout { // EnqueueAtomicsWaitAsyncTimeoutJob ( WL, waiterRecord ) // https://tc39.es/ecma262/#sec-enqueueatomicswaitasynctimeoutjob // 1. Let timeoutJob be a new Job Abstract Closure with no parameters that captures WL and waiterRecord and performs the following steps when called: let job = TimeoutJob::with_realm( move |_| { // a. Perform EnterCriticalSection(WL). let mut waiters = FutexWaiters::get()?; // b. If WL.[[Waiters]] contains waiterRecord, then if let Some(waiter) = weak_waiter.upgrade() && waiter.link.is_linked() { // i. Let timeOfJobExecution be the time value (UTC) identifying the current time. // ii. Assert: ℝ(timeOfJobExecution) ≥ waiterRecord.[[TimeoutTime]] (ignoring potential non-monotonicity of time values). // iii. Set waiterRecord.[[Result]] to "timed-out". // SAFETY: the node is linked and still valid thanks to the reference count. unsafe { // iv. Perform RemoveWaiter(WL, waiterRecord). waiters.remove_waiter(&waiter); } } // c. Perform LeaveCriticalSection(WL). // d. Return unused. Ok(JsValue::undefined()) }, // 3. Let currentRealm be the current Realm Record. context.realm().clone(), // 2. Let now be the time value (UTC) identifying the current time. timeout, ); let tc = job.cancelled_flag(); // 4. Perform HostEnqueueTimeoutJob(timeoutJob, currentRealm, 𝔽(waiterRecord.[[TimeoutTime]]) - now). context.enqueue_job(job.into()); // 5. Return unused. Some(tc) } else { None }; context.enqueue_job( NativeAsyncJob::new(async move |context| { if let Ok(result) = receiver.await { // v. Perform NotifyWaiter(WL, waiterRecord). functions .resolve .call( &JsValue::undefined(), &[result.to_js_string().into()], &mut context.borrow_mut(), ) .expect("default resolving functions cannot error"); } else { // The "else" branch can only happen if the channel was dropped in both // the timeout job and the whole waiters map, which is only possible if // we panicked. We just GIGO then, since it doesn't make sense to // resolve a promise in a thread that is panicking. } if let Some(flag) = timeout_cancel { flag.set(); } Ok(JsValue::undefined()) }) .into(), ); Ok(AtomicsWaitResult::Ok) } /// Notifies at most `count` agents waiting on the memory address pointed to by `buffer[offset..]`. pub(super) fn notify(buffer: &SharedArrayBuffer, offset: usize, count: u64) -> JsResult { let addr = buffer.as_ptr().addr() + offset; // 7. Let WL be GetWaiterList(block, indexedPosition). // 8. Perform EnterCriticalSection(WL). let mut waiters = FutexWaiters::get()?; // 9. Let S be RemoveWaiters(WL, c). // 10. For each element W of S, do // a. Perform NotifyWaiter(WL, W). let count = waiters.notify_many(addr, count); // 11. Perform LeaveCriticalSection(WL). drop(waiters); Ok(count) } ================================================ FILE: core/engine/src/builtins/atomics/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `Atomics` object. //! //! The `Atomics` object contains synchronization methods to orchestrate multithreading //! on contexts that live in separate threads. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-atomics-object //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics mod futex; use std::sync::atomic::Ordering; use crate::{ Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, builtins::{BuiltInObject, OrdinaryObject}, context::intrinsics::Intrinsics, js_string, object::{JsObject, JsPromise}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, sys::time::Duration, value::IntegerOrInfinity, }; use super::{ BuiltInBuilder, IntrinsicObject, array_buffer::{BufferObject, BufferRef}, typed_array::{Atomic, ContentType, Element, TypedArray, TypedArrayElement, TypedArrayKind}, }; /// Javascript `Atomics` object. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct Atomics; impl IntrinsicObject for Atomics { fn init(realm: &Realm) { let builder = BuiltInBuilder::with_intrinsic::(realm) .static_property( JsSymbol::to_string_tag(), Self::NAME, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .static_method(Atomics::add, js_string!("add"), 3) .static_method(Atomics::bit_and, js_string!("and"), 3) .static_method(Atomics::compare_exchange, js_string!("compareExchange"), 4) .static_method(Atomics::swap, js_string!("exchange"), 3) .static_method(Atomics::is_lock_free, js_string!("isLockFree"), 1) .static_method(Atomics::load, js_string!("load"), 2) .static_method(Atomics::bit_or, js_string!("or"), 3) .static_method(Atomics::store, js_string!("store"), 3) .static_method(Atomics::sub, js_string!("sub"), 3) .static_method(Atomics::wait::, js_string!("wait"), 4) .static_method(Atomics::wait::, js_string!("waitAsync"), 4) .static_method(Atomics::notify, js_string!("notify"), 3) .static_method(Atomics::bit_xor, js_string!("xor"), 3); #[cfg(feature = "experimental")] let builder = builder.static_method(Atomics::pause, js_string!("pause"), 0); builder.build(); } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().atomics() } } impl BuiltInObject for Atomics { const NAME: JsString = StaticJsStrings::ATOMICS; } macro_rules! atomic_op { ($(#[$attr:meta])* $name:ident) => { $(#[$attr])* fn $name(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let array = args.get_or_undefined(0); let index = args.get_or_undefined(1); let value = args.get_or_undefined(2); // AtomicReadModifyWrite ( typedArray, index, value, op ) // // 1. Let buffer be ? ValidateIntegerTypedArray(typedArray). let (ta, buf_len) = validate_integer_typed_array(array, false)?; // 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index). let access = validate_atomic_access(&ta, buf_len, index, context)?; // 3. If typedArray.[[ContentType]] is BigInt, let v be ? ToBigInt(value). // 4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). // 7. Let elementType be TypedArrayElementType(typedArray). let value = access.kind.get_element(value, context)?; // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 6. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call // to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could // cause the buffer to become detached. let ta = ta.borrow(); let ta = ta.data(); let mut buffer = ta.viewed_array_buffer().as_buffer_mut(); let Some(mut data) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") .into()); }; let data = data.subslice_mut(access.byte_offset..); // 8. Return GetModifySetValueInBuffer(buffer, indexedPosition, elementType, v, op). // SAFETY: The integer indexed object guarantees that the buffer is aligned. // The call to `validate_atomic_access` guarantees that the index is in-bounds. let value: TypedArrayElement = unsafe { match value { TypedArrayElement::Int8(num) => { i8::read_mut(data).$name(num, Ordering::SeqCst).into() } TypedArrayElement::Uint8(num) => { u8::read_mut(data).$name(num, Ordering::SeqCst).into() } TypedArrayElement::Int16(num) => i16::read_mut(data) .$name(num, Ordering::SeqCst) .into(), TypedArrayElement::Uint16(num) => u16::read_mut(data) .$name(num, Ordering::SeqCst) .into(), TypedArrayElement::Int32(num) => i32::read_mut(data) .$name(num, Ordering::SeqCst) .into(), TypedArrayElement::Uint32(num) => u32::read_mut(data) .$name(num, Ordering::SeqCst) .into(), TypedArrayElement::BigInt64(num) => i64::read_mut(data) .$name(num, Ordering::SeqCst) .into(), TypedArrayElement::BigUint64(num) => u64::read_mut(data) .$name(num, Ordering::SeqCst) .into(), TypedArrayElement::Uint8Clamped(_) | TypedArrayElement::Float32(_) | TypedArrayElement::Float64(_) => unreachable!( "must have been filtered out by the call to `validate_integer_typed_array`" ), #[cfg(feature = "float16")] TypedArrayElement::Float16(_) => unreachable!( "must have been filtered out by the call to `validate_integer_typed_array`" ), } }; Ok(value.into()) } }; } impl Atomics { /// [`Atomics.isLockFree ( size )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.islockfree fn is_lock_free(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let n be ? ToIntegerOrInfinity(size). let n = args.get_or_undefined(0).to_integer_or_infinity(context)?; // 2. Let AR be the Agent Record of the surrounding agent. Ok(match n.as_integer() { // 3. If n = 1, return AR.[[IsLockFree1]]. Some(1) => <::Atomic as Atomic>::is_lock_free(), // 4. If n = 2, return AR.[[IsLockFree2]]. Some(2) => <::Atomic as Atomic>::is_lock_free(), // 5. If n = 4, return true. Some(4) => true, // 6. If n = 8, return AR.[[IsLockFree8]]. Some(8) => <::Atomic as Atomic>::is_lock_free(), // 7. Return false. _ => false, } .into()) } /// [`Atomics.load ( typedArray, index )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.load fn load(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let array = args.get_or_undefined(0); let index = args.get_or_undefined(1); // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). let (ta, buf_len) = validate_integer_typed_array(array, false)?; let access = validate_atomic_access(&ta, buf_len, index, context)?; // 2. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). let ta = ta.borrow(); let ta = ta.data(); let buffer = ta.viewed_array_buffer().as_buffer(); let Some(data) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") .into()); }; let data = data.subslice(access.byte_offset..); // 3. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 4. Let elementType be TypedArrayElementType(typedArray). // 5. Return GetValueFromBuffer(buffer, indexedPosition, elementType, true, seq-cst). // SAFETY: The integer indexed object guarantees that the buffer is aligned. // The call to `validate_atomic_access` guarantees that the index is in-bounds. let value = unsafe { data.get_value(access.kind, Ordering::SeqCst) }; Ok(value.into()) } /// [`Atomics.store ( typedArray, index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.store fn store(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let array = args.get_or_undefined(0); let index = args.get_or_undefined(1); let value = args.get_or_undefined(2); // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). let (ta, buf_len) = validate_integer_typed_array(array, false)?; let access = validate_atomic_access(&ta, buf_len, index, context)?; // bit of a hack to preserve the converted value // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). let converted: JsValue = if access.kind.content_type() == ContentType::BigInt { value.to_bigint(context)?.into() } else { // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). match value.to_integer_or_infinity(context)? { IntegerOrInfinity::PositiveInfinity => f64::INFINITY, IntegerOrInfinity::Integer(i) => i as f64, IntegerOrInfinity::NegativeInfinity => f64::NEG_INFINITY, } .into() }; let value = access.kind.get_element(&converted, context)?; // 4. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). let ta = ta.borrow(); let ta = ta.data(); let mut buffer = ta.viewed_array_buffer().as_buffer_mut(); let Some(mut buffer) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") .into()); }; let mut data = buffer.subslice_mut(access.byte_offset..); // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 6. Let elementType be TypedArrayElementType(typedArray). // 7. Perform SetValueInBuffer(buffer, indexedPosition, elementType, v, true, seq-cst). // SAFETY: The integer indexed object guarantees that the buffer is aligned. // The call to `validate_atomic_access` guarantees that the index is in-bounds. unsafe { data.set_value(value, Ordering::SeqCst); } // 8. Return v. Ok(converted) } /// [`Atomics.compareExchange ( typedArray, index, expectedValue, replacementValue )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.compareexchange fn compare_exchange(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let array = args.get_or_undefined(0); let index = args.get_or_undefined(1); let expected = args.get_or_undefined(2); let replacement = args.get_or_undefined(3); // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). // 2. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 3. Let block be buffer.[[ArrayBufferData]]. let (ta, buf_len) = validate_integer_typed_array(array, false)?; let access = validate_atomic_access(&ta, buf_len, index, context)?; // 4. If typedArray.[[ContentType]] is bigint, then // a. Let expected be ? ToBigInt(expectedValue). // b. Let replacement be ? ToBigInt(replacementValue). // 5. Else, // a. Let expected be 𝔽(? ToIntegerOrInfinity(expectedValue)). // b. Let replacement be 𝔽(? ToIntegerOrInfinity(replacementValue)). let exp = access.kind.get_element(expected, context)?.to_bits(); let rep = access.kind.get_element(replacement, context)?.to_bits(); // 6. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). let ta = ta.borrow(); let ta = ta.data(); let mut buffer = ta.viewed_array_buffer().as_buffer_mut(); let Some(mut buffer) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") .into()); }; let data = buffer.subslice_mut(access.byte_offset..); // 7. Let elementType be TypedArrayElementType(typedArray). // 8. Let elementSize be TypedArrayElementSize(typedArray). // 9. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. // 10. Let expectedBytes be NumericToRawBytes(elementType, expected, isLittleEndian). // 11. Let replacementBytes be NumericToRawBytes(elementType, replacement, isLittleEndian). // 12. If IsSharedArrayBuffer(buffer) is true, then // a. Let rawBytesRead be AtomicCompareExchangeInSharedBlock(block, indexedPosition, elementSize, expectedBytes, replacementBytes). // 13. Else, // a. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[indexedPosition]. // b. If ByteListEqual(rawBytesRead, expectedBytes) is true, then // i. Store the individual bytes of replacementBytes into block, starting at block[indexedPosition]. // 14. Return RawBytesToNumeric(elementType, rawBytesRead, isLittleEndian). // SAFETY: The integer indexed object guarantees that the buffer is aligned. // The call to `validate_atomic_access` guarantees that the index is in-bounds. let value: TypedArrayElement = unsafe { match access.kind { TypedArrayKind::Int8 => i8::read_mut(data) .compare_exchange(exp as i8, rep as i8, Ordering::SeqCst) .into(), TypedArrayKind::Uint8 => u8::read_mut(data) .compare_exchange(exp as u8, rep as u8, Ordering::SeqCst) .into(), TypedArrayKind::Int16 => i16::read_mut(data) .compare_exchange(exp as i16, rep as i16, Ordering::SeqCst) .into(), TypedArrayKind::Uint16 => u16::read_mut(data) .compare_exchange(exp as u16, rep as u16, Ordering::SeqCst) .into(), TypedArrayKind::Int32 => i32::read_mut(data) .compare_exchange(exp as i32, rep as i32, Ordering::SeqCst) .into(), TypedArrayKind::Uint32 => u32::read_mut(data) .compare_exchange(exp as u32, rep as u32, Ordering::SeqCst) .into(), TypedArrayKind::BigInt64 => i64::read_mut(data) .compare_exchange(exp as i64, rep as i64, Ordering::SeqCst) .into(), TypedArrayKind::BigUint64 => u64::read_mut(data) .compare_exchange(exp, rep, Ordering::SeqCst) .into(), TypedArrayKind::Uint8Clamped | TypedArrayKind::Float32 | TypedArrayKind::Float64 => unreachable!( "must have been filtered out by the call to `validate_integer_typed_array`" ), #[cfg(feature = "float16")] TypedArrayKind::Float16 => unreachable!( "must have been filtered out by the call to `validate_integer_typed_array`" ), } }; Ok(value.into()) } // =========== Atomics.ops start =========== atomic_op! { /// [`Atomics.add ( typedArray, index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.add add } atomic_op! { /// [`Atomics.and ( typedArray, index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.and bit_and } atomic_op! { /// [`Atomics.exchange ( typedArray, index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.exchange swap } atomic_op! { /// [`Atomics.or ( typedArray, index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.or bit_or } atomic_op! { /// [`Atomics.sub ( typedArray, index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.sub sub } atomic_op! { /// [`Atomics.xor ( typedArray, index, value )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.xor bit_xor } /// [`Atomics.wait ( typedArray, index, value, timeout )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.wait fn wait( _: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let array = args.get_or_undefined(0); let index = args.get_or_undefined(1); let value = args.get_or_undefined(2); let timeout = args.get_or_undefined(3); // 1. Let taRecord be ? ValidateIntegerTypedArray(typedArray, true). let (ta, buf_len) = validate_integer_typed_array(array, true)?; // 2. Let buffer be taRecord.[[Object]].[[ViewedArrayBuffer]]. // 3. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception. let buffer = match ta.borrow().data().viewed_array_buffer() { BufferObject::SharedBuffer(buf) => buf.clone(), BufferObject::Buffer(_) => { return Err(JsNativeError::typ() .with_message("cannot use `ArrayBuffer` for an atomic wait") .into()); } }; // 4. Let i be ? ValidateAtomicAccess(taRecord, index). let access = validate_atomic_access(&ta, buf_len, index, context)?; // 5. Let arrayTypeName be typedArray.[[TypedArrayName]]. let value = if access.kind == TypedArrayKind::BigInt64 { // 6. If typedArray.[[TypedArrayName]] is "BigInt64Array", let v be ? ToBigInt64(value). value.to_big_int64(context)? } else { // 7. Else, let v be ? ToInt32(value). i64::from(value.to_i32(context)?) }; // 8. Let q be ? ToNumber(timeout). // 9. If q is either NaN or +∞𝔽, let t be +∞; else if q is -∞𝔽, let t be 0; else let t be max(ℝ(q), 0). let mut timeout = timeout.to_number(context)?; // convert to nanoseconds to discard any excessively big timeouts. timeout = timeout.clamp(0.0, f64::INFINITY) * 1000.0 * 1000.0; let timeout = if timeout.is_nan() || timeout.is_infinite() || timeout > u64::MAX as f64 { None } else { Some(Duration::from_nanos(timeout as u64)) }; // 10. If mode is sync and AgentCanSuspend() is false, throw a TypeError exception. if !ASYNC && !context.can_block() { return Err(JsNativeError::typ() .with_message("agent cannot be suspended") .into()); } // SAFETY: the validity of `addr` is verified by our call to `validate_atomic_access`. if ASYNC { let (promise, resolvers) = JsPromise::new_pending(context); let result = unsafe { if access.kind == TypedArrayKind::BigInt64 { futex::wait_async( buffer.borrow().data(), buf_len, access.byte_offset, value, timeout, resolvers, context, )? } else { // value must fit into `i32` since it came from an `i32` above. futex::wait_async( buffer.borrow().data(), buf_len, access.byte_offset, value as i32, timeout, resolvers, context, )? } }; let (is_async, value) = match result { futex::AtomicsWaitResult::NotEqual => (false, js_string!("not-equal").into()), futex::AtomicsWaitResult::TimedOut => (false, js_string!("timed-out").into()), futex::AtomicsWaitResult::Ok => (true, promise.into()), }; Ok(context .intrinsics() .templates() .wait_async() .create(OrdinaryObject, vec![is_async.into(), value]) .into()) } else { let result = unsafe { if access.kind == TypedArrayKind::BigInt64 { futex::wait( buffer.borrow().data(), buf_len, access.byte_offset, value, timeout, )? } else { // value must fit into `i32` since it came from an `i32` above. futex::wait( buffer.borrow().data(), buf_len, access.byte_offset, value as i32, timeout, )? } }; Ok(match result { futex::AtomicsWaitResult::NotEqual => js_string!("not-equal"), futex::AtomicsWaitResult::TimedOut => js_string!("timed-out"), futex::AtomicsWaitResult::Ok => js_string!("ok"), } .into()) } } /// [`Atomics.notify ( typedArray, index, count )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.notify fn notify(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let array = args.get_or_undefined(0); let index = args.get_or_undefined(1); let count = args.get_or_undefined(2); // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index, true). let (ta, buf_len) = validate_integer_typed_array(array, true)?; let access = validate_atomic_access(&ta, buf_len, index, context)?; // 2. If count is undefined, then let count = if count.is_undefined() { // a. Let c be +∞. u64::MAX } else { // 3. Else, // a. Let intCount be ? ToIntegerOrInfinity(count). // b. Let c be max(intCount, 0). match count.to_integer_or_infinity(context)? { IntegerOrInfinity::PositiveInfinity => u64::MAX, IntegerOrInfinity::Integer(i) => i64::max(i, 0) as u64, IntegerOrInfinity::NegativeInfinity => 0, } }; // 4. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 5. Let block be buffer.[[ArrayBufferData]]. // 6. If IsSharedArrayBuffer(buffer) is false, return +0𝔽. let ta = ta.borrow(); let BufferRef::SharedBuffer(shared) = ta.data().viewed_array_buffer().as_buffer() else { return Ok(0.into()); }; let count = futex::notify(&shared, access.byte_offset, count)?; // 12. Let n be the number of elements in S. // 13. Return 𝔽(n). Ok(count.into()) } /// [`Atomics.pause ( [ iterationNumber ] )`][spec] /// /// [spec]: https://tc39.es/proposal-atomics-microwait/#Atomics.pause #[cfg(feature = "experimental")] fn pause(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { use super::Number; let iteration_number = args.get_or_undefined(0); // 1. If iterationNumber is not undefined, then let iterations = if iteration_number.is_undefined() { 1 } else { // a. If iterationNumber is not an integral Number, throw a TypeError exception. if !Number::is_integer(iteration_number) { return Err(JsNativeError::typ() .with_message("`iterationNumber` must be an integral Number") .into()); } // b. If ℝ(iterationNumber) < 0, throw a RangeError exception. let iteration_number = iteration_number.to_number(context)? as i16; if iteration_number < 0 { return Err(JsNativeError::range() .with_message("`iterationNumber` must be a positive integer") .into()); } // Clamp to u16 so that the main thread cannot block using this. iteration_number as u16 }; // 2. If the execution environment of the ECMAScript implementation supports a signal that the current executing code // is in a spin-wait loop, send that signal. An ECMAScript implementation may send that signal multiple times, // determined by iterationNumber when not undefined. The number of times the signal is sent for an integral Number // N is at most the number of times it is sent for N + 1. for _ in 0..iterations { std::hint::spin_loop(); } // 3. Return undefined. Ok(JsValue::undefined()) } } /// [`ValidateIntegerTypedArray ( typedArray, waitable )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-validateintegertypedarray fn validate_integer_typed_array( array: &JsValue, waitable: bool, ) -> JsResult<(JsObject, usize)> { // 1. Let taRecord be ? ValidateTypedArray(typedArray, unordered). // 2. NOTE: Bounds checking is not a synchronizing operation when typedArray's backing buffer is a growable SharedArrayBuffer. let ta_record = TypedArray::validate(array, Ordering::Relaxed)?; { let array = ta_record.0.borrow(); // 3. If waitable is true, then if waitable { // a. If typedArray.[[TypedArrayName]] is neither "Int32Array" nor "BigInt64Array", throw a TypeError exception. if ![TypedArrayKind::Int32, TypedArrayKind::BigInt64].contains(&array.data().kind()) { return Err(JsNativeError::typ() .with_message("can only atomically wait using Int32 or BigInt64 arrays") .into()); } } else { // 4. Else, // a. Let type be TypedArrayElementType(typedArray). // b. If IsUnclampedIntegerElementType(type) is false and IsBigIntElementType(type) is false, throw a TypeError exception. if !array.data().kind().supports_atomic_ops() { return Err(JsNativeError::typ() .with_message( "platform doesn't support atomic operations on the provided `TypedArray`", ) .into()); } } } // 5. Return taRecord. Ok(ta_record) } #[derive(Debug, Copy, Clone)] struct AtomicAccess { byte_offset: usize, kind: TypedArrayKind, } /// [`ValidateAtomicAccess ( taRecord, requestIndex )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-validateatomicaccess fn validate_atomic_access( array: &JsObject, buf_len: usize, request_index: &JsValue, context: &mut Context, ) -> JsResult { // 5. Let typedArray be taRecord.[[Object]]. let (length, kind, offset) = { let array = array.borrow(); let array = array.data(); // 1. Let length be typedArray.[[ArrayLength]]. // 6. Let elementSize be TypedArrayElementSize(typedArray). // 7. Let offset be typedArray.[[ByteOffset]]. ( array.array_length(buf_len), array.kind(), array.byte_offset(), ) }; // 2. Let accessIndex be ? ToIndex(requestIndex). let access_index = request_index.to_index(context)?; // 3. Assert: accessIndex ≥ 0. // ensured by the type. // 4. If accessIndex ≥ length, throw a RangeError exception. if access_index >= length { return Err(JsNativeError::range() .with_message("index for typed array outside of bounds") .into()); } // 8. Return (accessIndex × elementSize) + offset. let offset = ((access_index * kind.element_size()) + offset) as usize; Ok(AtomicAccess { byte_offset: offset, kind, }) } #[cfg(test)] mod tests; ================================================ FILE: core/engine/src/builtins/atomics/tests.rs ================================================ use std::{rc::Rc, time::Duration}; use crate::{ Context, JsValue, builtins::atomics::Atomics, js_string, module::IdleModuleLoader, object::{JsInt32Array, JsPromise, JsSharedArrayBuffer}, value::TryFromJs, }; #[test] fn waiterlist_block_indexedposition_wake() { const NUMAGENT: i32 = 2; const RUNNING: i32 = 4; let context = &mut Context::builder() .can_block(true) .module_loader(Rc::new(IdleModuleLoader)) .build() .unwrap(); let buffer = JsSharedArrayBuffer::new(size_of::() * 5, context).unwrap(); let inner_buffer = buffer.inner(); let i32a = JsInt32Array::from_shared_array_buffer(buffer.clone(), context).unwrap(); std::thread::scope(|s| { let mut threads = Vec::new(); for idx in [2, 0] { let buffer = inner_buffer.clone(); let handle = s.spawn(move || { let context = &mut Context::builder() .can_block(true) .module_loader(Rc::new(IdleModuleLoader)) .build() .unwrap(); let buffer = JsSharedArrayBuffer::from_buffer(buffer, context); let i32a = JsInt32Array::from_shared_array_buffer(buffer, context).unwrap(); Atomics::add( &JsValue::undefined(), &[i32a.clone().into(), RUNNING.into(), 1.into()], context, ) .unwrap(); let promise = JsPromise::try_from_js( &Atomics::wait::( &JsValue::undefined(), &[ i32a.clone().into(), idx.into(), 0.into(), f64::INFINITY.into(), ], context, ) .unwrap() .as_object() .unwrap() .get(js_string!("value"), context) .unwrap(), context, ) .unwrap(); assert_eq!( promise .await_blocking(context) .unwrap() .to_string(context) .unwrap() .to_std_string_lossy(), "ok" ); }); threads.push(handle); } while Atomics::load( &JsValue::undefined(), &[i32a.clone().into(), RUNNING.into()], context, ) .unwrap() .to_i32(context) .unwrap() != NUMAGENT {} std::thread::sleep(Duration::from_millis(100)); assert_eq!( Atomics::load( &JsValue::undefined(), &[i32a.clone().into(), RUNNING.into()], context, ) .unwrap() .to_i32(context) .unwrap(), NUMAGENT ); for missing in [1, 3] { assert_eq!( Atomics::notify( &JsValue::undefined(), &[i32a.clone().into(), missing.into()], context ) .unwrap() .to_i32(context) .unwrap(), 0 ); } for exists in [2, 0] { assert_eq!( Atomics::notify( &JsValue::undefined(), &[i32a.clone().into(), exists.into()], context ) .unwrap() .to_i32(context) .unwrap(), 1 ); } }); } ================================================ FILE: core/engine/src/builtins/bigint/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `BigInt` object. //! //! `BigInt` is a built-in object that provides a way to represent whole numbers larger //! than the largest number JavaScript can reliably represent with the Number primitive //! and represented by the `Number.MAX_SAFE_INTEGER` constant. //! `BigInt` can be used for arbitrarily large integers. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-bigint-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt use crate::{ Context, JsArgs, JsBigInt, JsExpect, JsResult, JsString, JsValue, builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, object::JsObject, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::{IntegerOrInfinity, PreferredType}, }; use num_bigint::ToBigInt; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; #[cfg(test)] mod tests; /// `BigInt` implementation. #[derive(Debug, Clone, Copy)] pub struct BigInt; impl IntrinsicObject for BigInt { fn init(realm: &Realm) { BuiltInBuilder::from_standard_constructor::(realm) .method(Self::to_string, js_string!("toString"), 0) .method(Self::to_locale_string, js_string!("toLocaleString"), 0) .method(Self::value_of, js_string!("valueOf"), 0) .static_method(Self::as_int_n, js_string!("asIntN"), 2) .static_method(Self::as_uint_n, js_string!("asUintN"), 2) .property( JsSymbol::to_string_tag(), Self::NAME, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for BigInt { const NAME: JsString = StaticJsStrings::BIG_INT; } impl BuiltInConstructor for BigInt { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 4; const CONSTRUCTOR_STORAGE_SLOTS: usize = 2; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::bigint; /// `BigInt()` /// /// The `BigInt()` constructor is used to create `BigInt` objects. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is not undefined, throw a TypeError exception. if !new_target.is_undefined() { return Err(JsNativeError::typ() .with_message("BigInt is not a constructor") .into()); } let value = args.get_or_undefined(0); // 2. Let prim be ? ToPrimitive(value, number). let prim = value.to_primitive(context, PreferredType::Number)?; // 3. If Type(prim) is Number, return ? NumberToBigInt(prim). if let Some(number) = prim.as_number() { return Self::number_to_bigint(number); } // 4. Otherwise, return ? ToBigInt(prim). Ok(prim.to_bigint(context)?.into()) } } impl BigInt { /// `NumberToBigInt ( number )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-numbertobigint fn number_to_bigint(number: f64) -> JsResult { // 1. If IsIntegralNumber(number) is false, throw a RangeError exception. if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { return Err(JsNativeError::range() .with_message(format!("cannot convert {number} to a BigInt")) .into()); } // 2. Return the BigInt value that represents ℝ(number). Ok(JsBigInt::from( number .to_bigint() .js_expect("This conversion must be safe")?, ) .into()) } /// The abstract operation `thisBigIntValue` takes argument value. /// /// The phrase “this `BigInt` value” within the specification of a method refers to the /// result returned by calling the abstract operation `thisBigIntValue` with the `this` value /// of the method invocation passed as the argument. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue fn this_bigint_value(value: &JsValue) -> JsResult { value // 1. If Type(value) is BigInt, return value. .as_bigint() // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // a. Assert: Type(value.[[BigIntData]]) is BigInt. // b. Return value.[[BigIntData]]. .or_else(|| { value .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().cloned()) }) // 3. Throw a TypeError exception. .ok_or_else(|| { JsNativeError::typ() .with_message("'this' is not a BigInt") .into() }) } /// `BigInt.prototype.toString( [radix] )` /// /// The `toString()` method returns a string representing the specified `BigInt` object. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let x be ? thisBigIntValue(this value). let x = Self::this_bigint_value(this)?; let radix = args.get_or_undefined(0); // 2. If radix is undefined, let radixMV be 10. let radix_mv = if radix.is_undefined() { // 5. If radixMV = 10, return ! ToString(x). // Note: early return optimization. return Ok(js_string!(x.to_string()).into()); // 3. Else, let radixMV be ? ToIntegerOrInfinity(radix). } else { radix.to_integer_or_infinity(context)? }; // 4. If radixMV < 2 or radixMV > 36, throw a RangeError exception. let radix_mv = match radix_mv { IntegerOrInfinity::Integer(i) if (2..=36).contains(&i) => i, _ => { return Err(JsNativeError::range() .with_message("radix must be an integer at least 2 and no greater than 36") .into()); } }; // 5. If radixMV = 10, return ! ToString(x). if radix_mv == 10 { return Ok(js_string!(x.to_string()).into()); } // 1. Let x be ? thisBigIntValue(this value). // 6. Return the String representation of this Number value using the radix specified by radixMV. // Letters a-z are used for digits with values 10 through 35. // The precise algorithm is implementation-defined, however the algorithm should be a generalization of that specified in 6.1.6.2.23. Ok(JsValue::new(js_string!(x.to_string_radix(radix_mv as u32)))) } #[allow( unused_variables, reason = "`args` is used if the `intl` feature is enabled" )] fn to_locale_string( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { #[cfg(feature = "intl")] { // 1. Let x be ? ThisBigIntValue(this value). use fixed_decimal::Decimal; use crate::builtins::intl::NumberFormat; let x = Self::this_bigint_value(this)?; let locales = args.get_or_undefined(0).clone(); let options = args.get_or_undefined(1).clone(); // 2. Let numberFormat be ? Construct(%Intl.NumberFormat%, « locales, options »). let number_format = NumberFormat::new(&locales, &options, context)?; let x = &mut Decimal::try_from_str(&x.to_string()) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; // 3. Return FormatNumeric(numberFormat, ℝ(x)). Ok(js_string!(number_format.format(x).to_string()).into()) } #[cfg(not(feature = "intl"))] { // Arguments are reserved, so we cannot pass them to the `toString` method. Self::to_string(this, &[], context) } } /// `BigInt.prototype.valueOf()` /// /// The `valueOf()` method returns the wrapped primitive value of a Number object. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { Ok(JsValue::new(Self::this_bigint_value(this)?)) } /// `BigInt.asIntN()` /// /// The `BigInt.asIntN()` method wraps the value of a `BigInt` to a signed integer between `-2**(width - 1)` and `2**(width-1) - 1`. /// /// [spec]: https://tc39.es/ecma262/#sec-bigint.asintn /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asIntN #[allow(clippy::wrong_self_convention)] pub(crate) fn as_int_n( _: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let (modulo, bits) = Self::calculate_as_uint_n(args, context)?; if bits > 0 && modulo >= JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits) - 1))? { Ok(JsValue::new(JsBigInt::sub( &modulo, &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)))?, ))) } else { Ok(JsValue::new(modulo)) } } /// `BigInt.asUintN()` /// /// The `BigInt.asUintN()` method wraps the value of a `BigInt` to an unsigned integer between `0` and `2**(width) - 1`. /// /// [spec]: https://tc39.es/ecma262/#sec-bigint.asuintn /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asUintN #[allow(clippy::wrong_self_convention)] pub(crate) fn as_uint_n( _: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let (modulo, _) = Self::calculate_as_uint_n(args, context)?; Ok(JsValue::new(modulo)) } /// Helper function to wrap the value of a `BigInt` to an unsigned integer. /// /// This function expects the same arguments as `as_uint_n` and wraps the value of a `BigInt`. /// Additionally to the wrapped unsigned value it returns the converted `bits` argument, so it /// can be reused from the `as_int_n` method. fn calculate_as_uint_n(args: &[JsValue], context: &mut Context) -> JsResult<(JsBigInt, u32)> { let bits_arg = args.get_or_undefined(0); let bigint_arg = args.get_or_undefined(1); let bits = bits_arg.to_index(context)?; let bits = u32::try_from(bits).unwrap_or(u32::MAX); let bigint = bigint_arg.to_bigint(context)?; Ok(( JsBigInt::mod_floor( &bigint, &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)))?, ), bits, )) } } ================================================ FILE: core/engine/src/builtins/bigint/tests.rs ================================================ use boa_macros::js_str; use crate::{JsBigInt, JsNativeErrorKind, TestAction, run_test_actions}; #[test] fn equality() { run_test_actions([ TestAction::assert("0n == 0n"), TestAction::assert("!(1n == 0n)"), TestAction::assert( "1000000000000000000000000000000000n == 1000000000000000000000000000000000n", ), TestAction::assert("0n == ''"), TestAction::assert("100n == '100'"), TestAction::assert("!(100n == '100.5')"), TestAction::assert("10000000000000000n == '10000000000000000'"), TestAction::assert("'' == 0n"), TestAction::assert("'100' == 100n"), TestAction::assert("!('100.5' == 100n)"), TestAction::assert("'10000000000000000' == 10000000000000000n"), TestAction::assert("0n == 0"), TestAction::assert("0n == 0.0"), TestAction::assert("100n == 100"), TestAction::assert("100n == 100.0"), TestAction::assert("!(100n == '100.5')"), TestAction::assert("!(100n == '1005')"), TestAction::assert("10000000000000000n == 10000000000000000"), TestAction::assert("0 == 0n"), TestAction::assert("0.0 == 0n"), TestAction::assert("100 == 100n"), TestAction::assert("100.0 == 100n"), TestAction::assert("!(100.5 == 100n)"), TestAction::assert("!(1005 == 100n)"), TestAction::assert("10000000000000000 == 10000000000000000n"), ]); } #[test] fn bigint_function_conversion_from_integer() { run_test_actions([ TestAction::assert_eq("BigInt(1000)", JsBigInt::from(1000)), TestAction::assert_eq( "BigInt(20000000000000000)", JsBigInt::from_string("20000000000000000").unwrap(), ), TestAction::assert_eq( "BigInt(1000000000000000000000000000000000)", JsBigInt::from_string("999999999999999945575230987042816").unwrap(), ), ]); } #[test] fn bigint_function_conversion_from_rational() { run_test_actions([ TestAction::assert_eq("BigInt(0.0)", JsBigInt::from(0)), TestAction::assert_eq("BigInt(1.0)", JsBigInt::from(1)), TestAction::assert_eq("BigInt(10000.0)", JsBigInt::from(10_000)), ]); } #[test] fn bigint_function_throws() { run_test_actions([ TestAction::assert_native_error( "BigInt(0.1)", JsNativeErrorKind::Range, "cannot convert 0.1 to a BigInt", ), TestAction::assert_native_error( "BigInt(null)", JsNativeErrorKind::Type, "cannot convert null to a BigInt", ), TestAction::assert_native_error( "BigInt(undefined)", JsNativeErrorKind::Type, "cannot convert undefined to a BigInt", ), ]); } #[test] fn bigint_function_conversion_from_string() { run_test_actions([ TestAction::assert_eq("BigInt('')", JsBigInt::from(0)), TestAction::assert_eq("BigInt(' ')", JsBigInt::from(0)), TestAction::assert_eq( "BigInt('200000000000000000')", JsBigInt::from_string("200000000000000000").unwrap(), ), TestAction::assert_eq( "BigInt('1000000000000000000000000000000000')", JsBigInt::from_string("1000000000000000000000000000000000").unwrap(), ), TestAction::assert_eq("BigInt('0b1111')", JsBigInt::from(15)), TestAction::assert_eq("BigInt('0o70')", JsBigInt::from(56)), TestAction::assert_eq("BigInt('0xFF')", JsBigInt::from(255)), ]); } #[test] fn operations() { run_test_actions([ TestAction::assert_eq("10000n + 1000n", JsBigInt::from(11_000)), TestAction::assert_eq("10000n - 1000n", JsBigInt::from(9_000)), TestAction::assert_eq( "123456789n * 102030n", JsBigInt::from_string("12596296181670").unwrap(), ), TestAction::assert_eq("15000n / 50n", JsBigInt::from(300)), TestAction::assert_eq("15001n / 50n", JsBigInt::from(300)), TestAction::assert_native_error( "1n/0n", JsNativeErrorKind::Range, "BigInt division by zero", ), TestAction::assert_eq("15007n % 10n", JsBigInt::from(7)), TestAction::assert_native_error( "1n % 0n", JsNativeErrorKind::Range, "BigInt division by zero", ), TestAction::assert_eq( "100n ** 10n", JsBigInt::from_string("100000000000000000000").unwrap(), ), TestAction::assert_native_error( "10n ** (-10n)", JsNativeErrorKind::Range, "BigInt negative exponent", ), TestAction::assert_eq("8n << 2n", JsBigInt::from(32)), TestAction::assert_native_error( "1000n << 1000000000000000n", JsNativeErrorKind::Range, "Maximum BigInt size exceeded", ), TestAction::assert_eq("8n >> 2n", JsBigInt::from(2)), TestAction::assert_eq("1000n >> 1000000000000000n", JsBigInt::zero()), TestAction::assert_eq("(-1000n) >> 1000000000000000n", JsBigInt::from(-1)), TestAction::assert_eq("0n >> 1000000000000000n", JsBigInt::zero()), // shift_left with large negative amount (equivalent to large right-shift) TestAction::assert_eq("1000n << (-1000000000000000n)", JsBigInt::zero()), TestAction::assert_eq("(-1000n) << (-1000000000000000n)", JsBigInt::from(-1)), TestAction::assert_eq("0n << (-1000000000000000n)", JsBigInt::zero()), ]); } #[test] fn to_string() { run_test_actions([ TestAction::assert_eq("1000n.toString()", js_str!("1000")), TestAction::assert_eq("1000n.toString(2)", js_str!("1111101000")), TestAction::assert_eq("255n.toString(16)", js_str!("ff")), TestAction::assert_eq("1000n.toString(36)", js_str!("rs")), ]); } #[test] fn to_string_invalid_radix() { run_test_actions([ TestAction::assert_native_error( "10n.toString(null)", JsNativeErrorKind::Range, "radix must be an integer at least 2 and no greater than 36", ), TestAction::assert_native_error( "10n.toString(-1)", JsNativeErrorKind::Range, "radix must be an integer at least 2 and no greater than 36", ), TestAction::assert_native_error( "10n.toString(37)", JsNativeErrorKind::Range, "radix must be an integer at least 2 and no greater than 36", ), ]); } #[test] fn as_int_n() { run_test_actions([ TestAction::assert_eq("BigInt.asIntN(0, 1n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asIntN(1, 1n)", JsBigInt::from(-1)), TestAction::assert_eq("BigInt.asIntN(3, 10n)", JsBigInt::from(2)), TestAction::assert_eq("BigInt.asIntN({}, 1n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asIntN(2, 0n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asIntN(2, -0n)", JsBigInt::from(0)), TestAction::assert_eq( "BigInt.asIntN(2, -123456789012345678901n)", JsBigInt::from(-1), ), TestAction::assert_eq( "BigInt.asIntN(2, -123456789012345678900n)", JsBigInt::from(0), ), TestAction::assert_eq( "BigInt.asIntN(2, 123456789012345678900n)", JsBigInt::from(0), ), TestAction::assert_eq( "BigInt.asIntN(2, 123456789012345678901n)", JsBigInt::from(1), ), TestAction::assert_eq( "BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)", JsBigInt::from(-1), ), TestAction::assert_eq( "BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)", JsBigInt::from_string("1606938044258990275541962092341162602522202993782792835301375") .unwrap(), ), TestAction::assert_eq( "BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)", JsBigInt::from_string("-741470203160010616172516490008037905920749803227695190508460") .unwrap(), ), TestAction::assert_eq( "BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)", JsBigInt::from_string("865467841098979659369445602333124696601453190555097644792916") .unwrap(), ), ]); } #[test] fn as_int_n_errors() { run_test_actions([ TestAction::assert_native_error( "BigInt.asIntN(-1, 0n)", JsNativeErrorKind::Range, "Index must be between 0 and 2^53 - 1", ), TestAction::assert_native_error( "BigInt.asIntN(-2.5, 0n)", JsNativeErrorKind::Range, "Index must be between 0 and 2^53 - 1", ), TestAction::assert_native_error( "BigInt.asIntN(9007199254740992, 0n)", JsNativeErrorKind::Range, "Index must be between 0 and 2^53 - 1", ), TestAction::assert_native_error( "BigInt.asIntN(0n, 0n)", JsNativeErrorKind::Type, "argument must not be a bigint", ), ]); } #[test] fn as_uint_n() { run_test_actions([ TestAction::assert_eq("BigInt.asUintN(0, -2n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asUintN(0, -1n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asUintN(0, 0n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asUintN(0, 1n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asUintN(0, 2n)", JsBigInt::from(0)), // TestAction::assert_eq("BigInt.asUintN(1, -3n)", JsBigInt::from(1)), TestAction::assert_eq("BigInt.asUintN(1, -2n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asUintN(1, -1n)", JsBigInt::from(1)), TestAction::assert_eq("BigInt.asUintN(1, 0n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asUintN(1, 1n)", JsBigInt::from(1)), TestAction::assert_eq("BigInt.asUintN(1, 2n)", JsBigInt::from(0)), TestAction::assert_eq("BigInt.asUintN(1, 3n)", JsBigInt::from(1)), // TestAction::assert_eq( "BigInt.asUintN(1, -123456789012345678901n)", JsBigInt::from(1), ), TestAction::assert_eq( "BigInt.asUintN(1, -123456789012345678900n)", JsBigInt::from(0), ), TestAction::assert_eq( "BigInt.asUintN(1, 123456789012345678900n)", JsBigInt::from(0), ), TestAction::assert_eq( "BigInt.asUintN(1, 123456789012345678901n)", JsBigInt::from(1), ), // TestAction::assert_eq( "BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)", JsBigInt::from_string("1606938044258990275541962092341162602522202993782792835301375") .unwrap(), ), TestAction::assert_eq( "BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)", JsBigInt::from_string("3213876088517980551083924184682325205044405987565585670602751") .unwrap(), ), ]); } #[test] fn as_uint_n_errors() { run_test_actions([ TestAction::assert_native_error( "BigInt.asUintN(-1, 0n)", JsNativeErrorKind::Range, "Index must be between 0 and 2^53 - 1", ), TestAction::assert_native_error( "BigInt.asUintN(-2.5, 0n)", JsNativeErrorKind::Range, "Index must be between 0 and 2^53 - 1", ), TestAction::assert_native_error( "BigInt.asUintN(9007199254740992, 0n)", JsNativeErrorKind::Range, "Index must be between 0 and 2^53 - 1", ), TestAction::assert_native_error( "BigInt.asUintN(0n, 0n)", JsNativeErrorKind::Type, "argument must not be a bigint", ), ]); } ================================================ FILE: core/engine/src/builtins/boolean/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `Boolean` object. //! //! The `Boolean` object is an object wrapper for a boolean value. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-boolean-object //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean #[cfg(test)] mod tests; use crate::{ Context, JsResult, JsString, JsValue, builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, object::{JsObject, internal_methods::get_prototype_from_constructor}, realm::Realm, string::StaticJsStrings, }; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; /// Boolean implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct Boolean; impl IntrinsicObject for Boolean { fn init(realm: &Realm) { BuiltInBuilder::from_standard_constructor::(realm) .method(Self::to_string, js_string!("toString"), 0) .method(Self::value_of, js_string!("valueOf"), 0) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for Boolean { const NAME: JsString = StaticJsStrings::BOOLEAN; } impl BuiltInConstructor for Boolean { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::boolean; /// `[[Construct]]` Create a new boolean object /// /// `[[Call]]` Creates a new boolean primitive fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // Get the argument, if any let data = args.first().is_some_and(JsValue::to_boolean); if new_target.is_undefined() { return Ok(JsValue::new(data)); } let prototype = get_prototype_from_constructor(new_target, StandardConstructors::boolean, context)?; let boolean = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); Ok(boolean.into()) } } impl Boolean { /// An Utility function used to get the internal `[[BooleanData]]`. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue fn this_boolean_value(value: &JsValue) -> JsResult { value .as_boolean() .or_else(|| { value .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) }) .ok_or_else(|| { JsNativeError::typ() .with_message("'this' is not a boolean") .into() }) } /// The `toString()` method returns a string representing the specified `Boolean` object. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-boolean-object /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let boolean = Self::this_boolean_value(this)?; Ok(JsValue::new(if boolean { js_string!("true") } else { js_string!("false") })) } /// The `valueOf()` method returns the primitive value of a `Boolean` object. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { Ok(JsValue::new(Self::this_boolean_value(this)?)) } } ================================================ FILE: core/engine/src/builtins/boolean/tests.rs ================================================ use crate::{TestAction, run_test_actions}; use indoc::indoc; /// Test the correct type is returned from call and construct #[test] fn construct_and_call() { run_test_actions([ TestAction::assert_with_op("new Boolean(1)", |val, _| val.is_object()), TestAction::assert_with_op("Boolean(0)", |val, _| val.is_boolean()), ]); } #[test] fn constructor_gives_true_instance() { run_test_actions([ TestAction::run(indoc! {r#" var trueVal = new Boolean(true); var trueNum = new Boolean(1); var trueString = new Boolean("true"); var trueBool = new Boolean(trueVal); "#}), // Values should all be objects TestAction::assert_with_op("trueVal", |val, _| val.is_object()), TestAction::assert_with_op("trueNum", |val, _| val.is_object()), TestAction::assert_with_op("trueString", |val, _| val.is_object()), TestAction::assert_with_op("trueBool", |val, _| val.is_object()), // Values should all be truthy TestAction::assert("trueVal.valueOf()"), TestAction::assert("trueNum.valueOf()"), TestAction::assert("trueString.valueOf()"), TestAction::assert("trueBool.valueOf()"), ]); } #[test] fn instances_have_correct_proto_set() { run_test_actions([TestAction::assert( "Object.getPrototypeOf(new Boolean(true)) === Boolean.prototype", )]); } ================================================ FILE: core/engine/src/builtins/builder.rs ================================================ use crate::{ JsObject, JsString, JsValue, NativeFunction, js_string, native_function::{NativeFunctionObject, NativeFunctionPointer}, object::{ CONSTRUCTOR, FunctionBinding, JsFunction, JsPrototype, PROTOTYPE, shape::{property_table::PropertyTableInner, slot::SlotAttributes}, }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, string::StaticJsStrings, }; use super::{BuiltInConstructor, IntrinsicObject, function::ConstructorKind}; /// Marker for a constructor function. // TODO: Remove this marker and use `Constructor` directly. #[allow(dead_code)] pub(crate) struct Constructor { prototype: JsObject, inherits: JsPrototype, attributes: Attribute, } /// Marker for a constructor function without a custom prototype for its instances. // TODO: Remove this marker and use `ConstructorNoProto` directly. #[allow(dead_code)] pub(crate) struct ConstructorNoProto; /// Marker for an ordinary function. pub(crate) struct OrdinaryFunction; /// Indicates if the marker is a constructor. pub(crate) trait IsConstructor { const IS_CONSTRUCTOR: bool; } impl IsConstructor for Constructor { const IS_CONSTRUCTOR: bool = true; } impl IsConstructor for ConstructorNoProto { const IS_CONSTRUCTOR: bool = true; } impl IsConstructor for OrdinaryFunction { const IS_CONSTRUCTOR: bool = false; } /// Marker for a callable object. pub(crate) struct Callable { function: NativeFunctionPointer, name: JsString, length: usize, kind: Kind, realm: Realm, } /// Marker for an ordinary object. pub(crate) struct OrdinaryObject; /// Applies the pending builder data to the object. pub(crate) trait ApplyToObject { fn apply_to(self, object: &JsObject); } impl ApplyToObject for Constructor { fn apply_to(self, object: &JsObject) { object.insert( PROTOTYPE, PropertyDescriptor::builder() .value(self.prototype.clone()) .writable(false) .enumerable(false) .configurable(false), ); { let mut prototype = self.prototype.borrow_mut(); prototype.set_prototype(self.inherits); prototype.insert( CONSTRUCTOR, PropertyDescriptor::builder() .value(object.clone()) .writable(self.attributes.writable()) .enumerable(self.attributes.enumerable()) .configurable(self.attributes.configurable()), ); } } } impl ApplyToObject for ConstructorNoProto { fn apply_to(self, _: &JsObject) {} } impl ApplyToObject for OrdinaryFunction { fn apply_to(self, _: &JsObject) {} } impl ApplyToObject for Callable { fn apply_to(self, object: &JsObject) { { let mut function = object .downcast_mut::() .expect("Builtin must be a function object"); function.f = NativeFunction::from_fn_ptr(self.function); function.constructor = S::IS_CONSTRUCTOR.then_some(ConstructorKind::Base); function.realm = Some(self.realm); } object.insert( StaticJsStrings::LENGTH, PropertyDescriptor::builder() .value(self.length) .writable(false) .enumerable(false) .configurable(true), ); object.insert( js_string!("name"), PropertyDescriptor::builder() .value(self.name) .writable(false) .enumerable(false) .configurable(true), ); self.kind.apply_to(object); } } impl ApplyToObject for OrdinaryObject { fn apply_to(self, _: &JsObject) {} } /// Builder for creating built-in objects, like `Array`. /// /// The marker `ObjectType` restricts the methods that can be called depending on the /// type of object that is being constructed. #[derive(Debug)] #[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"] pub(crate) struct BuiltInBuilder<'ctx, Kind> { realm: &'ctx Realm, object: JsObject, kind: Kind, prototype: JsObject, } impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { pub(crate) fn with_intrinsic( realm: &'ctx Realm, ) -> BuiltInBuilder<'ctx, OrdinaryObject> { BuiltInBuilder { realm, object: I::get(realm.intrinsics()), kind: OrdinaryObject, prototype: realm.intrinsics().constructors().object().prototype(), } } } pub(crate) struct BuiltInConstructorWithPrototype<'ctx> { realm: &'ctx Realm, function: NativeFunctionPointer, name: JsString, length: usize, constructor_property_table: PropertyTableInner, constructor_storage: Vec, constructor: JsObject, #[cfg(debug_assertions)] constructor_storage_slots_expected: usize, prototype_property_table: PropertyTableInner, prototype_storage: Vec, prototype: JsObject, #[cfg(debug_assertions)] prototype_storage_slots_expected: usize, __proto__: JsPrototype, inherits: Option, attributes: Attribute, /// If `Some`, the `constructor` property on the prototype will be installed /// as a get/set accessor pair instead of as a writable data property. /// This is needed by `Iterator.prototype.constructor` per the spec /// (web-compat requirement). constructor_accessor: Option<(JsFunction, JsFunction)>, } #[allow(dead_code)] impl BuiltInConstructorWithPrototype<'_> { /// The number of storage slots that are always present in a standard /// constructor object. /// /// See [`BuiltInConstructorWithPrototype::build`]. const OWN_CONSTRUCTOR_STORAGE_SLOTS: usize = 3; /// The number of storage slots properties that are always present in a /// standard constructor's prototype object when `constructor` is a **data** property. /// /// See [`BuiltInConstructorWithPrototype::build`]. const OWN_PROTOTYPE_STORAGE_SLOTS: usize = 1; /// The number of storage slots properties that are always present in a /// standard constructor's prototype object when `constructor` is installed /// as a get/set **accessor** property (two slots: getter + setter). /// /// See [`BuiltInConstructorWithPrototype::constructor_accessor`]. const OWN_PROTOTYPE_STORAGE_SLOTS_ACCESSOR: usize = 2; /// Specify how many arguments the constructor function takes. /// /// Default is `0`. #[inline] pub(crate) const fn length(mut self, length: usize) -> Self { self.length = length; self } /// Specify the name of the constructor function. /// /// Default is `""` pub(crate) fn name(mut self, name: JsString) -> Self { self.name = name; self } /// Adds a new static method to the builtin object. pub(crate) fn static_method( mut self, function: NativeFunctionPointer, binding: B, length: usize, ) -> Self where B: Into, { let binding = binding.into(); let function = BuiltInBuilder::callable(self.realm, function) .name(binding.name) .length(length) .build(); debug_assert!( !self .constructor_property_table .map .contains_key(&binding.binding) ); self.constructor_property_table.insert( binding.binding, SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, ); self.constructor_storage.push(function.into()); self } /// Adds a new static data property to the builtin object. pub(crate) fn static_property(mut self, key: K, value: V, attribute: Attribute) -> Self where K: Into, V: Into, { let key = key.into(); debug_assert!(!self.constructor_property_table.map.contains_key(&key)); self.constructor_property_table .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); self.constructor_storage.push(value.into()); self } /// Adds a new static accessor property to the builtin object. pub(crate) fn static_accessor( mut self, key: K, get: Option, set: Option, attribute: Attribute, ) -> Self where K: Into, { let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); attributes.set(SlotAttributes::GET, get.is_some()); attributes.set(SlotAttributes::SET, set.is_some()); let key = key.into(); debug_assert!(!self.constructor_property_table.map.contains_key(&key)); self.constructor_property_table.insert(key, attributes); self.constructor_storage.extend([ get.map(JsValue::new).unwrap_or_default(), set.map(JsValue::new).unwrap_or_default(), ]); self } /// Specify the `[[Prototype]]` internal field of the builtin object. /// /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. pub(crate) fn prototype(mut self, prototype: JsObject) -> Self { self.__proto__ = Some(prototype); self } /// Adds a new method to the constructor's prototype. pub(crate) fn method( mut self, function: NativeFunctionPointer, binding: B, length: usize, ) -> Self where B: Into, { let binding = binding.into(); let function = BuiltInBuilder::callable(self.realm, function) .name(binding.name) .length(length) .build(); debug_assert!( !self .prototype_property_table .map .contains_key(&binding.binding) ); self.prototype_property_table.insert( binding.binding, SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, ); self.prototype_storage.push(function.into()); self } /// Adds a new data property to the constructor's prototype. pub(crate) fn property(mut self, key: K, value: V, attribute: Attribute) -> Self where K: Into, V: Into, { let key = key.into(); debug_assert!(!self.prototype_property_table.map.contains_key(&key)); self.prototype_property_table .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); self.prototype_storage.push(value.into()); self } /// Adds new accessor property to the constructor's prototype. pub(crate) fn accessor( mut self, key: K, get: Option, set: Option, attribute: Attribute, ) -> Self where K: Into, { let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); attributes.set(SlotAttributes::GET, get.is_some()); attributes.set(SlotAttributes::SET, set.is_some()); let key = key.into(); debug_assert!(!self.prototype_property_table.map.contains_key(&key)); self.prototype_property_table.insert(key, attributes); self.prototype_storage.extend([ get.map(JsValue::new).unwrap_or_default(), set.map(JsValue::new).unwrap_or_default(), ]); self } /// Specifies the parent prototype which objects created by this constructor inherit from. /// /// Default is `Object.prototype`. #[allow(clippy::missing_const_for_fn)] pub(crate) fn inherits(mut self, prototype: JsPrototype) -> Self { self.inherits = prototype; self } /// Specifies the property attributes of the prototype's "constructor" property. pub(crate) const fn constructor_attributes(mut self, attributes: Attribute) -> Self { self.attributes = attributes; self } /// Installs `Iterator.prototype.constructor` (or any analogous property) as a /// configurable, non-enumerable **accessor** descriptor instead of the default /// writable data property. /// /// Per the [ECMAScript spec][spec], `Iterator.prototype.constructor` must be an /// accessor for web-compatibility reasons: previously the property did not exist, so /// making it a setter that ignores writes to the prototype avoids breaking existing code /// that assigns `Iterator.prototype.constructor = ...`. /// /// When this method is called the `PROTOTYPE_STORAGE_SLOTS` constant on the /// implementing built-in **must** account for two extra slots (getter + setter) /// instead of the usual one slot for a data property. /// /// [spec]: https://tc39.es/ecma262/#sec-iterator.prototype.constructor pub(crate) fn constructor_accessor(mut self, get: JsFunction, set: JsFunction) -> Self { self.constructor_accessor = Some((get, set)); self } #[track_caller] pub(crate) fn build(mut self) { let length = self.length; let name = self.name.clone(); let prototype = self.prototype.clone(); self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); self = self.static_property(PROTOTYPE, prototype, Attribute::empty()); // Install the `constructor` property on the prototype — either as a writable // data property (the common case) or as a get/set accessor (needed by // `Iterator.prototype.constructor` for web-compat, see `constructor_accessor`). if let Some((get, set)) = self.constructor_accessor.take() { // Accessor path: two storage slots (getter + setter). The `CONSTRUCTOR` key // must NOT already be present (no duplicate insertion). let mut attributes = SlotAttributes::CONFIGURABLE; attributes.set(SlotAttributes::GET, true); attributes.set(SlotAttributes::SET, true); debug_assert!( !self .prototype_property_table .map .contains_key(&CONSTRUCTOR.into()) ); self.prototype_property_table .insert(CONSTRUCTOR.into(), attributes); self.prototype_storage .extend([JsValue::new(get), JsValue::new(set)]); #[cfg(debug_assertions)] assert!( self.prototype_storage.len() <= self.prototype_storage_slots_expected + Self::OWN_PROTOTYPE_STORAGE_SLOTS_ACCESSOR, "expected to allocate at most {} prototype storage slots, got {}. \ constant {}::PROTOTYPE_STORAGE_SLOTS may need to be adjusted", self.prototype_storage_slots_expected, self.prototype_storage.len() - Self::OWN_PROTOTYPE_STORAGE_SLOTS_ACCESSOR, self.name.display_escaped(), ); } else { // Data property path (the default). let attributes = self.attributes; let object = self.constructor.clone(); self = self.property(CONSTRUCTOR, object, attributes); #[cfg(debug_assertions)] assert!( self.prototype_storage.len() <= self.prototype_storage_slots_expected + Self::OWN_PROTOTYPE_STORAGE_SLOTS, "expected to allocate at most {} prototype storage slots, got {}. \ constant {}::PROTOTYPE_STORAGE_SLOTS may need to be adjusted", self.prototype_storage_slots_expected, self.prototype_storage.len() - Self::OWN_PROTOTYPE_STORAGE_SLOTS, self.name.display_escaped(), ); } #[cfg(debug_assertions)] assert!( self.constructor_storage.len() <= self.constructor_storage_slots_expected + Self::OWN_CONSTRUCTOR_STORAGE_SLOTS, "expected to allocate at most {} constructor storage slots, got {}. \ constant {}::CONSTRUCTOR_STORAGE_SLOTS may need to be adjusted", self.constructor_storage_slots_expected, self.constructor_storage.len() - Self::OWN_CONSTRUCTOR_STORAGE_SLOTS, self.name.display_escaped(), ); { let mut prototype = self.prototype.borrow_mut(); prototype .properties_mut() .shape .as_unique() .expect("The object should have a unique shape") .override_internal(self.prototype_property_table, self.inherits); let prototype_old_storage = std::mem::replace( &mut prototype.properties_mut().storage, self.prototype_storage, ); debug_assert_eq!(prototype_old_storage.len(), 0); } { let mut function = self .constructor .downcast_mut::() .expect("Builtin must be a function object"); function.f = NativeFunction::from_fn_ptr(self.function); function.constructor = Some(ConstructorKind::Base); function.realm = Some(self.realm.clone()); } let mut object = self.constructor.borrow_mut(); object .properties_mut() .shape .as_unique() .expect("The object should have a unique shape") .override_internal(self.constructor_property_table, self.__proto__); let object_old_storage = std::mem::replace( &mut object.properties_mut().storage, self.constructor_storage, ); debug_assert_eq!(object_old_storage.len(), 0); } pub(crate) fn build_without_prototype(mut self) { let length = self.length; let name = self.name.clone(); self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); { let mut function = self .constructor .downcast_mut::() .expect("Builtin must be a function object"); function.f = NativeFunction::from_fn_ptr(self.function); function.constructor = Some(ConstructorKind::Base); function.realm = Some(self.realm.clone()); } let mut object = self.constructor.borrow_mut(); object .properties_mut() .shape .as_unique() .expect("The object should have a unique shape") .override_internal(self.constructor_property_table, self.__proto__); let object_old_storage = std::mem::replace( &mut object.properties_mut().storage, self.constructor_storage, ); debug_assert_eq!(object_old_storage.len(), 0); } } pub(crate) struct BuiltInCallable<'ctx> { realm: &'ctx Realm, function: NativeFunctionPointer, name: JsString, length: usize, } impl BuiltInCallable<'_> { /// Specify how many arguments the constructor function takes. /// /// Default is `0`. #[inline] pub(crate) const fn length(mut self, length: usize) -> Self { self.length = length; self } /// Specify the name of the constructor function. /// /// Default is `""` pub(crate) fn name(mut self, name: JsString) -> Self { self.name = name; self } pub(crate) fn build(self) -> JsFunction { let object = self.realm.intrinsics().templates().function().create( NativeFunctionObject { f: NativeFunction::from_fn_ptr(self.function), name: self.name.clone(), constructor: None, realm: Some(self.realm.clone()), }, vec![JsValue::new(self.length), JsValue::new(self.name)], ); JsFunction::from_object_unchecked(object) } } impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { pub(crate) fn callable( realm: &'ctx Realm, function: NativeFunctionPointer, ) -> BuiltInCallable<'ctx> { BuiltInCallable { realm, function, length: 0, name: js_string!(), } } pub(crate) fn callable_with_intrinsic( realm: &'ctx Realm, function: NativeFunctionPointer, ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { realm, object: I::get(realm.intrinsics()), kind: Callable { function, name: js_string!(), length: 0, kind: OrdinaryFunction, realm: realm.clone(), }, prototype: realm.intrinsics().constructors().function().prototype(), } } pub(crate) fn callable_with_object( realm: &'ctx Realm, object: JsObject, function: NativeFunctionPointer, ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { realm, object, kind: Callable { function, name: js_string!(), length: 0, kind: OrdinaryFunction, realm: realm.clone(), }, prototype: realm.intrinsics().constructors().function().prototype(), } } } impl<'ctx> BuiltInBuilder<'ctx, Callable> { /// Create a new builder for a constructor function. /// /// This sets the properties ahead of time for optimizations /// (less reallocations). pub(crate) fn from_standard_constructor( realm: &'ctx Realm, ) -> BuiltInConstructorWithPrototype<'ctx> { let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors()); BuiltInConstructorWithPrototype { realm, function: SC::constructor, name: js_string!(SC::NAME), length: SC::CONSTRUCTOR_ARGUMENTS, constructor_property_table: PropertyTableInner::with_capacity( SC::CONSTRUCTOR_STORAGE_SLOTS + BuiltInConstructorWithPrototype::OWN_CONSTRUCTOR_STORAGE_SLOTS, ), constructor_storage: Vec::with_capacity( SC::CONSTRUCTOR_STORAGE_SLOTS + BuiltInConstructorWithPrototype::OWN_CONSTRUCTOR_STORAGE_SLOTS, ), constructor: constructor.constructor(), #[cfg(debug_assertions)] constructor_storage_slots_expected: SC::CONSTRUCTOR_STORAGE_SLOTS, prototype_property_table: PropertyTableInner::with_capacity( SC::PROTOTYPE_STORAGE_SLOTS + BuiltInConstructorWithPrototype::OWN_PROTOTYPE_STORAGE_SLOTS, ), prototype_storage: Vec::with_capacity( SC::PROTOTYPE_STORAGE_SLOTS + BuiltInConstructorWithPrototype::OWN_PROTOTYPE_STORAGE_SLOTS, ), prototype: constructor.prototype(), #[cfg(debug_assertions)] prototype_storage_slots_expected: SC::PROTOTYPE_STORAGE_SLOTS, __proto__: Some(realm.intrinsics().constructors().function().prototype()), inherits: Some(realm.intrinsics().constructors().object().prototype()), attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, constructor_accessor: None, } } } impl BuiltInBuilder<'_, T> { /// Adds a new static method to the builtin object. pub(crate) fn static_method( self, function: NativeFunctionPointer, binding: B, length: usize, ) -> Self where B: Into, { let binding = binding.into(); let function = BuiltInBuilder::callable(self.realm, function) .name(binding.name) .length(length) .build(); self.object.insert( binding.binding, PropertyDescriptor::builder() .value(function) .writable(true) .enumerable(false) .configurable(true), ); self } /// Adds a new static data property to the builtin object. pub(crate) fn static_property(self, key: K, value: V, attribute: Attribute) -> Self where K: Into, V: Into, { let property = PropertyDescriptor::builder() .value(value) .writable(attribute.writable()) .enumerable(attribute.enumerable()) .configurable(attribute.configurable()); self.object.insert(key, property); self } /// Specify the `[[Prototype]]` internal field of the builtin object. /// /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. pub(crate) fn prototype(mut self, prototype: JsObject) -> Self { self.prototype = prototype; self } } impl BuiltInBuilder<'_, Callable> { /// Specify how many arguments the constructor function takes. /// /// Default is `0`. #[inline] pub(crate) const fn length(mut self, length: usize) -> Self { self.kind.length = length; self } /// Specify the name of the constructor function. /// /// Default is `""` pub(crate) fn name(mut self, name: JsString) -> Self { self.kind.name = name; self } } impl BuiltInBuilder<'_, OrdinaryObject> { /// Build the builtin object. pub(crate) fn build(self) -> JsObject { self.kind.apply_to(&self.object); self.object.set_prototype(Some(self.prototype)); self.object } } impl BuiltInBuilder<'_, Callable> { /// Build the builtin callable. pub(crate) fn build(self) -> JsFunction { self.kind.apply_to(&self.object); self.object.set_prototype(Some(self.prototype)); JsFunction::from_object_unchecked(self.object) } } ================================================ FILE: core/engine/src/builtins/dataview/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `DataView` object. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-dataview-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView use std::sync::atomic::Ordering; use crate::{ Context, JsArgs, JsData, JsResult, JsString, builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, object::{JsObject, internal_methods::get_prototype_from_constructor}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::JsValue, }; use boa_gc::{Finalize, Trace}; use bytemuck::{bytes_of, bytes_of_mut}; use super::{ BuiltInBuilder, BuiltInConstructor, IntrinsicObject, array_buffer::{ BufferObject, utils::{BytesConstPtr, BytesMutPtr, memcpy}, }, typed_array::{self, TypedArrayElement}, }; /// The internal representation of a `DataView` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct DataView { pub(crate) viewed_array_buffer: BufferObject, pub(crate) byte_length: Option, pub(crate) byte_offset: u64, } impl DataView { /// Abstract operation [`GetViewByteLength ( viewRecord )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-getviewbytelength fn byte_length(&self, buf_byte_len: usize) -> u64 { // 1. Assert: IsViewOutOfBounds(viewRecord) is false. debug_assert!(!self.is_out_of_bounds(buf_byte_len)); // 2. Let view be viewRecord.[[Object]]. // 3. If view.[[ByteLength]] is not auto, return view.[[ByteLength]]. if let Some(byte_length) = self.byte_length { return byte_length; } // 4. Assert: IsFixedLengthArrayBuffer(view.[[ViewedArrayBuffer]]) is false. // 5. Let byteOffset be view.[[ByteOffset]]. // 6. Let byteLength be viewRecord.[[CachedBufferByteLength]]. // 7. Assert: byteLength is not detached. // 8. Return byteLength - byteOffset. buf_byte_len as u64 - self.byte_offset } /// Abstract operation [`IsViewOutOfBounds ( viewRecord )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-isviewoutofbounds fn is_out_of_bounds(&self, buf_byte_len: usize) -> bool { let buf_byte_len = buf_byte_len as u64; // 1. Let view be viewRecord.[[Object]]. // 2. Let bufferByteLength be viewRecord.[[CachedBufferByteLength]]. // 3. Assert: IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true if and only if bufferByteLength is detached. // 4. If bufferByteLength is detached, return true. // handled by the caller // 5. Let byteOffsetStart be view.[[ByteOffset]]. // 6. If view.[[ByteLength]] is auto, then // a. Let byteOffsetEnd be bufferByteLength. // 7. Else, // a. Let byteOffsetEnd be byteOffsetStart + view.[[ByteLength]]. let byte_offset_end = self .byte_length .map_or(buf_byte_len, |byte_length| byte_length + self.byte_offset); // 8. If byteOffsetStart > bufferByteLength or byteOffsetEnd > bufferByteLength, return true. // 9. NOTE: 0-length DataViews are not considered out-of-bounds. // 10. Return false. self.byte_offset > buf_byte_len || byte_offset_end > buf_byte_len } } impl IntrinsicObject for DataView { fn init(realm: &Realm) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let get_buffer = BuiltInBuilder::callable(realm, Self::get_buffer) .name(js_string!("get buffer")) .build(); let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) .name(js_string!("get byteLength")) .build(); let get_byte_offset = BuiltInBuilder::callable(realm, Self::get_byte_offset) .name(js_string!("get byteOffset")) .build(); let builder = BuiltInBuilder::from_standard_constructor::(realm) .accessor( js_string!("buffer"), Some(get_buffer), None, flag_attributes, ) .accessor( js_string!("byteLength"), Some(get_byte_length), None, flag_attributes, ) .accessor( js_string!("byteOffset"), Some(get_byte_offset), None, flag_attributes, ) .method(Self::get_big_int64, js_string!("getBigInt64"), 1) .method(Self::get_big_uint64, js_string!("getBigUint64"), 1) .method(Self::get_float32, js_string!("getFloat32"), 1) .method(Self::get_float64, js_string!("getFloat64"), 1) .method(Self::get_int8, js_string!("getInt8"), 1) .method(Self::get_int16, js_string!("getInt16"), 1) .method(Self::get_int32, js_string!("getInt32"), 1) .method(Self::get_uint8, js_string!("getUint8"), 1) .method(Self::get_uint16, js_string!("getUint16"), 1) .method(Self::get_uint32, js_string!("getUint32"), 1) .method(Self::set_big_int64, js_string!("setBigInt64"), 2) .method(Self::set_big_uint64, js_string!("setBigUint64"), 2) .method(Self::set_float32, js_string!("setFloat32"), 2) .method(Self::set_float64, js_string!("setFloat64"), 2) .method(Self::set_int8, js_string!("setInt8"), 2) .method(Self::set_int16, js_string!("setInt16"), 2) .method(Self::set_int32, js_string!("setInt32"), 2) .method(Self::set_uint8, js_string!("setUint8"), 2) .method(Self::set_uint16, js_string!("setUint16"), 2) .method(Self::set_uint32, js_string!("setUint32"), 2) .property( JsSymbol::to_string_tag(), Self::NAME, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); #[cfg(feature = "float16")] let builder = builder .method(Self::get_float16, js_string!("getFloat16"), 1) .method(Self::set_float16, js_string!("setFloat16"), 2); builder.build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for DataView { const NAME: JsString = StaticJsStrings::DATA_VIEW; } impl BuiltInConstructor for DataView { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 29; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::data_view; /// `DataView ( buffer [ , byteOffset [ , byteLength ] ] )` /// /// The `DataView` view provides a low-level interface for reading and writing multiple number /// types in a binary `ArrayBuffer`, without having to care about the platform's endianness. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview-buffer-byteoffset-bytelength /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/DataView fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { return Err(JsNativeError::typ() .with_message("cannot call `DataView` constructor without `new`") .into()); } let byte_len = args.get_or_undefined(2); // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). let buffer = args .get_or_undefined(0) .as_object() .and_then(|o| o.clone().into_buffer_object().ok()) .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?; // 3. Let offset be ? ToIndex(byteOffset). let offset = args.get_or_undefined(1).to_index(context)?; let (buf_byte_len, is_fixed_len) = { let buffer = buffer.as_buffer(); // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. let Some(slice) = buffer.bytes(Ordering::SeqCst) else { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); }; // 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst). let buf_len = slice.len() as u64; // 6. If offset > bufferByteLength, throw a RangeError exception. if offset > buf_len { return Err(JsNativeError::range() .with_message("Start offset is outside the bounds of the buffer") .into()); } // 7. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer). (buf_len, buffer.is_fixed_len()) }; // 8. If byteLength is undefined, then let view_byte_len = if byte_len.is_undefined() { // a. If bufferIsFixedLength is true, then // i. Let viewByteLength be bufferByteLength - offset. // b. Else, // i. Let viewByteLength be auto. is_fixed_len.then_some(buf_byte_len - offset) } else { // 9. Else, // a. Let viewByteLength be ? ToIndex(byteLength). let byte_len = byte_len.to_index(context)?; // b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. if offset + byte_len > buf_byte_len { return Err(JsNativeError::range() .with_message("Invalid data view length") .into()); } Some(byte_len) }; // 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", // « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::data_view, context)?; // 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 12. Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst). let Some(buf_byte_len) = buffer .as_buffer() .bytes(Ordering::SeqCst) .map(|s| s.len() as u64) else { return Err(JsNativeError::typ() .with_message("ArrayBuffer can't be detached") .into()); }; // 13. If offset > bufferByteLength, throw a RangeError exception. if offset > buf_byte_len { return Err(JsNativeError::range() .with_message("DataView offset outside of buffer array bounds") .into()); } // 14. If byteLength is not undefined, then // a. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. if !byte_len.is_undefined() && let Some(view_byte_len) = view_byte_len && offset + view_byte_len > buf_byte_len { return Err(JsNativeError::range() .with_message("DataView offset outside of buffer array bounds") .into()); } let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, Self { // 15. Set O.[[ViewedArrayBuffer]] to buffer. viewed_array_buffer: buffer, // 16. Set O.[[ByteLength]] to viewByteLength. byte_length: view_byte_len, // 17. Set O.[[ByteOffset]] to offset. byte_offset: offset, }, ); // 18. Return O. Ok(obj.into()) } } impl DataView { /// `get DataView.prototype.buffer` /// /// The buffer accessor property represents the `ArrayBuffer` or `SharedArrayBuffer` referenced /// by the `DataView` at construction time. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-get-dataview.prototype.buffer /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/buffer pub(crate) fn get_buffer( this: &JsValue, _args: &[JsValue], _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let object = this.as_object(); let view = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. let buffer = view.viewed_array_buffer.clone(); // 5. Return buffer. Ok(buffer.into()) } /// `get DataView.prototype.byteLength` /// /// The `byteLength` accessor property represents the length (in bytes) of the dataview. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-get-dataview.prototype.bytelength /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/byteLength pub(crate) fn get_byte_length( this: &JsValue, _args: &[JsValue], _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. let object = this.as_object(); let view = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst). // 5. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. let buffer = view.viewed_array_buffer.as_buffer(); let Some(slice) = buffer .bytes(Ordering::SeqCst) .filter(|s| !view.is_out_of_bounds(s.len())) else { return Err(JsNativeError::typ() .with_message("view out of bounds for its inner buffer") .into()); }; // 6. Let size be GetViewByteLength(viewRecord). let size = view.byte_length(slice.len()); // 7. Return 𝔽(size). Ok(size.into()) } /// `get DataView.prototype.byteOffset` /// /// The `byteOffset` accessor property represents the offset (in bytes) of this view from the /// start of its `ArrayBuffer` or `SharedArrayBuffer`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-get-dataview.prototype.byteoffset /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/byteOffset pub(crate) fn get_byte_offset( this: &JsValue, _args: &[JsValue], _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let object = this.as_object(); let view = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. let buffer = view.viewed_array_buffer.as_buffer(); // 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst). // 5. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. if buffer .bytes(Ordering::SeqCst) .filter(|b| !view.is_out_of_bounds(b.len())) .is_none() { return Err(JsNativeError::typ() .with_message("data view is outside the bounds of its inner buffer") .into()); } // 6. Let offset be O.[[ByteOffset]]. let offset = view.byte_offset; // 7. Return 𝔽(offset). Ok(offset.into()) } /// `GetViewValue ( view, requestIndex, isLittleEndian, type )` /// /// The abstract operation `GetViewValue` takes arguments view, requestIndex, `isLittleEndian`, /// and type. It is used by functions on `DataView` instances to retrieve values from the /// view's buffer. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-getviewvalue fn get_view_value( view: &JsValue, request_index: &JsValue, is_little_endian: &JsValue, context: &mut Context, ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. let object = view.as_object(); let view = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; // 4. Set isLittleEndian to ToBoolean(isLittleEndian). let is_little_endian = is_little_endian.to_boolean(); // 6. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered). // 7. NOTE: Bounds checking is not a synchronizing operation when view's backing buffer is a growable SharedArrayBuffer. // 8. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. let buffer = view.viewed_array_buffer.as_buffer(); let Some(data) = buffer .bytes(Ordering::Relaxed) .filter(|buf| !view.is_out_of_bounds(buf.len())) else { return Err(JsNativeError::typ() .with_message("view out of bounds for its inner buffer") .into()); }; // 5. Let viewOffset be view.[[ByteOffset]]. let view_offset = view.byte_offset; // 9. Let viewSize be GetViewByteLength(viewRecord). let view_size = view.byte_length(data.len()); // 10. Let elementSize be the Element Size value specified in Table 71 for Element Type type. let element_size = size_of::() as u64; // 11. If getIndex + elementSize > viewSize, throw a RangeError exception. if get_index + element_size > view_size { return Err(JsNativeError::range() .with_message("Offset is outside the bounds of the DataView") .into()); } // 12. Let bufferIndex be getIndex + viewOffset. let buffer_index = (get_index + view_offset) as usize; let src = data.subslice(buffer_index..); debug_assert!(src.len() >= size_of::()); // 13. Return GetValueFromBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, false, unordered, isLittleEndian). // SAFETY: All previous checks ensure the element fits in the buffer. let value: TypedArrayElement = unsafe { let mut value = T::zeroed(); memcpy( src.as_ptr(), BytesMutPtr::Bytes(bytes_of_mut(&mut value).as_mut_ptr()), size_of::(), ); if is_little_endian { value.to_little_endian() } else { value.to_big_endian() } .into() }; Ok(value.into()) } /// `DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] )` /// /// The `getBigInt64()` method gets a signed 64-bit integer (long long) at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getbigint64 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigInt64 pub(crate) fn get_big_int64( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] )` /// /// The `getBigUint64()` method gets an unsigned 64-bit integer (unsigned long long) at the /// specified byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getbiguint64 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64 pub(crate) fn get_big_uint64( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getFloat16 ( byteOffset [ , littleEndian ] )` /// /// The `getFloat16()` method gets a signed 16-bit float (float) at the specified byte offset /// from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getfloat16 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat16 #[cfg(feature = "float16")] pub(crate) fn get_float16( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getFloat32 ( byteOffset [ , littleEndian ] )` /// /// The `getFloat32()` method gets a signed 32-bit float (float) at the specified byte offset /// from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getfloat32 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat32 pub(crate) fn get_float32( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getFloat64 ( byteOffset [ , littleEndian ] )` /// /// The `getFloat64()` method gets a signed 64-bit float (double) at the specified byte offset /// from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getfloat64 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat64 pub(crate) fn get_float64( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getInt8 ( byteOffset [ , littleEndian ] )` /// /// The `getInt8()` method gets a signed 8-bit integer (byte) at the specified byte offset /// from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getint8 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt8 pub(crate) fn get_int8( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getInt16 ( byteOffset [ , littleEndian ] )` /// /// The `getInt16()` method gets a signed 16-bit integer (short) at the specified byte offset /// from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getint16 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt16 pub(crate) fn get_int16( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getInt32 ( byteOffset [ , littleEndian ] )` /// /// The `getInt32()` method gets a signed 32-bit integer (long) at the specified byte offset /// from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getint32 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32 pub(crate) fn get_int32( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getUint8 ( byteOffset [ , littleEndian ] )` /// /// The `getUint8()` method gets an unsigned 8-bit integer (unsigned byte) at the specified /// byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getuint8 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint8 pub(crate) fn get_uint8( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getUint16 ( byteOffset [ , littleEndian ] )` /// /// The `getUint16()` method gets an unsigned 16-bit integer (unsigned short) at the specified /// byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getuint16 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16 pub(crate) fn get_uint16( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `DataView.prototype.getUint32 ( byteOffset [ , littleEndian ] )` /// /// The `getUint32()` method gets an unsigned 32-bit integer (unsigned long) at the specified /// byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.getuint32 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32 pub(crate) fn get_uint32( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `SetViewValue ( view, requestIndex, isLittleEndian, type )` /// /// The abstract operation `SetViewValue` takes arguments view, requestIndex, `isLittleEndian`, /// type, and value. It is used by functions on `DataView` instances to store values into the /// view's buffer. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-setviewvalue fn set_view_value( view: &JsValue, request_index: &JsValue, is_little_endian: &JsValue, value: &JsValue, context: &mut Context, ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. let object = view.as_object(); let view = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; // 4. If IsBigIntElementType(type) is true, let numberValue be ? ToBigInt(value). // 5. Otherwise, let numberValue be ? ToNumber(value). let value = T::from_js_value(value, context)?; // 6. Set isLittleEndian to ToBoolean(isLittleEndian). let is_little_endian = is_little_endian.to_boolean(); // 8. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered). // 9. NOTE: Bounds checking is not a synchronizing operation when view's backing buffer is a growable SharedArrayBuffer. // 10. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. let mut buffer = view.viewed_array_buffer.as_buffer_mut(); let Some(mut data) = buffer .bytes(Ordering::Relaxed) .filter(|buf| !view.is_out_of_bounds(buf.len())) else { return Err(JsNativeError::typ() .with_message("view out of bounds for its inner buffer") .into()); }; // 11. Let viewSize be GetViewByteLength(viewRecord). let view_size = view.byte_length(data.len()); // 7. Let viewOffset be view.[[ByteOffset]]. let view_offset = view.byte_offset; // 12. Let elementSize be the Element Size value specified in Table 71 for Element Type type. let elem_size = size_of::(); // 13. If getIndex + elementSize > viewSize, throw a RangeError exception. if get_index + elem_size as u64 > view_size { return Err(JsNativeError::range() .with_message("Offset is outside the bounds of DataView") .into()); } // 14. Let bufferIndex be getIndex + viewOffset. let buffer_index = (get_index + view_offset) as usize; let mut target = data.subslice_mut(buffer_index..); debug_assert!(target.len() >= size_of::()); // 15. Perform SetValueInBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, numberValue, false, unordered, isLittleEndian). // SAFETY: All previous checks ensure the element fits in the buffer. unsafe { let value = if is_little_endian { value.to_little_endian() } else { value.to_big_endian() }; memcpy( BytesConstPtr::Bytes(bytes_of(&value).as_ptr()), target.as_ptr(), size_of::(), ); } // 16. Return undefined. Ok(JsValue::undefined()) } /// `DataView.prototype.setBigInt64 ( byteOffset, value [ , littleEndian ] )` /// /// The `setBigInt64()` method stores a signed 64-bit integer (long long) value at the /// specified byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setbigint64 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigInt64 pub(crate) fn set_big_int64( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setBigUint64 ( byteOffset, value [ , littleEndian ] )` /// /// The `setBigUint64()` method stores an unsigned 64-bit integer (unsigned long long) value at /// the specified byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setbiguint64 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigUint64 pub(crate) fn set_big_uint64( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setFloat16 ( byteOffset, value [ , littleEndian ] )` /// /// The `setFloat16()` method stores a signed 16-bit float (float) value at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setfloat16 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat16 #[cfg(feature = "float16")] pub(crate) fn set_float16( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float32, value). Self::set_view_value::( this, byte_offset, is_little_endian, value, context, ) } /// `DataView.prototype.setFloat32 ( byteOffset, value [ , littleEndian ] )` /// /// The `setFloat32()` method stores a signed 32-bit float (float) value at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setfloat32 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat32 pub(crate) fn set_float32( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float32, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setFloat64 ( byteOffset, value [ , littleEndian ] )` /// /// The `setFloat64()` method stores a signed 64-bit float (double) value at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setfloat64 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat64 pub(crate) fn set_float64( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float64, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setInt8 ( byteOffset, value [ , littleEndian ] )` /// /// The `setInt8()` method stores a signed 8-bit integer (byte) value at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setint8 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt8 pub(crate) fn set_int8( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int8, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setInt16 ( byteOffset, value [ , littleEndian ] )` /// /// The `setInt16()` method stores a signed 16-bit integer (short) value at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setint16 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16 pub(crate) fn set_int16( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int16, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setInt32 ( byteOffset, value [ , littleEndian ] )` /// /// The `setInt32()` method stores a signed 32-bit integer (long) value at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setint32 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt32 pub(crate) fn set_int32( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int32, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setUint8 ( byteOffset, value [ , littleEndian ] )` /// /// The `setUint8()` method stores an unsigned 8-bit integer (byte) value at the specified byte /// offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setuint8 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint8 pub(crate) fn set_uint8( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint8, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setUint16 ( byteOffset, value [ , littleEndian ] )` /// /// The `setUint16()` method stores an unsigned 16-bit integer (unsigned short) value at the /// specified byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setuint16 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16 pub(crate) fn set_uint16( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint16, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `DataView.prototype.setUint32 ( byteOffset, value [ , littleEndian ] )` /// /// The `setUint32()` method stores an unsigned 32-bit integer (unsigned long) value at the /// specified byte offset from the start of the `DataView`. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-dataview.prototype.setuint32 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32 pub(crate) fn set_uint32( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let byte_offset = args.get_or_undefined(0); let value = args.get_or_undefined(1); let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint32, value). Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } } ================================================ FILE: core/engine/src/builtins/date/mod.rs ================================================ //! Boa's implementation of ECMAScript's `Date` object. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-date-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date use crate::{ Context, JsArgs, JsData, JsResult, JsString, builtins::{ BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, date::utils::{ MS_PER_MINUTE, date_from_time, date_string, day, hour_from_time, local_time, make_date, make_day, make_full_year, make_time, min_from_time, month_from_time, ms_from_time, pad_five, pad_four, pad_six, pad_three, pad_two, parse_date, sec_from_time, time_clip, time_string, time_within_day, time_zone_string, to_date_string_t, utc_t, week_day, year_from_time, }, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, object::{JsObject, internal_methods::get_prototype_from_constructor}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::{JsValue, PreferredType}, }; use boa_gc::{Finalize, Trace}; use boa_macros::js_str; pub(crate) mod utils; #[cfg(test)] mod tests; /// The internal representation of a `Date` object. #[derive(Debug, Copy, Clone, Trace, Finalize, JsData)] #[boa_gc(empty_trace)] pub struct Date(f64); impl Date { /// Creates a new `Date`. pub(crate) const fn new(dt: f64) -> Self { Self(dt) } /// Creates a new `Date` from the current UTC time of the host. pub(crate) fn utc_now(context: &mut Context) -> Self { Self(context.clock().system_time_millis() as f64) } /// Formats this date as an ISO 8601 string for display purposes. /// /// Returns `None` if the date value is not finite (i.e. `Invalid Date`). pub(crate) fn to_iso_display(self) -> Option { let tv = self.0; if !tv.is_finite() { return None; } let year = year_from_time(tv); let year_str = if year.is_positive() && year >= 10000 { format!("+{year:06}") } else if year >= 0 { format!("{year:04}") } else { format!("-{:06}", year.unsigned_abs()) }; Some(format!( "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", year_str, month_from_time(tv) + 1, date_from_time(tv), hour_from_time(tv), min_from_time(tv), sec_from_time(tv), ms_from_time(tv), )) } } impl IntrinsicObject for Date { fn init(realm: &Realm) { let to_utc_string = BuiltInBuilder::callable(realm, Self::to_utc_string) .name(js_string!("toUTCString")) .length(0) .build(); let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive) .name(js_string!("[Symbol.toPrimitive]")) .length(1) .build(); let builder = BuiltInBuilder::from_standard_constructor::(realm) .static_method(Self::now, js_string!("now"), 0) .static_method(Self::parse, js_string!("parse"), 1) .static_method(Self::utc, js_string!("UTC"), 7) .method(Self::get_date::, js_string!("getDate"), 0) .method(Self::get_day::, js_string!("getDay"), 0) .method(Self::get_full_year::, js_string!("getFullYear"), 0) .method(Self::get_hours::, js_string!("getHours"), 0) .method( Self::get_milliseconds::, js_string!("getMilliseconds"), 0, ) .method(Self::get_minutes::, js_string!("getMinutes"), 0) .method(Self::get_month::, js_string!("getMonth"), 0) .method(Self::get_seconds::, js_string!("getSeconds"), 0) .method(Self::get_time, js_string!("getTime"), 0) .method( Self::get_timezone_offset, js_string!("getTimezoneOffset"), 0, ) .method(Self::get_date::, js_string!("getUTCDate"), 0) .method(Self::get_day::, js_string!("getUTCDay"), 0) .method( Self::get_full_year::, js_string!("getUTCFullYear"), 0, ) .method(Self::get_hours::, js_string!("getUTCHours"), 0) .method( Self::get_milliseconds::, js_string!("getUTCMilliseconds"), 0, ) .method(Self::get_minutes::, js_string!("getUTCMinutes"), 0) .method(Self::get_month::, js_string!("getUTCMonth"), 0) .method(Self::get_seconds::, js_string!("getUTCSeconds"), 0) .method(Self::get_year, js_string!("getYear"), 0) .method(Self::set_date::, js_string!("setDate"), 1) .method(Self::set_full_year::, js_string!("setFullYear"), 3) .method(Self::set_hours::, js_string!("setHours"), 4) .method( Self::set_milliseconds::, js_string!("setMilliseconds"), 1, ) .method(Self::set_minutes::, js_string!("setMinutes"), 3) .method(Self::set_month::, js_string!("setMonth"), 2) .method(Self::set_seconds::, js_string!("setSeconds"), 2) .method(Self::set_time, js_string!("setTime"), 1) .method(Self::set_date::, js_string!("setUTCDate"), 1) .method( Self::set_full_year::, js_string!("setUTCFullYear"), 3, ) .method(Self::set_hours::, js_string!("setUTCHours"), 4) .method( Self::set_milliseconds::, js_string!("setUTCMilliseconds"), 1, ) .method(Self::set_minutes::, js_string!("setUTCMinutes"), 3) .method(Self::set_month::, js_string!("setUTCMonth"), 2) .method(Self::set_seconds::, js_string!("setUTCSeconds"), 2) .method(Self::set_year, js_string!("setYear"), 1) .method(Self::to_date_string, js_string!("toDateString"), 0) .method(Self::to_iso_string, js_string!("toISOString"), 0) .method(Self::to_json, js_string!("toJSON"), 1) .method( Self::to_locale_date_string, js_string!("toLocaleDateString"), 0, ) .method(Self::to_locale_string, js_string!("toLocaleString"), 0) .method( Self::to_locale_time_string, js_string!("toLocaleTimeString"), 0, ) .method(Self::to_string, js_string!("toString"), 0) .method(Self::to_time_string, js_string!("toTimeString"), 0) .method(Self::value_of, js_string!("valueOf"), 0) .property( js_string!("toGMTString"), to_utc_string.clone(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( js_string!("toUTCString"), to_utc_string, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( JsSymbol::to_primitive(), to_primitive, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); #[cfg(feature = "temporal")] let builder = builder.method( Self::to_temporal_instant, js_string!("toTemporalInstant"), 0, ); builder.build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for Date { const NAME: JsString = StaticJsStrings::DATE; } impl BuiltInConstructor for Date { const CONSTRUCTOR_ARGUMENTS: usize = 7; const PROTOTYPE_STORAGE_SLOTS: usize = 48; const CONSTRUCTOR_STORAGE_SLOTS: usize = 3; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::date; /// [`Date ( ...values )`][spec] /// /// - When called as a function, returns a string displaying the current time in the UTC timezone. /// - When called as a constructor, it returns a new `Date` object from the provided arguments. /// The [MDN documentation][mdn] has a more extensive explanation on the usages and return /// values for all possible arguments. /// /// [spec]: https://tc39.es/ecma262/#sec-date-constructor /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, then if new_target.is_undefined() { // a. Let now be the time value (UTC) identifying the current time. let now = context.clock().system_time_millis(); // b. Return ToDateString(now). return Ok(JsValue::from(to_date_string_t( now as f64, context.host_hooks().as_ref(), ))); } // 2. Let numberOfArgs be the number of elements in values. let dv = match args { // 3. If numberOfArgs = 0, then [] => { // a. Let dv be the time value (UTC) identifying the current time. Self::utc_now(context) } // 4. Else if numberOfArgs = 1, then // a. Let value be values[0]. [value] => { // b. If value is an Object and value has a [[DateValue]] internal slot, then let object = value.as_object(); let tv = if let Some(date) = object.as_ref().and_then(JsObject::downcast_ref::) { // i. Let tv be value.[[DateValue]]. date.0 } // c. Else, else { // i. Let v be ? ToPrimitive(value). let v = value.to_primitive(context, PreferredType::Default)?; // ii. If v is a String, then if let Some(v) = v.as_string() { // 1. Assert: The next step never returns an abrupt completion because v is a String. // 2. Let tv be the result of parsing v as a date, in exactly the same manner as for the parse method (21.4.3.2). let tv = parse_date(&v, context.host_hooks().as_ref()); if let Some(tv) = tv { tv as f64 } else { f64::NAN } } // iii. Else, else { // 1. Let tv be ? ToNumber(v). v.to_number(context)? } }; // d. Let dv be TimeClip(tv). Self(time_clip(tv)) } // 5. Else, _ => { // Separating this into its own function to simplify the logic. //let dt = Self::construct_date(args, context)? // .and_then(|dt| context.host_hooks().local_from_naive_local(dt).earliest()); //Self(dt.map(|dt| dt.timestamp_millis())) // a. Assert: numberOfArgs ≥ 2. // b. Let y be ? ToNumber(values[0]). let y = args.get_or_undefined(0).to_number(context)?; // c. Let m be ? ToNumber(values[1]). let m = args.get_or_undefined(1).to_number(context)?; // d. If numberOfArgs > 2, let dt be ? ToNumber(values[2]); else let dt be 1𝔽. let dt = args.get(2).map_or(Ok(1.0), |n| n.to_number(context))?; // e. If numberOfArgs > 3, let h be ? ToNumber(values[3]); else let h be +0𝔽. let h = args.get(3).map_or(Ok(0.0), |n| n.to_number(context))?; // f. If numberOfArgs > 4, let min be ? ToNumber(values[4]); else let min be +0𝔽. let min = args.get(4).map_or(Ok(0.0), |n| n.to_number(context))?; // g. If numberOfArgs > 5, let s be ? ToNumber(values[5]); else let s be +0𝔽. let s = args.get(5).map_or(Ok(0.0), |n| n.to_number(context))?; // h. If numberOfArgs > 6, let milli be ? ToNumber(values[6]); else let milli be +0𝔽. let milli = args.get(6).map_or(Ok(0.0), |n| n.to_number(context))?; // i. Let yr be MakeFullYear(y). let yr = make_full_year(y); // j. Let finalDate be MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)). let final_date = make_date(make_day(yr, m, dt), make_time(h, min, s, milli)); // k. Let dv be TimeClip(UTC(finalDate)). Self(time_clip(utc_t(final_date, context.host_hooks().as_ref()))) } }; // 6. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%Date.prototype%", « [[DateValue]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::date, context)?; // 7. Set O.[[DateValue]] to dv. let obj = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, dv); // 8. Return O. Ok(obj.into()) } } impl Date { /// `Date.now()` /// /// The static `Date.now()` method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.now /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now #[allow(clippy::unnecessary_wraps)] pub(crate) fn now(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { Ok(JsValue::new(context.clock().system_time_millis())) } /// `Date.parse()` /// /// The `Date.parse()` method parses a string representation of a date, and returns the number of milliseconds since /// January 1, 1970, 00:00:00 UTC or `NaN` if the string is unrecognized or, in some cases, contains illegal date /// values. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.parse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse pub(crate) fn parse(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let date = args.get_or_undefined(0).to_string(context)?; Ok(parse_date(&date, context.host_hooks().as_ref()) .map_or(JsValue::from(f64::NAN), JsValue::from)) } /// `Date.UTC()` /// /// The `Date.UTC()` method accepts parameters similar to the `Date` constructor, but treats them as UTC. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.utc /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC pub(crate) fn utc(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let y be ? ToNumber(year). let y = args.get_or_undefined(0).to_number(context)?; // 2. If month is present, let m be ? ToNumber(month); else let m be +0𝔽. let m = args .get(1) .map_or(Ok(0f64), |value| value.to_number(context))?; // 3. If date is present, let dt be ? ToNumber(date); else let dt be 1𝔽. let dt = args .get(2) .map_or(Ok(1f64), |value| value.to_number(context))?; // 4. If hours is present, let h be ? ToNumber(hours); else let h be +0𝔽. let h = args .get(3) .map_or(Ok(0f64), |value| value.to_number(context))?; // 5. If minutes is present, let min be ? ToNumber(minutes); else let min be +0𝔽. let min = args .get(4) .map_or(Ok(0f64), |value| value.to_number(context))?; // 6. If seconds is present, let s be ? ToNumber(seconds); else let s be +0𝔽. let s = args .get(5) .map_or(Ok(0f64), |value| value.to_number(context))?; // 7. If ms is present, let milli be ? ToNumber(ms); else let milli be +0𝔽. let milli = args .get(6) .map_or(Ok(0f64), |value| value.to_number(context))?; // 8. Let yr be MakeFullYear(y). let yr = make_full_year(y); // 9. Return TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli))). Ok(JsValue::from(time_clip(make_date( make_day(yr, m, dt), make_time(h, min, s, milli), )))) } /// [`Date.prototype.getDate ( )`][local] and /// [`Date.prototype.getUTCDate ( )`][utc]. /// /// The `getDate()` method returns the day of the month for the specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.getdate /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutcdate pub(crate) fn get_date( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::new(f64::NAN)); } if LOCAL { // 5. Return DateFromTime(LocalTime(t)). Ok(JsValue::from(date_from_time(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return DateFromTime(t). Ok(JsValue::from(date_from_time(t))) } } /// [`Date.prototype.getDay ( )`][local] and /// [`Date.prototype.getUTCDay ( )`][utc]. /// /// The `getDay()` method returns the day of the week for the specified date, where 0 represents /// Sunday. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.getday /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutcday pub(crate) fn get_day( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 5. Return WeekDay(LocalTime(t)). Ok(JsValue::from(week_day(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return WeekDay(t). Ok(JsValue::from(week_day(t))) } } /// [`Date.prototype.getYear()`][spec]. /// /// The `getYear()` method returns the year in the specified date according to local time. /// Because `getYear()` does not return full years ("year 2000 problem"), it is no longer used /// and has been replaced by the `getFullYear()` method. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.getyear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getYear pub(crate) fn get_year( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } // 5. Return YearFromTime(LocalTime(t)) - 1900𝔽. Ok(JsValue::from( year_from_time(local_time(t, context.host_hooks().as_ref())) - 1900, )) } /// [`Date.prototype.getFullYear ( )`][local] and /// [`Date.prototype.getUTCFullYear ( )`][utc]. /// /// The `getFullYear()` method returns the year of the specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.getfullyear /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutcfullyear pub(crate) fn get_full_year( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 5. Return YearFromTime(LocalTime(t)). Ok(JsValue::from(year_from_time(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return YearFromTime(t). Ok(JsValue::from(year_from_time(t))) } } /// [`Date.prototype.getHours ( )`][local] and /// [`Date.prototype.getUTCHours ( )`][utc]. /// /// The `getHours()` method returns the hour for the specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.gethours /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutchours pub(crate) fn get_hours( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 5. Return HourFromTime(LocalTime(t)). Ok(JsValue::from(hour_from_time(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return HourFromTime(t). Ok(JsValue::from(hour_from_time(t))) } } /// [`Date.prototype.getMilliseconds ( )`][local] and /// [`Date.prototype.getUTCMilliseconds ( )`][utc]. /// /// The `getMilliseconds()` method returns the milliseconds in the specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.getmilliseconds /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutcmilliseconds pub(crate) fn get_milliseconds( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 5. Return msFromTime(LocalTime(t)). Ok(JsValue::from(ms_from_time(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return msFromTime(t). Ok(JsValue::from(ms_from_time(t))) } } /// [`Date.prototype.getMinutes ( )`][local] and /// [`Date.prototype.getUTCMinutes ( )`][utc]. /// /// The `getMinutes()` method returns the minutes in the specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.getminutes /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutcminutes pub(crate) fn get_minutes( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 5. Return MinFromTime(LocalTime(t)). Ok(JsValue::from(min_from_time(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return MinFromTime(t). Ok(JsValue::from(min_from_time(t))) } } /// [`Date.prototype.getMonth ( )`][local] and /// [`Date.prototype.getUTCMonth ( )`][utc]. /// /// The `getMonth()` method returns the month in the specified date, as a zero-based value /// (where zero indicates the first month of the year). /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.getmonth /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutcmonth pub(crate) fn get_month( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 5. Return MonthFromTime(LocalTime(t)). Ok(JsValue::from(month_from_time(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return MonthFromTime(t). Ok(JsValue::from(month_from_time(t))) } } /// [`Date.prototype.getSeconds ( )`][local] and /// [`Date.prototype.getUTCSeconds ( )`][utc]. /// /// The `getSeconds()` method returns the seconds in the specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.getseconds /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.getutcseconds pub(crate) fn get_seconds( this: &JsValue, _args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 5. Return SecFromTime(LocalTime(t)). Ok(JsValue::from(sec_from_time(local_time( t, context.host_hooks().as_ref(), )))) } else { // 5. Return SecFromTime(t). Ok(JsValue::from(sec_from_time(t))) } } /// `Date.prototype.getTime()`. /// /// The `getTime()` method returns the number of milliseconds since the Unix Epoch. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.gettime /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime pub(crate) fn get_time( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Return dateObject.[[DateValue]]. Ok(this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0 .into()) } /// `Date.prototype.getTimeZoneOffset()`. /// /// The `getTimezoneOffset()` method returns the time zone difference, in minutes, from current locale (host system /// settings) to UTC. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.gettimezoneoffset /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset pub(crate) fn get_timezone_offset( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } // 5. Return (t - LocalTime(t)) / msPerMinute. Ok(JsValue::from( (t - local_time(t, context.host_hooks().as_ref())) / MS_PER_MINUTE, )) } /// [`Date.prototype.setDate ( date )`][local] and /// [`Date.prototype.setUTCDate ( date )`][utc]. /// /// The `setDate()` method sets the day of the `Date` object relative to the beginning of the /// currently set month. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.setdate /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.setutcdate pub(crate) fn set_date( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let mut t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Let dt be ? ToNumber(date). let dt = args.get_or_undefined(0).to_number(context)?; // 5. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 6. Set t to LocalTime(t). t = local_time(t, context.host_hooks().as_ref()); } // 7. Let newDate be MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)). let new_date = make_date( make_day(year_from_time(t).into(), month_from_time(t).into(), dt), time_within_day(t), ); let u = if LOCAL { // 8. Let u be TimeClip(UTC(newDate)). time_clip(utc_t(new_date, context.host_hooks().as_ref())) } else { // 8. Let v be TimeClip(newDate). time_clip(new_date) }; let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 9. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 10. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setFullYear ( year [ , month [ , date ] ] )`][local] and /// [Date.prototype.setUTCFullYear ( year [ , month [ , date ] ] )][utc]. /// /// The `setFullYear()` method sets the full year for a specified date and returns the new /// timestamp. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.setfullyear /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.setutcfullyear pub(crate) fn set_full_year( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); let t = if LOCAL { // 5. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). if t.is_nan() { 0.0 } else { local_time(t, context.host_hooks().as_ref()) } } else { // 4. If t is NaN, set t to +0𝔽. if t.is_nan() { 0.0 } else { t } }; // 4. Let y be ? ToNumber(year). let y = args.get_or_undefined(0).to_number(context)?; // 6. If month is not present, let m be MonthFromTime(t); otherwise, let m be ? ToNumber(month). let m = if let Some(month) = args.get(1) { month.to_number(context)? } else { month_from_time(t).into() }; // 7. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date). let dt = if let Some(date) = args.get(2) { date.to_number(context)? } else { date_from_time(t).into() }; // 8. Let newDate be MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)). let new_date = make_date(make_day(y, m, dt), time_within_day(t)); let u = if LOCAL { // 9. Let u be TimeClip(UTC(newDate)). time_clip(utc_t(new_date, context.host_hooks().as_ref())) } else { // 9. Let u be TimeClip(newDate). time_clip(new_date) }; let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 10. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 11. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setHours ( hour [ , min [ , sec [ , ms ] ] ] )`][local] and /// [`Date.prototype.setUTCHours ( hour [ , min [ , sec [ , ms ] ] ] )`][utc]. /// /// The `setHours()` method sets the hours for a specified date, and returns the number /// of milliseconds since January 1, 1970 00:00:00 UTC until the time represented by the /// updated `Date` instance. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.sethours /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.setutchours #[allow(clippy::many_single_char_names)] pub(crate) fn set_hours( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let mut t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Let h be ? ToNumber(hour). let h = args.get_or_undefined(0).to_number(context)?; // 5. If min is present, let m be ? ToNumber(min). let m = args.get(1).map(|v| v.to_number(context)).transpose()?; // 6. If sec is present, let s be ? ToNumber(sec). let s = args.get(2).map(|v| v.to_number(context)).transpose()?; // 7. If ms is present, let milli be ? ToNumber(ms). let milli = args.get(3).map(|v| v.to_number(context)).transpose()?; // 8. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 9. Set t to LocalTime(t). t = local_time(t, context.host_hooks().as_ref()); } // 10. If min is not present, let m be MinFromTime(t). let m: f64 = m.unwrap_or_else(|| min_from_time(t).into()); // 11. If sec is not present, let s be SecFromTime(t). let s = s.unwrap_or_else(|| sec_from_time(t).into()); // 12. If ms is not present, let milli be msFromTime(t). let milli = milli.unwrap_or_else(|| ms_from_time(t).into()); // 13. Let date be MakeDate(Day(t), MakeTime(h, m, s, milli)). let date = make_date(day(t), make_time(h, m, s, milli)); let u = if LOCAL { // 14. Let u be TimeClip(UTC(date)). time_clip(utc_t(date, context.host_hooks().as_ref())) } else { // 14. Let u be TimeClip(date). time_clip(date) }; let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 15. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 16. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setMilliseconds ( ms )`[local] and /// [`Date.prototype.setUTCMilliseconds ( ms )`][utc]. /// /// The `setMilliseconds()` method sets the milliseconds for a specified date according to local time. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.setmilliseconds /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.setutcmilliseconds pub(crate) fn set_milliseconds( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let mut t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Set ms to ? ToNumber(ms). let ms = args.get_or_undefined(0).to_number(context)?; // 5. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 6. Set t to LocalTime(t). t = local_time(t, context.host_hooks().as_ref()); } // 7. Let time be MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms). let time = make_time( hour_from_time(t).into(), min_from_time(t).into(), sec_from_time(t).into(), ms, ); let u = if LOCAL { // 8. Let u be TimeClip(UTC(MakeDate(Day(t), time))). time_clip(utc_t( make_date(day(t), time), context.host_hooks().as_ref(), )) } else { // 8. Let u be TimeClip(MakeDate(Day(t), time)). time_clip(make_date(day(t), time)) }; let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 9. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 10. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setMinutes ( min [ , sec [ , ms ] ] )`][local] and /// [`Date.prototype.setUTCMinutes ( min [ , sec [ , ms ] ] )`][utc]. /// /// The `setMinutes()` method sets the minutes for a specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.setminutes /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.setutcminutes pub(crate) fn set_minutes( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let mut t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Let m be ? ToNumber(min). let m = args.get_or_undefined(0).to_number(context)?; // 5. If sec is present, let s be ? ToNumber(sec). let s = args.get(1).map(|v| v.to_number(context)).transpose()?; // 6. If ms is present, let milli be ? ToNumber(ms). let milli = args.get(2).map(|v| v.to_number(context)).transpose()?; // 7. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } if LOCAL { // 8. Set t to LocalTime(t). t = local_time(t, context.host_hooks().as_ref()); } // 9. If sec is not present, let s be SecFromTime(t). let s = s.unwrap_or_else(|| sec_from_time(t).into()); // 10. If ms is not present, let milli be msFromTime(t). let milli = milli.unwrap_or_else(|| ms_from_time(t).into()); // 11. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)). let date = make_date(day(t), make_time(hour_from_time(t).into(), m, s, milli)); let u = if LOCAL { // 12. Let u be TimeClip(UTC(date)). time_clip(utc_t(date, context.host_hooks().as_ref())) } else { // 12. Let u be TimeClip(date). time_clip(date) }; let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 13. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 14. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setMonth ( month [ , date ] )`][local] and /// [`Date.prototype.setUTCMonth ( month [ , date ] )`][utc]. /// /// The `setMonth()` method sets the month for a specified date according to the currently set /// year. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.setmonth /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.setutcmonth pub(crate) fn set_month( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let mut t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Let m be ? ToNumber(month). let m = args.get_or_undefined(0).to_number(context)?; // 5. If date is present, let dt be ? ToNumber(date). let dt = args.get(1).map(|v| v.to_number(context)).transpose()?; // 6. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } // 7. Set t to LocalTime(t). if LOCAL { t = local_time(t, context.host_hooks().as_ref()); } // 8. If date is not present, let dt be DateFromTime(t). let dt = dt.unwrap_or_else(|| date_from_time(t).into()); // 9. Let newDate be MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)). let new_date = make_date( make_day(year_from_time(t).into(), m, dt), time_within_day(t), ); let u = if LOCAL { // 10. Let u be TimeClip(UTC(newDate)). time_clip(utc_t(new_date, context.host_hooks().as_ref())) } else { // 10. Let u be TimeClip(newDate). time_clip(new_date) }; let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 11. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 12. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setSeconds ( sec [ , ms ] )`[local] and /// [`Date.prototype.setUTCSeconds ( sec [ , ms ] )`][utc]. /// /// The `setSeconds()` method sets the seconds for a specified date. /// /// [local]: https://tc39.es/ecma262/#sec-date.prototype.setseconds /// [utc]: https://tc39.es/ecma262/#sec-date.prototype.setutcseconds pub(crate) fn set_seconds( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let mut t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Let s be ? ToNumber(sec). let s = args.get_or_undefined(0).to_number(context)?; // 5. If ms is present, let milli be ? ToNumber(ms). let milli = args.get(1).map(|v| v.to_number(context)).transpose()?; // 6. If t is NaN, return NaN. if t.is_nan() { return Ok(JsValue::from(f64::NAN)); } // 7. Set t to LocalTime(t). if LOCAL { t = local_time(t, context.host_hooks().as_ref()); } // 8. If ms is not present, let milli be msFromTime(t). let milli = milli.unwrap_or_else(|| ms_from_time(t).into()); // 9. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)). let date = make_date( day(t), make_time(hour_from_time(t).into(), min_from_time(t).into(), s, milli), ); let u = if LOCAL { // 10. Let u be TimeClip(UTC(date)). time_clip(utc_t(date, context.host_hooks().as_ref())) } else { // 10. Let u be TimeClip(date). time_clip(date) }; let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 11. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 12. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setYear()`][spec]. /// /// The `setYear()` method sets the year for a specified date according to local time. /// /// # Note /// /// The [`Self::set_full_year`] method is preferred for nearly all purposes, because it avoids /// the “year 2000 problem.” /// /// More information: /// - [MDN documentation][mdn] /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setYear /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.setyear pub(crate) fn set_year( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be dateObject.[[DateValue]]. let t = date.0; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Let y be ? ToNumber(year). let y = args.get_or_undefined(0).to_number(context)?; // 5. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). let t = if t.is_nan() { 0.0 } else { local_time(t, context.host_hooks().as_ref()) }; // 6. Let yyyy be MakeFullYear(y). let yyyy = make_full_year(y); // 7. Let d be MakeDay(yyyy, MonthFromTime(t), DateFromTime(t)). let d = make_day(yyyy, month_from_time(t).into(), date_from_time(t).into()); // 8. Let date be MakeDate(d, TimeWithinDay(t)). let date = make_date(d, time_within_day(t)); // 9. Let u be TimeClip(UTC(date)). let u = time_clip(utc_t(date, context.host_hooks().as_ref())); let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 10. Set dateObject.[[DateValue]] to u. date_mut.0 = u; // 11. Return u. Ok(JsValue::from(u)) } /// [`Date.prototype.setTime()`][spec]. /// /// The `setTime()` method sets the Date object to the time represented by a number of milliseconds /// since January 1, 1970, 00:00:00 UTC. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.settime /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setTime pub(crate) fn set_time( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). let object = this.as_object(); let date = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 3. Let t be ? ToNumber(time). let t = args.get_or_undefined(0).to_number(context)?; // NOTE (nekevss): `downcast_ref` is used and then dropped for a short lived borrow. // ToNumber() may call userland code which can modify the underlying date // which will cause a panic. In order to avoid this, we drop the borrow, // here and only `downcast_mut` when date will be modified. drop(date); // 4. Let v be TimeClip(t). let v = time_clip(t); let object = this.as_object(); let mut date_mut = object .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; // 5. Set dateObject.[[DateValue]] to v. date_mut.0 = v; // 6. Return v. Ok(JsValue::from(v)) } /// [`Date.prototype.toDateString()`][spec]. /// /// The `toDateString()` method returns the date portion of a Date object in English. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.todatestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toDateString pub(crate) fn to_date_string( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let tv be dateObject.[[DateValue]]. let tv = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If tv is NaN, return "Invalid Date". if tv.is_nan() { return Ok(js_string!("Invalid Date").into()); } // 5. Let t be LocalTime(tv). let t = local_time(tv, context.host_hooks().as_ref()); // 6. Return DateString(t). Ok(JsValue::from(date_string(t))) } /// [`Date.prototype.toISOString()`][spec]. /// /// The `toISOString()` method returns a string in simplified extended ISO format /// ([ISO 8601][iso8601]). /// /// More information: /// - [MDN documentation][mdn] /// /// [iso8601]: http://en.wikipedia.org/wiki/ISO_8601 /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toisostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString pub(crate) fn to_iso_string( this: &JsValue, _: &[JsValue], _: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let tv be dateObject.[[DateValue]]. let tv = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If tv is not finite, throw a RangeError exception. if !tv.is_finite() { return Err(JsNativeError::range() .with_message("Invalid time value") .into()); } // 5. If tv corresponds with a year that cannot be represented in the Date Time String Format, throw a RangeError exception. // 6. Return a String representation of tv in the Date Time String Format on the UTC time scale, // including all format elements and the UTC offset representation "Z". let year = year_from_time(tv); let year = if year >= 10000 { js_string!(js_str!("+"), pad_six(year.unsigned_abs(), &mut [0; 6])) } else if year >= 0 { pad_four(year.unsigned_abs(), &mut [0; 4]).into() } else { js_string!(js_str!("-"), pad_six(year.unsigned_abs(), &mut [0; 6])) }; let mut binding = [0; 2]; let month = pad_two(month_from_time(tv) + 1, &mut binding); let mut binding = [0; 2]; let day = pad_two(date_from_time(tv), &mut binding); let mut binding = [0; 2]; let hour = pad_two(hour_from_time(tv), &mut binding); let mut binding = [0; 2]; let minute = pad_two(min_from_time(tv), &mut binding); let mut binding = [0; 2]; let second = pad_two(sec_from_time(tv), &mut binding); let mut binding = [0; 3]; let millisecond = pad_three(ms_from_time(tv), &mut binding); Ok(JsValue::from(js_string!( &year, js_str!("-"), month, js_str!("-"), day, js_str!("T"), hour, js_str!(":"), minute, js_str!(":"), second, js_str!("."), millisecond, js_str!("Z") ))) } /// [`Date.prototype.toJSON()`][spec]. /// /// The `toJSON()` method returns a string representation of the `Date` object. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.tojson /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON pub(crate) fn to_json( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; // 2. Let tv be ? ToPrimitive(O, number). let tv = this.to_primitive(context, PreferredType::Number)?; // 3. If Type(tv) is Number and tv is not finite, return null. if tv.as_number().is_some_and(|x| !f64::is_finite(x)) { return Ok(JsValue::null()); } // 4. Return ? Invoke(O, "toISOString"). let func = o.get(js_string!("toISOString"), context)?; func.call(this, &[], context) } /// [`Date.prototype.toLocaleDateString()`][spec]. /// /// The `toLocaleDateString()` method returns the date portion of the given Date instance according /// to language-specific conventions. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sup-date.prototype.tolocaledatestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString #[allow( unused_variables, reason = "`args` and `context` are used when the `intl` feature is enabled" )] pub(crate) fn to_locale_date_string( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { #[cfg(feature = "intl")] { use crate::builtins::intl::date_time_format::{ FormatDefaults, FormatType, format_date_time_locale, }; // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let x be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If x is NaN, return "Invalid Date". if t.is_nan() { return Ok(JsValue::new(js_string!("Invalid Date"))); } // 5. Let dateFormat be ? CreateDateTimeFormat(%Intl.DateTimeFormat%, locales, options, date, date). // 6. Return ! FormatDateTime(dateFormat, x). let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); format_date_time_locale( locales, options, FormatType::Date, FormatDefaults::Date, t, context, ) } #[cfg(not(feature = "intl"))] { Self::to_string(this, &[], context) } } /// [`Date.prototype.toLocaleString()`][spec]. /// /// The `toLocaleString()` method returns a string representing the specified Date object. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sup-date.prototype.tolocalestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString #[allow( unused_variables, reason = "`args` and `context` are used when the `intl` feature is enabled" )] pub(crate) fn to_locale_string( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { #[cfg(feature = "intl")] { use crate::builtins::intl::date_time_format::{ FormatDefaults, FormatType, format_date_time_locale, }; // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let x be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If x is NaN, return "Invalid Date". if t.is_nan() { return Ok(JsValue::new(js_string!("Invalid Date"))); } // 5. Let dateFormat be ? CreateDateTimeFormat(%Intl.DateTimeFormat%, locales, options, any, all). // 6. Return ! FormatDateTime(dateFormat, x). let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); format_date_time_locale( locales, options, FormatType::Any, FormatDefaults::All, t, context, ) } #[cfg(not(feature = "intl"))] { Self::to_string(this, &[], context) } } /// [`Date.prototype.toLocaleTimeString()`][spec]. /// /// The `toLocaleTimeString()` method returns the time portion of a Date object in human readable /// form in American English. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sup-date.prototype.tolocaletimestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString #[allow( unused_variables, reason = "`args` and `context` are used when the `intl` feature is enabled" )] pub(crate) fn to_locale_time_string( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { #[cfg(feature = "intl")] { use crate::builtins::intl::date_time_format::{ FormatDefaults, FormatType, format_date_time_locale, }; // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let x be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If x is NaN, return "Invalid Date". if t.is_nan() { return Ok(JsValue::new(js_string!("Invalid Date"))); } // 5. Let timeFormat be ? CreateDateTimeFormat(%Intl.DateTimeFormat%, locales, options, time, time). // 6. Return ! FormatDateTime(timeFormat, x). let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); format_date_time_locale( locales, options, FormatType::Time, FormatDefaults::Time, t, context, ) } #[cfg(not(feature = "intl"))] { Self::to_string(this, &[], context) } } /// [`Date.prototype.toString()`][spec]. /// /// The `toString()` method returns a string representing the specified Date object. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString pub(crate) fn to_string( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let tv be dateObject.[[DateValue]]. let tv = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. Return ToDateString(tv). Ok(JsValue::from(to_date_string_t( tv, context.host_hooks().as_ref(), ))) } /// [`Date.prototype.toTimeString()`][spec]. /// /// The `toTimeString()` method returns the time portion of a Date object in human readable form /// in American English. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.totimestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toTimeString pub(crate) fn to_time_string( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let tv be dateObject.[[DateValue]]. let tv = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If tv is NaN, return "Invalid Date". if tv.is_nan() { return Ok(js_string!("Invalid Date").into()); } // 5. Let t be LocalTime(tv). let t = local_time(tv, context.host_hooks().as_ref()); // 6. Return the string-concatenation of TimeString(t) and TimeZoneString(tv). Ok(JsValue::from(js_string!( &time_string(t), &time_zone_string(t, context.host_hooks().as_ref()) ))) } /// [`Date.prototype.toUTCString()`][spec]. /// /// The `toUTCString()` method returns a string representing the specified Date object. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toutcstring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString pub(crate) fn to_utc_string( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let tv be dateObject.[[DateValue]]. let tv = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0; // 4. If tv is NaN, return "Invalid Date". if tv.is_nan() { return Ok(js_string!("Invalid Date").into()); } // 5. Let weekday be the Name of the entry in Table 63 with the Number WeekDay(tv). let weekday = match week_day(tv) { 0 => js_str!("Sun"), 1 => js_str!("Mon"), 2 => js_str!("Tue"), 3 => js_str!("Wed"), 4 => js_str!("Thu"), 5 => js_str!("Fri"), 6 => js_str!("Sat"), _ => unreachable!(), }; // 6. Let month be the Name of the entry in Table 64 with the Number MonthFromTime(tv). let month = match month_from_time(tv) { 0 => js_str!("Jan"), 1 => js_str!("Feb"), 2 => js_str!("Mar"), 3 => js_str!("Apr"), 4 => js_str!("May"), 5 => js_str!("Jun"), 6 => js_str!("Jul"), 7 => js_str!("Aug"), 8 => js_str!("Sep"), 9 => js_str!("Oct"), 10 => js_str!("Nov"), 11 => js_str!("Dec"), _ => unreachable!(), }; // 7. Let day be ToZeroPaddedDecimalString(ℝ(DateFromTime(tv)), 2). let mut binding = [0; 2]; let day = pad_two(date_from_time(tv), &mut binding); // 8. Let yv be YearFromTime(tv). let yv = year_from_time(tv); // 9. If yv is +0𝔽 or yv > +0𝔽, let yearSign be the empty String; otherwise, let yearSign be "-". let year_sign = if yv >= 0 { js_str!("") } else { js_str!("-") }; // 10. Let paddedYear be ToZeroPaddedDecimalString(abs(ℝ(yv)), 4). let yv = yv.unsigned_abs(); let padded_year: JsString = if yv >= 100_000 { pad_six(yv, &mut [0; 6]).into() } else if yv >= 10000 { pad_five(yv, &mut [0; 5]).into() } else { pad_four(yv, &mut [0; 4]).into() }; // 11. Return the string-concatenation of // weekday, // ",", // the code unit 0x0020 (SPACE), // day, // the code unit 0x0020 (SPACE), // month, // the code unit 0x0020 (SPACE), // yearSign, // paddedYear, // the code unit 0x0020 (SPACE), // and TimeString(tv). Ok(JsValue::from(js_string!( weekday, js_str!(","), js_str!(" "), day, js_str!(" "), month, js_str!(" "), year_sign, &padded_year, js_str!(" "), &time_string(tv) ))) } /// [`Date.prototype.valueOf()`][spec]. /// /// The `valueOf()` method returns the primitive value of a `Date` object. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf pub(crate) fn value_of( this: &JsValue, _args: &[JsValue], _context: &mut Context, ) -> JsResult { // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Return dateObject.[[DateValue]]. Ok(this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0 .into()) } /// [`Date.prototype [ @@toPrimitive ] ( hint )`][spec]. /// /// The \[@@toPrimitive\]() method converts a Date object to a primitive value. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-date.prototype-@@toprimitive /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/@@toPrimitive pub(crate) fn to_primitive( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. If Type(O) is not Object, throw a TypeError exception. let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Date.prototype[@@toPrimitive] called on non object") })?; let hint = args.get_or_undefined(0); let try_first = match hint.as_string() { // 3. If hint is "string" or "default", then // a. Let tryFirst be string. Some(string) if string == "string" || string == "default" => PreferredType::String, // 4. Else if hint is "number", then // a. Let tryFirst be number. Some(number) if number == "number" => PreferredType::Number, // 5. Else, throw a TypeError exception. _ => { return Err(JsNativeError::typ() .with_message("Date.prototype[@@toPrimitive] called with invalid hint") .into()); } }; // 6. Return ? OrdinaryToPrimitive(O, tryFirst). o.ordinary_to_primitive(context, try_first) } /// 14.9.1 `Date.prototype.toTemporalInstant ()` /// /// Returns a JavaScript Date as a `Temporal.Instant`. /// /// More information: /// - [Temporal Proposal][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/proposal-temporal/#sec-date.prototype.totemporalinstant /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toTemporalInstant/ #[cfg(feature = "temporal")] pub(crate) fn to_temporal_instant( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { use crate::{builtins::temporal::create_temporal_instant, js_error}; use num_traits::FromPrimitive; use temporal_rs::Instant; const NS_PER_MILLISECOND: i128 = 1_000_000; // 1. Let dateObject be the this value. // 2. Perform ? RequireInternalSlot(dateObject, [[DateValue]]). // 3. Let t be dateObject.[[DateValue]]. let t = this .as_object() .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| js_error!(TypeError: "'this' is not a Date"))? .0; // 4. Let ns be ? NumberToBigInt(t) × ℤ(10**6). let ns = i128::from_f64(t) .ok_or(js_error!(RangeError: "[[DateValue]] is not an integral number."))? * NS_PER_MILLISECOND; // 5. Return ! CreateTemporalInstant(ns). create_temporal_instant(Instant::try_new(ns)?, None, context) } } ================================================ FILE: core/engine/src/builtins/date/tests.rs ================================================ use crate::{ JsNativeErrorKind, TestAction, builtins::date::utils::fast_atoi::{process_4, process_8}, js_string, run_test_actions, }; use boa_macros::js_str; use indoc::indoc; use time::{OffsetDateTime, macros::format_description}; // NOTE: Javascript Uses 0-based months, where time uses 1-based months. // Many of the assertions look wrong because of this. fn month_from_u8(month: u8) -> time::Month { match month { 1 => time::Month::January, 2 => time::Month::February, 3 => time::Month::March, 4 => time::Month::April, 5 => time::Month::May, 6 => time::Month::June, 7 => time::Month::July, 8 => time::Month::August, 9 => time::Month::September, 10 => time::Month::October, 11 => time::Month::November, 12 => time::Month::December, _ => unreachable!(), } } fn from_local( year: i32, month: u8, date: u8, hour: u8, minute: u8, second: u8, millisecond: u16, ) -> OffsetDateTime { let t = time::Date::from_calendar_date(year, month_from_u8(month), date) .unwrap() .with_hms_milli(hour, minute, second, millisecond) .unwrap() .assume_utc(); let offset = time::UtcOffset::local_offset_at(t).unwrap(); t.replace_offset(offset) } fn timestamp_from_local( year: i32, month: u8, date: u8, hour: u8, minute: u8, second: u8, millisecond: u16, ) -> i64 { let t = from_local(year, month, date, hour, minute, second, millisecond); t.unix_timestamp() * 1000 + i64::from(t.millisecond()) } fn timestamp_from_utc( year: i32, month: u8, date: u8, hour: u8, minute: u8, second: u8, millisecond: u16, ) -> i64 { let t = time::Date::from_calendar_date(year, month_from_u8(month), date) .unwrap() .with_hms_milli(hour, minute, second, millisecond) .unwrap() .assume_utc(); t.unix_timestamp() * 1000 + i64::from(t.millisecond()) } #[test] fn parse_ascii_digits() { let parse_8_ascii_digits = |val: &[u8; 8], len: usize| -> u64 { let val = u64::from_le_bytes(*val); process_8(val, len) }; assert_eq!(12_345_678, parse_8_ascii_digits(b"12345678", 8)); assert_eq!(123_456, parse_8_ascii_digits(b"123456xx", 6)); assert_eq!(123, parse_8_ascii_digits(b"123xxxxx", 3)); assert_eq!(123, parse_8_ascii_digits(b"000123xx", 6)); assert_eq!(0, parse_8_ascii_digits(b"00000000", 8)); let parse_4_ascii_digits = |val: &[u8; 4], len: usize| -> u64 { let val = u32::from_le_bytes(*val); u64::from(process_4(val, len)) }; assert_eq!(1234, parse_4_ascii_digits(b"1234", 4)); assert_eq!(12, parse_4_ascii_digits(b"12xx", 2)); assert_eq!(3, parse_4_ascii_digits(b"003x", 3)); assert_eq!(23, parse_4_ascii_digits(b"023x", 3)); assert_eq!(0, parse_4_ascii_digits(b"0000", 4)); } #[test] fn date_this_time_value() { run_test_actions([TestAction::assert_native_error( "({toString: Date.prototype.toString}).toString()", JsNativeErrorKind::Type, "'this' is not a Date", )]); } #[test] fn date_ctor_call() { run_test_actions([ TestAction::run("let a = new Date()"), TestAction::inspect_context(|_| std::thread::sleep(std::time::Duration::from_millis(1))), TestAction::assert("a.getTime() != new Date().getTime()"), ]); } #[test] fn date_ctor_call_string() { run_test_actions([TestAction::assert_eq( "new Date('2020-06-08T09:16:15.779-06:30').getTime()", timestamp_from_utc(2020, 6, 8, 15, 46, 15, 779), )]); } #[test] fn date_ctor_call_string_invalid() { run_test_actions([TestAction::assert_eq( "new Date('nope').getTime()", f64::NAN, )]); } #[test] fn date_ctor_call_number() { run_test_actions([TestAction::assert_eq( "new Date(1594199775779).getTime()", timestamp_from_utc(2020, 7, 8, 9, 16, 15, 779), )]); } #[test] fn date_ctor_call_date() { run_test_actions([TestAction::assert_eq( "new Date(new Date(1594199775779)).getTime()", timestamp_from_utc(2020, 7, 8, 9, 16, 15, 779), )]); } #[test] fn date_ctor_call_multiple() { run_test_actions([TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).getTime()", timestamp_from_local(2020, 7, 8, 9, 16, 15, 779), )]); } #[test] fn date_ctor_call_multiple_90s() { run_test_actions([TestAction::assert_eq( "new Date(99, 6, 8, 9, 16, 15, 779).getTime()", timestamp_from_local(1999, 7, 8, 9, 16, 15, 779), )]); } #[test] fn date_ctor_call_multiple_nan() { run_test_actions([ TestAction::assert_eq("new Date(1/0, 6, 8, 9, 16, 15, 779).getTime()", f64::NAN), TestAction::assert_eq("new Date(2020, 1/0, 8, 9, 16, 15, 779).getTime()", f64::NAN), TestAction::assert_eq("new Date(2020, 6, 1/0, 9, 16, 15, 779).getTime()", f64::NAN), TestAction::assert_eq("new Date(2020, 6, 8, 1/0, 16, 15, 779).getTime()", f64::NAN), TestAction::assert_eq("new Date(2020, 6, 8, 9, 1/0, 15, 779).getTime()", f64::NAN), TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 1/0, 779).getTime()", f64::NAN), TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 1/0).getTime()", f64::NAN), ]); } #[test] fn date_ctor_now_call() { run_test_actions([ TestAction::run("let a = Date.now()"), TestAction::inspect_context(|_| std::thread::sleep(std::time::Duration::from_millis(1))), TestAction::assert("a != Date.now()"), ]); } #[test] fn date_ctor_parse_call() { run_test_actions([TestAction::assert_eq( "Date.parse('2020-06-08T09:16:15.779-07:30')", 1_591_634_775_779_i64, )]); } #[test] fn date_ctor_utc_call() { run_test_actions([TestAction::assert_eq( "Date.UTC(2020, 6, 8, 9, 16, 15, 779)", 1_594_199_775_779_i64, )]); } #[test] fn date_ctor_utc_call_nan() { run_test_actions([ TestAction::assert_eq("Date.UTC(1/0, 6, 8, 9, 16, 15, 779)", f64::NAN), TestAction::assert_eq("Date.UTC(2020, 1/0, 8, 9, 16, 15, 779)", f64::NAN), TestAction::assert_eq("Date.UTC(2020, 6, 1/0, 9, 16, 15, 779)", f64::NAN), TestAction::assert_eq("Date.UTC(2020, 6, 8, 1/0, 16, 15, 779)", f64::NAN), TestAction::assert_eq("Date.UTC(2020, 6, 8, 9, 1/0, 15, 779)", f64::NAN), TestAction::assert_eq("Date.UTC(2020, 6, 8, 9, 16, 1/0, 779)", f64::NAN), TestAction::assert_eq("Date.UTC(2020, 6, 8, 9, 16, 15, 1/0)", f64::NAN), ]); } #[test] fn date_proto_get_date_call() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getDate()", 8), TestAction::assert_eq("new Date(1/0).getDate()", f64::NAN), ]); } #[test] fn date_proto_get_day_call() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getDay()", 3), TestAction::assert_eq("new Date(1/0).getDay()", f64::NAN), ]); } #[test] fn date_proto_get_full_year_call() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getFullYear()", 2020), TestAction::assert_eq("new Date(1/0).getFullYear()", f64::NAN), ]); } #[test] fn date_proto_get_hours_call() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getHours()", 9), TestAction::assert_eq("new Date(1/0).getHours()", f64::NAN), ]); } #[test] fn date_proto_get_milliseconds_call() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).getMilliseconds()", 779, ), TestAction::assert_eq("new Date(1/0).getMilliseconds()", f64::NAN), ]); } #[test] fn date_proto_get_minutes_call() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getMinutes()", 16), TestAction::assert_eq("new Date(1/0).getMinutes()", f64::NAN), ]); } #[test] fn date_proto_get_month() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getMonth()", 6), TestAction::assert_eq("new Date(1/0).getMonth()", f64::NAN), ]); } #[test] fn date_proto_get_seconds() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getSeconds()", 15), TestAction::assert_eq("new Date(1/0).getSeconds()", f64::NAN), ]); } #[test] fn date_proto_get_time() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).getTime()", timestamp_from_local(2020, 7, 8, 9, 16, 15, 779), ), TestAction::assert_eq("new Date(1/0).getTime()", f64::NAN), ]); } #[test] fn date_proto_get_year() { run_test_actions([ TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getYear()", 120), TestAction::assert_eq("new Date(1/0).getYear()", f64::NAN), ]); } #[test] fn date_proto_get_timezone_offset() { run_test_actions([ TestAction::assert(indoc! {r#" new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset() === new Date('1975-08-19T23:15:30-02:00').getTimezoneOffset() "#}), // NB: Host Settings, not TZ specified in the DateTime. TestAction::assert_eq( "new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset()", { let t = from_local(1975, 8, 19, 23, 15, 30, 0); -t.offset().whole_seconds() / 60 }, ), ]); } #[test] fn date_proto_get_utc_date_call() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCDate()", 8, ), TestAction::assert_eq("new Date(1/0).getUTCDate()", f64::NAN), ]); } #[test] fn date_proto_get_utc_day_call() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCDay()", 3, ), TestAction::assert_eq("new Date(1/0).getUTCDay()", f64::NAN), ]); } #[test] fn date_proto_get_utc_full_year_call() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCFullYear()", 2020, ), TestAction::assert_eq("new Date(1/0).getUTCFullYear()", f64::NAN), ]); } #[test] fn date_proto_get_utc_hours_call() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCHours()", 9, ), TestAction::assert_eq("new Date(1/0).getUTCHours()", f64::NAN), ]); } #[test] fn date_proto_get_utc_milliseconds_call() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMilliseconds()", 779, ), TestAction::assert_eq("new Date(1/0).getUTCMilliseconds()", f64::NAN), ]); } #[test] fn date_proto_get_utc_minutes_call() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMinutes()", 16, ), TestAction::assert_eq("new Date(1/0).getUTCMinutes()", f64::NAN), ]); } #[test] fn date_proto_get_utc_month() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMonth()", 6, ), TestAction::assert_eq("new Date(1/0).getUTCMonth()", f64::NAN), ]); } #[test] fn date_proto_get_utc_seconds() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCSeconds()", 15, ), TestAction::assert_eq("new Date(1/0).getUTCSeconds()", f64::NAN), ]); } #[test] fn date_proto_set_date() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setDate(21)", timestamp_from_local(2020, 7, 21, 9, 16, 15, 779), ), // Date wraps to previous month for 0. TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setDate(0)", timestamp_from_local(2020, 6, 30, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setDate(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_full_year() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012)", timestamp_from_local(2012, 7, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 8)", timestamp_from_local(2012, 9, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 8, 10)", timestamp_from_local(2012, 9, 10, 9, 16, 15, 779), ), // Out-of-bounds TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 35)", timestamp_from_local(2014, 12, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, -35)", timestamp_from_local(2009, 2, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 9, 950)", timestamp_from_local(2015, 5, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 9, -950)", timestamp_from_local(2010, 2, 23, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_hours() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11)", timestamp_from_local(2020, 7, 8, 11, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11, 35)", timestamp_from_local(2020, 7, 8, 11, 35, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11, 35, 23)", timestamp_from_local(2020, 7, 8, 11, 35, 23, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11, 35, 23, 537)", timestamp_from_local(2020, 7, 8, 11, 35, 23, 537), ), // Out-of-bounds TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(10000, 20000, 30000, 40123)", timestamp_from_local(2021, 9, 11, 21, 40, 40, 123), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_milliseconds() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMilliseconds(597)", timestamp_from_local(2020, 7, 8, 9, 16, 15, 597), ), // Out-of-bounds // Thorough tests are done by setHours TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMilliseconds(40123)", timestamp_from_local(2020, 7, 8, 9, 16, 55, 123), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMilliseconds(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_minutes() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(11)", timestamp_from_local(2020, 7, 8, 9, 11, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(11, 35)", timestamp_from_local(2020, 7, 8, 9, 11, 35, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(11, 35, 537)", timestamp_from_local(2020, 7, 8, 9, 11, 35, 537), ), // Out-of-bounds // Thorough tests are done by setHours TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(600000, 30000, 40123)", timestamp_from_local(2021, 8, 29, 9, 20, 40, 123), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_month() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(11)", timestamp_from_local(2020, 12, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(11, 16)", timestamp_from_local(2020, 12, 16, 9, 16, 15, 779), ), // Out-of-bounds // Thorough tests are done by setFullYear TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(40, 83)", timestamp_from_local(2023, 7, 22, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_seconds() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(11)", timestamp_from_local(2020, 7, 8, 9, 16, 11, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(11, 487)", timestamp_from_local(2020, 7, 8, 9, 16, 11, 487), ), // Out-of-bounds // Thorough tests are done by setHour TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(40000000, 40123)", timestamp_from_local(2021, 10, 14, 8, 23, 20, 123), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(1/0)", f64::NAN, ), ]); } #[test] fn set_year() { run_test_actions([ TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setYear(98)", timestamp_from_local(1998, 7, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setYear(2001)", timestamp_from_local(2001, 7, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setYear(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_time() { run_test_actions([TestAction::assert_eq( "new Date().setTime(new Date(2020, 6, 8, 9, 16, 15, 779).getTime())", timestamp_from_local(2020, 7, 8, 9, 16, 15, 779), )]); } #[test] fn date_proto_set_utc_date() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCDate(21)", timestamp_from_utc(2020, 7, 21, 9, 16, 15, 779), ), // Date wraps to previous month for 0. TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCDate(0)", timestamp_from_utc(2020, 6, 30, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCDate(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_utc_full_year() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012)", timestamp_from_utc(2012, 7, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 8)", timestamp_from_utc(2012, 9, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 8, 10)", timestamp_from_utc(2012, 9, 10, 9, 16, 15, 779), ), // Out-of-bounds TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 35)", timestamp_from_utc(2014, 12, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, -35)", timestamp_from_utc(2009, 2, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 9, 950)", timestamp_from_utc(2015, 5, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 9, -950)", timestamp_from_utc(2010, 2, 23, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_utc_hours() { // The local-time constructor `new Date(2020, 6, 8, 9, 16, 15, 779)` converts // local time to UTC internally. The resulting UTC components depend on the // host timezone offset, so we compute them dynamically. let base = from_local(2020, 7, 8, 9, 16, 15, 779).to_offset(time::UtcOffset::UTC); let base_y = base.year(); let base_m = base.month() as u8; let base_d = base.day(); let base_min = base.minute(); let base_sec = base.second(); let base_ms = base.millisecond(); run_test_actions([ // setUTCHours(11): only the UTC hour changes; date/min/sec/ms stay. TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11)", timestamp_from_utc(base_y, base_m, base_d, 11, base_min, base_sec, base_ms), ), // setUTCHours(h, m): hour and minutes are overridden; sec/ms stay. TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11, 35)", timestamp_from_utc(base_y, base_m, base_d, 11, 35, base_sec, base_ms), ), // setUTCHours(h, m, s): hour, minutes, seconds overridden; ms stays. TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11, 35, 23)", timestamp_from_utc(base_y, base_m, base_d, 11, 35, 23, base_ms), ), // setUTCHours(h, m, s, ms): all time components overridden. TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11, 35, 23, 537)", timestamp_from_utc(base_y, base_m, base_d, 11, 35, 23, 537), ), // Out-of-bounds: all time components specified, overflow wraps across days. // The base timestamp is Day(t) from the local-time date's internal UTC value. TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(10000, 20000, 30000, 40123)", { // setUTCHours computes MakeDate(Day(t), MakeTime(h, m, s, ms)). // Day(t) is the UTC day count of the base date, so we reconstruct // by starting from the base UTC midnight and adding the overflow. let base_day_ms = timestamp_from_utc(base_y, base_m, base_d, 0, 0, 0, 0); let total_ms = i64::from(10000) * 3_600_000 + i64::from(20000) * 60_000 + i64::from(30000) * 1_000 + 40123; base_day_ms + total_ms }, ), TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_utc_milliseconds() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMilliseconds(597)", timestamp_from_utc(2020, 7, 8, 9, 16, 15, 597), ), // Out-of-bounds // Thorough tests are done by setHours TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMilliseconds(40123)", timestamp_from_utc(2020, 7, 8, 9, 16, 55, 123), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMilliseconds(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_utc_minutes() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(11)", timestamp_from_utc(2020, 7, 8, 9, 11, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(11, 35)", timestamp_from_utc(2020, 7, 8, 9, 11, 35, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(11, 35, 537)", timestamp_from_utc(2020, 7, 8, 9, 11, 35, 537), ), // Out-of-bounds // Thorough tests are done by setHours TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(600000, 30000, 40123)", timestamp_from_utc(2021, 8, 29, 9, 20, 40, 123), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_utc_month() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(11)", timestamp_from_utc(2020, 12, 8, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(11, 16)", timestamp_from_utc(2020, 12, 16, 9, 16, 15, 779), ), // Out-of-bounds // Thorough tests are done by setFullYear TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(40, 83)", timestamp_from_utc(2023, 7, 22, 9, 16, 15, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_set_utc_seconds() { run_test_actions([ TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(11)", timestamp_from_utc(2020, 7, 8, 9, 16, 11, 779), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(11, 487)", timestamp_from_utc(2020, 7, 8, 9, 16, 11, 487), ), // Out-of-bounds // Thorough tests are done by setHour TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(40000000, 40123)", timestamp_from_utc(2021, 10, 14, 8, 23, 20, 123), ), TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(1/0)", f64::NAN, ), ]); } #[test] fn date_proto_to_date_string() { run_test_actions([TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).toDateString()", js_str!("Wed Jul 08 2020"), )]); } #[test] fn date_proto_to_gmt_string() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toGMTString()", js_str!("Wed, 08 Jul 2020 09:16:15 GMT"), )]); } #[test] fn date_proto_to_iso_string() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toISOString()", js_str!("2020-07-08T09:16:15.779Z"), )]); } #[test] fn date_proto_to_iso_string_year_zero() { run_test_actions([TestAction::assert_eq( r#"new Date("0000-06-15T00:00:00Z").toISOString()"#, js_str!("0000-06-15T00:00:00.000Z"), )]); } #[test] fn date_proto_to_json() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toJSON()", js_str!("2020-07-08T09:16:15.779Z"), )]); } #[test] fn date_proto_to_string() { let to_string_format = format_description!( "[weekday repr:short] [month repr:short] [day] [year] [hour]:[minute]:[second] GMT[offset_hour sign:mandatory][offset_minute][end]" ); let t = from_local(2020, 7, 8, 9, 16, 15, 779) .format(to_string_format) .unwrap(); run_test_actions([TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).toString()", js_string!(t), )]); } #[test] fn date_proto_to_time_string() { let to_time_string_format = format_description!( "[hour]:[minute]:[second] GMT[offset_hour sign:mandatory][offset_minute][end]" ); let t = from_local(2020, 7, 8, 9, 16, 15, 779) .format(to_time_string_format) .unwrap(); run_test_actions([TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).toTimeString()", js_string!(t), )]); } #[test] fn date_proto_to_utc_string() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toUTCString()", js_str!("Wed, 08 Jul 2020 09:16:15 GMT"), )]); } #[test] fn date_proto_value_of() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).valueOf()", 1_594_199_775_779_i64, )]); } #[test] fn date_neg() { run_test_actions([TestAction::assert_eq( "-new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779))", -1_594_199_775_779_i64, )]); } #[test] fn date_json() { run_test_actions([TestAction::assert_eq( "JSON.stringify({ date: new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)) })", js_string!(r#"{"date":"2020-07-08T09:16:15.779Z"}"#), )]); } #[test] fn date_parse_hour24_validation() { run_test_actions([ // 24:00:00.000 is valid (midnight end-of-day) TestAction::assert("!isNaN(Date.parse('2024-01-01T24:00:00Z'))"), TestAction::assert("!isNaN(Date.parse('2024-01-01T24:00:00.000Z'))"), // hour 24 with non-zero minutes/seconds/ms must be NaN TestAction::assert("isNaN(Date.parse('2024-01-01T24:30:00Z'))"), TestAction::assert("isNaN(Date.parse('2024-01-01T24:00:01Z'))"), TestAction::assert("isNaN(Date.parse('2024-01-01T24:00:00.001Z'))"), ]); } #[test] #[cfg(feature = "intl")] fn date_proto_to_locale_string_intl() { run_test_actions([ // Invalid receiver: spec requires TypeError TestAction::assert_native_error( "Date.prototype.toLocaleString.call({})", JsNativeErrorKind::Type, "'this' is not a Date", ), TestAction::assert_native_error( "Date.prototype.toLocaleDateString.call({})", JsNativeErrorKind::Type, "'this' is not a Date", ), TestAction::assert_native_error( "Date.prototype.toLocaleTimeString.call({})", JsNativeErrorKind::Type, "'this' is not a Date", ), TestAction::assert_eq("new Date(NaN).toLocaleString()", js_str!("Invalid Date")), TestAction::assert("typeof new Date(2020, 6, 8).toLocaleString() === 'string'"), TestAction::assert("typeof new Date(2020, 6, 8).toLocaleDateString() === 'string'"), TestAction::assert("typeof new Date(2020, 6, 8).toLocaleTimeString() === 'string'"), TestAction::assert("typeof new Date(0).toLocaleString('en-US') === 'string'"), TestAction::assert("typeof new Date(0).toLocaleDateString('en-US') === 'string'"), TestAction::assert("typeof new Date(0).toLocaleDateString('de-DE') === 'string'"), TestAction::assert("typeof new Date(0).toLocaleTimeString('en-US') === 'string'"), // Prove locale pipeline: different locales produce different output TestAction::assert( "new Date(0).toLocaleDateString('en-US') !== new Date(0).toLocaleDateString('de-DE')", ), TestAction::assert( "new Date(0).toLocaleString('en-US') !== new Date(0).toLocaleString('de-DE')", ), TestAction::assert( "new Date(0).toLocaleTimeString('en-US') !== new Date(0).toLocaleTimeString('de-DE')", ), // Prove ToDateTimeOptions pipeline: options affect output TestAction::assert( "typeof new Date(0).toLocaleDateString('en-US', { dateStyle: 'short' }) === 'string'", ), // Prove output is a string and not empty TestAction::assert( "new Date(0).toLocaleDateString('en-US', { dateStyle: 'short' }).length > 0", ), ]); } ================================================ FILE: core/engine/src/builtins/date/utils.rs ================================================ use crate::{JsStr, JsString, context::HostHooks, js_string, value::IntegerOrInfinity}; use boa_macros::js_str; use boa_string::JsStrVariant; use std::slice::Iter; use std::str; use std::{borrow::Cow, iter::Peekable}; use time::{OffsetDateTime, PrimitiveDateTime, macros::format_description}; // Time-related Constants // // More info: // - [ECMAScript reference][spec] // // https://tc39.es/ecma262/#sec-time-related-constants // HoursPerDay = 24 const HOURS_PER_DAY: f64 = 24.0; // MinutesPerHour = 60 const MINUTES_PER_HOUR: f64 = 60.0; // SecondsPerMinute = 60 const SECONDS_PER_MINUTE: f64 = 60.0; // msPerSecond = 1000𝔽 const MS_PER_SECOND: f64 = 1000.0; // msPerMinute = 60000𝔽 = msPerSecond × 𝔽(SecondsPerMinute) pub(super) const MS_PER_MINUTE: f64 = MS_PER_SECOND * SECONDS_PER_MINUTE; // msPerHour = 3600000𝔽 = msPerMinute × 𝔽(MinutesPerHour) const MS_PER_HOUR: f64 = MS_PER_MINUTE * MINUTES_PER_HOUR; // msPerDay = 86400000𝔽 = msPerHour × 𝔽(HoursPerDay) const MS_PER_DAY: f64 = MS_PER_HOUR * HOURS_PER_DAY; /// Abstract operation `Day ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-day pub(super) fn day(t: f64) -> f64 { // 1. Return 𝔽(floor(ℝ(t / msPerDay))). (t / MS_PER_DAY).floor() } /// Abstract operation `TimeWithinDay ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-timewithinday pub(super) fn time_within_day(t: f64) -> f64 { // 1. Return 𝔽(ℝ(t) modulo ℝ(msPerDay)). t.rem_euclid(MS_PER_DAY) } /// Abstract operation `DaysInYear ( y )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-daysinyear fn days_in_year(y: f64) -> u16 { // 1. Let ry be ℝ(y). let ry = y; // 2. If (ry modulo 400) = 0, return 366𝔽. if ry.rem_euclid(400.0) == 0.0 { return 366; } // 3. If (ry modulo 100) = 0, return 365𝔽. if ry.rem_euclid(100.0) == 0.0 { return 365; } // 4. If (ry modulo 4) = 0, return 366𝔽. if ry.rem_euclid(4.0) == 0.0 { return 366; } // 5. Return 365𝔽. 365 } /// Abstract operation `DayFromYear ( y )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-dayfromyear fn day_from_year(y: f64) -> f64 { // 1. Let ry be ℝ(y). // 2. NOTE: In the following steps, each _numYearsN_ is the number of years divisible by N // that occur between the epoch and the start of year y. // (The number is negative if y is before the epoch.) // 3. Let numYears1 be (ry - 1970). let num_years_1 = y - 1970.0; // 4. Let numYears4 be floor((ry - 1969) / 4). let num_years_4 = ((y - 1969.0) / 4.0).floor(); // 5. Let numYears100 be floor((ry - 1901) / 100). let num_years_100 = ((y - 1901.0) / 100.0).floor(); // 6. Let numYears400 be floor((ry - 1601) / 400). let num_years_400 = ((y - 1601.0) / 400.0).floor(); // 7. Return 𝔽(365 × numYears1 + numYears4 - numYears100 + numYears400). 365.0 * num_years_1 + num_years_4 - num_years_100 + num_years_400 } /// Abstract operation `TimeFromYear ( y )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-timefromyear fn time_from_year(y: f64) -> f64 { // 1. Return msPerDay × DayFromYear(y). MS_PER_DAY * day_from_year(y) } /// Abstract operation `YearFromTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-yearfromtime pub(crate) fn year_from_time(t: f64) -> i32 { const MS_PER_AVERAGE_YEAR: f64 = 12.0 * 30.436_875 * MS_PER_DAY; // 1. Return the largest integral Number y (closest to +∞) such that TimeFromYear(y) ≤ t. let mut year = (((t + MS_PER_AVERAGE_YEAR / 2.0) / MS_PER_AVERAGE_YEAR).floor()) as i32 + 1970; if time_from_year(year.into()) > t { year -= 1; } year } /// Abstract operation `DayWithinYear ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-daywithinyear fn day_within_year(t: f64) -> u16 { // 1. Return Day(t) - DayFromYear(YearFromTime(t)). (day(t) - day_from_year(year_from_time(t).into())) as u16 } /// Abstract operation `InLeapYear ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-inleapyear fn in_leap_year(t: f64) -> u16 { // 1. If DaysInYear(YearFromTime(t)) is 366𝔽, return 1𝔽; else return +0𝔽. (days_in_year(year_from_time(t).into()) == 366).into() } /// Abstract operation `MonthFromTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-monthfromtime pub(crate) fn month_from_time(t: f64) -> u8 { // 1. Let inLeapYear be InLeapYear(t). let in_leap_year = in_leap_year(t); // 2. Let dayWithinYear be DayWithinYear(t). let day_within_year = day_within_year(t); match day_within_year { // 3. If dayWithinYear < 31𝔽, return +0𝔽. t if t < 31 => 0, // 4. If dayWithinYear < 59𝔽 + inLeapYear, return 1𝔽. t if t < 59 + in_leap_year => 1, // 5. If dayWithinYear < 90𝔽 + inLeapYear, return 2𝔽. t if t < 90 + in_leap_year => 2, // 6. If dayWithinYear < 120𝔽 + inLeapYear, return 3𝔽. t if t < 120 + in_leap_year => 3, // 7. If dayWithinYear < 151𝔽 + inLeapYear, return 4𝔽. t if t < 151 + in_leap_year => 4, // 8. If dayWithinYear < 181𝔽 + inLeapYear, return 5𝔽. t if t < 181 + in_leap_year => 5, // 9. If dayWithinYear < 212𝔽 + inLeapYear, return 6𝔽. t if t < 212 + in_leap_year => 6, // 10. If dayWithinYear < 243𝔽 + inLeapYear, return 7𝔽. t if t < 243 + in_leap_year => 7, // 11. If dayWithinYear < 273𝔽 + inLeapYear, return 8𝔽. t if t < 273 + in_leap_year => 8, // 12. If dayWithinYear < 304𝔽 + inLeapYear, return 9𝔽. t if t < 304 + in_leap_year => 9, // 13. If dayWithinYear < 334𝔽 + inLeapYear, return 10𝔽. t if t < 334 + in_leap_year => 10, // 14. Assert: dayWithinYear < 365𝔽 + inLeapYear. // 15. Return 11𝔽. _ => 11, } } /// Abstract operation `DateFromTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-datefromtime pub(crate) fn date_from_time(t: f64) -> u8 { // 1. Let inLeapYear be InLeapYear(t). let in_leap_year = in_leap_year(t); // 2. Let dayWithinYear be DayWithinYear(t). let day_within_year = day_within_year(t); // 3. Let month be MonthFromTime(t). let month = month_from_time(t); let date = match month { // 4. If month is +0𝔽, return dayWithinYear + 1𝔽. 0 => day_within_year + 1, // 5. If month is 1𝔽, return dayWithinYear - 30𝔽. 1 => day_within_year - 30, // 6. If month is 2𝔽, return dayWithinYear - 58𝔽 - inLeapYear. 2 => day_within_year - 58 - in_leap_year, // 7. If month is 3𝔽, return dayWithinYear - 89𝔽 - inLeapYear. 3 => day_within_year - 89 - in_leap_year, // 8. If month is 4𝔽, return dayWithinYear - 119𝔽 - inLeapYear. 4 => day_within_year - 119 - in_leap_year, // 9. If month is 5𝔽, return dayWithinYear - 150𝔽 - inLeapYear. 5 => day_within_year - 150 - in_leap_year, // 10. If month is 6𝔽, return dayWithinYear - 180𝔽 - inLeapYear. 6 => day_within_year - 180 - in_leap_year, // 11. If month is 7𝔽, return dayWithinYear - 211𝔽 - inLeapYear. 7 => day_within_year - 211 - in_leap_year, // 12. If month is 8𝔽, return dayWithinYear - 242𝔽 - inLeapYear. 8 => day_within_year - 242 - in_leap_year, // 13. If month is 9𝔽, return dayWithinYear - 272𝔽 - inLeapYear. 9 => day_within_year - 272 - in_leap_year, // 14. If month is 10𝔽, return dayWithinYear - 303𝔽 - inLeapYear. 10 => day_within_year - 303 - in_leap_year, // 15. Assert: month is 11𝔽. // 16. Return dayWithinYear - 333𝔽 - inLeapYear. _ => day_within_year - 333 - in_leap_year, }; date as u8 } /// Abstract operation `WeekDay ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-weekday pub(super) fn week_day(t: f64) -> u8 { // 1. Return 𝔽(ℝ(Day(t) + 4𝔽) modulo 7). (day(t) + 4.0).rem_euclid(7.0) as u8 } /// Abstract operation `HourFromTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-hourfromtime pub(crate) fn hour_from_time(t: f64) -> u8 { // 1. Return 𝔽(floor(ℝ(t / msPerHour)) modulo HoursPerDay). ((t / MS_PER_HOUR).floor()).rem_euclid(HOURS_PER_DAY) as u8 } /// Abstract operation `MinFromTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-minfromtime pub(crate) fn min_from_time(t: f64) -> u8 { // 1. Return 𝔽(floor(ℝ(t / msPerMinute)) modulo MinutesPerHour). ((t / MS_PER_MINUTE).floor()).rem_euclid(MINUTES_PER_HOUR) as u8 } /// Abstract operation `SecFromTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-secfromtime pub(crate) fn sec_from_time(t: f64) -> u8 { // 1. Return 𝔽(floor(ℝ(t / msPerSecond)) modulo SecondsPerMinute). ((t / MS_PER_SECOND).floor()).rem_euclid(SECONDS_PER_MINUTE) as u8 } /// Abstract operation `msFromTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-msfromtime pub(crate) fn ms_from_time(t: f64) -> u16 { // 1. Return 𝔽(ℝ(t) modulo ℝ(msPerSecond)). t.rem_euclid(MS_PER_SECOND) as u16 } /// Abstract operation `LocalTime ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-localtime pub(super) fn local_time(t: f64, hooks: &dyn HostHooks) -> f64 { t + f64::from(local_timezone_offset_seconds(t, hooks)) * MS_PER_SECOND } /// Abstract operation `UTC ( t )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-utc-t pub(super) fn utc_t(t: f64, hooks: &dyn HostHooks) -> f64 { // 1. If t is not finite, return NaN. if !t.is_finite() { return f64::NAN; } t - f64::from(local_timezone_offset_seconds(t, hooks)) * MS_PER_SECOND } /// Abstract operation `MakeTime ( hour, min, sec, ms )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-maketime pub(super) fn make_time(hour: f64, min: f64, sec: f64, ms: f64) -> f64 { // 1. If hour is not finite, min is not finite, sec is not finite, or ms is not finite, return NaN. if !hour.is_finite() || !min.is_finite() || !sec.is_finite() || !ms.is_finite() { return f64::NAN; } // 2. Let h be 𝔽(! ToIntegerOrInfinity(hour)). let h = hour.abs().floor().copysign(hour); // 3. Let m be 𝔽(! ToIntegerOrInfinity(min)). let m = min.abs().floor().copysign(min); // 4. Let s be 𝔽(! ToIntegerOrInfinity(sec)). let s = sec.abs().floor().copysign(sec); // 5. Let milli be 𝔽(! ToIntegerOrInfinity(ms)). let milli = ms.abs().floor().copysign(ms); // 6. Return ((h × msPerHour + m × msPerMinute) + s × msPerSecond) + milli. ((h * MS_PER_HOUR + m * MS_PER_MINUTE) + s * MS_PER_SECOND) + milli } /// Abstract operation `MakeDay ( year, month, date )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-makeday pub(super) fn make_day(year: f64, month: f64, date: f64) -> f64 { // 1. If year is not finite, month is not finite, or date is not finite, return NaN. if !year.is_finite() || !month.is_finite() || !date.is_finite() { return f64::NAN; } // 2. Let y be 𝔽(! ToIntegerOrInfinity(year)). let y = year.abs().floor().copysign(year); // 3. Let m be 𝔽(! ToIntegerOrInfinity(month)). let m = month.abs().floor().copysign(month); // 4. Let dt be 𝔽(! ToIntegerOrInfinity(date)). let dt = date.abs().floor().copysign(date); // 5. Let ym be y + 𝔽(floor(ℝ(m) / 12)). let ym = y + (m / 12.0).floor(); // 6. If ym is not finite, return NaN. if !ym.is_finite() { return f64::NAN; } // 7. Let mn be 𝔽(ℝ(m) modulo 12). let mn = m.rem_euclid(12.0) as u8; // 8. Find a finite time value t such that YearFromTime(t) is ym, MonthFromTime(t) is mn, // and DateFromTime(t) is 1𝔽; // but if this is not possible (because some argument is out of range), return NaN. let rest = if mn > 1 { 1.0 } else { 0.0 }; let days_within_year_to_end_of_month = match mn { 0 => 0.0, 1 => 31.0, 2 => 59.0, 3 => 90.0, 4 => 120.0, 5 => 151.0, 6 => 181.0, 7 => 212.0, 8 => 243.0, 9 => 273.0, 10 => 304.0, 11 => 334.0, 12 => 365.0, _ => unreachable!(), }; let t = (day_from_year(ym + rest) - 365.0 * rest + days_within_year_to_end_of_month) * MS_PER_DAY; // 9. Return Day(t) + dt - 1𝔽. day(t) + dt - 1.0 } /// Abstract operation `MakeDate ( day, time )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-makedate pub(super) fn make_date(day: f64, time: f64) -> f64 { // 1. If day is not finite or time is not finite, return NaN. if !day.is_finite() || !time.is_finite() { return f64::NAN; } // 2. Let tv be day × msPerDay + time. let tv = day * MS_PER_DAY + time; // 3. If tv is not finite, return NaN. if !tv.is_finite() { return f64::NAN; } // 4. Return tv. tv } /// Abstract operation `MakeFullYear ( year )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-makefullyear pub(super) fn make_full_year(year: f64) -> f64 { // 1. If year is NaN, return NaN. if year.is_nan() { return f64::NAN; } // 2. Let truncated be ! ToIntegerOrInfinity(year). let truncated = IntegerOrInfinity::from(year); // 3. If truncated is in the inclusive interval from 0 to 99, return 1900𝔽 + 𝔽(truncated). // 4. Return 𝔽(truncated). match truncated { IntegerOrInfinity::Integer(i) if (0..=99).contains(&i) => 1900.0 + i as f64, IntegerOrInfinity::Integer(i) => i as f64, IntegerOrInfinity::PositiveInfinity => f64::INFINITY, IntegerOrInfinity::NegativeInfinity => f64::NEG_INFINITY, } } /// Abstract operation `TimeClip ( time )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-timeclip pub(crate) fn time_clip(time: f64) -> f64 { // 1. If time is not finite, return NaN. if !time.is_finite() { return f64::NAN; } // 2. If abs(ℝ(time)) > 8.64 × 10**15, return NaN. if time.abs() > 8.64e15 { return f64::NAN; } // 3. Return 𝔽(! ToIntegerOrInfinity(time)). let time = time.trunc(); if time.abs() == 0.0 { return 0.0; } time } /// Abstract operation `TimeString ( tv )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-timestring pub(super) fn time_string(tv: f64) -> JsString { // 1. Let hour be ToZeroPaddedDecimalString(ℝ(HourFromTime(tv)), 2). let mut binding = [0; 2]; let hour = pad_two(hour_from_time(tv), &mut binding); // 2. Let minute be ToZeroPaddedDecimalString(ℝ(MinFromTime(tv)), 2). let mut binding = [0; 2]; let minute = pad_two(min_from_time(tv), &mut binding); // 3. Let second be ToZeroPaddedDecimalStringbindingFromTime(tv)), 2). let mut binding = [0; 2]; let second = pad_two(sec_from_time(tv), &mut binding); // 4. Return the string-concatenation of // hour, // ":", // minute, // ":", // second, // the code unit 0x0020 (SPACE), // and "GMT". js_string!( hour, js_str!(":"), minute, js_str!(":"), second, js_str!(" GMT") ) } /// Abstract operation `DateString ( tv )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-datestring pub(super) fn date_string(tv: f64) -> JsString { // 1. Let weekday be the Name of the entry in Table 63 with the Number WeekDay(tv). let weekday = match week_day(tv) { 0 => js_str!("Sun"), 1 => js_str!("Mon"), 2 => js_str!("Tue"), 3 => js_str!("Wed"), 4 => js_str!("Thu"), 5 => js_str!("Fri"), 6 => js_str!("Sat"), _ => unreachable!(), }; // 2. Let month be the Name of the entry in Table 64 with the Number MonthFromTime(tv). let month = match month_from_time(tv) { 0 => js_str!("Jan"), 1 => js_str!("Feb"), 2 => js_str!("Mar"), 3 => js_str!("Apr"), 4 => js_str!("May"), 5 => js_str!("Jun"), 6 => js_str!("Jul"), 7 => js_str!("Aug"), 8 => js_str!("Sep"), 9 => js_str!("Oct"), 10 => js_str!("Nov"), 11 => js_str!("Dec"), _ => unreachable!(), }; // 3. Let day be ToZeroPaddedDecimalString(ℝ(DateFromTime(tv)), 2). let mut binding = [0; 2]; let day = pad_two(date_from_time(tv), &mut binding); // 4. Let yv be YearFromTime(tv). let yv = year_from_time(tv); // 5. If yv is +0𝔽 or yv > +0𝔽, let yearSign be the empty String; otherwise, let yearSign be "-". let year_sign = if yv >= 0 { js_str!("") } else { js_str!("-") }; // 6. Let paddedYear be ToZeroPaddedDecimalString(abs(ℝ(yv)), 4). let yv = yv.unsigned_abs(); let padded_year: JsString = if yv >= 100_000 { pad_six(yv, &mut [0; 6]).into() } else if yv >= 10000 { pad_five(yv, &mut [0; 5]).into() } else { pad_four(yv, &mut [0; 4]).into() }; // 7. Return the string-concatenation of // weekday, // the code unit 0x0020 (SPACE), // month, // the code unit 0x0020 (SPACE), // day, // the code unit 0x0020 (SPACE), // yearSign, // and paddedYear. js_string!( weekday, js_str!(" "), month, js_str!(" "), day, js_str!(" "), year_sign, &padded_year ) } /// Abstract operation `TimeZoneString ( tv )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-timezoneestring pub(super) fn time_zone_string(t: f64, hooks: &dyn HostHooks) -> JsString { // 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier(). // 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then // a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier). // 3. Else, // a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(tv) × 10**6)). // 4. Let offset be 𝔽(truncate(offsetNs / 10**6)). let offset = f64::from(local_timezone_offset_seconds(t, hooks)) * MS_PER_SECOND; //let offset = hooks.local_timezone_offset_seconds((t / MS_PER_SECOND).floor() as i64); // 5. If offset is +0𝔽 or offset > +0𝔽, then let (offset_sign, abs_offset) = if offset >= 0.0 { // a. Let offsetSign be "+". // b. Let absOffset be offset. (js_str!("+"), offset) } // 6. Else, else { // a. Let offsetSign be "-". // b. Let absOffset be -offset. (js_str!("-"), -offset) }; // 7. Let offsetMin be ToZeroPaddedDecimalString(ℝ(MinFromTime(absOffset)), 2). let mut binding = [0; 2]; let offset_min = pad_two(min_from_time(abs_offset), &mut binding); // 8. Let offsetHour be ToZeroPaddedDecimalString(ℝ(HourFromTime(absOffset)), 2). let mut binding = [0; 2]; let offset_hour = pad_two(hour_from_time(abs_offset), &mut binding); // 9. Let tzName be an implementation-defined string that is either the empty String or the // string-concatenation of the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), // an implementation-defined timezone name, and the code unit 0x0029 (RIGHT PARENTHESIS). // 10. Return the string-concatenation of offsetSign, offsetHour, offsetMin, and tzName. js_string!(offset_sign, offset_hour, offset_min) } /// Abstract operation `ToDateString ( tv )` /// /// More info: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-todatestring pub(super) fn to_date_string_t(tv: f64, hooks: &dyn HostHooks) -> JsString { // 1. If tv is NaN, return "Invalid Date". if tv.is_nan() { return js_string!("Invalid Date"); } // 2. Let t be LocalTime(tv). let t = local_time(tv, hooks); // 3. Return the string-concatenation of // DateString(t), // the code unit 0x0020 (SPACE), // TimeString(t), // and TimeZoneString(tv). js_string!( &date_string(t), js_str!(" "), &time_string(t), &time_zone_string(t, hooks) ) } fn local_timezone_offset_seconds(t: f64, hooks: &dyn HostHooks) -> i32 { let millis = t.rem_euclid(MS_PER_SECOND); let seconds = ((t - millis) / MS_PER_SECOND) as i64; hooks.local_timezone_offset_seconds(seconds) } pub(super) fn pad_two(t: u8, output: &mut [u8; 2]) -> JsStr<'_> { *output = if t < 10 { [b'0', b'0' + t] } else { [b'0' + (t / 10), b'0' + (t % 10)] }; debug_assert!(output.is_ascii()); JsStr::latin1(output) } pub(super) fn pad_three(t: u16, output: &mut [u8; 3]) -> JsStr<'_> { *output = [ b'0' + (t / 100) as u8, b'0' + ((t / 10) % 10) as u8, b'0' + (t % 10) as u8, ]; JsStr::latin1(output) } pub(super) fn pad_four(t: u32, output: &mut [u8; 4]) -> JsStr<'_> { *output = [ b'0' + (t / 1000) as u8, b'0' + ((t / 100) % 10) as u8, b'0' + ((t / 10) % 10) as u8, b'0' + (t % 10) as u8, ]; JsStr::latin1(output) } pub(super) fn pad_five(t: u32, output: &mut [u8; 5]) -> JsStr<'_> { *output = [ b'0' + (t / 10_000) as u8, b'0' + ((t / 1000) % 10) as u8, b'0' + ((t / 100) % 10) as u8, b'0' + ((t / 10) % 10) as u8, b'0' + (t % 10) as u8, ]; JsStr::latin1(output) } pub(super) fn pad_six(t: u32, output: &mut [u8; 6]) -> JsStr<'_> { *output = [ b'0' + (t / 100_000) as u8, b'0' + ((t / 10_000) % 10) as u8, b'0' + ((t / 1000) % 10) as u8, b'0' + ((t / 100) % 10) as u8, b'0' + ((t / 10) % 10) as u8, b'0' + (t % 10) as u8, ]; JsStr::latin1(output) } /// Parse a date string according to the steps specified in [`Date.parse`][spec]. /// /// We parse three different formats: /// - The [`Date Time String Format`][spec-format] specified in the spec: `YYYY-MM-DDTHH:mm:ss.sssZ` /// - The `toString` format: `Thu Jan 01 1970 00:00:00 GMT+0000` /// - The `toUTCString` format: `Thu, 01 Jan 1970 00:00:00 GMT` /// /// [spec]: https://tc39.es/ecma262/#sec-date.parse /// [spec-format]: https://tc39.es/ecma262/#sec-date-time-string-format pub(super) fn parse_date(date: &JsString, hooks: &dyn HostHooks) -> Option { // All characters must be ASCII so we can return early if we find a non-ASCII character. let owned_js_str = date.as_str(); let date = match owned_js_str.variant() { JsStrVariant::Latin1(s) => { if !s.is_ascii() { return None; } // SAFETY: Since all characters are ASCII we can safely convert this into str. Cow::Borrowed(unsafe { str::from_utf8_unchecked(s) }) } JsStrVariant::Utf16(s) => { let date = String::from_utf16(s).ok()?; if !date.is_ascii() { return None; } Cow::Owned(date) } }; // Date Time String Format: 'YYYY-MM-DDTHH:mm:ss.sssZ' if let Some(dt) = DateParser::new(&date, hooks).parse() { return Some(dt); } // `toString` format: `Thu Jan 01 1970 00:00:00 GMT+0000` if let Ok(t) = OffsetDateTime::parse( &date, &format_description!( "[weekday repr:short] [month repr:short] [day] [year] [hour]:[minute]:[second] GMT[offset_hour sign:mandatory][offset_minute][end]" ), ) { return Some(t.unix_timestamp() * 1000 + i64::from(t.millisecond())); } // `toUTCString` format: `Thu, 01 Jan 1970 00:00:00 GMT` if let Ok(t) = PrimitiveDateTime::parse( &date, &format_description!( "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT[end]" ), ) { let t = t.assume_utc(); return Some(t.unix_timestamp() * 1000 + i64::from(t.millisecond())); } None } /// Parses a date string according to the [`Date Time String Format`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-date-time-string-format struct DateParser<'a> { hooks: &'a dyn HostHooks, input: Peekable>, year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32, millisecond: u32, offset: i64, } // Copied from https://github.com/RoDmitry/atoi_simd/blob/master/src/fallback.rs, // which is based on https://rust-malaysia.github.io/code/2020/07/11/faster-integer-parsing.html. #[doc(hidden)] #[allow(clippy::inline_always)] pub(in crate::builtins::date) mod fast_atoi { #[inline(always)] pub(in crate::builtins::date) const fn process_8(mut val: u64, len: usize) -> u64 { val <<= 64_usize.saturating_sub(len << 3); // << 3 - same as mult by 8 val = (val & 0x0F0F_0F0F_0F0F_0F0F).wrapping_mul(0xA01) >> 8; val = (val & 0x00FF_00FF_00FF_00FF).wrapping_mul(0x64_0001) >> 16; (val & 0x0000_FFFF_0000_FFFF).wrapping_mul(0x2710_0000_0001) >> 32 } #[inline(always)] pub(in crate::builtins::date) const fn process_4(mut val: u32, len: usize) -> u32 { val <<= 32_usize.saturating_sub(len << 3); // << 3 - same as mult by 8 val = (val & 0x0F0F_0F0F).wrapping_mul(0xA01) >> 8; (val & 0x00FF_00FF).wrapping_mul(0x64_0001) >> 16 } } impl<'a> DateParser<'a> { fn new(s: &'a str, hooks: &'a dyn HostHooks) -> Self { Self { hooks, input: s.as_bytes().iter().peekable(), year: 0, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, offset: 0, } } fn next_expect(&mut self, expect: u8) -> Option<()> { self.input .next() .and_then(|c| if *c == expect { Some(()) } else { None }) } fn next_ascii_digit(&mut self) -> Option { self.input .next() .and_then(|c| if c.is_ascii_digit() { Some(*c) } else { None }) } fn next_n_ascii_digits(&mut self) -> Option<[u8; N]> { let mut digits = [0; N]; for digit in &mut digits { *digit = self.next_ascii_digit()?; } Some(digits) } fn parse_n_ascii_digits(&mut self) -> Option { assert!(N <= 8, "parse_n_ascii_digits parses no more than 8 digits"); if N == 0 { return None; } let ascii_digits = self.next_n_ascii_digits::()?; match N { 1..4 => { // When N is small, process digits naively. let mut res = 0; for digit in ascii_digits { res = res * 10 + u64::from(digit & 0xF); } Some(res) } 4 => { // Process digits as an u32 block. let mut src = [0; 4]; src[..N].copy_from_slice(&ascii_digits); let val = u32::from_le_bytes(src); Some(u64::from(fast_atoi::process_4(val, N))) } _ => { // Process digits as an u64 block. let mut src = [0; 8]; src[..N].copy_from_slice(&ascii_digits); let val = u64::from_le_bytes(src); Some(fast_atoi::process_8(val, N)) } } } fn finish(&mut self) -> Option { if self.input.peek().is_some() { return None; } // Hour 24 is only valid as 24:00:00.000 if self.hour == 24 && (self.minute != 0 || self.second != 0 || self.millisecond != 0) { return None; } let date = make_date( make_day(self.year.into(), (self.month - 1).into(), self.day.into()), make_time( self.hour.into(), self.minute.into(), self.second.into(), self.millisecond.into(), ), ); let date = date + (self.offset as f64) * MS_PER_MINUTE; let t = time_clip(date); if t.is_finite() { Some(t as i64) } else { None } } fn finish_local(&mut self) -> Option { if self.input.peek().is_some() { return None; } // Hour 24 is only valid as 24:00:00.000 if self.hour == 24 && (self.minute != 0 || self.second != 0 || self.millisecond != 0) { return None; } let date = make_date( make_day(self.year.into(), (self.month - 1).into(), self.day.into()), make_time( self.hour.into(), self.minute.into(), self.second.into(), self.millisecond.into(), ), ); let t = time_clip(utc_t(date, self.hooks)); if t.is_finite() { Some(t as i64) } else { None } } #[allow(clippy::as_conversions)] fn parse(&mut self) -> Option { self.parse_year()?; match self.input.peek() { Some(b'T') => return self.parse_time(), None => return self.finish(), _ => {} } self.next_expect(b'-')?; self.month = self.parse_n_ascii_digits::<2>()? as u32; if self.month < 1 || self.month > 12 { return None; } match self.input.peek() { Some(b'T') => return self.parse_time(), None => return self.finish(), _ => {} } self.next_expect(b'-')?; self.day = self.parse_n_ascii_digits::<2>()? as u32; if self.day < 1 || self.day > 31 { return None; } match self.input.peek() { Some(b'T') => self.parse_time(), _ => self.finish(), } } #[allow(clippy::as_conversions)] fn parse_year(&mut self) -> Option<()> { if let &&sign @ (b'+' | b'-') = self.input.peek()? { // Consume the sign. self.input.next(); let year = self.parse_n_ascii_digits::<6>()? as i32; let neg = sign == b'-'; if neg && year == 0 { return None; } self.year = if neg { -year } else { year }; } else { self.year = self.parse_n_ascii_digits::<4>()? as i32; } Some(()) } #[allow(clippy::as_conversions)] fn parse_time(&mut self) -> Option { self.next_expect(b'T')?; self.hour = self.parse_n_ascii_digits::<2>()? as u32; if self.hour > 24 { return None; } self.next_expect(b':')?; self.minute = self.parse_n_ascii_digits::<2>()? as u32; if self.minute > 59 { return None; } match self.input.peek() { Some(b':') => self.input.next(), None => return self.finish_local(), _ => { self.parse_timezone()?; return self.finish(); } }; self.second = self.parse_n_ascii_digits::<2>()? as u32; if self.second > 59 { return None; } match self.input.peek() { Some(b'.') => self.input.next(), None => return self.finish_local(), _ => { self.parse_timezone()?; return self.finish(); } }; self.millisecond = self.parse_n_ascii_digits::<3>()? as u32; if self.input.peek().is_some() { self.parse_timezone()?; self.finish() } else { self.finish_local() } } #[allow(clippy::as_conversions)] fn parse_timezone(&mut self) -> Option<()> { match self.input.next() { Some(b'Z') => return Some(()), Some(sign @ (b'+' | b'-')) => { let neg = *sign == b'-'; let offset_hour = self.parse_n_ascii_digits::<2>()? as i64; if offset_hour > 23 { return None; } self.offset = if neg { offset_hour } else { -offset_hour } * 60; if self.input.peek().is_none() { return Some(()); } self.next_expect(b':')?; let offset_minute = self.parse_n_ascii_digits::<2>()? as i64; if offset_minute > 59 { return None; } self.offset += if neg { offset_minute } else { -offset_minute }; } _ => return None, } Some(()) } } ================================================ FILE: core/engine/src/builtins/error/aggregate.rs ================================================ //! This module implements the global `AggregateError` object. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-aggregate-error //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError use crate::{ Context, JsArgs, JsExpect, JsResult, JsString, JsValue, builtins::{ Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, iterable::IteratorHint, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::{JsObject, internal_methods::get_prototype_from_constructor}, property::{Attribute, PropertyDescriptorBuilder}, realm::Realm, string::StaticJsStrings, }; use super::{Error, ErrorKind}; #[derive(Debug, Clone, Copy)] pub(crate) struct AggregateError; impl IntrinsicObject for AggregateError { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for AggregateError { const NAME: JsString = StaticJsStrings::AGGREGATE_ERROR; } impl BuiltInConstructor for AggregateError { const CONSTRUCTOR_ARGUMENTS: usize = 2; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::aggregate_error; /// [`AggregateError ( errors, message [ , options ] )`][spec] /// /// Creates a new aggregate error object. /// /// [spec]: AggregateError ( errors, message [ , options ] ) fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. let new_target = &if new_target.is_undefined() { context .active_function_object() .unwrap_or_else(|| { context .intrinsics() .constructors() .aggregate_error() .constructor() }) .into() } else { new_target.clone() }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor( new_target, StandardConstructors::aggregate_error, context, )?; let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, Error::with_caller_position(ErrorKind::Aggregate, context), ) .upcast(); // 3. If message is not undefined, then let message = args.get_or_undefined(1); if !message.is_undefined() { // a. Let msg be ? ToString(message). let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). o.create_non_enumerable_data_property_or_throw(js_string!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). Error::install_error_cause(&o, args.get_or_undefined(2), context)?; // 5. Let errorsList be ? IteratorToList(? GetIterator(errors, sync)). let errors = args.get_or_undefined(0); let errors_list = errors .get_iterator(IteratorHint::Sync, context)? .into_list(context)?; // 6. Perform ! DefinePropertyOrThrow(O, "errors", // PropertyDescriptor { // [[Configurable]]: true, // [[Enumerable]]: false, // [[Writable]]: true, // [[Value]]: CreateArrayFromList(errorsList) // }). o.define_property_or_throw( js_string!("errors"), PropertyDescriptorBuilder::new() .configurable(true) .enumerable(false) .writable(true) .value(Array::create_array_from_list(errors_list, context)) .build(), context, ) .js_expect("should not fail according to spec")?; // 5. Return O. Ok(o.into()) } } ================================================ FILE: core/engine/src/builtins/error/eval.rs ================================================ //! This module implements the global `EvalError` object. //! //! Indicates an error regarding the global `eval()` function. //! This exception is not thrown by JavaScript anymore, however //! the `EvalError` object remains for compatibility. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError use crate::{ Context, JsResult, JsString, JsValue, builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::JsObject, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{Error, ErrorKind}; /// JavaScript `EvalError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct EvalError; impl IntrinsicObject for EvalError { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for EvalError { const NAME: JsString = StaticJsStrings::EVAL_ERROR; } impl BuiltInConstructor for EvalError { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::eval_error; /// Create a new error object. fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { Error::native_error_constructor( new_target, args, context, ErrorKind::Eval, StandardConstructors::eval_error, ) } } ================================================ FILE: core/engine/src/builtins/error/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `Error` object. //! //! Error objects are thrown when runtime errors occur. //! The Error object can also be used as a base object for user-defined exceptions. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-error-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error use crate::{ Context, JsArgs, JsData, JsResult, JsString, JsValue, builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::{IgnoreEq, JsNativeError}, js_string, object::{JsObject, internal_methods::get_prototype_from_constructor}, property::Attribute, realm::Realm, string::StaticJsStrings, vm::shadow_stack::{Backtrace, ShadowEntry}, }; use boa_gc::{Finalize, Trace}; use boa_macros::js_str; pub(crate) mod aggregate; pub(crate) mod eval; pub(crate) mod range; pub(crate) mod reference; pub(crate) mod syntax; pub(crate) mod r#type; pub(crate) mod uri; #[cfg(test)] mod tests; pub(crate) use self::aggregate::AggregateError; pub(crate) use self::eval::EvalError; pub(crate) use self::range::RangeError; pub(crate) use self::reference::ReferenceError; pub(crate) use self::syntax::SyntaxError; pub(crate) use self::r#type::TypeError; pub(crate) use self::uri::UriError; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; /// A tag of built-in `Error` object, [ECMAScript spec][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-error-objects #[derive(Debug, Copy, Clone, Eq, PartialEq, Trace, Finalize, JsData)] #[boa_gc(empty_trace)] #[non_exhaustive] pub enum ErrorKind { /// The `AggregateError` object type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-aggregate-error-objects Aggregate, /// The `Error` object type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-error-objects Error, /// The `EvalError` type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror Eval, /// The `TypeError` type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror Type, /// The `RangeError` type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror Range, /// The `ReferenceError` type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror Reference, /// The `SyntaxError` type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror Syntax, /// The `URIError` type. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror Uri, } /// A built-in `Error` object, per the [ECMAScript spec][spec]. /// /// This is used internally to convert between [`JsObject`] and /// [`JsNativeError`] correctly, but it can also be used to manually create `Error` /// objects. However, the recommended way to create them is to construct a /// `JsNativeError` first, then call [`JsNativeError::into_opaque`], /// which will assign its prototype, properties and kind automatically. /// /// For a description of every error kind and its usage, see /// [`JsNativeErrorKind`][crate::error::JsNativeErrorKind]. /// /// [spec]: https://tc39.es/ecma262/#sec-error-objects #[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize, JsData)] pub struct Error { pub(crate) tag: ErrorKind, // The position of where the Error was created does not affect equality check. #[unsafe_ignore_trace] pub(crate) position: IgnoreEq>, // The backtrace captured when this error was thrown. Stored here so it // survives the JsError → JsValue → JsError round-trip through promise // rejection. Does not affect equality checks. #[unsafe_ignore_trace] pub(crate) backtrace: IgnoreEq>, } impl Error { /// Create a new [`Error`]. #[inline] #[must_use] pub fn new(tag: ErrorKind) -> Self { Self { tag, position: IgnoreEq(None), backtrace: IgnoreEq(None), } } /// Create a new [`Error`] with the given optional [`ShadowEntry`]. pub(crate) fn with_shadow_entry(tag: ErrorKind, entry: Option) -> Self { Self { tag, position: IgnoreEq(entry), backtrace: IgnoreEq(None), } } /// Get the position from the last called function. pub(crate) fn with_caller_position(tag: ErrorKind, context: &Context) -> Self { Self { tag, position: IgnoreEq(context.vm.shadow_stack.caller_position()), backtrace: IgnoreEq(None), } } } impl IntrinsicObject for Error { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; let builder = BuiltInBuilder::from_standard_constructor::(realm) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .method(Self::to_string, js_string!("toString"), 0); #[cfg(feature = "experimental")] let builder = builder.static_method(Error::is_error, js_string!("isError"), 1); builder.build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for Error { const NAME: JsString = StaticJsStrings::ERROR; } impl BuiltInConstructor for Error { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 3; const CONSTRUCTOR_STORAGE_SLOTS: usize = 1; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::error; /// `Error( message [ , options ] )` /// /// Creates a new error object. fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. let new_target = &if new_target.is_undefined() { context .active_function_object() .unwrap_or_else(|| context.intrinsics().constructors().error().constructor()) .into() } else { new_target.clone() }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::error, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, Error::with_caller_position(ErrorKind::Error, context), ) .upcast(); // 3. If message is not undefined, then let message = args.get_or_undefined(0); if !message.is_undefined() { // a. Let msg be ? ToString(message). let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). o.create_non_enumerable_data_property_or_throw(js_string!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). Self::install_error_cause(&o, args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) } } impl Error { pub(crate) fn install_error_cause( o: &JsObject, options: &JsValue, context: &mut Context, ) -> JsResult<()> { // 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then // 1.a. Let cause be ? Get(options, "cause"). if let Some(options) = options.as_object() && let Some(cause) = options.try_get(js_string!("cause"), context)? { // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause). o.create_non_enumerable_data_property_or_throw(js_string!("cause"), cause, context); } // 2. Return unused. Ok(()) } /// `Error.prototype.toString()` /// /// The `toString()` method returns a string representing the specified Error object. /// /// More information: /// - [MDN documentation][mdn] /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. If Type(O) is not Object, throw a TypeError exception. let o = this .as_object() .ok_or_else(|| JsNativeError::typ().with_message("'this' is not an Object"))?; // 3. Let name be ? Get(O, "name"). let name = o.get(js_string!("name"), context)?; // 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name). let name = if name.is_undefined() { js_string!("Error") } else { name.to_string(context)? }; // 5. Let msg be ? Get(O, "message"). let msg = o.get(js_string!("message"), context)?; // 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg). let msg = if msg.is_undefined() { js_string!() } else { msg.to_string(context)? }; // 7. If name is the empty String, return msg. if name.is_empty() { return Ok(msg.into()); } // 8. If msg is the empty String, return name. if msg.is_empty() { return Ok(name.into()); } // 9. Return the string-concatenation of name, the code unit 0x003A (COLON), // the code unit 0x0020 (SPACE), and msg. Ok(js_string!(&name, js_str!(": "), &msg).into()) } /// [`Error.isError`][spec]. /// /// Returns a boolean indicating whether the argument is a built-in Error instance or not. /// /// [spec]: https://tc39.es/proposal-is-error/#sec-error.iserror #[cfg(feature = "experimental")] #[allow(clippy::unnecessary_wraps)] fn is_error(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { // 1. Return IsError(arg). // https://tc39.es/proposal-is-error/#sec-iserror // 1. If argument is not an Object, return false. // 2. If argument has an [[ErrorData]] internal slot, return true. // 3. Return false. Ok(args .get_or_undefined(0) .as_object() .is_some_and(|o| o.is::()) .into()) } /// Shared constructor logic for all `NativeError` subtypes. /// /// Implements the [`NativeError ( message [ , options ] )`][spec] algorithm, /// which is identical for `EvalError`, `RangeError`, `ReferenceError`, /// `SyntaxError`, `TypeError`, and `URIError`. /// /// [spec]: https://tc39.es/ecma262/#sec-nativeerror pub(super) fn native_error_constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, error_kind: ErrorKind, constructor_fn: fn(&StandardConstructors) -> &StandardConstructor, ) -> JsResult { let new_target = &if new_target.is_undefined() { context .active_function_object() .unwrap_or_else(|| { constructor_fn(context.intrinsics().constructors()).constructor() }) .into() } else { new_target.clone() }; let prototype = get_prototype_from_constructor(new_target, constructor_fn, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, Error::with_caller_position(error_kind, context), ) .upcast(); let message = args.get_or_undefined(0); if !message.is_undefined() { let msg = message.to_string(context)?; o.create_non_enumerable_data_property_or_throw(js_string!("message"), msg, context); } Error::install_error_cause(&o, args.get_or_undefined(1), context)?; Ok(o.into()) } } ================================================ FILE: core/engine/src/builtins/error/range.rs ================================================ //! This module implements the global `RangeError` object. //! //! Indicates a value that is not in the set or range of allowable values. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError use crate::{ Context, JsResult, JsString, JsValue, builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::JsObject, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{Error, ErrorKind}; /// JavaScript `RangeError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct RangeError; impl IntrinsicObject for RangeError { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for RangeError { const NAME: JsString = StaticJsStrings::RANGE_ERROR; } impl BuiltInConstructor for RangeError { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::range_error; /// Create a new error object. fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { Error::native_error_constructor( new_target, args, context, ErrorKind::Range, StandardConstructors::range_error, ) } } ================================================ FILE: core/engine/src/builtins/error/reference.rs ================================================ //! This module implements the global `ReferenceError` object. //! //! Indicates an error that occurs when de-referencing an invalid reference //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError use crate::{ Context, JsResult, JsString, JsValue, builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::JsObject, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{Error, ErrorKind}; #[derive(Debug, Clone, Copy)] pub(crate) struct ReferenceError; impl IntrinsicObject for ReferenceError { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for ReferenceError { const NAME: JsString = StaticJsStrings::REFERENCE_ERROR; } impl BuiltInConstructor for ReferenceError { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::reference_error; /// Create a new error object. fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { Error::native_error_constructor( new_target, args, context, ErrorKind::Reference, StandardConstructors::reference_error, ) } } ================================================ FILE: core/engine/src/builtins/error/syntax.rs ================================================ //! This module implements the global `SyntaxError` object. //! //! The `SyntaxError` object represents an error when trying to interpret syntactically invalid code. //! It is thrown when the JavaScript context encounters tokens or token order that does not conform //! to the syntax of the language when parsing code. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError use crate::{ Context, JsResult, JsString, JsValue, builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::JsObject, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{Error, ErrorKind}; /// JavaScript `SyntaxError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct SyntaxError; impl IntrinsicObject for SyntaxError { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for SyntaxError { const NAME: JsString = StaticJsStrings::SYNTAX_ERROR; } impl BuiltInConstructor for SyntaxError { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::syntax_error; /// Create a new error object. fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { Error::native_error_constructor( new_target, args, context, ErrorKind::Syntax, StandardConstructors::syntax_error, ) } } ================================================ FILE: core/engine/src/builtins/error/tests.rs ================================================ use crate::{TestAction, run_test_actions}; use boa_macros::js_str; use indoc::indoc; #[test] fn error_to_string() { run_test_actions([ TestAction::assert_eq("(new Error('1')).toString()", js_str!("Error: 1")), TestAction::assert_eq("(new RangeError('2')).toString()", js_str!("RangeError: 2")), TestAction::assert_eq( "(new ReferenceError('3')).toString()", js_str!("ReferenceError: 3"), ), TestAction::assert_eq( "(new SyntaxError('4')).toString()", js_str!("SyntaxError: 4"), ), TestAction::assert_eq("(new TypeError('5')).toString()", js_str!("TypeError: 5")), TestAction::assert_eq("(new EvalError('6')).toString()", js_str!("EvalError: 6")), TestAction::assert_eq("(new URIError('7')).toString()", js_str!("URIError: 7")), // no message TestAction::assert_eq("(new Error()).toString()", js_str!("Error")), TestAction::assert_eq("(new RangeError()).toString()", js_str!("RangeError")), TestAction::assert_eq( "(new ReferenceError()).toString()", js_str!("ReferenceError"), ), TestAction::assert_eq("(new SyntaxError()).toString()", js_str!("SyntaxError")), TestAction::assert_eq("(new TypeError()).toString()", js_str!("TypeError")), TestAction::assert_eq("(new EvalError()).toString()", js_str!("EvalError")), TestAction::assert_eq("(new URIError()).toString()", js_str!("URIError")), // no name TestAction::assert_eq( indoc! {r#" let message = new Error('message'); message.name = ''; message.toString() "#}, js_str!("message"), ), ]); } #[test] fn error_names() { run_test_actions([ TestAction::assert_eq("Error.name", js_str!("Error")), TestAction::assert_eq("EvalError.name", js_str!("EvalError")), TestAction::assert_eq("RangeError.name", js_str!("RangeError")), TestAction::assert_eq("ReferenceError.name", js_str!("ReferenceError")), TestAction::assert_eq("SyntaxError.name", js_str!("SyntaxError")), TestAction::assert_eq("URIError.name", js_str!("URIError")), TestAction::assert_eq("TypeError.name", js_str!("TypeError")), TestAction::assert_eq("AggregateError.name", js_str!("AggregateError")), ]); } #[test] fn error_lengths() { run_test_actions([ TestAction::assert_eq("Error.length", 1), TestAction::assert_eq("EvalError.length", 1), TestAction::assert_eq("RangeError.length", 1), TestAction::assert_eq("ReferenceError.length", 1), TestAction::assert_eq("SyntaxError.length", 1), TestAction::assert_eq("URIError.length", 1), TestAction::assert_eq("TypeError.length", 1), TestAction::assert_eq("AggregateError.length", 2), ]); } ================================================ FILE: core/engine/src/builtins/error/type.rs ================================================ //! This module implements the global `TypeError` object. //! //! The `TypeError` object represents an error when an operation could not be performed, //! typically (but not exclusively) when a value is not of the expected type. //! //! A `TypeError` may be thrown when: //! - an operand or argument passed to a function is incompatible with the type expected by that operator or function. //! - when attempting to modify a value that cannot be changed. //! - when attempting to use a value in an inappropriate way. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError use crate::{ Context, JsResult, JsString, JsValue, NativeFunction, builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, native_function::NativeFunctionObject, object::JsObject, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{Error, ErrorKind}; /// JavaScript `TypeError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct TypeError; impl IntrinsicObject for TypeError { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for TypeError { const NAME: JsString = StaticJsStrings::TYPE_ERROR; } impl BuiltInConstructor for TypeError { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::type_error; /// Create a new error object. fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { Error::native_error_constructor( new_target, args, context, ErrorKind::Type, StandardConstructors::type_error, ) } } #[derive(Debug, Clone, Copy)] pub(crate) struct ThrowTypeError; impl IntrinsicObject for ThrowTypeError { fn init(realm: &Realm) { let obj = BuiltInBuilder::with_intrinsic::(realm) .prototype(realm.intrinsics().constructors().function().prototype()) .static_property(StaticJsStrings::LENGTH, 0, Attribute::empty()) .static_property(js_string!("name"), js_string!(), Attribute::empty()) .build(); { let mut obj = obj .downcast_mut::() .expect("`%ThrowTypeError%` must be a function"); obj.f = NativeFunction::from_fn_ptr(|_, _, _| { Err(JsNativeError::typ() .with_message( "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \ functions or the arguments objects for calls to them", ) .into()) }); obj.name = js_string!(); obj.constructor = None; obj.realm = Some(realm.clone()); } obj.borrow_mut().extensible = false; } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().throw_type_error().into() } } ================================================ FILE: core/engine/src/builtins/error/uri.rs ================================================ //! This module implements the global `URIError` object. //! //! The `URIError` object represents an error when a global URI handling //! function was used in a wrong way. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError use crate::{ Context, JsResult, JsString, JsValue, builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::JsObject, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{Error, ErrorKind}; /// JavaScript `URIError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct UriError; impl IntrinsicObject for UriError { fn init(realm: &Realm) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) .property(js_string!("message"), js_string!(), attribute) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for UriError { const NAME: JsString = StaticJsStrings::URI_ERROR; } impl BuiltInConstructor for UriError { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::uri_error; /// Create a new error object. fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { Error::native_error_constructor( new_target, args, context, ErrorKind::Uri, StandardConstructors::uri_error, ) } } ================================================ FILE: core/engine/src/builtins/escape/mod.rs ================================================ //! Boa's implementation of ECMAScript's string escaping functions. //! //! The `escape()` function replaces all characters with escape sequences, with the exception of ASCII //! word characters (A–Z, a–z, 0–9, _) and @*_+-./. //! //! The `unescape()` function replaces any escape sequence with the character that it represents. //! //! More information: //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-additional-properties-of-the-global-object use crate::{ Context, JsArgs, JsObject, JsResult, JsString, JsValue, context::intrinsics::Intrinsics, js_string, realm::Realm, string::StaticJsStrings, }; use super::{BuiltInBuilder, BuiltInObject, IntrinsicObject}; /// The `escape` function #[derive(Debug, Clone, Copy)] pub(crate) struct Escape; impl IntrinsicObject for Escape { fn init(realm: &Realm) { BuiltInBuilder::callable_with_intrinsic::(realm, escape) .name(Self::NAME) .length(1) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().escape().into() } } impl BuiltInObject for Escape { const NAME: JsString = StaticJsStrings::ESCAPE; } /// Builtin JavaScript `escape ( string )` function. fn escape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { /// Returns `true` if the codepoint `cp` is part of the `unescapedSet`. fn is_unescaped(cp: u16) -> bool { let Ok(cp) = TryInto::::try_into(cp) else { return false; }; // 4. Let unescapedSet be the string-concatenation of the ASCII word characters and "@*+-./". cp.is_ascii_alphanumeric() || [b'_', b'@', b'*', b'+', b'-', b'.', b'/'].contains(&cp) } // 1. Set string to ? ToString(string). let string = args.get_or_undefined(0).to_string(context)?; // 3. Let R be the empty String. let mut vec = Vec::with_capacity(string.len()); // 2. Let len be the length of string. // 5. Let k be 0. // 6. Repeat, while k < len, // a. Let C be the code unit at index k within string. for cp in &string { // b. If unescapedSet contains C, then if is_unescaped(cp) { // i. Let S be C. vec.push(cp); continue; } // c. Else, // i. Let n be the numeric value of C. // ii. If n < 256, then let c = if cp < 256 { // 1. Let hex be the String representation of n, formatted as an uppercase hexadecimal number. // 2. Let S be the string-concatenation of "%" and ! StringPad(hex, 2𝔽, "0", start). format!("%{cp:02X}") } // iii. Else, else { // 1. Let hex be the String representation of n, formatted as an uppercase hexadecimal number. // 2. Let S be the string-concatenation of "%u" and ! StringPad(hex, 4𝔽, "0", start). format!("%u{cp:04X}") }; // d. Set R to the string-concatenation of R and S. // e. Set k to k + 1. vec.extend(c.encode_utf16()); } // 7. Return R. Ok(js_string!(&vec[..]).into()) } /// The `unescape` function #[derive(Debug, Clone, Copy)] pub(crate) struct Unescape; impl IntrinsicObject for Unescape { fn init(realm: &Realm) { BuiltInBuilder::callable_with_intrinsic::(realm, unescape) .name(Self::NAME) .length(1) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().unescape().into() } } impl BuiltInObject for Unescape { const NAME: JsString = StaticJsStrings::UNESCAPE; } /// Builtin JavaScript `unescape ( string )` function. fn unescape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { /// Converts a char `cp` to its corresponding hex digit value. fn to_hex_digit(cp: u16) -> Option { char::from_u32(u32::from(cp)) .and_then(|c| c.to_digit(16)) .and_then(|d| d.try_into().ok()) } // 1. Set string to ? ToString(string). let string = args.get_or_undefined(0).to_string(context)?; // 3. Let R be the empty String. let mut vec = Vec::with_capacity(string.len()); let mut codepoints = >::new(string.iter()); // 2. Let len be the length of string. // 4. Let k be 0. // 5. Repeat, while k < len, loop { // a. Let C be the code unit at index k within string. let Some(cp) = codepoints.next() else { break; }; // b. If C is the code unit 0x0025 (PERCENT SIGN), then if cp != u16::from(b'%') { vec.push(cp); continue; } // i. Let hexDigits be the empty String. // ii. Let optionalAdvance be 0. // TODO: Try blocks :( let Some(unescaped_cp) = (|| match *codepoints.peek_n(5) { // iii. If k + 5 < len and the code unit at index k + 1 within string is the code unit // 0x0075 (LATIN SMALL LETTER U), then [u, n1, n2, n3, n4] if u == u16::from(b'u') => { // 1. Set hexDigits to the substring of string from k + 2 to k + 6. // 2. Set optionalAdvance to 5. let n1 = to_hex_digit(n1)?; let n2 = to_hex_digit(n2)?; let n3 = to_hex_digit(n3)?; let n4 = to_hex_digit(n4)?; // TODO: https://github.com/rust-lang/rust/issues/77404 for _ in 0..5 { codepoints.next(); } Some((n1 << 12) + (n2 << 8) + (n3 << 4) + n4) } // iv. Else if k + 3 ≤ len, then [n1, n2, ..] => { // 1. Set hexDigits to the substring of string from k + 1 to k + 3. // 2. Set optionalAdvance to 2. let n1 = to_hex_digit(n1)?; let n2 = to_hex_digit(n2)?; // TODO: https://github.com/rust-lang/rust/issues/77404 for _ in 0..2 { codepoints.next(); } Some((n1 << 4) + n2) } _ => None, })() else { vec.push(u16::from(b'%')); continue; }; // v. Let parseResult be ParseText(StringToCodePoints(hexDigits), HexDigits[~Sep]). // vi. If parseResult is a Parse Node, then // 1. Let n be the MV of parseResult. // 2. Set C to the code unit whose numeric value is n. // 3. Set k to k + optionalAdvance. // c. Set R to the string-concatenation of R and C. // d. Set k to k + 1. vec.push(unescaped_cp); } // 6. Return R. Ok(js_string!(&vec[..]).into()) } /// An iterator that can peek `N` items. struct PeekableN where I: Iterator, { iterator: I, buffer: [I::Item; N], buffered_end: usize, } impl PeekableN where I: Iterator, I::Item: Default + Copy, { /// Creates a new `PeekableN`. fn new(iterator: I) -> Self { Self { iterator, buffer: [I::Item::default(); N], buffered_end: 0, } } /// Peeks `n` items from the iterator. fn peek_n(&mut self, count: usize) -> &[I::Item] { if count <= self.buffered_end { return &self.buffer[..count]; } for _ in 0..(count - self.buffered_end) { let Some(next) = self.iterator.next() else { return &self.buffer[..self.buffered_end]; }; self.buffer[self.buffered_end] = next; self.buffered_end += 1; } &self.buffer[..count] } } impl Iterator for PeekableN where I: Iterator, I::Item: Copy, { type Item = I::Item; fn next(&mut self) -> Option { if self.buffered_end > 0 { let item = self.buffer[0]; self.buffer.rotate_left(1); self.buffered_end -= 1; return Some(item); } self.iterator.next() } } ================================================ FILE: core/engine/src/builtins/eval/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `eval` function. //! //! The `eval()` function evaluates ECMAScript code represented as a string. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-eval-x //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval use crate::{ Context, JsArgs, JsExpect, JsResult, JsString, JsValue, SpannedSourceText, builtins::{BuiltInObject, function::OrdinaryFunction}, bytecompiler::{ByteCompiler, prepare_eval_declaration_instantiation}, context::intrinsics::Intrinsics, environments::SavedEnvironments, error::JsNativeError, js_string, object::JsObject, realm::Realm, spanned_source_text::SourceText, string::StaticJsStrings, vm::{CallFrame, CallFrameFlags, source_info::SourcePath}, }; use boa_ast::{ operations::{ContainsSymbol, contains, contains_arguments}, scope::Scope, }; use boa_gc::Gc; use boa_parser::{Parser, Source}; use super::{BuiltInBuilder, IntrinsicObject}; #[derive(Debug, Clone, Copy)] pub(crate) struct Eval; impl IntrinsicObject for Eval { fn init(realm: &Realm) { BuiltInBuilder::callable_with_intrinsic::(realm, Self::eval) .name(Self::NAME) .length(1) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().eval().into() } } impl BuiltInObject for Eval { const NAME: JsString = StaticJsStrings::EVAL; } impl Eval { /// `19.2.1 eval ( x )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-eval-x fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Return ? PerformEval(x, false, false). Self::perform_eval(args.get_or_undefined(0), false, None, false, context) } /// `19.2.1.1 PerformEval ( x, strictCaller, direct )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-performeval pub(crate) fn perform_eval( x: &JsValue, direct: bool, lexical_scope: Option, mut strict: bool, context: &mut Context, ) -> JsResult { bitflags::bitflags! { /// Flags used to throw early errors on invalid `eval` calls. #[derive(Default)] struct Flags: u8 { const IN_FUNCTION = 0b0001; const IN_METHOD = 0b0010; const IN_DERIVED_CONSTRUCTOR = 0b0100; const IN_CLASS_FIELD_INITIALIZER = 0b1000; } } /// Possible actions that can be executed after exiting this function to restore the environment to its /// original state. enum EnvStackAction { Truncate(usize), Restore(SavedEnvironments), } // 1. Assert: If direct is false, then strictCaller is also false. debug_assert!(direct || !strict); // 2. If Type(x) is not String, return x. // TODO: rework parser to take an iterator of `u32` unicode codepoints let Some(x) = x.as_string() else { return Ok(x.clone()); }; // Because of implementation details the following code differs from the spec. // 3. Let evalRealm be the current Realm Record. // 4. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval // and of the eval function itself. let eval_realm = context.realm().clone(); // 5. Perform ? HostEnsureCanCompileStrings(evalRealm, « », x, direct). context .host_hooks() .ensure_can_compile_strings(eval_realm, &[], &x, direct, context)?; // 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection: // a. Let script be ParseText(StringToCodePoints(x), Script). // b. If script is a List of errors, throw a SyntaxError exception. // c. If script Contains ScriptBody is false, return undefined. // d. Let body be the ScriptBody of script. let x = x.to_vec(); let source = Source::from_utf16(&x); let mut parser = Parser::new(source); parser.set_identifier(context.next_parser_identifier()); if strict { parser.set_strict(); } let (mut body, source) = parser.parse_eval(direct, context.interner_mut())?; // 6. Let inFunction be false. // 7. Let inMethod be false. // 8. Let inDerivedConstructor be false. // 9. Let inClassFieldInitializer be false. // a. Let thisEnvRec be GetThisEnvironment(). let flags = match { let frame = context.vm.frame(); frame .environments .get_this_environment(frame.realm.environment()) } .as_function() { // 10. If direct is true, then // b. If thisEnvRec is a Function Environment Record, then Some(function_env) if direct => { // i. Let F be thisEnvRec.[[FunctionObject]]. let function_object = function_env .slots() .function_object() .downcast_ref::() .js_expect("must be function object")?; // ii. Set inFunction to true. let mut flags = Flags::IN_FUNCTION; // iii. Set inMethod to thisEnvRec.HasSuperBinding(). if function_env.has_super_binding() { flags |= Flags::IN_METHOD; } // iv. If F.[[ConstructorKind]] is derived, set inDerivedConstructor to true. if function_object.is_derived_constructor() { flags |= Flags::IN_DERIVED_CONSTRUCTOR; } // v. Let classFieldInitializerName be F.[[ClassFieldInitializerName]]. // vi. If classFieldInitializerName is not empty, set inClassFieldInitializer to true. if function_object.in_class_field_initializer() { flags |= Flags::IN_CLASS_FIELD_INITIALIZER; } flags } _ => Flags::default(), }; if !flags.contains(Flags::IN_FUNCTION) && contains(&body, ContainsSymbol::NewTarget) { return Err(JsNativeError::syntax() .with_message("invalid `new.target` expression inside eval") .into()); } if !flags.contains(Flags::IN_METHOD) && contains(&body, ContainsSymbol::SuperProperty) { return Err(JsNativeError::syntax() .with_message("invalid `super` reference inside eval") .into()); } if !flags.contains(Flags::IN_DERIVED_CONSTRUCTOR) && contains(&body, ContainsSymbol::SuperCall) { return Err(JsNativeError::syntax() .with_message("invalid `super` call inside eval") .into()); } if flags.contains(Flags::IN_CLASS_FIELD_INITIALIZER) && contains_arguments(&body) { return Err(JsNativeError::syntax() .with_message("invalid `arguments` reference inside eval") .into()); } strict |= body.strict(); // Because our environment model does not map directly to the spec, this section looks very different. // 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`. // 14 - 33 are in the following section, together with EvalDeclarationInstantiation. let action = if direct { // If the call to eval is direct, the code is executed in the current environment. // Poison the last parent function environment, because it may contain new declarations after/during eval. if !strict { { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame.environments.poison_until_last_function(global); } } // Set the compile time environment to the current running environment and save the number of current environments. let environments_len = context.vm.frame().environments.len(); // Pop any added runtime environments that were not removed during the eval execution. EnvStackAction::Truncate(environments_len) } else { // If the call to eval is indirect, the code is executed in the global environment. // Pop all environments before the eval execution. let saved = context.vm.frame_mut().environments.pop_to_global(); // Restore all environments to the state from before the eval execution. EnvStackAction::Restore(saved) }; let context = &mut context.guard(move |ctx| match action { EnvStackAction::Truncate(len) => ctx.vm.frame_mut().environments.truncate(len), EnvStackAction::Restore(saved) => { ctx.vm.frame_mut().environments.truncate(0); ctx.vm.frame_mut().environments.restore_from_saved(saved); } }); let (var_environment, mut variable_scope) = if let Some(e) = context.vm.frame().environments.outer_function_environment() { (e.0, e.1) } else { ( context.realm().environment().clone(), context.realm().scope().clone(), ) }; let lexical_scope = lexical_scope.unwrap_or(context.realm().scope().clone()); let lexical_scope = Scope::new(lexical_scope, strict); let mut annex_b_function_names = Vec::new(); prepare_eval_declaration_instantiation( &mut annex_b_function_names, &body, strict, if strict { &lexical_scope } else { &variable_scope }, &lexical_scope, context, )?; let in_with = context.vm.frame().environments.has_object_environment(); let source_text = SourceText::new(source); let spanned_source_text = SpannedSourceText::new_source_only(source_text); let mut compiler = ByteCompiler::new( js_string!(""), body.strict(), false, variable_scope.clone(), lexical_scope.clone(), false, false, context.interner_mut(), in_with, spanned_source_text, // TODO: Could give more information from previous shadow stack. SourcePath::Eval, ); // Increments the number of open environments to // account for the environment scope pushed to // the context at the end of `perform_eval`. compiler.current_open_environments_count += 1; if strict { variable_scope = lexical_scope.clone(); compiler.variable_scope = lexical_scope.clone(); } #[cfg(feature = "annex-b")] { compiler .annex_b_function_names .clone_from(&annex_b_function_names); } let bindings = body .analyze_scope_eval( strict, &variable_scope, &lexical_scope, &annex_b_function_names, compiler.interner(), ) .map_err(|e| JsNativeError::syntax().with_message(e))?; compiler.eval_declaration_instantiation(&body, strict, &variable_scope, bindings); compiler.compile_statement_list(body.statements(), true, false); let code_block = Gc::new(compiler.finish()); // Strict calls don't need extensions, since all strict eval calls push a new // function environment before evaluating. if !strict { var_environment.extend_from_compile(); } let env_fp = context.vm.frame().environments.len() as u32; let environments = context.vm.frame().environments.clone(); let realm = context.realm().clone(); context.vm.push_frame_with_stack( CallFrame::new(code_block.clone(), None, environments, realm) .with_env_fp(env_fp) .with_flags(CallFrameFlags::EXIT_EARLY), JsValue::undefined(), JsValue::null(), ); context.realm().resize_global_env(); // Pushing the scope here ensures any function objects created // in `eval_declaration_instantiation` get their proper // environment stack. { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame .environments .push_lexical(lexical_scope.num_bindings_non_local(), global); } context .eval_declaration_instantiation(&code_block) .inspect_err(|_| { context.vm.pop_frame(); })?; let record = context.run(); context.vm.pop_frame(); record.consume() } } ================================================ FILE: core/engine/src/builtins/finalization_registry/mod.rs ================================================ //! Boa's implementation of ECMAScript's `FinalizationRegistry` object. use std::{ cell::{Cell, RefCell}, slice, }; use boa_gc::{Ephemeron, Finalize, Gc, Trace, WeakGc}; use crate::{ Context, JsArgs, JsData, JsObject, JsResult, JsSymbol, JsValue, JsVariant, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, job::{Job, JobCallback, NativeAsyncJob}, js_error, js_string, object::{ ErasedVTableObject, JsFunction, VTableObject, internal_methods::get_prototype_from_constructor, }, property::Attribute, realm::Realm, string::StaticJsStrings, }; use super::{BuiltInConstructor, BuiltInObject, IntrinsicObject, builder::BuiltInBuilder}; #[cfg(test)] mod tests; /// On GG collection, sends a message to a [`FinalizationRegistry`] indicating that it needs to /// be collected. #[derive(Trace)] struct CleanupSignaler(#[unsafe_ignore_trace] Cell>>); impl Finalize for CleanupSignaler { fn finalize(&self) { if let Some(sender) = self.0.take() && let Some(sender) = sender.upgrade() { // We don't need to handle errors: // - If the channel is full, the `FinalizationRegistry` has already // been enqueued for cleanup. // - If the channel is closed, the `FinalizationRegistry` was // GC'd, so we don't need to worry about cleanups. let _ = sender.try_send(()); } } } /// A cell tracked by a [`FinalizationRegistry`]. #[derive(Trace, Finalize)] pub(crate) struct RegistryCell { target: Ephemeron, held_value: JsValue, unregister_token: Option>, } /// Boa's implementation of ECMAScript's [`FinalizationRegistry`] builtin object. /// /// `FinalizationRegistry` provides a way to request that a cleanup callback get called at some point /// when a value registered with the registry has been reclaimed (garbage-collected). /// /// [`FinalizationRegistry`]: https://tc39.es/ecma262/#sec-finalization-registry-objects #[derive(Trace, Finalize, JsData)] pub(crate) struct FinalizationRegistry { realm: Realm, callback: JobCallback, #[unsafe_ignore_trace] cleanup_notifier: async_channel::Sender<()>, cells: Vec, } impl IntrinsicObject for FinalizationRegistry { fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } fn init(realm: &Realm) { BuiltInBuilder::from_standard_constructor::(realm) .property( JsSymbol::to_string_tag(), js_string!("FinalizationRegistry"), Attribute::CONFIGURABLE, ) .method(Self::register, js_string!("register"), 2) .method(Self::unregister, js_string!("unregister"), 1) .build(); } } impl BuiltInObject for FinalizationRegistry { const NAME: crate::JsString = StaticJsStrings::FINALIZATION_REGISTRY; const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE); } impl BuiltInConstructor for FinalizationRegistry { const CONSTRUCTOR_ARGUMENTS: usize = 1; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const PROTOTYPE_STORAGE_SLOTS: usize = 3; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::finalization_registry; /// Constructor [`FinalizationRegistry ( cleanupCallback )`][cons] /// /// [cons]: https://tc39.es/ecma262/#sec-finalization-registry-cleanup-callback fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { return Err(js_error!( TypeError: "FinalizationRegistry: cannot call constructor without `new`" )); } // 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception. let callback = args .get_or_undefined(0) .as_object() .and_then(JsFunction::from_object) .ok_or_else(|| { js_error!( TypeError: "FinalizationRegistry: \ cleanup callback of registry must be callable" ) })?; // 3. Let finalizationRegistry be ? OrdinaryCreateFromConstructor(NewTarget, // "%FinalizationRegistry.prototype%", « [[Realm]], [[CleanupCallback]], [[Cells]] »). let prototype = get_prototype_from_constructor( new_target, StandardConstructors::finalization_registry, context, )?; // 4. Let fn be the active function object. // 5. Set finalizationRegistry.[[Realm]] to fn.[[Realm]]. let realm = context.vm.frame().realm.clone(); // 6. Set finalizationRegistry.[[CleanupCallback]] to HostMakeJobCallback(cleanupCallback). let callback = context.host_hooks().make_job_callback(callback, context); // 7. Set finalizationRegistry.[[Cells]] to a new empty List. let cells = Vec::new(); let (sender, receiver) = async_channel::bounded(1); let registry = JsObject::new_unique( prototype, FinalizationRegistry { realm, callback, cells, cleanup_notifier: sender, }, ); let weak_registry = WeakGc::new(registry.inner()); { async fn inner_cleanup( weak_registry: WeakGc>, receiver: async_channel::Receiver<()>, context: &RefCell<&mut Context>, ) -> JsResult { let Ok(()) = receiver.recv().await else { return Ok(JsValue::undefined()); }; let Some(registry) = weak_registry.upgrade().map(JsObject::from_inner) else { return Ok(JsValue::undefined()); }; let result = FinalizationRegistry::cleanup(®istry, &mut context.borrow_mut()); context .borrow_mut() .enqueue_job(Job::FinalizationRegistryCleanupJob(NativeAsyncJob::new( async move |context| inner_cleanup(weak_registry, receiver, context).await, ))); result.map(|()| JsValue::undefined()) } context.enqueue_job(Job::FinalizationRegistryCleanupJob(NativeAsyncJob::new( async move |ctx| inner_cleanup(weak_registry, receiver, ctx).await, ))); } // 8. Return finalizationRegistry. Ok(registry.upcast().into()) } } impl FinalizationRegistry { /// [`FinalizationRegistry.prototype.register ( target, heldValue [ , unregisterToken ] )`][spec] /// /// [spec]: https://tc39.es/ecma262/sec-finalization-registry.prototype.register fn register(this: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult { // 1. Let finalizationRegistry be the this value. // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). let this = this.as_object(); let mut registry = this .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| { js_error!( TypeError: "FinalizationRegistry.prototype.register: \ invalid object type for `this`", ) })?; let target = args.get_or_undefined(0); let held_value = args.get_or_undefined(1); let unregister_token = args.get_or_undefined(2); // 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception. // // [`CanBeHeldWeakly ( v )`](https://tc39.es/ecma262/#sec-canbeheldweakly) // // 1. If v is an Object, return true. // 2. If v is a Symbol and KeyForSymbol(v) is undefined, return true. // 3. Return false. // // TODO: support Symbols let Some(target_obj) = target.as_object() else { return Err(js_error!( TypeError: "FinalizationRegistry.prototype.register: \ `target` must be an Object or Symbol", )); }; // 4. If SameValue(target, heldValue) is true, throw a TypeError exception. if target == held_value { return Err(js_error!( TypeError: "FinalizationRegistry.prototype.register: \ `heldValue` cannot be the same as `target`" )); } // 5. If CanBeHeldWeakly(unregisterToken) is false, then // // // [`CanBeHeldWeakly ( v )`](https://tc39.es/ecma262/#sec-canbeheldweakly) // // 1. If v is an Object, return true. // 2. If v is a Symbol and KeyForSymbol(v) is undefined, return true. // 3. Return false. // // TODO: support Symbols let unregister_token = match unregister_token.variant() { JsVariant::Object(obj) => Some(WeakGc::new(obj.inner())), // b. Set unregisterToken to empty. JsVariant::Undefined => None, // a. If unregisterToken is not undefined, throw a TypeError exception. _ => { return Err(js_error!( TypeError: "FinalizationRegistry.prototype.register: \ `unregisterToken` must be an Object, a Symbol, or undefined", )); } }; // 6. Let cell be the Record { [[WeakRefTarget]]: target, [[HeldValue]]: heldValue, [[UnregisterToken]]: unregisterToken }. let cell = RegistryCell { target: Ephemeron::new( target_obj.inner(), CleanupSignaler(Cell::new(Some( registry.cleanup_notifier.clone().downgrade(), ))), ), held_value: held_value.clone(), unregister_token, }; // 7. Append cell to finalizationRegistry.[[Cells]]. registry.cells.push(cell); // 8. Return undefined. Ok(JsValue::undefined()) } /// [`FinalizationRegistry.prototype.unregister ( unregisterToken )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-finalization-registry.prototype.unregister fn unregister(this: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult { // 1. Let finalizationRegistry be the this value. // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). let this = this.as_object(); let mut registry = this .as_ref() .and_then(JsObject::downcast_mut::) .ok_or_else(|| { js_error!( TypeError: "FinalizationRegistry.prototype.register: \ invalid object type for `this`", ) })?; // 3. If CanBeHeldWeakly(unregisterToken) is false, throw a TypeError exception.\ // // // [`CanBeHeldWeakly ( v )`](https://tc39.es/ecma262/#sec-canbeheldweakly) // // 1. If v is an Object, return true. // 2. If v is a Symbol and KeyForSymbol(v) is undefined, return true. // 3. Return false. // // TODO: support Symbols let unregister_token = args.get_or_undefined(0).as_object(); let unregister_token = unregister_token .as_ref() .map(JsObject::inner) .ok_or_else(|| { js_error!( TypeError: "FinalizationRegistry.prototype.unregister: \ `unregisterToken` must be an Object or a Symbol.", ) })?; // 4. Let removed be false. let mut removed = false; let mut i = 0; // 5. For each Record { [[WeakRefTarget]], [[HeldValue]], [[UnregisterToken]] } cell of finalizationRegistry.[[Cells]], do while i < registry.cells.len() { let cell = ®istry.cells[i]; // a. If cell.[[UnregisterToken]] is not empty and SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then if let Some(tok) = cell.unregister_token.as_ref() && let Some(tok) = tok.upgrade() && Gc::ptr_eq(&tok, unregister_token) { // i. Remove cell from finalizationRegistry.[[Cells]]. let cell = registry.cells.swap_remove(i); let _key = cell.target.key(); // TODO: it might be better to add a special ref for the value that // also preserves the original key instead. cell.target.value().and_then(|v| v.0.take()); // ii. Set removed to true. removed = true; } else { i += 1; } } // 6. Return removed. Ok(removed.into()) } /// Abstract operation [`CleanupFinalizationRegistry ( finalizationRegistry )`][spec]. /// /// Cleans up all the cells of the finalization registry that are determined to be /// unreachable by the garbage collector. /// /// # Panics /// /// Panics if `obj` is not a `FinalizationRegistry` object. /// /// [spec]: https://tc39.es/ecma262/#sec-cleanup-finalization-registry pub(crate) fn cleanup( obj: &JsObject, context: &mut Context, ) -> JsResult<()> { // 1. Assert: finalizationRegistry has [[Cells]] and [[CleanupCallback]] internal slots. // let obj = obj.borrow_mut(); // let registry = obj.data_mut(); // 2. Let callback be finalizationRegistry.[[CleanupCallback]]. let callback = std::mem::replace( &mut obj.borrow_mut().data_mut().callback, JobCallback::new(context.intrinsics().objects().throw_type_error(), ()), ); let mut i = 0; let result = loop { if i >= obj.borrow().data().cells.len() { break Ok(()); } // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is empty, an implementation may perform the following steps: if obj.borrow().data().cells[i].target.has_value() { i += 1; } else { // a. Choose any such cell. // b. Remove cell from finalizationRegistry.[[Cells]]. let cell = obj.borrow_mut().data_mut().cells.swap_remove(i); // c. Perform ? HostCallJobCallback(callback, undefined, « cell.[[HeldValue]] »). let result = context.host_hooks().call_job_callback( &callback, &JsValue::undefined(), slice::from_ref(&cell.held_value), context, ); if let Err(err) = result { break Err(err); } } }; obj.borrow_mut().data_mut().callback = callback; // 4. Return unused. result } } ================================================ FILE: core/engine/src/builtins/finalization_registry/tests.rs ================================================ mod miri { use indoc::indoc; use crate::{TestAction, run_test_actions}; #[test] fn finalization_registry_simple() { run_test_actions([ TestAction::run(indoc! {r#" let counter = 0; const registry = new FinalizationRegistry(() => { counter++; }); registry.register(["foo"]); "#}), TestAction::assert_eq("counter", 0), TestAction::inspect_context(|_| boa_gc::force_collect()), TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()), // Callback should run at least once TestAction::assert_eq("counter", 1), ]); } #[test] fn finalization_registry_unregister() { run_test_actions([ TestAction::run(indoc! {r#" let counter = 0; const registry = new FinalizationRegistry(() => { counter++; }); { let array = ["foo"]; registry.register(array, undefined, array); registry.unregister(array); } "#}), TestAction::assert_eq("counter", 0), TestAction::inspect_context(|_| boa_gc::force_collect()), TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()), // Callback shouldn't run TestAction::assert_eq("counter", 0), ]); } #[test] fn finalization_registry_held_value_handover() { run_test_actions([ TestAction::run(indoc! {r#" let counter = 0; const registry = new FinalizationRegistry((value) => { counter += value.increment; }); registry.register(["foo"], { increment: 5 }); "#}), TestAction::assert_eq("counter", 0), TestAction::inspect_context(|_| boa_gc::force_collect()), TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()), // Registry should handover the held value as argument TestAction::assert_eq("counter", 5), ]); } #[test] fn finalization_registry_unrelated_unregister_token() { run_test_actions([ TestAction::run(indoc! {r#" let counter = 0; const registry = new FinalizationRegistry((value) => { counter += 1; }); registry.register(["foo"], undefined, {}); registry.unregister({}); "#}), TestAction::assert_eq("counter", 0), TestAction::inspect_context(|_| boa_gc::force_collect()), TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()), // Object should not have been unregistered if the token is not the correct one TestAction::assert_eq("counter", 1), ]); } } ================================================ FILE: core/engine/src/builtins/function/arguments.rs ================================================ use crate::{ Context, JsData, JsExpect, JsResult, JsValue, bytecompiler::ToJsString, environments::DeclarativeEnvironment, object::{ JsObject, internal_methods::{ InternalMethodPropertyContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, ordinary_set, ordinary_try_get, }, }, property::{DescriptorKind, PropertyDescriptor, PropertyKey}, }; use boa_ast::{function::FormalParameterList, operations::bound_names, scope::Scope}; use boa_gc::{Finalize, Gc, Trace}; use boa_interner::Interner; use rustc_hash::FxHashMap; use thin_vec::{ThinVec, thin_vec}; #[derive(Debug, Copy, Clone, Trace, Finalize, JsData)] #[boa_gc(empty_trace)] pub(crate) struct UnmappedArguments; impl UnmappedArguments { /// Creates a new unmapped Arguments ordinary object. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createunmappedargumentsobject #[allow(clippy::new_ret_no_self)] pub(crate) fn new(arguments_list: &[JsValue], context: &mut Context) -> JsObject { // 1. Let len be the number of elements in argumentsList. let len = arguments_list.len(); let values_function = context.intrinsics().objects().array_prototype_values(); let throw_type_error = context.intrinsics().objects().throw_type_error(); // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »). // 3. Set obj.[[ParameterMap]] to undefined. // skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]` let obj = context .intrinsics() .templates() .unmapped_arguments() .create( Self, vec![ // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). len.into(), // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, // [[Configurable]]: true }). values_function.into(), // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, // [[Configurable]]: false }). throw_type_error.clone().into(), // get throw_type_error.into(), // set ], ); // 5. Let index be 0. // 6. Repeat, while index < len, // a. Let val be argumentsList[index]. // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). // c. Set index to index + 1. obj.borrow_mut() .properties_mut() .override_indexed_properties(arguments_list.iter().cloned().collect()); // 9. Return obj. obj } } /// `MappedArguments` represents an Arguments exotic object. /// /// This struct stores all the data to access mapped function parameters in their environment. #[derive(Debug, Clone, Trace, Finalize)] pub(crate) struct MappedArguments { #[unsafe_ignore_trace] binding_indices: Vec>, environment: Gc, } impl JsData for MappedArguments { fn internal_methods(&self) -> &'static InternalObjectMethods { static METHODS: InternalObjectMethods = InternalObjectMethods { __get_own_property__: arguments_exotic_get_own_property, __define_own_property__: arguments_exotic_define_own_property, __try_get__: arguments_exotic_try_get, __get__: arguments_exotic_get, __set__: arguments_exotic_set, __delete__: arguments_exotic_delete, ..ORDINARY_INTERNAL_METHODS }; &METHODS } } impl MappedArguments { /// Deletes the binding with the given index from the parameter map. pub(crate) fn delete(&mut self, index: u32) { if let Some(binding) = self.binding_indices.get_mut(index as usize) { *binding = None; } } /// Get the value of the binding at the given index from the function environment. /// /// Note: This function is the abstract getter closure described in 10.4.4.7.1 `MakeArgGetter ( name, env )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-makearggetter pub(crate) fn get(&self, index: u32) -> Option { let binding_index = self .binding_indices .get(index as usize) .copied() .flatten()?; self.environment.get(binding_index) } /// Set the value of the binding at the given index in the function environment. /// /// Note: This function is the abstract setter closure described in 10.4.4.7.2 `MakeArgSetter ( name, env )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-makeargsetter pub(crate) fn set(&self, index: u32, value: &JsValue) { if let Some(binding_index) = self.binding_indices.get(index as usize).copied().flatten() { self.environment.set(binding_index, value.clone()); } } } impl MappedArguments { pub(crate) fn binding_indices( formals: &FormalParameterList, scope: &Scope, interner: &Interner, ) -> ThinVec> { // Section 17-19 are done first, for easier object creation in 11. // // The section 17-19 differs from the spec, due to the way the runtime environments work. // // This section creates getters and setters for all mapped arguments. // Getting and setting values on the `arguments` object will actually access the bindings in the environment: // ``` // function f(a) {console.log(a); arguments[0] = 1; console.log(a)}; // f(0) // 0, 1 // ``` // // The spec assumes, that identifiers are used at runtime to reference bindings in the environment. // We use indices to access environment bindings at runtime. // To map to function parameters to binding indices, we use the fact, that bindings in a // function environment start with all of the arguments in order: // // Note: The first binding (binding 0) is where "arguments" is stored. // // `function f (a,b,c)` // | binding index | `arguments` property key | identifier | // | 1 | 0 | a | // | 2 | 1 | b | // | 3 | 2 | c | // // Notice that the binding index does not correspond to the argument index: // `function f (a,a,b)` => binding indices 0 (a), 1 (b), 2 (c) // | binding index | `arguments` property key | identifier | // | - | 0 | - | // | 1 | 1 | a | // | 2 | 2 | b | // // While the `arguments` object contains all arguments, they must not be all bound. // In the case of duplicate parameter names, the last one is bound as the environment binding. // // The following logic implements the steps 17-19 adjusted for our environment structure. let mut bindings = FxHashMap::default(); let mut property_index = 0; for name in bound_names(formals) { let binding_index = scope .get_binding(&name.to_js_string(interner)) .expect("binding must exist") .binding_index(); let entry = bindings .entry(name) .or_insert((binding_index, property_index)); entry.1 = property_index; property_index += 1; } let mut binding_indices = thin_vec![None; property_index]; for (binding_index, property_index) in bindings.values() { binding_indices[*property_index] = Some(*binding_index); } binding_indices } /// Creates a new mapped Arguments exotic object. /// /// #[allow(clippy::new_ret_no_self)] pub(crate) fn new( func: &JsObject, binding_indices: &[Option], arguments_list: &[JsValue], env: &Gc, context: &Context, ) -> JsObject { // 1. Assert: formals does not contain a rest parameter, any binding patterns, or any initializers. // It may contain duplicate identifiers. // 2. Let len be the number of elements in argumentsList. let len = arguments_list.len(); // 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »). // 4. Set obj.[[GetOwnProperty]] as specified in 10.4.4.1. // 5. Set obj.[[DefineOwnProperty]] as specified in 10.4.4.2. // 6. Set obj.[[Get]] as specified in 10.4.4.3. // 7. Set obj.[[Set]] as specified in 10.4.4.4. // 8. Set obj.[[Delete]] as specified in 10.4.4.5. // 9. Set obj.[[Prototype]] to %Object.prototype%. let range = binding_indices.len().min(len); let map = MappedArguments { binding_indices: binding_indices[..range].to_vec(), environment: env.clone(), }; // %Array.prototype.values% let values_function = context.intrinsics().objects().array_prototype_values(); // 11. Set obj.[[ParameterMap]] to map. let obj = context.intrinsics().templates().mapped_arguments().create( map, vec![ // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). len.into(), // 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, // [[Configurable]]: true }). values_function.into(), // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). func.clone().into(), ], ); // 14. Let index be 0. // 15. Repeat, while index < len, // a. Let val be argumentsList[index]. // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). // Note: Direct initialization of indexed array is used here because `CreateDataPropertyOrThrow` // would cause a panic while executing exotic argument object set methods before the variables // in the environment are initialized. obj.borrow_mut() .properties_mut() .override_indexed_properties(arguments_list.iter().cloned().collect()); // 22. Return obj. obj } } /// `[[GetOwnProperty]]` for arguments exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-getownproperty-p pub(crate) fn arguments_exotic_get_own_property( obj: &JsObject, key: &PropertyKey, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult> { // 1. Let desc be OrdinaryGetOwnProperty(args, P). // 2. If desc is undefined, return desc. let Some(desc) = ordinary_get_own_property(obj, key, context)? else { return Ok(None); }; // 3. Let map be args.[[ParameterMap]]. // 4. Let isMapped be ! HasOwnProperty(map, P). // 5. If isMapped is true, then if let PropertyKey::Index(index) = key && let Some(value) = obj .downcast_ref::() .js_expect("arguments exotic method must only be callable from arguments objects")? .get(index.get()) { // a. Set desc.[[Value]] to Get(map, P). return Ok(Some( PropertyDescriptor::builder() .value(value) .maybe_writable(desc.writable()) .maybe_enumerable(desc.enumerable()) .maybe_configurable(desc.configurable()) .build(), )); } // 6. Return desc. Ok(Some(desc)) } /// `[[DefineOwnProperty]]` for arguments exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-defineownproperty-p-desc #[allow(clippy::needless_pass_by_value)] pub(crate) fn arguments_exotic_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult { // 2. Let isMapped be HasOwnProperty(map, P). let mapped = if let &PropertyKey::Index(index) = &key { // 1. Let map be args.[[ParameterMap]]. obj.downcast_ref::() .js_expect("arguments exotic method must only be callable from arguments objects")? .get(index.get()) .map(|value| (index, value)) } else { None }; let new_arg_desc = match desc.kind() { // 4. If isMapped is true and IsDataDescriptor(Desc) is true, then // a. If Desc.[[Value]] is not present and Desc.[[Writable]] is present and its // value is false, then DescriptorKind::Data { writable: Some(false), value: None, } => // i. Set newArgDesc to a copy of Desc. // ii. Set newArgDesc.[[Value]] to Get(map, P). { if let Some((_, value)) = &mapped { PropertyDescriptor::builder() .value(value.clone()) .writable(false) .maybe_enumerable(desc.enumerable()) .maybe_configurable(desc.configurable()) .build() } else { desc.clone() } } // 3. Let newArgDesc be Desc. _ => desc.clone(), }; // 5. Let allowed be ? OrdinaryDefineOwnProperty(args, P, newArgDesc). // 6. If allowed is false, return false. if !ordinary_define_own_property(obj, key, new_arg_desc, context)? { return Ok(false); } // 7. If isMapped is true, then if let Some((index, _)) = mapped { // 1. Let map be args.[[ParameterMap]]. let mut map = obj .downcast_mut::() .js_expect("arguments exotic method must only be callable from arguments objects")?; // a. If IsAccessorDescriptor(Desc) is true, then if desc.is_accessor_descriptor() { // i. Call map.[[Delete]](P). map.delete(index.get()); } // b. Else, else { // i. If Desc.[[Value]] is present, then if let Some(value) = desc.value() { // 1. Let setStatus be Set(map, P, Desc.[[Value]], false). // 2. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. map.set(index.get(), value); } // ii. If Desc.[[Writable]] is present and its value is false, then if desc.writable() == Some(false) { // 1. Call map.[[Delete]](P). map.delete(index.get()); } } } // 8. Return true. Ok(true) } /// Internal optimization method for `Arguments` exotic objects. /// /// This method combines the internal methods `OrdinaryHasProperty` and `[[Get]]`. /// /// More information: /// - [ECMAScript reference OrdinaryHasProperty][spec0] /// - [ECMAScript reference Get][spec1] /// /// [spec0]: https://tc39.es/ecma262/#sec-ordinaryhasproperty /// [spec1]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver pub(crate) fn arguments_exotic_try_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult> { // 1. Let map be args.[[ParameterMap]]. // 2. Let isMapped be ! HasOwnProperty(map, P). if let PropertyKey::Index(index) = key && let Some(value) = obj .downcast_ref::() .js_expect("arguments exotic method must only be callable from arguments objects")? .get(index.get()) { // a. Assert: map contains a formal parameter mapping for P. // b. Return Get(map, P). return Ok(Some(value)); } // 3. If isMapped is false, then // a. Return ? OrdinaryGet(args, P, Receiver). ordinary_try_get(obj, key, receiver, context) } /// `[[Get]]` for arguments exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver pub(crate) fn arguments_exotic_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult { // 1. Let map be args.[[ParameterMap]]. // 2. Let isMapped be ! HasOwnProperty(map, P). if let PropertyKey::Index(index) = key && let Some(value) = obj .downcast_ref::() .js_expect("arguments exotic method must only be callable from arguments objects")? .get(index.get()) { // a. Assert: map contains a formal parameter mapping for P. // b. Return Get(map, P). return Ok(value); } // 3. If isMapped is false, then // a. Return ? OrdinaryGet(args, P, Receiver). ordinary_get(obj, key, receiver, context) } /// `[[Set]]` for arguments exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver pub(crate) fn arguments_exotic_set( obj: &JsObject, key: PropertyKey, value: JsValue, receiver: JsValue, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult { // 1. If SameValue(args, Receiver) is false, then // a. Let isMapped be false. // 2. Else, if let PropertyKey::Index(index) = &key && JsValue::same_value(&obj.clone().into(), &receiver) { // a. Let map be args.[[ParameterMap]]. // b. Let isMapped be ! HasOwnProperty(map, P). // 3. If isMapped is true, then // a. Let setStatus be Set(map, P, V, false). // b. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. obj.downcast_ref::() .js_expect("arguments exotic method must only be callable from arguments objects")? .set(index.get(), &value); } // 4. Return ? OrdinarySet(args, P, V, Receiver). ordinary_set(obj, key, value, receiver, context) } /// `[[Delete]]` for arguments exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-delete-p pub(crate) fn arguments_exotic_delete( obj: &JsObject, key: &PropertyKey, context: &mut InternalMethodPropertyContext<'_>, ) -> JsResult { // 3. Let result be ? OrdinaryDelete(args, P). let result = ordinary_delete(obj, key, context)?; if result && let PropertyKey::Index(index) = key { // 1. Let map be args.[[ParameterMap]]. // 2. Let isMapped be ! HasOwnProperty(map, P). // 4. If result is true and isMapped is true, then // a. Call map.[[Delete]](P). obj.downcast_mut::() .js_expect("arguments exotic method must only be callable from arguments objects")? .delete(index.get()); } // 5. Return result. Ok(result) } ================================================ FILE: core/engine/src/builtins/function/bound.rs ================================================ use boa_gc::{Finalize, Trace}; use crate::{ Context, JsExpect, JsObject, JsResult, JsValue, object::{ JsData, internal_methods::{ CallValue, InternalMethodCallContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }, }, }; /// Binds a `Function Object` when `bind` is called. #[derive(Debug, Trace, Finalize)] pub struct BoundFunction { target_function: JsObject, this: JsValue, args: Vec, } impl JsData for BoundFunction { fn internal_methods(&self) -> &'static InternalObjectMethods { static CONSTRUCTOR_METHODS: InternalObjectMethods = InternalObjectMethods { __call__: bound_function_exotic_call, __construct__: bound_function_exotic_construct, ..ORDINARY_INTERNAL_METHODS }; static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods { __call__: bound_function_exotic_call, ..ORDINARY_INTERNAL_METHODS }; if self.target_function.is_constructor() { &CONSTRUCTOR_METHODS } else { &FUNCTION_METHODS } } } impl BoundFunction { /// Abstract operation `BoundFunctionCreate` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-boundfunctioncreate pub fn create( target_function: JsObject, this: JsValue, args: Vec, context: &mut Context, ) -> JsResult { // 1. Let proto be ? targetFunction.[[GetPrototypeOf]](). let proto = target_function.__get_prototype_of__(context)?; // 2. Let internalSlotsList be the internal slots listed in Table 35, plus [[Prototype]] and [[Extensible]]. // 3. Let obj be ! MakeBasicObject(internalSlotsList). // 4. Set obj.[[Prototype]] to proto. // 5. Set obj.[[Call]] as described in 10.4.1.1. // 6. If IsConstructor(targetFunction) is true, then // a. Set obj.[[Construct]] as described in 10.4.1.2. // 7. Set obj.[[BoundTargetFunction]] to targetFunction. // 8. Set obj.[[BoundThis]] to boundThis. // 9. Set obj.[[BoundArguments]] to boundArgs. // 10. Return obj. Ok(JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), proto, Self { target_function, this, args, }, ) .upcast()) } /// Get a reference to the bound function's this. #[must_use] pub const fn this(&self) -> &JsValue { &self.this } /// Get a reference to the bound function's target function. #[must_use] pub const fn target_function(&self) -> &JsObject { &self.target_function } /// Get a reference to the bound function's args. #[must_use] pub fn args(&self) -> &[JsValue] { self.args.as_slice() } } /// Internal method `[[Call]]` for Bound Function Exotic Objects /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist #[allow(clippy::unnecessary_wraps)] fn bound_function_exotic_call( obj: &JsObject, argument_count: usize, context: &mut InternalMethodCallContext<'_>, ) -> JsResult { let bound_function = obj.downcast_ref::().js_expect( "bound function exotic method should only be callable from bound function objects", )?; // 1. Let target be F.[[BoundTargetFunction]]. let target = bound_function.target_function(); context .vm .stack .calling_convention_set_function(argument_count, target.clone().into()); // 2. Let boundThis be F.[[BoundThis]]. let bound_this = bound_function.this(); context .vm .stack .calling_convention_set_this(argument_count, bound_this.clone()); // 3. Let boundArgs be F.[[BoundArguments]]. let bound_args = bound_function.args(); // 4. Let args be the list-concatenation of boundArgs and argumentsList. context .vm .stack .calling_convention_insert_arguments(argument_count, bound_args); // 5. Return ? Call(target, boundThis, args). Ok(target.__call__(bound_args.len() + argument_count)) } /// Internal method `[[Construct]]` for Bound Function Exotic Objects /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget #[allow(clippy::unnecessary_wraps)] fn bound_function_exotic_construct( function_object: &JsObject, argument_count: usize, context: &mut InternalMethodCallContext<'_>, ) -> JsResult { let new_target = context.vm.stack.pop(); debug_assert!(new_target.is_object(), "new.target should be an object"); let bound_function = function_object.downcast_ref::().js_expect( "bound function exotic method should only be callable from bound function objects", )?; // 1. Let target be F.[[BoundTargetFunction]]. let target = bound_function.target_function(); // 2. Assert: IsConstructor(target) is true. // 3. Let boundArgs be F.[[BoundArguments]]. let bound_args = bound_function.args(); // 4. Let args be the list-concatenation of boundArgs and argumentsList. context .vm .stack .calling_convention_insert_arguments(argument_count, bound_args); // 5. If SameValue(F, newTarget) is true, set newTarget to target. let function_object: JsValue = function_object.clone().into(); let new_target = if JsValue::same_value(&function_object, &new_target) { target.clone().into() } else { new_target }; // 6. Return ? Construct(target, args, newTarget). context.vm.stack.push(new_target); Ok(target.__construct__(bound_args.len() + argument_count)) } ================================================ FILE: core/engine/src/builtins/function/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `Function` object and Native Functions. //! //! Objects wrap `Function`s and expose them via call/construct slots. //! //! The `Function` object is used for matching text with a pattern. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-function-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function use crate::{ Context, JsArgs, JsExpect, JsResult, JsStr, JsString, JsValue, SpannedSourceText, builtins::{ BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, }, bytecompiler::FunctionCompiler, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, environments::{EnvironmentStack, FunctionSlots, PrivateEnvironment, ThisBindingStatus}, error::JsNativeError, js_error, js_string, native_function::NativeFunctionObject, object::{ JsData, JsFunction, JsObject, PrivateElement, PrivateName, internal_methods::{ CallValue, InternalMethodCallContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, get_prototype_from_constructor, }, }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::IntegerOrInfinity, vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock}, }; use boa_ast::{ Position, Span, Spanned, StatementList, function::{FormalParameterList, FunctionBody}, operations::{ ContainsSymbol, all_private_identifiers_valid, bound_names, contains, lexically_declared_names, }, scope::BindingLocatorScope, }; use boa_gc::{self, Finalize, Gc, Trace, custom_trace}; use boa_interner::Sym; use boa_macros::js_str; use boa_parser::{Parser, Source}; use thin_vec::ThinVec; use super::Proxy; pub(crate) mod arguments; mod bound; pub use bound::BoundFunction; #[cfg(test)] mod tests; /// Represents the `[[ThisMode]]` internal slot of function objects. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects #[derive(Debug, Trace, Finalize, PartialEq, Eq, Clone)] pub enum ThisMode { /// The `this` value refers to the `this` value of a lexically enclosing function. Lexical, /// The `this` value is used exactly as provided by an invocation of the function. Strict, /// The `this` value of `undefined` or `null` is interpreted as a reference to the global object, /// and any other `this` value is first passed to `ToObject`. Global, } impl ThisMode { /// Returns `true` if the this mode is `Lexical`. #[must_use] pub const fn is_lexical(&self) -> bool { matches!(self, Self::Lexical) } /// Returns `true` if the this mode is `Strict`. #[must_use] pub const fn is_strict(&self) -> bool { matches!(self, Self::Strict) } /// Returns `true` if the this mode is `Global`. #[must_use] pub const fn is_global(&self) -> bool { matches!(self, Self::Global) } } /// Represents the `[[ConstructorKind]]` internal slot of function objects. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ConstructorKind { /// The class constructor is not derived. Base, /// The class constructor is a derived class constructor. Derived, } impl ConstructorKind { /// Returns `true` if the constructor kind is `Base`. #[must_use] pub const fn is_base(&self) -> bool { matches!(self, Self::Base) } /// Returns `true` if the constructor kind is `Derived`. #[must_use] pub const fn is_derived(&self) -> bool { matches!(self, Self::Derived) } } /// Record containing the field definition of classes. /// /// More information: /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-classfielddefinition-record-specification-type #[derive(Clone, Debug, Finalize)] pub enum ClassFieldDefinition { /// A class field definition with a `string` or `symbol` as a name. Public(PropertyKey, JsFunction, Option), /// A class field definition with a private name. Private(PrivateName, JsFunction), } unsafe impl Trace for ClassFieldDefinition { custom_trace! {this, mark, { match this { Self::Public(_key, func, _) => { mark(func); } Self::Private(_, func) => { mark(func); } } }} } /// Boa representation of a JavaScript Function Object. /// /// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code /// (AST Node). /// /// #[derive(Debug, Trace, Finalize)] pub struct OrdinaryFunction { /// The code block containing the compiled function. pub(crate) code: Gc, /// The `[[Environment]]` internal slot. pub(crate) environments: EnvironmentStack, /// The `[[HomeObject]]` internal slot. pub(crate) home_object: Option, /// The `[[ScriptOrModule]]` internal slot. pub(crate) script_or_module: Option, /// The [`Realm`] the function is defined in. pub(crate) realm: Realm, /// The `[[Fields]]` internal slot. fields: ThinVec, /// The `[[PrivateMethods]]` internal slot. private_methods: ThinVec<(PrivateName, PrivateElement)>, } impl JsData for OrdinaryFunction { fn internal_methods(&self) -> &'static InternalObjectMethods { static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods { __call__: function_call, ..ORDINARY_INTERNAL_METHODS }; static CONSTRUCTOR_METHODS: InternalObjectMethods = InternalObjectMethods { __call__: function_call, __construct__: function_construct, ..ORDINARY_INTERNAL_METHODS }; if self.code.has_prototype_property() { &CONSTRUCTOR_METHODS } else { &FUNCTION_METHODS } } } impl OrdinaryFunction { pub(crate) fn new( code: Gc, environments: EnvironmentStack, script_or_module: Option, realm: Realm, ) -> Self { Self { code, environments, home_object: None, script_or_module, realm, fields: ThinVec::default(), private_methods: ThinVec::default(), } } /// Returns the codeblock of the function. #[must_use] pub fn codeblock(&self) -> &CodeBlock { &self.code } /// Push a private environment to the function. pub(crate) fn push_private_environment(&mut self, environment: Gc) { self.environments.push_private(environment); } /// Returns true if the function object is a derived constructor. pub(crate) fn is_derived_constructor(&self) -> bool { self.code.is_derived_constructor() } /// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value. pub(crate) fn in_class_field_initializer(&self) -> bool { self.code.in_class_field_initializer() } /// Returns a reference to the function `[[HomeObject]]` slot if present. pub(crate) const fn get_home_object(&self) -> Option<&JsObject> { self.home_object.as_ref() } /// Sets the `[[HomeObject]]` slot if present. pub(crate) fn set_home_object(&mut self, object: JsObject) { self.home_object = Some(object); } /// Returns the values of the `[[Fields]]` internal slot. pub(crate) fn get_fields(&self) -> &[ClassFieldDefinition] { &self.fields } /// Pushes a value to the `[[Fields]]` internal slot if present. pub(crate) fn push_field( &mut self, key: PropertyKey, value: JsFunction, function_name: Option, ) { self.fields .push(ClassFieldDefinition::Public(key, value, function_name)); } /// Pushes a private value to the `[[Fields]]` internal slot if present. pub(crate) fn push_field_private(&mut self, name: PrivateName, value: JsFunction) { self.fields.push(ClassFieldDefinition::Private(name, value)); } /// Returns the values of the `[[PrivateMethods]]` internal slot. pub(crate) fn get_private_methods(&self) -> &[(PrivateName, PrivateElement)] { &self.private_methods } /// Pushes a private method to the `[[PrivateMethods]]` internal slot if present. pub(crate) fn push_private_method(&mut self, name: PrivateName, method: PrivateElement) { self.private_methods.push((name, method)); } /// Gets the `Realm` from where this function originates. #[must_use] pub const fn realm(&self) -> &Realm { &self.realm } /// Checks if this function is an ordinary function. pub(crate) fn is_ordinary(&self) -> bool { self.code.is_ordinary() } } /// The internal representation of a `Function` object. #[derive(Debug, Clone, Copy)] pub struct BuiltInFunctionObject; impl IntrinsicObject for BuiltInFunctionObject { fn init(realm: &Realm) { let has_instance = BuiltInBuilder::callable(realm, Self::has_instance) .name(js_string!("[Symbol.hasInstance]")) .length(1) .build(); let throw_type_error = realm.intrinsics().objects().throw_type_error(); BuiltInBuilder::from_standard_constructor::(realm) .method(Self::apply, js_string!("apply"), 2) .method(Self::bind, js_string!("bind"), 1) .method(Self::call, js_string!("call"), 1) .method(Self::to_string, js_string!("toString"), 0) .property(JsSymbol::has_instance(), has_instance, Attribute::default()) .accessor( js_string!("caller"), Some(throw_type_error.clone()), Some(throw_type_error.clone()), Attribute::CONFIGURABLE, ) .accessor( js_string!("arguments"), Some(throw_type_error.clone()), Some(throw_type_error), Attribute::CONFIGURABLE, ) .build(); let prototype = realm.intrinsics().constructors().function().prototype(); BuiltInBuilder::callable_with_object(realm, prototype.clone(), Self::prototype) .name(js_string!()) .length(0) .build(); prototype.set_prototype(Some(realm.intrinsics().constructors().object().prototype())); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for BuiltInFunctionObject { const NAME: JsString = StaticJsStrings::FUNCTION; } impl BuiltInConstructor for BuiltInFunctionObject { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 10; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::function; /// `Function ( p1, p2, … , pn, body )` /// /// The `apply()` method invokes self with the first argument as the `this` value /// and the rest of the arguments provided as an array (or an array-like object). /// /// More information: /// - [MDN documentation][mdn] /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-function-p1-p2-pn-body /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let active_function = context .active_function_object() .unwrap_or_else(|| context.intrinsics().constructors().function().constructor()); Self::create_dynamic_function(active_function, new_target, args, false, false, context) .map(Into::into) } } impl BuiltInFunctionObject { /// `CreateDynamicFunction ( constructor, newTarget, kind, args )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction pub(crate) fn create_dynamic_function( constructor: JsObject, new_target: &JsValue, args: &[JsValue], r#async: bool, generator: bool, context: &mut Context, ) -> JsResult { // 1. If newTarget is undefined, set newTarget to constructor. let new_target = if new_target.is_undefined() { constructor.into() } else { new_target.clone() }; let strict = context.is_strict(); let default = if r#async && generator { // 5. Else, // a. Assert: kind is async-generator. // b. Let prefix be "async function*". // c. Let exprSym be the grammar symbol AsyncGeneratorExpression. // d. Let bodySym be the grammar symbol AsyncGeneratorBody. // e. Let parameterSym be the grammar symbol FormalParameters[+Yield, +Await]. // f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%". StandardConstructors::async_generator_function } else if r#async { // 4. Else if kind is async, then // a. Let prefix be "async function". // b. Let exprSym be the grammar symbol AsyncFunctionExpression. // c. Let bodySym be the grammar symbol AsyncFunctionBody. // d. Let parameterSym be the grammar symbol FormalParameters[~Yield, +Await]. // e. Let fallbackProto be "%AsyncFunction.prototype%". StandardConstructors::async_function } else if generator { // 3. Else if kind is generator, then // a. Let prefix be "function*". // b. Let exprSym be the grammar symbol GeneratorExpression. // c. Let bodySym be the grammar symbol GeneratorBody. // d. Let parameterSym be the grammar symbol FormalParameters[+Yield, ~Await]. // e. Let fallbackProto be "%GeneratorFunction.prototype%". StandardConstructors::generator_function } else { // 2. If kind is normal, then // a. Let prefix be "function". // b. Let exprSym be the grammar symbol FunctionExpression. // c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await]. // d. Let parameterSym be the grammar symbol FormalParameters[~Yield, ~Await]. // e. Let fallbackProto be "%Function.prototype%". StandardConstructors::function }; // 22. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto). let prototype = get_prototype_from_constructor(&new_target, default, context)?; // 6. Let argCount be the number of elements in parameterArgs. let (body, param_list) = if let Some((body, params)) = args.split_last() { // 7. Let parameterStrings be a new empty List. let mut parameters = Vec::with_capacity(args.len()); // 8. For each element arg of parameterArgs, do for param in params { // a. Append ? ToString(arg) to parameterStrings. parameters.push(param.to_string(context)?); } // 9. Let bodyString be ? ToString(bodyArg). let body = body.to_string(context)?; (body, parameters) } else { (js_string!(), Vec::new()) }; let current_realm = context.realm().clone(); context.host_hooks().ensure_can_compile_strings( current_realm, ¶m_list, &body, false, context, )?; let parameters = if param_list.is_empty() { FormalParameterList::default() } else { // 12. Let P be the empty String. // 13. If argCount > 0, then // a. Set P to parameterStrings[0]. // b. Let k be 1. // c. Repeat, while k < argCount, // i. Let nextArgString be parameterStrings[k]. // ii. Set P to the string-concatenation of P, "," (a comma), and nextArgString. // iii. Set k to k + 1. // TODO: Replace with standard `Iterator::intersperse` iterator method when it's stabilized. // See: let parameters = itertools::Itertools::intersperse( param_list.iter().map(JsString::iter), js_str!(",").iter(), ) .flatten() .collect::>(); let mut parser = Parser::new(Source::from_utf16(¶meters)); parser.set_identifier(context.next_parser_identifier()); // 17. Let parameters be ParseText(StringToCodePoints(P), parameterSym). // 18. If parameters is a List of errors, throw a SyntaxError exception. let parameters = parser .parse_formal_parameters(context.interner_mut(), generator, r#async) .map_err(|e| { JsNativeError::syntax() .with_message(format!("failed to parse function parameters: {e}")) })?; // It is a Syntax Error if FormalParameters Contains YieldExpression is true. if generator && contains(¶meters, ContainsSymbol::YieldExpression) { return Err(JsNativeError::syntax().with_message( if r#async { "yield expression is not allowed in formal parameter list of async generator" } else { "yield expression is not allowed in formal parameter list of generator" } ).into()); } // It is a Syntax Error if FormalParameters Contains AwaitExpression is true. if r#async && contains(¶meters, ContainsSymbol::AwaitExpression) { return Err(JsNativeError::syntax() .with_message( if generator { "await expression is not allowed in formal parameter list of async function" } else { "await expression is not allowed in formal parameter list of async generator" }) .into()); } parameters }; let body = if body.is_empty() { FunctionBody::new(StatementList::default(), Span::new((1, 1), (1, 1))) } else { // 14. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED). let mut body_parse = Vec::with_capacity(body.len()); body_parse.push(u16::from(b'\n')); body_parse.extend(body.iter()); body_parse.push(u16::from(b'\n')); // 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym). // 20. If body is a List of errors, throw a SyntaxError exception. let mut parser = Parser::new(Source::from_utf16(&body_parse)); parser.set_identifier(context.next_parser_identifier()); // 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym). // 20. If body is a List of errors, throw a SyntaxError exception. let body = parser .parse_function_body(context.interner_mut(), generator, r#async) .map_err(|e| { JsNativeError::syntax() .with_message(format!("failed to parse function body: {e}")) })?; // It is a Syntax Error if AllPrivateIdentifiersValid of StatementList with argument « » // is false unless the source text containing ScriptBody is eval code that is being // processed by a direct eval. // https://tc39.es/ecma262/#sec-scripts-static-semantics-early-errors if !all_private_identifiers_valid(&body, Vec::new()) { return Err(JsNativeError::syntax() .with_message("invalid private identifier usage") .into()); } // 21. NOTE: The parameters and body are parsed separately to ensure that each is valid alone. For example, new Function("/*", "*/ ) {") does not evaluate to a function. // 22. NOTE: If this step is reached, sourceText must have the syntax of exprSym (although the reverse implication does not hold). The purpose of the next two steps is to enforce any Early Error rules which apply to exprSym directly. // 23. Let expr be ParseText(sourceText, exprSym). // 24. If expr is a List of errors, throw a SyntaxError exception. // Check for errors that apply for the combination of body and parameters. // Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code, // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments". if body.strict() { for name in bound_names(¶meters) { if name == Sym::ARGUMENTS || name == Sym::EVAL { return Err(JsNativeError::syntax() .with_message("Unexpected 'eval' or 'arguments' in strict mode") .into()); } } } // Early Error: If the source code matching FormalParameters is strict mode code, // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. if (body.strict()) && parameters.has_duplicates() { return Err(JsNativeError::syntax() .with_message("Duplicate parameter name not allowed in this context") .into()); } // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true // and IsSimpleParameterList of FormalParameters is false. if body.strict() && !parameters.is_simple() { return Err(JsNativeError::syntax() .with_message( "Illegal 'use strict' directive in function with non-simple parameter list", ) .into()); } // It is a Syntax Error if FunctionBody Contains SuperProperty is true. if contains(&body, ContainsSymbol::SuperProperty) { return Err(JsNativeError::syntax() .with_message("invalid `super` reference") .into()); } // It is a Syntax Error if FunctionBody Contains SuperCall is true. if contains(&body, ContainsSymbol::SuperCall) { return Err(JsNativeError::syntax() .with_message("invalid `super` call") .into()); } // It is a Syntax Error if any element of the BoundNames of FormalParameters // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors { let lexically_declared_names = lexically_declared_names(&body); for name in bound_names(¶meters) { if lexically_declared_names.contains(&name) { return Err(JsNativeError::syntax() .with_message(format!( "Redeclaration of formal parameter `{}`", context.interner().resolve_expect(name) )) .into()); } } } body }; // TODO: create SourceText : "anonymous(" parameters \n ") {" body_parse "}" let function_span_start = Position::new(1, 1); let function_span_end = body.span().end(); let mut function = boa_ast::function::FunctionExpression::new( None, parameters, body, None, false, Span::new(function_span_start, function_span_end), ); if let Err(reason) = function.analyze_scope(strict, context.realm().scope(), context.interner()) { return Err(js_error!(SyntaxError: "failed to analyze function scope: {}", reason)); } let in_with = context.vm.frame().environments.has_object_environment(); let spanned_source_text = SpannedSourceText::new_empty(); let code = FunctionCompiler::new(spanned_source_text) .name(js_string!("anonymous")) .generator(generator) .r#async(r#async) .in_with(in_with) .force_function_scope(true) .compile( function.parameters(), function.body(), context.realm().scope().clone(), context.realm().scope().clone(), function.scopes(), function.contains_direct_eval(), context.interner_mut(), ); let saved = context.vm.frame_mut().environments.pop_to_global(); let function_object = crate::vm::create_function_object(code, prototype, context); context .vm .frame_mut() .environments .restore_from_saved(saved); Ok(function_object) } /// `Function.prototype.apply ( thisArg, argArray )` /// /// The `apply()` method invokes self with the first argument as the `this` value /// and the rest of the arguments provided as an array (or an array-like object). /// /// More information: /// - [MDN documentation][mdn] /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.apply /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply fn apply(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let func be the this value. // 2. If IsCallable(func) is false, throw a TypeError exception. let func = this.as_callable().ok_or_else(|| { JsNativeError::typ().with_message(format!("{} is not a function", this.display())) })?; let this_arg = args.get_or_undefined(0); let arg_array = args.get_or_undefined(1); // 3. If argArray is undefined or null, then if arg_array.is_null_or_undefined() { // a. Perform PrepareForTailCall(). // TODO?: 3.a. PrepareForTailCall // b. Return ? Call(func, thisArg). return func.call(this_arg, &[], context); } // 4. Let argList be ? CreateListFromArrayLike(argArray). let arg_list = arg_array.create_list_from_array_like(&[], context)?; // 5. Perform PrepareForTailCall(). // TODO?: 5. PrepareForTailCall // 6. Return ? Call(func, thisArg, argList). func.call(this_arg, &arg_list, context) } /// `Function.prototype.bind ( thisArg, ...args )` /// /// The `bind()` method creates a new function that, when called, has its /// this keyword set to the provided value, with a given sequence of arguments /// preceding any provided when the new function is called. /// /// More information: /// - [MDN documentation][mdn] /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.bind /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind fn bind(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let Target be the this value. // 2. If IsCallable(Target) is false, throw a TypeError exception. let target = this.as_callable().ok_or_else(|| { JsNativeError::typ() .with_message("cannot bind `this` without a `[[Call]]` internal method") })?; let this_arg = args.get_or_undefined(0).clone(); let bound_args = args.get(1..).unwrap_or(&[]).to_vec(); let arg_count = bound_args.len() as i64; // 3. Let F be ? BoundFunctionCreate(Target, thisArg, args). let f = BoundFunction::create(target.clone(), this_arg, bound_args, context)?; // 4. Let L be 0. let mut l = JsValue::new(0); // 5. Let targetHasLength be ? HasOwnProperty(Target, "length"). // 6. If targetHasLength is true, then if target.has_own_property(StaticJsStrings::LENGTH, context)? { // a. Let targetLen be ? Get(Target, "length"). let target_len = target.get(StaticJsStrings::LENGTH, context)?; // b. If Type(targetLen) is Number, then if target_len.is_number() { // 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen). match target_len .to_integer_or_infinity(context) .js_expect("to_integer_or_infinity cannot fail for a number")? { // i. If targetLen is +∞𝔽, set L to +∞. IntegerOrInfinity::PositiveInfinity => l = f64::INFINITY.into(), // ii. Else if targetLen is -∞𝔽, set L to 0. IntegerOrInfinity::NegativeInfinity => {} // iii. Else, IntegerOrInfinity::Integer(target_len) => { // 2. Assert: targetLenAsInt is finite. // 3. Let argCount be the number of elements in args. // 4. Set L to max(targetLenAsInt - argCount, 0). l = (target_len - arg_count).max(0).into(); } } } } // 7. Perform ! SetFunctionLength(F, L). f.define_property_or_throw( StaticJsStrings::LENGTH, PropertyDescriptor::builder() .value(l) .writable(false) .enumerable(false) .configurable(true), context, ) .js_expect("defining the `length` property for a new object should not fail")?; // 8. Let targetName be ? Get(Target, "name"). let target_name = target.get(js_string!("name"), context)?; // 9. If Type(targetName) is not String, set targetName to the empty String. let target_name = target_name.as_string().unwrap_or_default(); // 10. Perform SetFunctionName(F, targetName, "bound"). set_function_name(&f, &target_name.into(), Some(js_str!("bound")), context)?; // 11. Return F. Ok(f.into()) } /// `Function.prototype.call ( thisArg, ...args )` /// /// The `call()` method calls a function with a given this value and arguments provided individually. /// /// More information: /// - [MDN documentation][mdn] /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.call /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call fn call(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let func be the this value. // 2. If IsCallable(func) is false, throw a TypeError exception. let func = this.as_callable().ok_or_else(|| { JsNativeError::typ().with_message(format!("{} is not a function", this.display())) })?; let this_arg = args.get_or_undefined(0); // 3. Perform PrepareForTailCall(). // TODO?: 3. Perform PrepareForTailCall // 4. Return ? Call(func, thisArg, args). func.call(this_arg, args.get(1..).unwrap_or(&[]), context) } /// `Function.prototype.toString()` /// /// More information: /// - [MDN documentation][mdn] /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString #[allow(clippy::wrong_self_convention)] fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let func be the this value. let func = this; // TODO: // 2. If func is an Object, func has a [[SourceText]] internal slot, func.[[SourceText]] is a sequence of Unicode code points,and HostHasSourceTextAvailable(func) is true, then // a. Return CodePointsToString(func.[[SourceText]]). // 3. If func is a built-in function object, return an implementation-defined String source code representation of func. // The representation must have the syntax of a NativeFunction. Additionally, if func has an [[InitialName]] internal slot and // func.[[InitialName]] is a String, the portion of the returned String that would be matched by // NativeFunctionAccessor_opt PropertyName must be the value of func.[[InitialName]]. // 4. If func is an Object and IsCallable(func) is true, return an implementation-defined String source code representation of func. // The representation must have the syntax of a NativeFunction. // 5. Throw a TypeError exception. let Some(object) = func.as_callable() else { return Err(JsNativeError::typ().with_message("not a function").into()); }; if object.is::() { let name = { // Is there a case here where if there is no name field on a value // name should default to None? Do all functions have names set? let value = object.get(js_string!("name"), &mut *context)?; if value.is_null_or_undefined() { js_string!() } else { value.to_string(context)? } }; return Ok( js_string!(js_str!("function "), &name, js_str!("() { [native code] }")).into(), ); } else if object.is::() || object.is::() { return Ok(js_string!("function () { [native code] }").into()); } let function = object .downcast_ref::() .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?; let code = function.codeblock(); if let Some(code_points) = code.source_info().text_spanned().to_code_points() { return Ok(JsString::from(code_points).into()); } Ok(js_string!( js_str!("function "), code.name(), js_str!("() { [native code] }") ) .into()) } /// `Function.prototype [ @@hasInstance ] ( V )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-function.prototype-@@hasinstance fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let F be the this value. // 2. Return ? OrdinaryHasInstance(F, V). Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into()) } #[allow(clippy::unnecessary_wraps)] fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { Ok(JsValue::undefined()) } } /// Abstract operation `SetFunctionName` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-setfunctionname pub(crate) fn set_function_name( function: &JsObject, name: &PropertyKey, prefix: Option>, context: &mut Context, ) -> JsResult<()> { // 1. Assert: F is an extensible object that does not have a "name" own property. // 2. If Type(name) is Symbol, then let mut name = match name { PropertyKey::Symbol(sym) => { // a. Let description be name's [[Description]] value. // b. If description is undefined, set name to the empty String. // c. Else, set name to the string-concatenation of "[", description, and "]". sym.description().map_or_else( || js_string!(), |desc| js_string!(js_str!("["), &desc, js_str!("]")), ) } PropertyKey::String(string) => string.clone(), PropertyKey::Index(index) => js_string!(format!("{}", index.get())), }; // 3. Else if name is a Private Name, then // a. Set name to name.[[Description]]. // todo: implement Private Names // 4. If F has an [[InitialName]] internal slot, then // a. Set F.[[InitialName]] to name. // todo: implement [[InitialName]] for builtins // 5. If prefix is present, then if let Some(prefix) = prefix { name = js_string!(prefix, js_str!(" "), &name); // b. If F has an [[InitialName]] internal slot, then // i. Optionally, set F.[[InitialName]] to name. // todo: implement [[InitialName]] for builtins } // 6. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor { [[Value]]: name, // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }). function .define_property_or_throw( js_string!("name"), PropertyDescriptor::builder() .value(name) .writable(false) .enumerable(false) .configurable(true), context, ) .js_expect("defining the `name` property must not fail per the spec")?; Ok(()) } /// Call this object. /// /// # Panics /// /// Panics if the object is currently mutably borrowed. // // pub(crate) fn function_call( function_object: &JsObject, argument_count: usize, #[allow( unused_variables, reason = "Only used if native-backtrace feature is enabled" )] context: &mut InternalMethodCallContext<'_>, ) -> JsResult { context.check_runtime_limits()?; let function = function_object .downcast_ref::() .js_expect("not a function")?; let realm = function.realm().clone(); if function.code.is_class_constructor() { debug_assert!( function.is_ordinary(), "only ordinary functions can be classes" ); return Err(JsNativeError::typ() .with_message("class constructor cannot be invoked without 'new'") .with_realm(realm) .into()); } let code = function.code.clone(); let environments = function.environments.clone(); let script_or_module = function.script_or_module.clone(); drop(function); let env_fp = environments.len() as u32; let frame = CallFrame::new(code, script_or_module, environments, realm) .with_argument_count(argument_count as u32) .with_env_fp(env_fp); #[cfg(feature = "native-backtrace")] { let native_source_info = context.native_source_info(); context .vm .shadow_stack .patch_last_native(native_source_info); } context.vm.push_frame(frame); context.vm.set_return_value(JsValue::undefined()); let context = context.context(); let lexical_this_mode = context.vm.frame().code_block.this_mode == ThisMode::Lexical; let this = if lexical_this_mode { ThisBindingStatus::Lexical } else { let this = context.vm.stack.get_this(context.vm.frame()); if context.vm.frame().code_block.strict() { context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED; ThisBindingStatus::Initialized(this) } else if this.is_null_or_undefined() { context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED; let this: JsValue = context.realm().global_this().clone().into(); context.vm.stack.set_this( context.vm.frames.last().js_expect("frame must exist")?, this.clone(), ); ThisBindingStatus::Initialized(this) } else { let this: JsValue = this .to_object(context) .js_expect("conversion cannot fail")? .into(); context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED; context.vm.stack.set_this( context.vm.frames.last().js_expect("frame must exist")?, this.clone(), ); ThisBindingStatus::Initialized(this) } }; let mut last_env = 0; let has_binding_identifier = context.vm.frame().code_block().has_binding_identifier(); let has_function_scope = context.vm.frame().code_block().has_function_scope(); if has_binding_identifier { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); let index = frame.environments.push_lexical(1, global); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, function_object.clone().into(), global, ); last_env += 1; } if has_function_scope { let scope = context.vm.frame().code_block().constant_scope(last_env); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame.environments.push_function( scope, FunctionSlots::new(this, function_object.clone(), None), global, ); } Ok(CallValue::Ready) } /// Construct an instance of this object with the specified arguments. /// /// # Panics /// /// Panics if the object is currently mutably borrowed. // fn function_construct( this_function_object: &JsObject, argument_count: usize, context: &mut InternalMethodCallContext<'_>, ) -> JsResult { context.check_runtime_limits()?; let function = this_function_object .downcast_ref::() .js_expect("not a function")?; let realm = function.realm().clone(); debug_assert!( function.is_ordinary(), "only ordinary functions can be constructed" ); let code = function.code.clone(); let environments = function.environments.clone(); let script_or_module = function.script_or_module.clone(); drop(function); let env_fp = environments.len() as u32; let new_target = context.vm.stack.pop(); let this = if code.is_derived_constructor() { None } else { // If the prototype of the constructor is not an object, then use the default object // prototype as prototype for the new object // see // see let prototype = get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?; let this = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, OrdinaryObject, ) .upcast(); this.initialize_instance_elements(this_function_object, context)?; Some(this) }; let mut frame = CallFrame::new(code, script_or_module, environments, realm) .with_argument_count(argument_count as u32) .with_env_fp(env_fp) .with_flags(CallFrameFlags::CONSTRUCT); // We push the `this` below so we can mark this function as having the this value // cached if it's initialized. frame .flags .set(CallFrameFlags::THIS_VALUE_CACHED, this.is_some()); #[cfg(feature = "native-backtrace")] { let native_source_info = context.native_source_info(); context .vm .shadow_stack .patch_last_native(native_source_info); } context.vm.push_frame(frame); context.vm.set_return_value(JsValue::undefined()); let mut last_env = 0; let has_binding_identifier = context.vm.frame().code_block().has_binding_identifier(); let has_function_scope = context.vm.frame().code_block().has_function_scope(); if has_binding_identifier { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); let index = frame.environments.push_lexical(1, global); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, this_function_object.clone().into(), global, ); last_env += 1; } if has_function_scope { let scope = context.vm.frame().code_block().constant_scope(last_env); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame.environments.push_function( scope, FunctionSlots::new( this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { ThisBindingStatus::Initialized(o.into()) }), this_function_object.clone(), Some( new_target .as_object() .js_expect("new.target should be an object")? .clone(), ), ), global, ); } let context = context.context(); context.vm.stack.set_this( context.vm.frames.last().js_expect("frame must exist")?, this.map(JsValue::new).unwrap_or_default(), ); Ok(CallValue::Ready) } ================================================ FILE: core/engine/src/builtins/function/tests.rs ================================================ use crate::{ JsNativeErrorKind, JsValue, TestAction, error::JsNativeError, js_string, native_function::NativeFunction, object::{FunctionObjectBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, run_test_actions, }; use boa_macros::js_str; use indoc::indoc; #[allow(clippy::float_cmp)] #[test] fn arguments_object() { run_test_actions([ TestAction::run(indoc! {r#" function jason(a, b) { return arguments[0]; } "#}), TestAction::assert_eq("jason(100, 6)", 100), ]); } #[test] fn self_mutating_function_when_calling() { run_test_actions([ TestAction::run(indoc! {r#" function x() { x.y = 3; } x(); "#}), TestAction::assert_eq("x.y", 3), ]); } #[test] fn self_mutating_function_when_constructing() { run_test_actions([ TestAction::run(indoc! {r#" function x() { x.y = 3; } new x(); "#}), TestAction::assert_eq("x.y", 3), ]); } #[test] fn function_prototype() { run_test_actions([ TestAction::assert_eq("Function.prototype.name", js_string!()), TestAction::assert_eq("Function.prototype.length", 0), TestAction::assert_eq("Function.prototype()", JsValue::undefined()), TestAction::assert_eq( "Function.prototype(1, '', new String(''))", JsValue::undefined(), ), TestAction::assert_native_error( "new Function.prototype()", JsNativeErrorKind::Type, "not a constructor", ), ]); } #[test] fn function_prototype_call() { run_test_actions([TestAction::assert_eq( "Object.prototype.toString.call(new Error())", js_str!("[object Error]"), )]); } #[test] fn function_prototype_call_throw() { run_test_actions([TestAction::assert_native_error( indoc! {r#" let call = Function.prototype.call; call(call) "#}, JsNativeErrorKind::Type, "undefined is not a function", )]); } #[test] fn function_prototype_call_multiple_args() { run_test_actions([ TestAction::run(indoc! {r#" function f(a, b) { this.a = a; this.b = b; } let o = {a: 0, b: 0}; f.call(o, 1, 2); "#}), TestAction::assert_eq("o.a", 1), TestAction::assert_eq("o.b", 2), ]); } #[test] fn function_prototype_apply() { run_test_actions([ TestAction::run("const numbers = [6, 7, 3, 4, 2]"), TestAction::assert_eq("Math.max.apply(null, numbers)", 7), TestAction::assert_eq("Math.min.apply(null, numbers)", 2), ]); } #[test] fn function_prototype_apply_on_object() { run_test_actions([ TestAction::run(indoc! {r#" function f(a, b) { this.a = a; this.b = b; } let o = {a: 0, b: 0}; f.apply(o, [1, 2]); "#}), TestAction::assert_eq("o.a", 1), TestAction::assert_eq("o.b", 2), ]); } #[test] fn closure_capture_clone() { run_test_actions([ TestAction::inspect_context(|ctx| { let string = js_string!("Hello"); let object = JsObject::with_object_proto(ctx.intrinsics()); object .define_property_or_throw( js_string!("key"), PropertyDescriptor::builder() .value(js_string!(" world!")) .writable(false) .enumerable(false) .configurable(false), ctx, ) .unwrap(); let func = FunctionObjectBuilder::new( ctx.realm(), NativeFunction::from_copy_closure_with_captures( |_, _, captures, context| { let (string, object) = &captures; let hw = js_string!( string, &object .__get_own_property__( &js_string!("key").into(), &mut context.into() )? .and_then(|prop| prop.value().and_then(JsValue::as_string)) .ok_or_else( || JsNativeError::typ().with_message("invalid `key` property") )? ); Ok(hw.into()) }, (string, object), ), ) .name(js_str!("closure")) .build(); ctx.register_global_property(js_str!("closure"), func, Attribute::default()) .unwrap(); }), TestAction::assert_eq("closure()", js_str!("Hello world!")), ]); } #[test] fn function_constructor_early_errors_super() { run_test_actions([ TestAction::assert_native_error( "Function('super()')()", JsNativeErrorKind::Syntax, "invalid `super` call", ), TestAction::assert_native_error( "Function('super.a')()", JsNativeErrorKind::Syntax, "invalid `super` reference", ), ]); } ================================================ FILE: core/engine/src/builtins/generator/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `Generator` object. //! //! A Generator is an instance of a generator function and conforms to both the Iterator and Iterable interfaces. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-generator-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator use crate::{ Context, JsArgs, JsData, JsError, JsExpect, JsResult, JsString, builtins::iterable::create_iter_result_object, context::intrinsics::Intrinsics, error::JsNativeError, error::PanicError, js_string, object::{CONSTRUCTOR, JsObject}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::JsValue, vm::{CallFrame, CallFrameFlags, CompletionRecord, GeneratorResumeKind, Stack}, }; use boa_gc::{Finalize, Trace, custom_trace}; use super::{BuiltInBuilder, IntrinsicObject}; /// Indicates the state of a generator. #[derive(Debug, Finalize)] pub(crate) enum GeneratorState { SuspendedStart { /// The `[[GeneratorContext]]` internal slot. context: GeneratorContext, }, SuspendedYield { /// The `[[GeneratorContext]]` internal slot. context: GeneratorContext, }, Executing, Completed, } // Need to manually implement, since `Trace` adds a `Drop` impl which disallows destructuring. unsafe impl Trace for GeneratorState { custom_trace!(this, mark, { match &this { Self::SuspendedStart { context } | Self::SuspendedYield { context } => mark(context), Self::Executing | Self::Completed => {} } }); } /// Holds all information that a generator needs to continue it's execution. /// /// All of the fields must be changed with those that are currently present in the /// context/vm before the generator execution starts/resumes and after it has ended/yielded. #[derive(Debug, Trace, Finalize)] pub(crate) struct GeneratorContext { pub(crate) stack: Stack, pub(crate) call_frame: Option, } impl GeneratorContext { /// Creates a new `GeneratorContext` from the current `Context` state. pub(crate) fn from_current(context: &mut Context, async_generator: Option) -> Self { let mut frame = context.vm.frame().clone(); frame.environments = context.vm.frame().environments.clone(); frame.realm = context.realm().clone(); // Split the stack at fp. The split-off portion starts at what was fp, // so adjust rp and fp to be relative to the new base. let mut stack = context.vm.stack.split_off_frame(&frame); frame.rp -= frame.fp; frame.fp = 0; // NOTE: Since we get a pre-built call frame with stack, and we reuse them. // So we don't need to push the registers in subsequent calls. frame.flags |= CallFrameFlags::REGISTERS_ALREADY_PUSHED; if let Some(async_generator) = async_generator { stack.set_register( &frame, CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX, async_generator.into(), ); } Self { call_frame: Some(frame), stack, } } /// Resumes execution with `GeneratorContext` as the current execution context. pub(crate) fn resume( &mut self, value: Option, resume_kind: GeneratorResumeKind, context: &mut Context, ) -> CompletionRecord { std::mem::swap(&mut context.vm.stack, &mut self.stack); let Some(frame) = self.call_frame.take() else { return CompletionRecord::Throw(PanicError::new("should have a call frame").into()); }; let fp = frame.fp; let rp = frame.rp; context.vm.push_frame(frame); let frame = context.vm.frame_mut(); frame.fp = fp; frame.rp = rp; frame.set_exit_early(true); if let Some(value) = value { context.vm.stack.push(value); } context.vm.stack.push(resume_kind); let result = context.run(); std::mem::swap(&mut context.vm.stack, &mut self.stack); self.call_frame = context.vm.pop_frame(); assert!(self.call_frame.is_some()); result } /// Returns the async generator object, if the function that this [`GeneratorContext`] is from an async generator, [`None`] otherwise. pub(crate) fn async_generator_object(&self) -> JsResult> { let Some(frame) = self.call_frame.as_ref() else { return Ok(None); }; if !frame.code_block().is_async_generator() { return Ok(None); } let val = self .stack .get_register(frame, CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX) .js_expect("registers must have an async generator object")?; Ok(val.as_object()) } } /// The internal representation of a `Generator` object. #[derive(Debug, Finalize, Trace, JsData)] pub struct Generator { /// The `[[GeneratorState]]` internal slot. pub(crate) state: GeneratorState, } impl IntrinsicObject for Generator { fn init(realm: &Realm) { BuiltInBuilder::with_intrinsic::(realm) .prototype( realm .intrinsics() .objects() .iterator_prototypes() .iterator(), ) .static_method(Self::next, js_string!("next"), 1) .static_method(Self::r#return, js_string!("return"), 1) .static_method(Self::throw, js_string!("throw"), 1) .static_property( JsSymbol::to_string_tag(), Self::NAME, Attribute::CONFIGURABLE, ) .static_property( CONSTRUCTOR, realm .intrinsics() .constructors() .generator_function() .prototype(), Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { intrinsics.objects().generator() } } impl Generator { const NAME: JsString = StaticJsStrings::GENERATOR; /// `Generator.prototype.next ( value )` /// /// The `next()` method returns an object with two properties done and value. /// You can also provide a parameter to the next method to send a value to the generator. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.next /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next pub(crate) fn next( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Return ? GeneratorResume(this value, value, empty). Self::generator_resume(this, args.get_or_undefined(0).clone(), context) } /// `Generator.prototype.return ( value )` /// /// The `return()` method returns the given value and finishes the generator. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.return /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return pub(crate) fn r#return( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let g be the this value. // 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. // 3. Return ? GeneratorResumeAbrupt(g, C, empty). Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context) } /// `Generator.prototype.throw ( exception )` /// /// The `throw()` method resumes the execution of a generator by throwing an error into it /// and returns an object with two properties done and value. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.throw /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/throw pub(crate) fn throw( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. Let g be the this value. // 2. Let C be ThrowCompletion(exception). // 3. Return ? GeneratorResumeAbrupt(g, C, empty). Self::generator_resume_abrupt( this, Err(JsError::from_opaque(args.get_or_undefined(0).clone())), context, ) } /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-generatorresume pub(crate) fn generator_resume( r#gen: &JsValue, value: JsValue, context: &mut Context, ) -> JsResult { // 1. Let state be ? GeneratorValidate(generator, generatorBrand). let Some(generator_obj) = r#gen.as_object() else { return Err(JsNativeError::typ() .with_message("Generator method called on non generator") .into()); }; let mut r#gen = generator_obj.downcast_mut::().ok_or_else(|| { JsNativeError::typ().with_message("generator resumed on non generator object") })?; // 4. Let genContext be generator.[[GeneratorContext]]. // 5. Let methodContext be the running execution context. // 6. Suspend methodContext. // 7. Set generator.[[GeneratorState]] to executing. let (mut generator_context, first_execution) = match std::mem::replace(&mut r#gen.state, GeneratorState::Executing) { GeneratorState::Executing => { return Err(JsNativeError::typ() .with_message("Generator should not be executing") .into()); } // 2. If state is completed, return CreateIterResultObject(undefined, true). GeneratorState::Completed => { r#gen.state = GeneratorState::Completed; return Ok(create_iter_result_object( JsValue::undefined(), true, context, )); } // 3. Assert: state is either suspendedStart or suspendedYield. GeneratorState::SuspendedStart { context } => (context, true), GeneratorState::SuspendedYield { context } => (context, false), }; drop(r#gen); let record = generator_context.resume( (!first_execution).then_some(value), GeneratorResumeKind::Normal, context, ); let mut r#gen = generator_obj .downcast_mut::() .js_expect("already checked this object type")?; // 8. Push genContext onto the execution context stack; genContext is now the running execution context. // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation. // 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. // 11. Return Completion(result). match record { CompletionRecord::Normal(value) => { r#gen.state = GeneratorState::SuspendedYield { context: generator_context, }; Ok(value) } CompletionRecord::Return(value) => { r#gen.state = GeneratorState::Completed; Ok(create_iter_result_object(value, true, context)) } CompletionRecord::Throw(err) => { r#gen.state = GeneratorState::Completed; Err(err) } } } /// `27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-generatorresumeabrupt pub(crate) fn generator_resume_abrupt( r#gen: &JsValue, abrupt_completion: JsResult, context: &mut Context, ) -> JsResult { // 1. Let state be ? GeneratorValidate(generator, generatorBrand). let Some(generator_obj) = r#gen.as_object() else { return Err(JsNativeError::typ() .with_message("Generator method called on non generator") .into()); }; let mut r#gen = generator_obj.downcast_mut::().ok_or_else(|| { JsNativeError::typ().with_message("generator resumed on non generator object") })?; // 4. Assert: state is suspendedYield. // 5. Let genContext be generator.[[GeneratorContext]]. // 6. Let methodContext be the running execution context. // 7. Suspend methodContext. // 8. Set generator.[[GeneratorState]] to executing. let mut generator_context = match std::mem::replace(&mut r#gen.state, GeneratorState::Executing) { GeneratorState::Executing => { return Err(JsNativeError::typ() .with_message("Generator should not be executing") .into()); } // 2. If state is suspendedStart, then // 3. If state is completed, then GeneratorState::SuspendedStart { .. } | GeneratorState::Completed => { // a. Set generator.[[GeneratorState]] to completed. r#gen.state = GeneratorState::Completed; // b. Once a generator enters the completed state it never leaves it and its // associated execution context is never resumed. Any execution state associated // with generator can be discarded at this point. // a. If abruptCompletion.[[Type]] is return, then if let Ok(value) = abrupt_completion { // i. Return CreateIterResultObject(abruptCompletion.[[Value]], true). let value = create_iter_result_object(value, true, context); return Ok(value); } // b. Return Completion(abruptCompletion). return abrupt_completion; } GeneratorState::SuspendedYield { context } => context, }; // 9. Push genContext onto the execution context stack; genContext is now the running execution context. // 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation. // 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. // 12. Return Completion(result). drop(r#gen); let (value, resume_kind) = match abrupt_completion { Ok(value) => (value, GeneratorResumeKind::Return), Err(err) => (err.into_opaque(context)?, GeneratorResumeKind::Throw), }; let record = generator_context.resume(Some(value), resume_kind, context); let mut r#gen = generator_obj.downcast_mut::().ok_or_else(|| { JsNativeError::typ().with_message("generator resumed on non generator object") })?; match record { CompletionRecord::Normal(value) => { r#gen.state = GeneratorState::SuspendedYield { context: generator_context, }; Ok(value) } CompletionRecord::Return(value) => { r#gen.state = GeneratorState::Completed; Ok(create_iter_result_object(value, true, context)) } CompletionRecord::Throw(err) => { r#gen.state = GeneratorState::Completed; Err(err) } } } } ================================================ FILE: core/engine/src/builtins/generator_function/mod.rs ================================================ //! Boa's implementation of ECMAScript's global `GeneratorFunction` object. //! //! The `GeneratorFunction` constructor creates a new generator function object. //! In ECMAScript, every generator function is actually a `GeneratorFunction` object. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! //! [spec]: https://tc39.es/ecma262/#sec-generatorfunction-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction use crate::{ Context, JsResult, JsString, builtins::{BuiltInObject, function::BuiltInFunctionObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, object::PROTOTYPE, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, value::JsValue, }; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; /// The internal representation of a `Generator` object. #[derive(Debug, Clone, Copy)] pub struct GeneratorFunction; impl IntrinsicObject for GeneratorFunction { fn init(realm: &Realm) { BuiltInBuilder::from_standard_constructor::(realm) .inherits(Some( realm.intrinsics().constructors().function().prototype(), )) .constructor_attributes(Attribute::CONFIGURABLE) .property( PROTOTYPE, realm.intrinsics().objects().generator(), Attribute::CONFIGURABLE, ) .property( JsSymbol::to_string_tag(), Self::NAME, Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> crate::object::JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for GeneratorFunction { const NAME: JsString = StaticJsStrings::GENERATOR_FUNCTION; } impl BuiltInConstructor for GeneratorFunction { const CONSTRUCTOR_ARGUMENTS: usize = 1; const PROTOTYPE_STORAGE_SLOTS: usize = 2; const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::generator_function; /// `GeneratorFunction ( p1, p2, … , pn, body )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-generatorfunction fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let active_function = context.active_function_object().unwrap_or_else(|| { context .intrinsics() .constructors() .generator_function() .constructor() }); BuiltInFunctionObject::create_dynamic_function( active_function, new_target, args, false, true, context, ) .map(Into::into) } } ================================================ FILE: core/engine/src/builtins/intl/collator/mod.rs ================================================ use boa_gc::{Finalize, Trace, custom_trace}; use icu_collator::{ CollatorPreferences, options::{AlternateHandling, MaxVariable}, preferences::{CollationCaseFirst, CollationNumericOrdering, CollationType}, provider::CollationMetadataV1, }; use icu_locale::{Locale, extensions::unicode}; use crate::{ Context, JsArgs, JsData, JsExpect, JsNativeError, JsResult, JsString, JsValue, builtins::{ BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, options::get_option, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, native_function::NativeFunction, object::{ FunctionObjectBuilder, JsFunction, JsObject, internal_methods::get_prototype_from_constructor, }, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, }; use super::{ Service, locale::{canonicalize_locale_list, filter_locales, resolve_locale}, options::{IntlOptions, coerce_options_to_object}, }; mod options; pub(crate) use options::*; #[derive(Debug, Finalize, JsData)] #[allow(clippy::struct_field_names)] pub(crate) struct Collator { locale: Locale, collation: Option, numeric: bool, case_first: Option, usage: Usage, sensitivity: Sensitivity, ignore_punctuation: bool, collator: icu_collator::Collator, bound_compare: Option, } // SAFETY: only `bound_compare` is a traceable object. unsafe impl Trace for Collator { custom_trace!(this, mark, mark(&this.bound_compare)); } impl Collator { /// Gets the inner [`icu_collator::Collator`] comparator. pub(crate) const fn collator(&self) -> &icu_collator::Collator { &self.collator } } impl Service for Collator { type LangMarker = CollationMetadataV1; type Preferences = CollatorPreferences; } impl IntrinsicObject for Collator { fn init(realm: &Realm) { let compare = BuiltInBuilder::callable(realm, Self::compare) .name(js_string!("get compare")) .build(); BuiltInBuilder::from_standard_constructor::(realm) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), 1, ) .property( JsSymbol::to_string_tag(), js_string!("Intl.Collator"), Attribute::CONFIGURABLE, ) .accessor( js_string!("compare"), Some(compare), None, Attribute::CONFIGURABLE, ) .method(Self::resolved_options, js_string!("resolvedOptions"), 0) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for Collator { const NAME: JsString = StaticJsStrings::COLLATOR; } impl BuiltInConstructor for Collator { const CONSTRUCTOR_ARGUMENTS: usize = 0; const PROTOTYPE_STORAGE_SLOTS: usize = 4; const CONSTRUCTOR_STORAGE_SLOTS: usize = 1; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::collator; /// Constructor [`Intl.Collator ( [ locales [ , options ] ] )`][spec]. /// /// Constructor for `Collator` objects. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sec-intl.collator /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. let new_target = &if new_target.is_undefined() { context .active_function_object() .unwrap_or_else(|| context.intrinsics().constructors().collator().constructor()) .into() } else { new_target.clone() }; // 2. Let internalSlotsList be « [[InitializedCollator]], [[Locale]], [[Usage]], [[Sensitivity]], [[IgnorePunctuation]], [[Collation]], [[BoundCompare]] ». // 3. If %Collator%.[[RelevantExtensionKeys]] contains "kn", then // a. Append [[Numeric]] as the last element of internalSlotsList. // 4. If %Collator%.[[RelevantExtensionKeys]] contains "kf", then // a. Append [[CaseFirst]] as the last element of internalSlotsList. // 5. Let collator be ? OrdinaryCreateFromConstructor(newTarget, "%Collator.prototype%", internalSlotsList). // 6. Return ? InitializeCollator(collator, locales, options). let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); // Abstract operation `InitializeCollator ( collator, locales, options )` // https://tc39.es/ecma402/#sec-initializecollator // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). let requested_locales = canonicalize_locale_list(locales, context)?; // 2. Set options to ? CoerceOptionsToObject(options). let options = coerce_options_to_object(options, context)?; // 3. Let usage be ? GetOption(options, "usage", string, « "sort", "search" », "sort"). // 4. Set collator.[[Usage]] to usage. // 5. If usage is "sort", then // a. Let localeData be %Collator%.[[SortLocaleData]]. // 6. Else, // a. Let localeData be %Collator%.[[SearchLocaleData]]. let usage = get_option(&options, js_string!("usage"), context)?.unwrap_or_default(); // 7. Let opt be a new Record. // 8. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). // 9. Set opt.[[localeMatcher]] to matcher. let matcher = get_option(&options, js_string!("localeMatcher"), context)?.unwrap_or_default(); // 10. Let collation be ? GetOption(options, "collation", string, empty, undefined). // 11. If collation is not undefined, then // a. If collation does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. // 12. Set opt.[[co]] to collation. // unicode `type`s that are not valid collation types are considered // "unsupported" instead of invalid. let collation = get_option::(&options, js_string!("collation"), context)? .and_then(|val| CollationType::try_from(&val).ok()); // 13. Let numeric be ? GetOption(options, "numeric", boolean, empty, undefined). // 14. If numeric is not undefined, then // a. Let numeric be ! ToString(numeric). // 15. Set opt.[[kn]] to numeric. let numeric = get_option(&options, js_string!("numeric"), context)?; // 16. Let caseFirst be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined). // 17. Set opt.[[kf]] to caseFirst. let case_first = get_option(&options, js_string!("caseFirst"), context)?; let mut intl_options = IntlOptions { matcher, preferences: { let mut prefs = CollatorPreferences::default(); prefs.collation_type = collation; prefs.numeric_ordering = numeric.map(|kn| { if kn { CollationNumericOrdering::True } else { CollationNumericOrdering::False } }); prefs.case_first = case_first; prefs }, }; // 18. Let relevantExtensionKeys be %Collator%.[[RelevantExtensionKeys]]. // 19. Let r be ResolveLocale(%Collator%.[[AvailableLocales]], requestedLocales, opt, relevantExtensionKeys, localeData). let locale = resolve_locale::( requested_locales, &mut intl_options, context.intl_provider(), )?; // 20. Set collator.[[Locale]] to r.[[locale]]. // 21. Let collation be r.[[co]]. // 22. If collation is null, let collation be "default". // 23. Set collator.[[Collation]] to collation. let collation = intl_options.preferences.collation_type; // 24. If relevantExtensionKeys contains "kn", then // a. Set collator.[[Numeric]] to SameValue(r.[[kn]], "true"). let numeric = intl_options.preferences.numeric_ordering == Some(CollationNumericOrdering::True); // 25. If relevantExtensionKeys contains "kf", then // a. Set collator.[[CaseFirst]] to r.[[kf]]. let case_first = intl_options.preferences.case_first; // 26. Let sensitivity be ? GetOption(options, "sensitivity", string, « "base", "accent", "case", "variant" », undefined). // 28. Set collator.[[Sensitivity]] to sensitivity. let sensitivity = get_option(&options, js_string!("sensitivity"), context)? // 27. If sensitivity is undefined, then // a. If usage is "sort", then // i. Let sensitivity be "variant". // b. Else, // i. Let dataLocale be r.[[dataLocale]]. // ii. Let dataLocaleData be localeData.[[]]. // iii. Let sensitivity be dataLocaleData.[[sensitivity]]. .or_else(|| (usage == Usage::Sort).then_some(Sensitivity::Variant)); // 29. Let ignorePunctuation be ? GetOption(options, "ignorePunctuation", boolean, empty, false). // 30. Set collator.[[IgnorePunctuation]] to ignorePunctuation. let ignore_punctuation = get_option(&options, js_string!("ignorePunctuation"), context)?; let (strength, case_level) = sensitivity.map(Sensitivity::to_collator_options).unzip(); let (alternate_handling, max_variable) = match ignore_punctuation { Some(true) => Some((AlternateHandling::Shifted, MaxVariable::Punctuation)).unzip(), Some(false) => (Some(AlternateHandling::NonIgnorable), None), None => None.unzip(), }; let mut options = icu_collator::options::CollatorOptions::default(); options.strength = strength; options.case_level = case_level; options.alternate_handling = alternate_handling; options.max_variable = max_variable; if usage == Usage::Search { intl_options.preferences.collation_type = Some(CollationType::Search); } let collator = icu_collator::Collator::try_new_with_buffer_provider( context.intl_provider().erased_provider(), intl_options.preferences, options, ) .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; let resolved_options = collator.as_borrowed().resolved_options(); let prototype = get_prototype_from_constructor(new_target, StandardConstructors::collator, context)?; let collator = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, Self { locale, collation, numeric, case_first, usage, sensitivity: sensitivity.unwrap_or(Sensitivity::Variant), ignore_punctuation: resolved_options.alternate_handling == AlternateHandling::Shifted && resolved_options.max_variable != MaxVariable::Space, collator, bound_compare: None, }, ); // 31. Return collator. Ok(collator.into()) } } impl Collator { /// [`Intl.Collator.supportedLocalesOf ( locales [ , options ] )`][spec]. /// /// Returns an array containing those of the provided locales that are supported in collation /// without having to fall back to the runtime's default locale. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sec-intl.collator.supportedlocalesof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/supportedLocalesOf fn supported_locales_of( _: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); // 1. Let availableLocales be %Collator%.[[AvailableLocales]]. // 2. Let requestedLocales be ? CanonicalizeLocaleList(locales). let requested_locales = canonicalize_locale_list(locales, context)?; // 3. Return ? FilterLocales(availableLocales, requestedLocales, options). filter_locales::(requested_locales, options, context).map(JsValue::from) } /// [`get Intl.Collator.prototype.compare`][spec]. /// /// Compares two strings according to the sort order of this Intl.Collator object. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sec-intl.collator.prototype.compare /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/compare fn compare(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let collator be the this value. // 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]). let this = this.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("`resolvedOptions` can only be called on a `Collator` object") })?; let collator_obj = this.clone(); let mut collator = this.downcast_mut::().ok_or_else(|| { JsNativeError::typ() .with_message("`resolvedOptions` can only be called on a `Collator` object") })?; // 3. If collator.[[BoundCompare]] is undefined, then // a. Let F be a new built-in function object as defined in 10.3.3.1. // b. Set F.[[Collator]] to collator. // c. Set collator.[[BoundCompare]] to F. let bound_compare = if let Some(f) = collator.bound_compare.clone() { f } else { let bound_compare = FunctionObjectBuilder::new( context.realm(), // 10.3.3.1. Collator Compare Functions // https://tc39.es/ecma402/#sec-collator-compare-functions NativeFunction::from_copy_closure_with_captures( |_, args, collator, context| { // 1. Let collator be F.[[Collator]]. // 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot. let collator = collator .downcast_ref::() .js_expect("checked above that the object was a collator object")?; // 3. If x is not provided, let x be undefined. // 5. Let X be ? ToString(x). let x = args .get_or_undefined(0) .to_string(context)? .iter() .collect::>(); // 4. If y is not provided, let y be undefined. // 6. Let Y be ? ToString(y). let y = args .get_or_undefined(1) .to_string(context)? .iter() .collect::>(); // 7. Return CompareStrings(collator, X, Y). let result = collator.collator.as_borrowed().compare_utf16(&x, &y) as i32; Ok(result.into()) }, collator_obj, ), ) .length(2) .build(); collator.bound_compare = Some(bound_compare.clone()); bound_compare }; // 4. Return collator.[[BoundCompare]]. Ok(bound_compare.into()) } /// [`Intl.Collator.prototype.resolvedOptions ( )`][spec]. /// /// Returns a new object with properties reflecting the locale and collation options computed /// during initialization of this `Intl.Collator` object. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sec-intl.collator.prototype.resolvedoptions /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/resolvedOptions fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let collator be the this value. // 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]). let object = this.as_object(); let collator = object .as_ref() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("`resolvedOptions` can only be called on a `Collator` object") })?; // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). let options = context .intrinsics() .templates() .ordinary_object() .create(OrdinaryObject, vec![]); // 4. For each row of Table 4, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. Let v be the value of collator's internal slot whose name is the Internal Slot value of the current row. // c. If the current row has an Extension Key value, then // i. Let extensionKey be the Extension Key value of the current row. // ii. If %Collator%.[[RelevantExtensionKeys]] does not contain extensionKey, then // 1. Let v be undefined. // d. If v is not undefined, then // i. Perform ! CreateDataPropertyOrThrow(options, p, v). // 5. Return options. options .create_data_property_or_throw( js_string!("locale"), js_string!(collator.locale.to_string()), context, ) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw( js_string!("usage"), match collator.usage { Usage::Search => js_string!("search"), Usage::Sort => js_string!("sort"), }, context, ) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw( js_string!("sensitivity"), match collator.sensitivity { Sensitivity::Base => js_string!("base"), Sensitivity::Accent => js_string!("accent"), Sensitivity::Case => js_string!("case"), Sensitivity::Variant => js_string!("variant"), }, context, ) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw( js_string!("ignorePunctuation"), collator.ignore_punctuation, context, ) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw( js_string!("collation"), collator .collation .map(|co| js_string!(co.as_str())) .unwrap_or(js_string!("default")), context, ) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw(js_string!("numeric"), collator.numeric, context) .js_expect("operation must not fail per the spec")?; if let Some(kf) = collator.case_first { options .create_data_property_or_throw( js_string!("caseFirst"), js_string!(kf.as_str()), context, ) .js_expect("operation must not fail per the spec")?; } // 5. Return options. Ok(options.into()) } } ================================================ FILE: core/engine/src/builtins/intl/collator/options.rs ================================================ use std::str::FromStr; use icu_collator::{ CollatorPreferences, options::{CaseLevel, Strength}, preferences::{CollationCaseFirst, CollationType}, provider::CollationMetadataV1, }; use icu_locale::LanguageIdentifier; use icu_provider::{ DataMarkerAttributes, prelude::icu_locale_core::{extensions::unicode, preferences::LocalePreferences}, }; use crate::{ Context, JsNativeError, JsResult, JsValue, builtins::{ intl::{ServicePreferences, locale::validate_extension}, options::{OptionType, ParsableOptionType}, }, context::icu::IntlProvider, }; #[derive(Debug, Clone, Copy)] pub(crate) enum Sensitivity { Base, Accent, Case, Variant, } impl Sensitivity { /// Converts the sensitivity option to the equivalent ICU4X collator options. pub(crate) const fn to_collator_options(self) -> (Strength, CaseLevel) { match self { Self::Base => (Strength::Primary, CaseLevel::Off), Self::Accent => (Strength::Secondary, CaseLevel::Off), Self::Case => (Strength::Primary, CaseLevel::On), Self::Variant => (Strength::Tertiary, CaseLevel::On), } } } #[derive(Debug)] pub(crate) struct ParseSensitivityError; impl std::fmt::Display for ParseSensitivityError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("provided string was not `base`, `accent`, `case` or `variant`") } } impl FromStr for Sensitivity { type Err = ParseSensitivityError; fn from_str(s: &str) -> Result { match s { "base" => Ok(Self::Base), "accent" => Ok(Self::Accent), "case" => Ok(Self::Case), "variant" => Ok(Self::Variant), _ => Err(ParseSensitivityError), } } } impl ParsableOptionType for Sensitivity {} #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub(crate) enum Usage { #[default] Sort, Search, } #[derive(Debug)] pub(crate) struct ParseUsageError; impl std::fmt::Display for ParseUsageError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("provided string was not `sort` or `search`") } } impl FromStr for Usage { type Err = ParseUsageError; fn from_str(s: &str) -> Result { match s { "sort" => Ok(Self::Sort), "search" => Ok(Self::Search), _ => Err(ParseUsageError), } } } impl ParsableOptionType for Usage {} impl OptionType for CollationCaseFirst { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_str() { "upper" => Ok(Self::Upper), "lower" => Ok(Self::Lower), "false" => Ok(Self::False), _ => Err(JsNativeError::range() .with_message("provided string was not `upper`, `lower` or `false`") .into()), } } } impl ServicePreferences for CollatorPreferences { fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { self.collation_type = self.collation_type.take().filter(|co| { let attr = DataMarkerAttributes::from_str_or_panic(co.as_str()); co != &CollationType::Search && validate_extension::(id, attr, provider) }); } impl_service_preferences!(collation_type, numeric_ordering, case_first); } ================================================ FILE: core/engine/src/builtins/intl/date_time_format/mod.rs ================================================ //! This module implements the global `Intl.DateTimeFormat` object. //! //! `Intl.DateTimeFormat` is a built-in object that has properties and methods for date and time i18n. //! //! More information: //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma402/#datetimeformat-objects use crate::{ Context, JsArgs, JsData, JsExpect, JsResult, JsString, JsValue, NativeFunction, builtins::{ BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, date::utils::{ date_from_time, hour_from_time, min_from_time, month_from_time, ms_from_time, sec_from_time, time_clip, year_from_time, }, intl::{ Service, date_time_format::options::{DateStyle, FormatMatcher, FormatOptions, TimeStyle}, locale::{canonicalize_locale_list, filter_locales, resolve_locale}, options::{IntlOptions, coerce_options_to_object}, }, options::get_option, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_error, js_string, object::{ FunctionObjectBuilder, JsFunction, JsObject, ObjectInitializer, internal_methods::get_prototype_from_constructor, }, property::{Attribute, PropertyDescriptor}, realm::Realm, string::StaticJsStrings, }; use boa_gc::{Finalize, Trace}; use icu_calendar::{Iso, preferences::CalendarAlgorithm}; use icu_datetime::{ DateTimeFormatter, DateTimeFormatterPreferences, fieldsets::{ builder::{DateFields, FieldSetBuilder}, enums::CompositeFieldSet, }, input::{Date, DateTime, Time, TimeZone, UtcOffset}, options::{Length, TimePrecision}, preferences::HourCycle as IcuHourCycle, }; use icu_decimal::preferences::NumberingSystem; use icu_decimal::provider::DecimalSymbolsV1; use icu_locale::{Locale, extensions::unicode::Value}; use icu_time::{ TimeZoneInfo, ZonedDateTime, zone::{IanaParser, models::Base}, }; use timezone_provider::provider::TimeZoneId; mod options; #[cfg(all(test, feature = "intl_bundled"))] mod tests; #[derive(Debug, Clone)] pub(crate) enum FormatTimeZone { UtcOffset(UtcOffset), Identifier((TimeZone, TimeZoneId)), } impl FormatTimeZone { pub(crate) fn to_time_zone_info(&self) -> TimeZoneInfo { match self { Self::Identifier((tz, _)) => tz.without_offset(), Self::UtcOffset(utc_offset) => TimeZone::UNKNOWN.with_offset(Some(*utc_offset)), } } } /// JavaScript `Intl.DateTimeFormat` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // Safety: No traceable types #[allow(dead_code)] pub(crate) struct DateTimeFormat { locale: Locale, calendar_algorithm: Option, // TODO: Potentially remove ? numbering_system: Option, hour_cycle: Option, date_style: Option, time_style: Option, time_zone: FormatTimeZone, fieldset: CompositeFieldSet, formatter: DateTimeFormatter, bound_format: Option, resolved_options: Option, } impl Service for DateTimeFormat { type LangMarker = DecimalSymbolsV1; type Preferences = DateTimeFormatterPreferences; } impl IntrinsicObject for DateTimeFormat { fn init(realm: &Realm) { use crate::JsSymbol; let get_format = BuiltInBuilder::callable(realm, Self::get_format) .name(js_string!("get format")) .build(); BuiltInBuilder::from_standard_constructor::(realm) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), 1, ) .accessor( js_string!("format"), Some(get_format), None, Attribute::CONFIGURABLE, ) .method(Self::resolved_options, js_string!("resolvedOptions"), 0) .property( JsSymbol::to_string_tag(), js_string!("Intl.DateTimeFormat"), Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for DateTimeFormat { const NAME: JsString = StaticJsStrings::DATE_TIME_FORMAT; } impl BuiltInConstructor for DateTimeFormat { const CONSTRUCTOR_ARGUMENTS: usize = 0; const PROTOTYPE_STORAGE_SLOTS: usize = 4; const CONSTRUCTOR_STORAGE_SLOTS: usize = 1; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::date_time_format; /// The `Intl.DateTimeFormat` constructor is the `%DateTimeFormat%` intrinsic object and a standard built-in property of the `Intl` object. /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#datetimeformat-objects /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // NOTE (nekevss): separate calls to `CreateDateTimeFormat` to avoid clone. // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. let new_target_inner = &if new_target.is_undefined() { context .active_function_object() .unwrap_or_else(|| { context .intrinsics() .constructors() .date_time_format() .constructor() }) .into() } else { new_target.clone() }; let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); // 2. Let dateTimeFormat be ? CreateDateTimeFormat(newTarget, locales, options, any, date). let dtf = create_date_time_format( locales, options, FormatType::Any, FormatDefaults::Date, context, )?; let prototype = get_prototype_from_constructor( new_target_inner, StandardConstructors::date_time_format, context, )?; let date_time_format = JsObject::from_proto_and_data(prototype, dtf); // 3. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then // a. Let this be the this value. // b. Return ? ChainDateTimeFormat(dateTimeFormat, NewTarget, this). // ChainDateTimeFormat ( dateTimeFormat, newTarget, this ) // let this = context.vm.stack.get_this(context.vm.frame()); let Some(this_obj) = this.as_object() else { return Ok(date_time_format.into()); }; let constructor = context .intrinsics() .constructors() .date_time_format() .constructor(); // 1. If newTarget is undefined and ? OrdinaryHasInstance(%Intl.DateTimeFormat%, this) is true, then if new_target.is_undefined() && JsValue::ordinary_has_instance(&constructor.into(), &this, context)? { let fallback_symbol = context .intrinsics() .objects() .intl() .borrow() .data() .fallback_symbol(); // a. Perform ? DefinePropertyOrThrow(this, %Intl%.[[FallbackSymbol]], // PropertyDescriptor{ [[Value]]: dateTimeFormat, [[Writable]]: false, // [[Enumerable]]: false, [[Configurable]]: false }). this_obj.define_property_or_throw( fallback_symbol, PropertyDescriptor::builder() .value(date_time_format) .writable(false) .enumerable(false) .configurable(false), context, )?; // b. Return this. Ok(this) } else { // 4. Return dateTimeFormat. Ok(date_time_format.into()) } } } impl DateTimeFormat { fn get_format(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let dtf be the this value. // 2. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then // a. Set dtf to ? UnwrapDateTimeFormat(dtf). // 3. Perform ? RequireInternalSlot(dtf, [[InitializedDateTimeFormat]]). let dtf_object = unwrap_date_time_format(this, context)?; let dtf_clone = dtf_object.clone(); let mut dtf = dtf_object.borrow_mut(); // 4. If dtf.[[BoundFormat]] is undefined, then // 5. Return dtf.[[BoundFormat]]. if let Some(bound_format) = &dtf.data_mut().bound_format.clone() { Ok(bound_format.clone().into()) } else { // a. Let F be a new built-in function object as defined in DateTime Format Functions (11.5.4). let bound_format = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( |_, args, dtf, context| { // 1. Let dtf be F.[[DateTimeFormat]]. // 2. Assert: dtf is an Object and dtf has an [[InitializedDateTimeFormat]] internal slot. let date = args.get_or_undefined(0); // 3. If date is not provided or is undefined, then let x = if date.is_undefined() { // NOTE (nekevss) i64 should be sufficient for a millisecond // representation. // a. Let x be ! Call(%Date.now%, undefined). context.clock().system_time_millis() as f64 // 4. Else, } else { // NOTE (nekevss) The i64 covers all MAX_SAFE_INTEGER values. // a. Let x be ? ToNumber(date). date.to_number(context)? }; // 5. Return ? FormatDateTime(dtf, x). // A.O 11.5.6 PartitionDateTimePattern: 1. TimeClip(x). 2. If NaN throw. Then ToLocalTime and format. let x = time_clip(x); if x.is_nan() { return Err(js_error!(RangeError: "formatted date cannot be NaN")); } let result = format_timestamp_with_dtf(dtf.borrow().data(), x, context)?; Ok(JsValue::from(result)) }, dtf_clone, ), ) .length(1) .build(); // b. Set F.[[DateTimeFormat]] to dtf. // c. Set dtf.[[BoundFormat]] to F. dtf.data_mut().bound_format = Some(bound_format.clone()); Ok(bound_format.into()) } } /// [`Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] )`][spec] /// /// Returns an array containing those of the provided locales that are /// supported in date and time formatting without having to fall back to /// the runtime's default locale. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sec-intl.datetimeformat.supportedlocalesof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/supportedLocalesOf fn supported_locales_of( _: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); // 1. Let availableLocales be %DateTimeFormat%.[[AvailableLocales]]. // 2. Let requestedLocales be ? CanonicalizeLocaleList(locales). let requested_locales = canonicalize_locale_list(locales, context)?; // 3. Return ? FilterLocales(availableLocales, requestedLocales, options). filter_locales::(requested_locales, options, context).map(JsValue::from) } /// [`Intl.DateTimeFormat.prototype.resolvedOptions ( )`][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.resolvedoptions /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { //This function provides access to the locale and options computed during initialization of the object. // 1. Let dtf be the this value. // 2. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then // a. Set dtf to ? UnwrapDateTimeFormat(dtf). // 3. Perform ? RequireInternalSlot(dtf, [[InitializedDateTimeFormat]]). let dtf_object = unwrap_date_time_format(this, context)?; if let Some(cached) = dtf_object.borrow().data().resolved_options.clone() { return Ok(cached.into()); } // 4. Let options be OrdinaryObjectCreate(%Object.prototype%). // 5. For each row of Table 15, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. If there is an Internal Slot value in the current row, then // i. Let v be the value of dtf's internal slot whose name is the Internal Slot value of the current row. // c. Else, // i. Let format be dtf.[[DateTimeFormat]]. // ii. If format has a field [[

]] and dtf.[[DateStyle]] is undefined and dtf.[[TimeStyle]] is undefined, then // 1. Let v be format.[[

]]. // iii. Else, // 1. Let v be undefined. // d. If v is not undefined, then // i. If there is a Conversion value in the current row, then // 1. Let conversion be the Conversion value of the current row. // 2. If conversion is hour12, then // a. If v is "h11" or "h12", set v to true. Otherwise, set v to false. // 3. Else, // a. Assert: conversion is number. // b. Set v to 𝔽(v). // ii. Perform ! CreateDataPropertyOrThrow(options, p, v). let result = { let dtf = dtf_object.borrow(); let dtf = dtf.data(); let mut options = ObjectInitializer::new(context); options.property( js_string!("locale"), js_string!(dtf.locale.to_string()), Attribute::all(), ); if let Some(ca) = &dtf.calendar_algorithm { options.property( js_string!("calendar"), js_string!(ca.as_str()), Attribute::all(), ); } if let Some(nu) = &dtf.numbering_system { options.property( js_string!("numberingSystem"), js_string!(nu.as_str()), Attribute::all(), ); } let time_zone_str = match &dtf.time_zone { FormatTimeZone::UtcOffset(offset) => { let seconds = offset.to_seconds(); let hours = seconds / 3600; let minutes = (seconds.abs() % 3600) / 60; format!("{hours:+03}:{minutes:02}") } FormatTimeZone::Identifier((tz, _id)) => tz.to_string(), }; options.property( js_string!("timeZone"), js_string!(time_zone_str), Attribute::all(), ); if let Some(hc) = &dtf.hour_cycle { options.property( js_string!("hourCycle"), js_string!(hc.as_str()), Attribute::all(), ); //h11/h12 -> true, h23/h24 -> false , because its h12 conversion time let hour12 = matches!(hc, IcuHourCycle::H11 | IcuHourCycle::H12); options.property(js_string!("hour12"), hour12, Attribute::all()); } if let Some(ds) = dtf.date_style { let ds_str = match ds { DateStyle::Full => "full", DateStyle::Long => "long", DateStyle::Medium => "medium", DateStyle::Short => "short", }; options.property( js_string!("dateStyle"), js_string!(ds_str), Attribute::all(), ); } if let Some(ts) = dtf.time_style { let ts_str = match ts { TimeStyle::Full => "full", TimeStyle::Long => "long", TimeStyle::Medium => "medium", TimeStyle::Short => "short", }; options.property( js_string!("timeStyle"), js_string!(ts_str), Attribute::all(), ); } options.build() }; // 6. Return options. dtf_object.borrow_mut().data_mut().resolved_options = Some(result.clone()); Ok(result.into()) } } // Represents a ISO8601 ToLocalTime Record // // https://tc39.es/ecma402/#sec-datetimeformat-tolocaltime-records struct ToLocalTime { year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8, subsecond: u32, } impl ToLocalTime { // NOTE (nekevss): we may need to adjust the below steps. // // The core problem is how to adopt the spec steps while also // acting as a proper intermediate between built-ins and ICU4X. /// The below steps are adapted from 11.5.6 and 11.5.12 pub(crate) fn from_local_epoch_milliseconds(local_millis: f64) -> JsResult { // 11.5.6, 1. Let x be TimeClip(x). let x = time_clip(local_millis); // 11.5.6, 2. If x is NaN, throw a RangeError exception. if x.is_nan() { return Err(js_error!(RangeError: "formattable time value cannot be NaN")); } // NOTE: The switch to BigInt just for the value to be reverted to float // during ToLocalTime calculations // 11.5.6, 3. Let epochNanoseconds be ℤ(ℝ(x) × 10**6). let epoch_nanoseconds = (x * 1_000_000f64) as i128; // We convert back to milliseconds: 𝔽(floor(tz / 10**6)) let t = epoch_nanoseconds.div_euclid(1_000_000) as f64; // 11.5.5, Step 12. Return ToLocalTime record values // Also see, 11.5.12 let year = year_from_time(t); let month = month_from_time(t); let day = date_from_time(t); let hour = hour_from_time(t); let minute = min_from_time(t); let second = sec_from_time(t); let ms = u32::from(ms_from_time(t)); // 11.5.5, Step 15.f.v. If p is "month", set v to v + 1. let month = month + 1; // This month is zero based (0-11) Ok(Self { year, month, day, hour, minute, second, subsecond: ms * 1_000_000, }) } pub(crate) fn to_formattable_datetime(&self) -> JsResult> { Ok(DateTime { date: Date::try_new_iso(self.year, self.month, self.day) .ok() .js_expect("TimeClip ensures valid range.")?, time: Time::try_new(self.hour, self.minute, self.second, self.subsecond) .ok() .js_expect("valid values")?, }) } } // ==== Abstract Operations ==== /// Creates a [`DateTimeFormat`] struct (internal slots only). The constructor wraps this in a /// `JsObject` with the correct prototype; Date.prototype.toLocaleString (and friends) use it /// directly with [`format_timestamp_with_dtf`] without allocating a JS object. pub(crate) fn create_date_time_format( locales: &JsValue, options: &JsValue, date_time_format_type: FormatType, defaults: FormatDefaults, context: &mut Context, ) -> JsResult { // 2. Let hour12 be undefined. <- TODO // 3. Let modifyResolutionOptions be a new Abstract Closure with parameters (options) that captures hour12 and performs the following steps when called: // a. Set hour12 to options.[[hour12]]. // b. Remove field [[hour12]] from options. // c. If hour12 is not undefined, set options.[[hc]] to null. // 4. Let optionsResolution be ? ResolveOptions(%Intl.DateTimeFormat%, %Intl.DateTimeFormat%.[[LocaleData]], // locales, options, « coerce-options », modifyResolutionOptions). // // NOTE: We inline ResolveOptions here. (Could be worked into an abstract operation util function) // ResolveOptions 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). let requested_locales = canonicalize_locale_list(locales, context)?; // NOTE: skip ResolveOptions 2, which is based on `REQUIRE-OPTIONS` vs `COERCE-OPTIONS` // ResolveOptions 3. If specialBehaviours is present and contains coerce-options, // set options to ? CoerceOptionsToObject(options). Otherwise, set options to ? GetOptionsObject(options). let options = coerce_options_to_object(options, context)?; // ResolveOptions 4. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). let matcher = get_option(&options, js_string!("localeMatcher"), context)?.unwrap_or_default(); // NOTE: We unroll the below const loop in step 6 using the // ResolutionOptionDescriptors from the internal slots // https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots let mut preferences = DateTimeFormatterPreferences::default(); // 6. For each Resolution Option Descriptor desc of constructor.[[ResolutionOptionDescriptors]], do // a. If desc has a [[Type]] field, let type be desc.[[Type]]. Otherwise, let type be string. // b. If desc has a [[Values]] field, let values be desc.[[Values]]. Otherwise, let values be empty. // c. Let value be ? GetOption(options, desc.[[Property]], type, values, undefined). // d. If value is not undefined, then // i. Set value to ! ToString(value). // ii. If value cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception. // e. Let key be desc.[[Key]]. // f. Set opt.[[]] to value. // Handle { [[Key]]: "ca", [[Property]]: "calendar" } preferences.calendar_algorithm = get_option::(&options, js_string!("calendar"), context)? .map(|ca| CalendarAlgorithm::try_from(&ca)) .transpose() .map_err(|_icu4x_error| js_error!(RangeError: "unknown calendar algorithm"))?; // { [[Key]]: "nu", [[Property]]: "numberingSystem" } preferences.numbering_system = get_option::(&options, js_string!("numberingSystem"), context)? .map(NumberingSystem::try_from) .transpose() .map_err(|_icu4x_error| js_error!(RangeError: "unknown numbering system"))?; // { [[Key]]: "hour12", [[Property]]: "hour12", [[Type]]: boolean } let hour_12 = get_option::(&options, js_string!("hour12"), context)?; // { [[Key]]: "hc", [[Property]]: "hourCycle", [[Values]]: « "h11", "h12", "h23", "h24" » } preferences.hour_cycle = get_option::(&options, js_string!("hourCycle"), context)? .map(|hc| { // Handle steps 3.a-c here // c. If hour12 is not undefined, set options.[[hc]] to null. if hour_12.is_some() { Ok(None) } else { IcuHourCycle::try_from(hc).map(Some) } }) .transpose() .map_err(|_icu4x_error| js_error!(RangeError: "unknown hour cycle"))? .flatten(); let mut intl_options = IntlOptions { matcher, preferences, }; // ResolveOptions 8. Let resolution be ResolveLocale(constructor.[[AvailableLocales]], requestedLocales, // opt, constructor.[[RelevantExtensionKeys]], localeData). let resolved_locale = resolve_locale::( requested_locales, &mut intl_options, context.intl_provider(), )?; // TODO: The resolved calendar, numbering system, and hour cycle should come from // the ICU4X locale resolution result, not hardcoded defaults. However, ICU4X does // not yet expose getters for these computed values on DateTimeFormatter. // This means e.g. `new Intl.DateTimeFormat("ar").resolvedOptions().numberingSystem` // incorrectly returns "latn" instead of "arab". // Tracked at: unicode-org/icu4x#5868 if intl_options.preferences.calendar_algorithm.is_none() { intl_options.preferences.calendar_algorithm = CalendarAlgorithm::try_from( &Value::try_from_str("gregory").expect("'gregory' is a valid BCP 47 value"), ) .ok(); } if intl_options.preferences.numbering_system.is_none() { intl_options.preferences.numbering_system = NumberingSystem::try_from( Value::try_from_str("latn").expect("'latn' is a valid BCP 47 value"), ) .ok(); } if intl_options.preferences.hour_cycle.is_none() { intl_options.preferences.hour_cycle = IcuHourCycle::try_from( &Value::try_from_str("h12").expect("'h12' is a valid BCP 47 value"), ) .ok(); } // 5. Set options to optionsResolution.[[Options]]. // 6. Let r be optionsResolution.[[ResolvedLocale]]. // 7. Set (deferred) dateTimeFormat.[[Locale]] to r.[[Locale]]. // 8. Let (deferred) resolvedCalendar be r.[[ca]]. // 9. Set (deferred) dateTimeFormat.[[Calendar]] to resolvedCalendar. // 10. Set (deferred) dateTimeFormat.[[NumberingSystem]] to r.[[nu]]. // 11. Let (deferred) resolvedLocaleData be r.[[LocaleData]]. // TODO: Handle hour12 and hc // 12. If hour12 is true, then // a. Let hc be resolvedLocaleData.[[hourCycle12]]. // 13. Else if hour12 is false, then // a. Let hc be resolvedLocaleData.[[hourCycle24]]. // 14. Else, // a. Assert: hour12 is undefined. // b. Let hc be r.[[hc]]. // c. If hc is null, set hc to resolvedLocaleData.[[hourCycle]]. // 15. Let timeZone be ? Get(options, "timeZone"). let time_zone = options.get(js_string!("timeZone"), context)?; // 16. If timeZone is undefined, then let time_zone = if time_zone.is_undefined() { // TODO (nekevss): Resolve system time zone // a. Set timeZone to SystemTimeZoneIdentifier(). JsString::from("Etc/UTC") // 17. Else, } else { // a. Set timeZone to ? ToString(timeZone). time_zone.to_string(context)? }; // 18. If IsTimeZoneOffsetString(timeZone) is true, then let time_zone_string = time_zone.to_std_string_escaped(); // Note: Should a timezone enum be part of temporal_rs, icu_time, or an ECMA402 wrapper lib let time_zone = if let Ok(utc_offset) = UtcOffset::try_from_str(&time_zone_string) { // a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset). // b. Assert: parseResult is a Parse Node. // c. If parseResult contains more than one MinuteSecond Parse Node, throw a RangeError exception. // d. Let offsetNanoseconds be ParseTimeZoneOffsetString(timeZone). // e. Let offsetMinutes be offsetNanoseconds / (6 × 10**10). // f. Assert: offsetMinutes is an integer. // g. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes). FormatTimeZone::UtcOffset(utc_offset) } else { // 19. Else, // a. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(timeZone). // b. If timeZoneIdentifierRecord is empty, throw a RangeError exception. // c. Set timeZone to timeZoneIdentifierRecord.[[PrimaryIdentifier]]. let parser = IanaParser::try_new_with_buffer_provider(context.intl_provider().erased_provider()) .map_err(|_| { JsNativeError::error().with_message("Failed to init time zone data provider") })?; let time_zone = parser.as_borrowed().parse(&time_zone_string); let time_zone_id = context .timezone_provider() .get(time_zone_string.as_bytes()) .map_err(|_| { JsNativeError::range() .with_message(format!("{time_zone_string:#?} was not a valid time zone.")) })?; FormatTimeZone::Identifier((time_zone, time_zone_id)) }; // 20. (deferred) Set dateTimeFormat.[[TimeZone]] to timeZone. // 21. Let formatOptions be a new Record. // 22. Set formatOptions.[[hourCycle]] to hc. // 23. Let hasExplicitFormatComponents be false. // NOTE (nekevss): Step 24 is adopted in the `FormatOptions` // 24. For each row of Table 16, except the header row, in table order, do // a. Let prop be the name given in the Property column of the current row. // b. If prop is "fractionalSecondDigits", then // i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, undefined). // c. Else, // i. Let values be a List whose elements are the strings given in the Values column of the current row. // ii. Let value be ? GetOption(options, prop, string, values, undefined). // d. Set formatOptions.[[]] to value. // e. If value is not undefined, then // i. Set hasExplicitFormatComponents to true. let mut format_options = FormatOptions::try_init(&options, preferences.hour_cycle, context)?; // TODO: how should formatMatcher be used? // 25. Let formatMatcher be ? GetOption(options, "formatMatcher", string, « "basic", "best fit" », "best fit"). let format_matcher = get_option::(&options, js_string!("formatMatcher"), context)? .unwrap_or(FormatMatcher::BestFit); // 26. Let dateStyle be ? GetOption(options, "dateStyle", string, « "full", "long", "medium", "short" », undefined). let date_style = get_option::(&options, js_string!("dateStyle"), context)?; // 27. Set dateTimeFormat.[[DateStyle]] to dateStyle. // 28. Let timeStyle be ? GetOption(options, "timeStyle", string, « "full", "long", "medium", "short" », undefined). let time_style = get_option::(&options, js_string!("timeStyle"), context)?; // 29. (deferred) Set dateTimeFormat.[[TimeStyle]] to timeStyle. // 30. If dateStyle is not undefined or timeStyle is not undefined, then let fieldset = if date_style.is_some() || time_style.is_some() { // a. If hasExplicitFormatComponents is true, then if format_options.has_explicit_format_components() { // i. Throw a TypeError exception. return Err( js_error!(TypeError: "cannot have explicit format components when timeStyle or dateStyle is defined"), ); } // b. If required is date and timeStyle is not undefined, then if date_time_format_type == FormatType::Date && time_style.is_some() { // i. Throw a TypeError exception. return Err( js_error!(TypeError: "timeStyle cannot be defined for a date DateTimeFormat"), ); } // c. If required is time and dateStyle is not undefined, then if date_time_format_type == FormatType::Time && date_style.is_some() { // i. Throw a TypeError exception. return Err( js_error!(TypeError: "dateStyle cannot be defined for a time DateTimeFormat"), ); } // TODO (nekevss): implement d-e // TODO (nekevss): Do we have access to the styles? // d. Let styles be resolvedLocaleData.[[styles]].[[]]. // e. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles). date_time_style_format(date_style, time_style)? // 31. Else, } else { // a. Let needDefaults be true. // b. If required is date or any, then // i. For each property name prop of « "weekday", "year", "month", "day" », do // 1. Let value be formatOptions.[[]]. // 2. If value is not undefined, set needDefaults to false. // c. If required is time or any, then // i. For each property name prop of « "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" », do // 1. Let value be formatOptions.[[]]. // 2. If value is not undefined, set needDefaults to false. let needs_defaults = format_options.check_dtf_type(date_time_format_type); // d. If needDefaults is true and defaults is either date or all, then if needs_defaults && defaults != FormatDefaults::Time { // i. For each property name prop of « "year", "month", "day" », do // 1. Set formatOptions.[[]] to "numeric". format_options.set_date_defaults(); } // e. If needDefaults is true and defaults is either time or all, then if needs_defaults && defaults != FormatDefaults::Date { // i. For each property name prop of « "hour", "minute", "second" », do // 1. Set formatOptions.[[]] to "numeric". format_options.set_time_defaults(); } // TODO (nekevss): Do we have access to the localized formats via `icu_datetime`. Is there // a specific API by which this is accessed. // // f. Let formats be resolvedLocaleData.[[formats]].[[]]. // g. If formatMatcher is "basic", then // i. Let bestFormat be BasicFormatMatcher(formatOptions, formats). // h. Else, // i. Let bestFormat be BestFitFormatMatcher(formatOptions, formats). match format_matcher { FormatMatcher::Basic | FormatMatcher::BestFit => { best_fit_date_time_format(&format_options)? } } }; // 32. Set dateTimeFormat.[[DateTimeFormat]] to bestFormat. // 33. If bestFormat has a field [[hour]], then // a. Set dateTimeFormat.[[HourCycle]] to hc. // 34. Return dateTimeFormat. let formatter = DateTimeFormatter::try_new_with_buffer_provider( context.intl_provider().erased_provider(), resolved_locale.clone().into(), fieldset, ) .map_err(|e| JsNativeError::range().with_message(format!("failed to load formatter: {e}")))?; Ok(DateTimeFormat { locale: resolved_locale, calendar_algorithm: intl_options.preferences.calendar_algorithm, numbering_system: intl_options.preferences.numbering_system, hour_cycle: intl_options.preferences.hour_cycle, date_style, time_style, time_zone, fieldset, formatter, bound_format: None, resolved_options: None, }) } /// Formats a timestamp (epoch milliseconds) using the given [`DateTimeFormat`] internals. /// /// This is the shared implementation used by: /// - the bound `format` function created in `get_format`, and /// - [`format_date_time_locale`] used by `Date.prototype.toLocaleString` (and friends). /// /// It corresponds to the *post*-`TimeClip` portion of /// [`FormatDateTime(dtf, x)`](https://tc39.es/ecma402/#sec-formatdatetime), /// and the `ToLocalTime` / `PartitionDateTimePattern` logic from /// [11.5.6](https://tc39.es/ecma402/#sec-partitiondatetimepattern) and /// [11.5.12](https://tc39.es/ecma402/#sec-tolocaltime). /// /// Callers must have already applied `TimeClip` and `NaN` check /// (`FormatDateTime` steps 1–2). This helper implements: /// /// 11.5.6 `PartitionDateTimePattern` ( dtf, x ) /// 1. Let x be TimeClip(x). (Done by caller) /// 2. If x is `NaN`, throw a `RangeError` exception. (Done by caller) /// 3. Let epochNanoseconds be ℤ(ℝ(x) × 10^6). /// 4. Let timeZone be dtf.[[`TimeZone`]]. /// 5. Let offsetNs be GetOffsetNanosecondsFor(timeZone, epochNanoseconds). /// 6. Let tz be 𝔽(ℝ(x) + ℝ(offsetNs) / 10^6). /// /// Then calls `ToLocalTime::from_local_epoch_milliseconds` to obtain calendar fields, /// and formats the resulting `ZonedDateTime` with ICU4X. fn format_timestamp_with_dtf( dtf: &DateTimeFormat, timestamp: f64, context: &mut Context, ) -> JsResult { // PartitionDateTimePattern ( dtf, x ) step 3: // Let epochNanoseconds be ℤ(ℝ(x) × 10^6). // // NOTE: `timestamp` is already `TimeClip`'d by the caller and represents *UTC epoch milliseconds*. let epoch_ns = timestamp as i128 * 1_000_000; // PartitionDateTimePattern ( dtf, x ) step 4: // Let timeZone be dtf.[[`TimeZone`]]. let time_zone = &dtf.time_zone; // PartitionDateTimePattern ( dtf, x ) step 5: // Let offsetNs be GetOffsetNanosecondsFor(timeZone, epochNanoseconds). // // NOTE: the spec describes the offset in *nanoseconds*. Internally, we obtain/normalize it to // seconds (and then milliseconds) for use with `ToLocalTime::from_local_epoch_milliseconds`. let time_zone_offset_seconds = match time_zone { FormatTimeZone::UtcOffset(offset) => offset.to_seconds(), FormatTimeZone::Identifier((_, time_zone_id)) => { let offset_seconds = context .timezone_provider() .transition_nanoseconds_for_utc_epoch_nanoseconds(*time_zone_id, epoch_ns) .map_err( |_e| js_error!(RangeError: "unable to determine transition nanoseconds"), )?; offset_seconds.0 as i32 } }; // PartitionDateTimePattern ( dtf, x ) step 6: // Let tz be 𝔽(ℝ(x) + ℝ(offsetNs) / 10^6). let tz = timestamp + f64::from(time_zone_offset_seconds * 1_000); let fields = ToLocalTime::from_local_epoch_milliseconds(tz)?; let dt = fields.to_formattable_datetime()?; let tz_info = time_zone.to_time_zone_info(); let tz_info_at_time = tz_info.at_date_time_iso(dt); let zdt = ZonedDateTime { date: dt.date, time: dt.time, zone: tz_info_at_time, }; let result = dtf.formatter.format(&zdt).to_string(); Ok(JsString::from(result)) } fn date_time_style_format( date_style: Option, time_style: Option, ) -> JsResult { let mut builder = FieldSetBuilder::default(); builder.length = match date_style { Some(DateStyle::Full | DateStyle::Long) => Some(Length::Long), Some(DateStyle::Medium) => Some(Length::Medium), Some(DateStyle::Short) => Some(Length::Short), None => match time_style { Some(TimeStyle::Full | TimeStyle::Long) => Some(Length::Long), Some(TimeStyle::Medium) => Some(Length::Medium), Some(TimeStyle::Short) => Some(Length::Short), None => return Err(js_error!(TypeError: "dateStyle or timeStyle must be defined")), }, }; builder.date_fields = match date_style { Some(DateStyle::Full) => Some(DateFields::YMDE), Some(DateStyle::Long | DateStyle::Medium | DateStyle::Short) => Some(DateFields::YMD), None => None, // NOTE: timeStyle being undefined is checked when setting length }; builder.time_precision = match time_style { Some(TimeStyle::Full | TimeStyle::Long | TimeStyle::Medium) => Some(TimePrecision::Second), Some(TimeStyle::Short) => Some(TimePrecision::Minute), None => None, // NOTE: dateStyle being undefined is checked when setting length }; builder .build_composite() .map_err(|e| JsNativeError::range().with_message(e.to_string()).into()) } fn best_fit_date_time_format(format_options: &FormatOptions) -> JsResult { let mut builder = FieldSetBuilder::default(); builder.length = format_options.to_length(); builder.date_fields = format_options.to_date_fields(); builder.time_precision = format_options.to_time_fields(); builder.zone_style = format_options.to_zone_style(); builder .build_composite() .map_err(|e| JsNativeError::range().with_message(e.to_string()).into()) } /// Represents the `required` and `defaults` arguments in the abstract operation /// `toDateTimeOptions`. #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum FormatType { Date, Time, Any, } /// Indicates which default fields should be applied when `ToDateTimeOptions` /// determines defaults are needed. `All` applies both date and time defaults. #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum FormatDefaults { Date, Time, /// Apply both date and time defaults (e.g. for `toLocaleString`). All, } /// Abstract operation [`UnwrapDateTimeFormat ( dtf )`][spec]. /// /// This also checks that the returned object is a `DateTimeFormat`, which skips the /// call to `RequireInternalSlot`. /// /// [spec]: https://tc39.es/ecma402/#sec-unwrapdatetimeformat fn unwrap_date_time_format( dtf: &JsValue, context: &mut Context, ) -> JsResult> { // 1. If Type(dtf) is not Object, throw a TypeError exception. let dtf_o = dtf.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("value was not an initialized `Intl.DateTimeFormat` object") })?; if let Ok(dtf) = dtf_o.clone().downcast::() { // 3. Return dtf. return Ok(dtf); } // 2. If dtf does not have an [[InitializedDateTimeFormat]] internal slot and // ? OrdinaryHasInstance(%Intl.DateTimeFormat%, dtf) is true, then let constructor = context .intrinsics() .constructors() .date_time_format() .constructor(); if JsValue::ordinary_has_instance(&constructor.into(), dtf, context)? { let fallback_symbol = context .intrinsics() .objects() .intl() .borrow() .data() .fallback_symbol(); // a. Return ? Get(dtf, %Intl%.[[FallbackSymbol]]). if let Some(dtf) = dtf_o .get(fallback_symbol, context)? .as_object() .and_then(|o| o.downcast::().ok()) { return Ok(dtf); } } Err(JsNativeError::typ() .with_message("object was not an initialized `Intl.DateTimeFormat` object") .into()) } /// Shared helper used by Date.prototype.toLocaleString, /// Date.prototype.toLocaleDateString, and Date.prototype.toLocaleTimeString. /// Applies `ToDateTimeOptions` defaults, calls [`create_date_time_format`], and formats /// the timestamp via [`format_timestamp_with_dtf`] without allocating a JS object. #[allow(clippy::too_many_arguments)] pub(crate) fn format_date_time_locale( locales: &JsValue, options: &JsValue, format_type: FormatType, defaults: FormatDefaults, timestamp: f64, context: &mut Context, ) -> JsResult { let options = coerce_options_to_object(options, context)?; if format_type != FormatType::Time && get_option::(&options, js_string!("dateStyle"), context)?.is_none() { options.create_data_property_or_throw( js_string!("dateStyle"), JsValue::from(js_string!("long")), context, )?; } if format_type != FormatType::Date && get_option::(&options, js_string!("timeStyle"), context)?.is_none() { options.create_data_property_or_throw( js_string!("timeStyle"), JsValue::from(js_string!("long")), context, )?; } let options_value = options.into(); let dtf = create_date_time_format(locales, &options_value, format_type, defaults, context)?; // FormatDateTime steps 1–2: TimeClip and NaN check (format_timestamp_with_dtf does ToLocalTime + format only). let x = time_clip(timestamp); if x.is_nan() { return Err(js_error!(RangeError: "formatted date cannot be NaN")); } let result = format_timestamp_with_dtf(&dtf, x, context)?; Ok(JsValue::from(result)) } ================================================ FILE: core/engine/src/builtins/intl/date_time_format/options.rs ================================================ //! Intl.DateTimeFormat options module use crate::{ Context, JsError, JsNativeError, JsObject, JsResult, JsValue, builtins::{ intl::{ ServicePreferences, date_time_format::FormatType, locale::validate_extension, options::get_number_option, }, options::{OptionType, get_option}, }, context::icu::IntlProvider, js_error, js_string, }; use icu_calendar::cal::{ Buddhist, ChineseTraditional, Coptic, Ethiopian, Gregorian, Hebrew, Hijri, Indian, Japanese, KoreanTraditional, Persian, Roc, hijri, }; use icu_datetime::{ DateTimeFormatterPreferences, fieldsets::builder::{DateFields, ZoneStyle}, options::{Length, SubsecondDigits as IcuSubsecondDigits, TimePrecision}, preferences::{CalendarAlgorithm, HijriCalendarAlgorithm, HourCycle as IcuHourCycle}, scaffold::CldrCalendar, }; use icu_decimal::provider::DecimalSymbolsV1; use icu_locale::extensions::unicode::Value; use icu_provider::{ DataMarker, DataMarkerAttributes, DryDataProvider, prelude::icu_locale_core::{ LanguageIdentifier, extensions::unicode, preferences::LocalePreferences, }, }; pub(crate) enum HourCycle { H11, H12, H23, H24, } impl OptionType for HourCycle { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "h11" => Ok(Self::H11), "h12" => Ok(Self::H12), "h23" => Ok(Self::H23), "h24" => Ok(Self::H24), _ => Err(js_error!(RangeError: "unknown hourCycle option")), } } } impl TryFrom for IcuHourCycle { type Error = JsError; fn try_from(hc: HourCycle) -> Result { match hc { HourCycle::H11 => Ok(IcuHourCycle::H11), HourCycle::H12 => Ok(IcuHourCycle::H12), HourCycle::H23 => Ok(IcuHourCycle::H23), // TODO: Work on support for H24, potentially remove depending on fate // of H24 option. HourCycle::H24 => Err(js_error!(RangeError: "h24 not currently supported.")), } } } pub(super) enum FormatMatcher { Basic, BestFit, } impl OptionType for FormatMatcher { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "basic" => Ok(Self::Basic), "best fit" => Ok(Self::BestFit), _ => Err(js_error!(RangeError: "unknown formatMatcher option")), } } } #[derive(Debug, Clone, Copy)] pub(super) enum DateStyle { Full, Long, Medium, Short, } impl OptionType for DateStyle { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "full" => Ok(Self::Full), "long" => Ok(Self::Long), "medium" => Ok(Self::Medium), "short" => Ok(Self::Short), _ => Err(js_error!(RangeError: "unknown dateStyle option")), } } } #[derive(Debug, Clone, Copy)] pub(super) enum TimeStyle { Full, Long, Medium, Short, } impl OptionType for TimeStyle { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "full" => Ok(Self::Full), "long" => Ok(Self::Long), "medium" => Ok(Self::Medium), "short" => Ok(Self::Short), _ => Err(js_error!(RangeError: "unknown timeStyle option")), } } } impl OptionType for CalendarAlgorithm { fn from_value(value: JsValue, context: &mut Context) -> JsResult { let s = value.to_string(context)?.to_std_string_escaped(); Value::try_from_str(&s) .ok() .and_then(|v| CalendarAlgorithm::try_from(&v).ok()) .ok_or_else(|| { JsNativeError::range() .with_message(format!("provided calendar `{s}` is invalid")) .into() }) } } // TODO: track https://github.com/unicode-org/icu4x/issues/6597 and // https://github.com/tc39/ecma402/issues/1002 for resolution on // `HourCycle::H24`. impl OptionType for IcuHourCycle { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_str() { "h11" => Ok(IcuHourCycle::H11), "h12" => Ok(IcuHourCycle::H12), "h23" => Ok(IcuHourCycle::H23), _ => Err(js_error!(RangeError: "provided hour cycle was not `h11`, `h12` or `h23`")), } } } // ==== Formatting options ==== // // This section includes formatting options that act as an intermediary between // user space and ICU4X's datetimeformat composite fields. pub(super) struct FormatOptions { _hour_cycle: Option, // -> ??? week_day: Option, // e -> Maps to DateField era: Option, // G -> Maps to YearStyle year: Option, // Y -> Maps to DateField month: Option, // M -> Maps to DateField day: Option, // D -> Maps to DateField day_period: Option, // a -> ??? hour: Option, // Maps to TimePrecision minute: Option, // Maps to TimePrecision second: Option, // Maps to TimePrecision fractional_second_digits: Option, // Maps to TimePrecision time_zone_name: Option, // Maps to ZoneStyle } impl FormatOptions { pub(super) fn try_init( options: &JsObject, hour_cycle: Option, // TODO: Is option correct? context: &mut Context, ) -> JsResult { // Below is adapted and inlined from Step 24 of `CreateDateTimeFormat` let week_day = get_option::(options, js_string!("weekDay"), context)?; let era = get_option::(options, js_string!("era"), context)?; let year = get_option::(options, js_string!("year"), context)?; let month = get_option::(options, js_string!("month"), context)?; let day = get_option::(options, js_string!("day"), context)?; let day_period = get_option::(options, js_string!("dayPeriod"), context)?; let hour = get_option::(options, js_string!("hour"), context)?; let minute = get_option::(options, js_string!("minute"), context)?; let second = get_option::(options, js_string!("second"), context)?; let fractional_second_digits = get_number_option(options, js_string!("fractionalSecondDigits"), 1, 3, context)? .map(SubsecondDigits::from_i32); let time_zone_name = get_option::(options, js_string!("timeZoneName"), context)?; Ok(Self { _hour_cycle: hour_cycle, week_day, era, year, month, day, day_period, hour, minute, second, fractional_second_digits, time_zone_name, }) } pub(super) fn set_date_defaults(&mut self) { self.year = Some(Year::Numeric); self.month = Some(Month::Numeric); self.day = Some(Day::Numeric); } pub(super) fn set_time_defaults(&mut self) { self.hour = Some(Hour::Numeric); self.minute = Some(Minute::Numeric); self.second = Some(Second::Numeric); } pub(super) fn has_explicit_format_components(&self) -> bool { match self { Self { week_day: None, era: None, year: None, month: None, day: None, day_period: None, hour: None, minute: None, second: None, fractional_second_digits: None, time_zone_name: None, .. } => false, // If any of the format fields is Some(_), return true _ => true, } } pub(super) fn check_dtf_type(&self, required: FormatType) -> bool { // a. Let needDefaults be true. // b. If required is date or any, then // i. For each property name prop of « "weekday", "year", "month", "day" », do // 1. Let value be formatOptions.[[]]. // 2. If value is not undefined, set needDefaults to false. // c. If required is time or any, then // i. For each property name prop of « "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" », do // 1. Let value be formatOptions.[[]]. // 2. If value is not undefined, set needDefaults to false. if required != FormatType::Time && self.has_date_defaults() { return false; } if required != FormatType::Date && self.has_time_defaults() { return false; } true } pub(super) fn has_date_defaults(&self) -> bool { self.week_day.is_some() || self.year.is_some() || self.month.is_some() || self.day.is_some() } pub(super) fn has_time_defaults(&self) -> bool { self.day_period.is_some() || self.hour.is_some() || self.minute.is_some() || self.second.is_some() || self.fractional_second_digits.is_some() } pub(super) fn to_length(&self) -> Option { match (self.month, self.week_day, self.day_period, self.era) { (Some(month), _, _, _) => Some(month.to_length()), (None, Some(week_day), _, _) => Some(week_day.to_length()), (None, None, Some(day_period), _) => Some(day_period.to_length()), (None, None, None, Some(era)) => Some(era.to_length()), (None, None, None, None) => None, } } /// Convert the current `FormatOptions` to a [`DateFields`]. pub(super) fn to_date_fields(&self) -> Option { match (self.year, self.month, self.day, self.week_day) { (Some(_y), _m, _d, Some(_e)) => Some(DateFields::YMDE), (Some(_y), _m, Some(_d), None) => Some(DateFields::YMD), (Some(_y), Some(_m), None, None) => Some(DateFields::YM), (Some(_y), None, None, None) => Some(DateFields::Y), (None, Some(_m), _d, Some(_e)) => Some(DateFields::MDE), (None, Some(_m), Some(_d), None) => Some(DateFields::MD), (None, Some(_m), None, None) => Some(DateFields::M), (None, None, Some(_d), Some(_e)) => Some(DateFields::DE), (None, None, Some(_d), None) => Some(DateFields::D), (None, None, None, Some(_e)) => Some(DateFields::E), (None, None, None, None) => None, } } /// Convert the current `FormatOptions` to a [`TimePrecision`]. pub(super) fn to_time_fields(&self) -> Option { match ( self.hour, self.minute, self.second, self.fractional_second_digits, ) { (_h, _m, _s, Some(digits)) => Some(TimePrecision::Subsecond(digits.into())), (_h, _m, Some(_s), None) => Some(TimePrecision::Second), (_h, Some(_m), None, None) => Some(TimePrecision::Minute), (Some(_h), None, None, None) => Some(TimePrecision::Hour), (None, None, None, None) => None, } } /// Convert the current `FormatOptions` to a [`ZoneStyle`]. pub(super) fn to_zone_style(&self) -> Option { self.time_zone_name.map(TimeZoneName::to_zone_style) } } // ==== Format Options ==== #[derive(Debug, Clone, Copy)] pub(crate) enum WeekDay { Narrow, Short, Long, } impl OptionType for WeekDay { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "narrow" => Ok(Self::Narrow), "short" => Ok(Self::Short), "long" => Ok(Self::Long), _ => Err(js_error!(RangeError: "unknown weekDay option")), } } } impl WeekDay { pub(crate) fn to_length(self) -> Length { match self { Self::Long => Length::Long, Self::Short => Length::Medium, Self::Narrow => Length::Short, } } } #[derive(Debug, Clone, Copy)] pub(crate) enum Era { Narrow, Short, Long, } impl Era { pub(crate) fn to_length(self) -> Length { match self { Self::Long => Length::Long, Self::Short => Length::Medium, Self::Narrow => Length::Short, } } } impl OptionType for Era { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "narrow" => Ok(Self::Narrow), "short" => Ok(Self::Short), "long" => Ok(Self::Long), _ => Err(js_error!(RangeError: "unknown era option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum Year { TwoDigit, Numeric, } impl OptionType for Year { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "2-digit" => Ok(Self::TwoDigit), "numeric" => Ok(Self::Numeric), _ => Err(js_error!(RangeError: "unknown year option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum Month { TwoDigit, Numeric, Narrow, Short, Long, } impl Month { pub(crate) fn to_length(self) -> Length { // NOTE (nekevss): after a brief glance, narrow does not appear to be // currently supported by ICU4X ... TBD match self { Self::Long => Length::Long, Self::Short => Length::Medium, _ => Length::Short, } } } impl OptionType for Month { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "2-digit" => Ok(Self::TwoDigit), "numeric" => Ok(Self::Numeric), "narrow" => Ok(Self::Narrow), "short" => Ok(Self::Short), "long" => Ok(Self::Long), _ => Err(js_error!(RangeError: "unknown month option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum Day { TwoDigit, Numeric, } impl OptionType for Day { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "2-digit" => Ok(Self::TwoDigit), "numeric" => Ok(Self::Numeric), _ => Err(js_error!(RangeError: "unknown day option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum DayPeriod { Narrow, Short, Long, } impl DayPeriod { pub(crate) fn to_length(self) -> Length { match self { Self::Long => Length::Long, Self::Short => Length::Medium, Self::Narrow => Length::Short, } } } impl OptionType for DayPeriod { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "narrow" => Ok(Self::Narrow), "short" => Ok(Self::Short), "long" => Ok(Self::Long), _ => Err(js_error!(RangeError: "unknown dayPeriod option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum Hour { TwoDigit, Numeric, } impl OptionType for Hour { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "2-digit" => Ok(Self::TwoDigit), "numeric" => Ok(Self::Numeric), _ => Err(js_error!(RangeError: "unknown hour option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum Minute { TwoDigit, Numeric, } impl OptionType for Minute { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "2-digit" => Ok(Self::TwoDigit), "numeric" => Ok(Self::Numeric), _ => Err(js_error!(RangeError: "unknown minute option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum Second { TwoDigit, Numeric, } impl OptionType for Second { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "2-digit" => Ok(Self::TwoDigit), "numeric" => Ok(Self::Numeric), _ => Err(js_error!(RangeError: "unknown second option")), } } } #[derive(Debug, Clone, Copy)] pub(crate) enum SubsecondDigits { S1, S2, S3, } impl SubsecondDigits { fn from_i32(i: i32) -> SubsecondDigits { match i { 1 => SubsecondDigits::S1, 2 => SubsecondDigits::S2, 3 => SubsecondDigits::S3, _ => unreachable!("subSecondDigits must be previously constrained."), } } } impl From for IcuSubsecondDigits { fn from(value: SubsecondDigits) -> Self { match value { SubsecondDigits::S1 => IcuSubsecondDigits::S1, SubsecondDigits::S2 => IcuSubsecondDigits::S2, SubsecondDigits::S3 => IcuSubsecondDigits::S3, } } } #[derive(Debug, Clone, Copy)] pub(crate) enum TimeZoneName { Short, Long, ShortOffset, LongOffset, ShortGeneric, LongGeneric, } impl OptionType for TimeZoneName { fn from_value(value: JsValue, context: &mut Context) -> JsResult { match value.to_string(context)?.to_std_string_escaped().as_ref() { "short" => Ok(Self::Short), "long" => Ok(Self::Long), "shortOffset" => Ok(Self::ShortOffset), "longOffset" => Ok(Self::LongOffset), "shortGeneric" => Ok(Self::ShortGeneric), "longGeneric" => Ok(Self::LongGeneric), _ => Err(js_error!(RangeError: "unknown timeZoneName option")), } } } impl TimeZoneName { fn to_zone_style(self) -> ZoneStyle { match self { TimeZoneName::LongGeneric => ZoneStyle::GenericLong, TimeZoneName::ShortGeneric => ZoneStyle::GenericShort, TimeZoneName::LongOffset => ZoneStyle::LocalizedOffsetLong, TimeZoneName::ShortOffset => ZoneStyle::LocalizedOffsetShort, TimeZoneName::Long => ZoneStyle::SpecificLong, TimeZoneName::Short => ZoneStyle::SpecificShort, } } } // The below handles the [[RelevantExtensionKeys]] of DateTimeFormatters // internal slots. // // See https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots impl ServicePreferences for DateTimeFormatterPreferences { fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { // Handle LDML unicode key "nu", Numbering system self.numbering_system = self.numbering_system.take().filter(|nu| { let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); validate_extension::(id, attr, provider) }); // Handle LDML unicode key "ca", Calendar algorithm self.calendar_algorithm = self.calendar_algorithm.take().filter(|ca| match ca { CalendarAlgorithm::Buddhist => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Chinese => { has_calendar_data_for_locale::(id, provider) } CalendarAlgorithm::Coptic => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Dangi => { has_calendar_data_for_locale::(id, provider) } CalendarAlgorithm::Ethiopic => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Gregory => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Hebrew => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Indian => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Japanese => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Persian => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Roc => has_calendar_data_for_locale::(id, provider), CalendarAlgorithm::Hijri(Some( HijriCalendarAlgorithm::Civil | HijriCalendarAlgorithm::Tbla, )) => has_calendar_data_for_locale::>(id, provider), CalendarAlgorithm::Hijri(Some(HijriCalendarAlgorithm::Umalqura)) => { has_calendar_data_for_locale::>(id, provider) } CalendarAlgorithm::Hijri(Some(HijriCalendarAlgorithm::Rgsa) | None) => true, _ => false, }); // NOTE (nekevss): issue: this will not support `H24` as ICU4X does // not currently support it. // // track: https://github.com/unicode-org/icu4x/issues/6597 // Handle LDML unicode key "hc", Hour cycle // No need to validate hour_cycle since it only affects formatting // behaviour. } impl_service_preferences!(numbering_system, calendar_algorithm, hour_cycle); } fn has_calendar_data_for_locale( id: &LanguageIdentifier, provider: &IntlProvider, ) -> bool where IntlProvider: DryDataProvider, { use icu_datetime::provider::neo::marker_attrs; use icu_provider::prelude::{ DataIdentifierBorrowed, DataRequest, DataRequestMetadata, icu_locale_core::preferences::LocalePreferences, }; let info = ::INFO; let locale = info.make_locale(LocalePreferences::from(id)); let req = DataRequest { id: DataIdentifierBorrowed::for_marker_attributes_and_locale(marker_attrs::ABBR, &locale), metadata: { let mut md = DataRequestMetadata::default(); md.silent = true; md }, }; let Ok(md) = DryDataProvider::dry_load(provider, req) else { return false; }; md.locale.is_none_or(|loc| !loc.is_unknown()) } ================================================ FILE: core/engine/src/builtins/intl/date_time_format/tests.rs ================================================ use indoc::indoc; use crate::{TestAction, run_test_actions}; // Intl.DateTimeFormat tests #[cfg(feature = "intl_bundled")] #[test] fn dtf_basic() { run_test_actions([ TestAction::run(indoc! {" // Setup date const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738)); let formatter = new Intl.DateTimeFormat('en-US'); let result = formatter.format(date); "}), TestAction::assert_eq("result === '12/20/20'", true), ]); run_test_actions([ TestAction::run(indoc! {" // Setup date const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738)); let formatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }); let result = formatter.format(date); "}), TestAction::assert_eq("result === 'Sunday, December 20, 2020'", true), ]); run_test_actions([ TestAction::run(indoc! {" // Setup date const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738)); let formatter = new Intl.DateTimeFormat('en-GB'); let result = formatter.format(date); "}), TestAction::assert_eq("result === '20/12/2020'", true), ]); run_test_actions([ TestAction::run(indoc! {" // Setup date const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738)); let formatter = new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long', }); let result = formatter.format(date); "}), TestAction::assert_eq("result === 'Sunday, 20 December 2020 at 03:23:16'", true), ]); run_test_actions([ TestAction::run(indoc! {" // Setup date const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738)); let formatter = new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long', timeZone: 'Australia/Sydney', }); let result = formatter.format(date); "}), TestAction::assert_eq("result === 'Sunday, 20 December 2020 at 14:23:16'", true), ]); } ================================================ FILE: core/engine/src/builtins/intl/list_format/mod.rs ================================================ use std::fmt::Write; use boa_gc::{Finalize, Trace}; use icu_list::{ ListFormatter, ListFormatterPreferences, options::{ListFormatterOptions, ListLength}, provider::{ListAndV1, ListFormatterPatterns}, }; use icu_locale::Locale; use crate::{ Context, JsArgs, JsData, JsExpect, JsNativeError, JsResult, JsString, JsValue, builtins::{ Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, intl::options::EmptyPreferences, iterable::IteratorHint, options::{get_option, get_options_object}, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::{JsObject, internal_methods::get_prototype_from_constructor}, property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, }; use super::{ Service, locale::{canonicalize_locale_list, filter_locales, resolve_locale}, options::IntlOptions, }; mod options; pub(crate) use options::*; #[derive(Debug, Trace, Finalize, JsData)] // Safety: `ListFormat` only contains non-traceable types. #[boa_gc(unsafe_empty_trace)] pub(crate) struct ListFormat { locale: Locale, typ: ListFormatType, style: ListLength, native: ListFormatter, } impl Service for ListFormat { type LangMarker = ListAndV1; const ATTRIBUTES: &'static icu_provider::DataMarkerAttributes = ListFormatterPatterns::WIDE; type Preferences = EmptyPreferences; } impl IntrinsicObject for ListFormat { fn init(realm: &Realm) { BuiltInBuilder::from_standard_constructor::(realm) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), 1, ) .property( JsSymbol::to_string_tag(), js_string!("Intl.ListFormat"), Attribute::CONFIGURABLE, ) .method(Self::format, js_string!("format"), 1) .method(Self::format_to_parts, js_string!("formatToParts"), 1) .method(Self::resolved_options, js_string!("resolvedOptions"), 0) .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } } impl BuiltInObject for ListFormat { const NAME: JsString = StaticJsStrings::LIST_FORMAT; } impl BuiltInConstructor for ListFormat { const CONSTRUCTOR_ARGUMENTS: usize = 0; const PROTOTYPE_STORAGE_SLOTS: usize = 4; const CONSTRUCTOR_STORAGE_SLOTS: usize = 1; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::list_format; /// Constructor [`Intl.ListFormat ( [ locales [ , options ] ] )`][spec]. /// /// Constructor for `ListFormat` objects. /// /// More information: /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma402/#sec-Intl.ListFormat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { return Err(JsNativeError::typ() .with_message("cannot call `Intl.ListFormat` constructor without `new`") .into()); } let locales = args.get_or_undefined(0); let options = args.get_or_undefined(1); // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). let requested_locales = canonicalize_locale_list(locales, context)?; // 4. Set options to ? GetOptionsObject(options). let options = get_options_object(options)?; // 5. Let opt be a new Record. // 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). let matcher = get_option(&options, js_string!("localeMatcher"), context)?.unwrap_or_default(); // 7. Set opt.[[localeMatcher]] to matcher. // 8. Let localeData be %ListFormat%.[[LocaleData]]. // 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales, opt, %ListFormat%.[[RelevantExtensionKeys]], localeData). // 10. Set listFormat.[[Locale]] to r.[[locale]]. let locale = resolve_locale::( requested_locales, &mut IntlOptions { matcher, ..Default::default() }, context.intl_provider(), )?; // 11. Let type be ? GetOption(options, "type", string, « "conjunction", "disjunction", "unit" », "conjunction"). // 12. Set listFormat.[[Type]] to type. let typ = get_option(&options, js_string!("type"), context)?.unwrap_or_default(); // 13. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long"). // 14. Set listFormat.[[Style]] to style. let style = get_option(&options, js_string!("style"), context)?.unwrap_or(ListLength::Wide); // 15. Let dataLocale be r.[[dataLocale]]. // 16. Let dataLocaleData be localeData.[[]]. // 17. Let dataLocaleTypes be dataLocaleData.[[]]. // 18. Set listFormat.[[Templates]] to dataLocaleTypes.[[