feat: add dependency
This commit is contained in:
19
javascript-engine/external/boa/boa_examples/Cargo.toml
vendored
Normal file
19
javascript-engine/external/boa/boa_examples/Cargo.toml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "boa_examples"
|
||||
description = "Usage examples of the Boa JavaScript engine."
|
||||
publish = false
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
boa_engine = { workspace = true, features = ["console"] }
|
||||
boa_ast.workspace = true
|
||||
boa_interner.workspace = true
|
||||
boa_gc.workspace = true
|
||||
boa_parser.workspace = true
|
||||
14
javascript-engine/external/boa/boa_examples/scripts/calc.js
vendored
Normal file
14
javascript-engine/external/boa/boa_examples/scripts/calc.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
add: function (a, b) {
|
||||
return a + b;
|
||||
},
|
||||
subtract: function (a, b) {
|
||||
return a - b;
|
||||
},
|
||||
multiply: function (a, b) {
|
||||
return a * b;
|
||||
},
|
||||
divide: function (a, b) {
|
||||
return a / b;
|
||||
},
|
||||
};
|
||||
8
javascript-engine/external/boa/boa_examples/scripts/calctest.js
vendored
Normal file
8
javascript-engine/external/boa/boa_examples/scripts/calctest.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//load module
|
||||
let calc = require("./scripts/calc.js");
|
||||
|
||||
console.log("Using calc module");
|
||||
console.log("Add: " + calc.add(3, 3));
|
||||
console.log("Subtract: " + calc.subtract(3, 3));
|
||||
console.log("Multiply: " + calc.multiply(3, 3));
|
||||
console.log("Divide: " + calc.divide(3, 3));
|
||||
11
javascript-engine/external/boa/boa_examples/scripts/enhancedglobal.js
vendored
Normal file
11
javascript-engine/external/boa/boa_examples/scripts/enhancedglobal.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
//access custom global variable
|
||||
console.log("Custom global: " + customstring);
|
||||
|
||||
//call a custom global function with arguments
|
||||
console.log("Custom function: " + rusty_hello("Boa! Boa!"));
|
||||
|
||||
//access a custom global object and call a member function of that object
|
||||
let a = 5;
|
||||
let b = 5;
|
||||
let result = rusty_obj.add(a, b);
|
||||
console.log("Custom object: Result from rusty_obj.add() : " + result);
|
||||
1
javascript-engine/external/boa/boa_examples/scripts/helloworld.js
vendored
Normal file
1
javascript-engine/external/boa/boa_examples/scripts/helloworld.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Hello World from JS file!");
|
||||
151
javascript-engine/external/boa/boa_examples/src/bin/classes.rs
vendored
Normal file
151
javascript-engine/external/boa/boa_examples/src/bin/classes.rs
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// NOTE: this example requires the `console` feature to run correctly.
|
||||
use boa_engine::{
|
||||
builtins::JsArgs,
|
||||
class::{Class, ClassBuilder},
|
||||
error::JsNativeError,
|
||||
property::Attribute,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
|
||||
use boa_gc::{Finalize, Trace};
|
||||
|
||||
// We create a new struct that is going to represent a person.
|
||||
//
|
||||
// We derive `Debug`, `Trace` and `Finalize`, it automatically implements `NativeObject`
|
||||
// so we can pass it as an object in Javascript.
|
||||
//
|
||||
// The fields of the struct are not accessible by Javascript unless we create accessors for them.
|
||||
/// Represents a `Person` object.
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
struct Person {
|
||||
/// The name of the person.
|
||||
name: JsString,
|
||||
/// The age of the person.
|
||||
age: u32,
|
||||
}
|
||||
|
||||
// Here we implement a static method for Person that matches the `NativeFunction` signature.
|
||||
//
|
||||
// NOTE: The function does not have to be implemented inside Person, it can be a free function,
|
||||
// or any function that matches the required signature.
|
||||
impl Person {
|
||||
/// Says hello if `this` is a `Person`
|
||||
fn say_hello(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// We check if this is an object.
|
||||
if let Some(object) = this.as_object() {
|
||||
// If it is we downcast the type to type `Person`.
|
||||
if let Some(person) = object.downcast_ref::<Person>() {
|
||||
// and print a message to stdout.
|
||||
println!(
|
||||
"Hello my name is {}, I'm {} years old",
|
||||
person.name.to_std_string_escaped(),
|
||||
person.age // Here we can access the native rust fields of the struct.
|
||||
);
|
||||
return Ok(JsValue::undefined());
|
||||
}
|
||||
}
|
||||
// If `this` was not an object or the type of `this` was not a native object `Person`,
|
||||
// we throw a `TypeError`.
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("'this' is not a Person object")
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Class for Person {
|
||||
// We set the binding name of this function to `"Person"`.
|
||||
// It does not have to be `"Person"`, it can be any string.
|
||||
const NAME: &'static str = "Person";
|
||||
// We set the length to `2` since we accept 2 arguments in the constructor.
|
||||
//
|
||||
// This is the same as `Object.length`.
|
||||
// NOTE: The default value of `LENGTH` is `0`.
|
||||
const LENGTH: usize = 2;
|
||||
|
||||
// This is what is called when we construct a `Person` with the expression `new Person()`.
|
||||
fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> {
|
||||
// We get the first argument. If it is unavailable we default to `undefined`,
|
||||
// and then we call `to_string()`.
|
||||
//
|
||||
// This is equivalent to `String(arg)`.
|
||||
let name = args.get_or_undefined(0).to_string(context)?;
|
||||
// We get the second argument. If it is unavailable we default to `undefined`,
|
||||
// and then we call `to_u32`.
|
||||
//
|
||||
// This is equivalent to `arg | 0`.
|
||||
let age = args.get(1).cloned().unwrap_or_default().to_u32(context)?;
|
||||
|
||||
// We construct a new native struct `Person`
|
||||
let person = Person { name, age };
|
||||
|
||||
Ok(person) // and we return it.
|
||||
}
|
||||
|
||||
/// Here is where the class is initialized.
|
||||
fn init(class: &mut ClassBuilder) -> JsResult<()> {
|
||||
// We add a inheritable method `sayHello` with `0` arguments of length.
|
||||
//
|
||||
// This function is added to the `Person` prototype.
|
||||
class.method("sayHello", 0, Self::say_hello);
|
||||
// We add a static method `is` using a closure, but it must be convertible
|
||||
// to a NativeFunction.
|
||||
// This means it must not contain state, or the code won't compile.
|
||||
//
|
||||
// This function is added to the `Person` class.
|
||||
class.static_method("is", 1, |_this, args, _ctx| {
|
||||
if let Some(arg) = args.get(0) {
|
||||
if let Some(object) = arg.as_object() {
|
||||
// We check if the type of `args[0]` is `Person`
|
||||
if object.is::<Person>() {
|
||||
return Ok(true.into()); // and return `true` if it is.
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false.into()) // Otherwise we return `false`.
|
||||
});
|
||||
|
||||
// We add an `"inheritedProperty"` property to the prototype of `Person` with
|
||||
// a value of `10` and default attribute flags `READONLY`, `NON_ENUMERABLE` and `PERMANENT`.
|
||||
class.property("inheritedProperty", 10, Attribute::default());
|
||||
|
||||
// Finally, we add a `"staticProperty"` property to `Person` with a value
|
||||
// of `"Im a static property"` and attribute flags `WRITABLE`, `ENUMERABLE` and `PERMANENT`.
|
||||
class.static_property(
|
||||
"staticProperty",
|
||||
"Im a static property",
|
||||
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::PERMANENT,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// First we need to create a Javascript context.
|
||||
let mut context = Context::default();
|
||||
|
||||
// Then we need to register the global class `Person` inside `context`.
|
||||
context.register_global_class::<Person>().unwrap();
|
||||
|
||||
// Having done all of that, we can execute Javascript code with `eval`,
|
||||
// and access the `Person` class defined in Rust!
|
||||
context
|
||||
.eval(
|
||||
r"
|
||||
let person = new Person('John', 19);
|
||||
person.sayHello();
|
||||
|
||||
if (Person.is(person)) {
|
||||
console.log('person is a Person class instance.');
|
||||
}
|
||||
if (!Person.is('Hello')) {
|
||||
console.log('\'Hello\' string is not a Person class instance.');
|
||||
}
|
||||
|
||||
console.log(Person.staticProperty);
|
||||
console.log(person.inheritedProperty);
|
||||
console.log(Person.prototype.inheritedProperty === person.inheritedProperty);
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
194
javascript-engine/external/boa/boa_examples/src/bin/closures.rs
vendored
Normal file
194
javascript-engine/external/boa/boa_examples/src/bin/closures.rs
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
// This example goes into the details on how to pass closures as functions inside Rust and call them
|
||||
// from Javascript.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
use boa_engine::{
|
||||
js_string,
|
||||
native_function::NativeFunction,
|
||||
object::{builtins::JsArray, FunctionObjectBuilder, JsObject},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
string::utf16,
|
||||
Context, JsError, JsNativeError, JsString, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, GcCell, Trace};
|
||||
|
||||
fn main() -> Result<(), JsError> {
|
||||
// We create a new `Context` to create a new Javascript executor.
|
||||
let mut context = Context::default();
|
||||
|
||||
// We make some operations in Rust that return a `Copy` value that we want to pass to a Javascript
|
||||
// function.
|
||||
let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1;
|
||||
|
||||
// We register a global closure function that has the name 'closure' with length 0.
|
||||
context.register_global_callable(
|
||||
"closure",
|
||||
0,
|
||||
NativeFunction::from_copy_closure(move |_, _, _| {
|
||||
println!("Called `closure`");
|
||||
// `variable` is captured from the main function.
|
||||
println!("variable = {variable}");
|
||||
println!();
|
||||
|
||||
// We return the moved variable as a `JsValue`.
|
||||
Ok(JsValue::new(variable))
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(context.eval("closure()")?, 255.into());
|
||||
|
||||
// We have created a closure with moved variables and executed that closure
|
||||
// inside Javascript!
|
||||
|
||||
// This struct is passed to a closure as a capture.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
struct BigStruct {
|
||||
greeting: JsString,
|
||||
object: JsObject,
|
||||
}
|
||||
|
||||
// We create a new `JsObject` with some data
|
||||
let object = JsObject::with_object_proto(&mut context);
|
||||
object.define_property_or_throw(
|
||||
"name",
|
||||
PropertyDescriptor::builder()
|
||||
.value("Boa dev")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false),
|
||||
&mut context,
|
||||
)?;
|
||||
|
||||
// Now, we execute some operations that return a `Clone` type
|
||||
let clone_variable = BigStruct {
|
||||
greeting: JsString::from("Hello!"),
|
||||
object,
|
||||
};
|
||||
|
||||
// We can use `FunctionBuilder` to define a closure with additional captures and custom property
|
||||
// attributes.
|
||||
let js_function = FunctionObjectBuilder::new(
|
||||
&mut context,
|
||||
NativeFunction::from_copy_closure_with_captures(
|
||||
|_, _, captures, context| {
|
||||
let mut captures = captures.borrow_mut();
|
||||
let BigStruct { greeting, object } = &mut *captures;
|
||||
println!("Called `createMessage`");
|
||||
// We obtain the `name` property of `captures.object`
|
||||
let name = object.get("name", context)?;
|
||||
|
||||
// We create a new message from our captured variable.
|
||||
let message = js_string!(
|
||||
utf16!("message from `"),
|
||||
&name.to_string(context)?,
|
||||
utf16!("`: "),
|
||||
greeting
|
||||
);
|
||||
|
||||
// We can also mutate the moved data inside the closure.
|
||||
captures.greeting = js_string!(greeting, utf16!(" Hello!"));
|
||||
|
||||
println!("{}", message.to_std_string_escaped());
|
||||
println!();
|
||||
|
||||
// We convert `message` into `JsValue` to be able to return it.
|
||||
Ok(message.into())
|
||||
},
|
||||
// Here is where we move `clone_variable` into the closure.
|
||||
GcCell::new(clone_variable),
|
||||
),
|
||||
)
|
||||
// And here we assign `createMessage` to the `name` property of the closure.
|
||||
.name("createMessage")
|
||||
// By default all `FunctionBuilder`s set the `length` property to `0` and
|
||||
// the `constructable` property to `false`.
|
||||
.build();
|
||||
|
||||
// We bind the newly constructed closure as a global property in Javascript.
|
||||
context.register_global_property(
|
||||
// We set the key to access the function the same as its name for
|
||||
// consistency, but it may be different if needed.
|
||||
"createMessage",
|
||||
// We pass `js_function` as a property value.
|
||||
js_function,
|
||||
// We assign to the "createMessage" property the desired attributes.
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
context.eval("createMessage()")?,
|
||||
"message from `Boa dev`: Hello!".into()
|
||||
);
|
||||
|
||||
// The data mutates between calls
|
||||
assert_eq!(
|
||||
context.eval("createMessage(); createMessage();")?,
|
||||
"message from `Boa dev`: Hello! Hello! Hello!".into()
|
||||
);
|
||||
|
||||
// We have moved `Clone` variables into a closure and executed that closure
|
||||
// inside Javascript!
|
||||
|
||||
// ADVANCED
|
||||
|
||||
// If we can ensure the captured variables are not traceable by the garbage collector,
|
||||
// we can pass any static closure easily.
|
||||
|
||||
let index = Cell::new(0i32);
|
||||
let numbers = RefCell::new(Vec::new());
|
||||
|
||||
// We register a global closure that is not `Copy`.
|
||||
context.register_global_callable(
|
||||
"enumerate",
|
||||
0,
|
||||
// Note that it is required to use `unsafe` code, since the compiler cannot verify that the
|
||||
// types captured by the closure are not traceable.
|
||||
unsafe {
|
||||
NativeFunction::from_closure(move |_, _, context| {
|
||||
println!("Called `enumerate`");
|
||||
// `index` is captured from the main function.
|
||||
println!("index = {}", index.get());
|
||||
println!();
|
||||
|
||||
numbers.borrow_mut().push(index.get());
|
||||
index.set(index.get() + 1);
|
||||
|
||||
// We return the moved variable as a `JsValue`.
|
||||
Ok(
|
||||
JsArray::from_iter(
|
||||
numbers.borrow().iter().cloned().map(JsValue::from),
|
||||
context,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// First call should return the array `[0]`.
|
||||
let result = context.eval("enumerate()")?;
|
||||
let object = result
|
||||
.as_object()
|
||||
.cloned()
|
||||
.ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?;
|
||||
let array = JsArray::from_object(object)?;
|
||||
|
||||
assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32));
|
||||
assert_eq!(array.get(1, &mut context)?, JsValue::undefined());
|
||||
|
||||
// First call should return the array `[0, 1]`.
|
||||
let result = context.eval("enumerate()")?;
|
||||
let object = result
|
||||
.as_object()
|
||||
.cloned()
|
||||
.ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?;
|
||||
let array = JsArray::from_object(object)?;
|
||||
|
||||
assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32));
|
||||
assert_eq!(array.get(1, &mut context)?, JsValue::from(1i32));
|
||||
assert_eq!(array.get(2, &mut context)?, JsValue::undefined());
|
||||
|
||||
// We have moved non-traceable variables into a closure and executed that closure inside Javascript!
|
||||
Ok(())
|
||||
}
|
||||
83
javascript-engine/external/boa/boa_examples/src/bin/commuter_visitor.rs
vendored
Normal file
83
javascript-engine/external/boa/boa_examples/src/bin/commuter_visitor.rs
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// This example demonstrates how to use visitors to modify an AST. Namely, the visitors shown here
|
||||
// are used to swap the operands of commutable arithmetic operations. For an example which simply
|
||||
// inspects the AST without modifying it, see symbol_visitor.rs.
|
||||
|
||||
use boa_ast::{
|
||||
expression::operator::{
|
||||
binary::{ArithmeticOp, BinaryOp},
|
||||
Binary,
|
||||
},
|
||||
visitor::{VisitWith, VisitorMut},
|
||||
Expression,
|
||||
};
|
||||
use boa_engine::Context;
|
||||
use boa_interner::ToInternedString;
|
||||
use boa_parser::Parser;
|
||||
use core::ops::ControlFlow;
|
||||
use std::{convert::Infallible, fs::File, io::BufReader};
|
||||
|
||||
/// Visitor which, when applied to a binary expression, will swap the operands. Use in other
|
||||
/// circumstances is undefined.
|
||||
#[derive(Default)]
|
||||
struct OpExchanger<'ast> {
|
||||
lhs: Option<&'ast mut Expression>,
|
||||
}
|
||||
|
||||
impl<'ast> VisitorMut<'ast> for OpExchanger<'ast> {
|
||||
type BreakTy = ();
|
||||
|
||||
fn visit_expression_mut(&mut self, node: &'ast mut Expression) -> ControlFlow<Self::BreakTy> {
|
||||
if let Some(lhs) = self.lhs.take() {
|
||||
core::mem::swap(lhs, node);
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
self.lhs = Some(node);
|
||||
// we do not traverse into the expression; we are only to be used with a binary op
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor which walks the AST and swaps the operands of commutable arithmetic binary expressions.
|
||||
#[derive(Default)]
|
||||
struct CommutorVisitor {}
|
||||
|
||||
impl<'ast> VisitorMut<'ast> for CommutorVisitor {
|
||||
type BreakTy = Infallible;
|
||||
|
||||
fn visit_binary_mut(&mut self, node: &'ast mut Binary) -> ControlFlow<Self::BreakTy> {
|
||||
if let BinaryOp::Arithmetic(op) = node.op() {
|
||||
match op {
|
||||
ArithmeticOp::Add | ArithmeticOp::Mul => {
|
||||
// set up the exchanger and swap lhs and rhs
|
||||
let mut exchanger = OpExchanger::default();
|
||||
assert!(matches!(
|
||||
exchanger.visit_binary_mut(node),
|
||||
ControlFlow::Break(_)
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// traverse further in; there may nested binary operations
|
||||
node.visit_with_mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut parser = Parser::new(BufReader::new(
|
||||
File::open("boa_examples/scripts/calc.js").unwrap(),
|
||||
));
|
||||
let mut ctx = Context::default();
|
||||
|
||||
let mut statements = parser.parse_all(ctx.interner_mut()).unwrap();
|
||||
|
||||
let mut visitor = CommutorVisitor::default();
|
||||
|
||||
assert!(matches!(
|
||||
visitor.visit_statement_list_mut(&mut statements),
|
||||
ControlFlow::Continue(_)
|
||||
));
|
||||
|
||||
println!("{}", statements.to_interned_string(ctx.interner()));
|
||||
}
|
||||
120
javascript-engine/external/boa/boa_examples/src/bin/jsarray.rs
vendored
Normal file
120
javascript-engine/external/boa/boa_examples/src/bin/jsarray.rs
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// This example shows how to manipulate a Javascript array using Rust code.
|
||||
|
||||
use boa_engine::{
|
||||
native_function::NativeFunction,
|
||||
object::{builtins::JsArray, FunctionObjectBuilder},
|
||||
string::utf16,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
fn main() -> JsResult<()> {
|
||||
// We create a new `Context` to create a new Javascript executor.
|
||||
let context = &mut Context::default();
|
||||
|
||||
// Create an empty array.
|
||||
let array = JsArray::new(context);
|
||||
|
||||
assert!(array.is_empty(context)?);
|
||||
|
||||
array.push("Hello, world", context)?; // [ "Hello, world" ]
|
||||
array.push(true, context)?; // [ "Hello, world", true ]
|
||||
|
||||
assert!(!array.is_empty(context)?);
|
||||
|
||||
assert_eq!(array.pop(context)?, JsValue::new(true)); // [ "Hello, world" ]
|
||||
assert_eq!(array.pop(context)?, JsValue::new("Hello, world")); // [ ]
|
||||
assert_eq!(array.pop(context)?, JsValue::undefined()); // [ ]
|
||||
|
||||
array.push(1, context)?; // [ 1 ]
|
||||
|
||||
assert_eq!(array.pop(context)?, JsValue::new(1)); // [ ]
|
||||
assert_eq!(array.pop(context)?, JsValue::undefined()); // [ ]
|
||||
|
||||
array.push_items(
|
||||
&[
|
||||
JsValue::new(10),
|
||||
JsValue::new(11),
|
||||
JsValue::new(12),
|
||||
JsValue::new(13),
|
||||
JsValue::new(14),
|
||||
],
|
||||
context,
|
||||
)?; // [ 10, 11, 12, 13, 14 ]
|
||||
|
||||
array.reverse(context)?; // [ 14, 13, 12, 11, 10 ]
|
||||
|
||||
assert_eq!(array.index_of(12, None, context)?, Some(2));
|
||||
|
||||
// We can also use JsObject method `.get()` through the Deref trait.
|
||||
let element = array.get(2, context)?; // array[ 0 ]
|
||||
assert_eq!(element, JsValue::new(12));
|
||||
// Or we can use the `.at(index)` method.
|
||||
assert_eq!(array.at(0, context)?, JsValue::new(14)); // first element
|
||||
assert_eq!(array.at(-1, context)?, JsValue::new(10)); // last element
|
||||
|
||||
// Join the array with an optional separator (default ",").
|
||||
let joined_array = array.join(None, context)?;
|
||||
assert_eq!(&joined_array, utf16!("14,13,12,11,10"));
|
||||
|
||||
array.fill(false, Some(1), Some(4), context)?;
|
||||
|
||||
let joined_array = array.join(Some("::".into()), context)?;
|
||||
assert_eq!(&joined_array, utf16!("14::false::false::false::10"));
|
||||
|
||||
let filter_callback = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(|_this, args, _context| {
|
||||
Ok(args.get(0).cloned().unwrap_or_default().is_number().into())
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
|
||||
let map_callback = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(|_this, args, context| {
|
||||
args.get(0)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.pow(&JsValue::new(2), context)
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
|
||||
let mut data = Vec::new();
|
||||
for i in 1..=5 {
|
||||
data.push(JsValue::new(i));
|
||||
}
|
||||
let another_array = JsArray::from_iter(data, context); // [ 1, 2, 3, 4, 5]
|
||||
|
||||
let chained_array = array // [ 14, false, false, false, 10 ]
|
||||
.filter(filter_callback, None, context)? // [ 14, 10 ]
|
||||
.map(map_callback, None, context)? // [ 196, 100 ]
|
||||
.sort(None, context)? // [ 100, 196 ]
|
||||
.concat(&[another_array.into()], context)? // [ 100, 196, 1, 2, 3, 4, 5 ]
|
||||
.slice(Some(1), Some(5), context)?; // [ 196, 1, 2, 3 ]
|
||||
|
||||
assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3"));
|
||||
|
||||
let reduce_callback = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(|_this, args, context| {
|
||||
let accumulator = args.get(0).cloned().unwrap_or_default();
|
||||
let value = args.get(1).cloned().unwrap_or_default();
|
||||
|
||||
accumulator.add(&value, context)
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
chained_array.reduce(reduce_callback, Some(JsValue::new(0)), context)?,
|
||||
JsValue::new(202)
|
||||
);
|
||||
|
||||
context
|
||||
.global_object()
|
||||
.clone()
|
||||
.set("myArray", array, true, context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
70
javascript-engine/external/boa/boa_examples/src/bin/jsarraybuffer.rs
vendored
Normal file
70
javascript-engine/external/boa/boa_examples/src/bin/jsarraybuffer.rs
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// This example shows how to manipulate a Javascript array using Rust code.
|
||||
|
||||
use boa_engine::{
|
||||
object::builtins::{JsArrayBuffer, JsDataView, JsUint32Array, JsUint8Array},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
fn main() -> JsResult<()> {
|
||||
// We create a new `Context` to create a new Javascript executor.
|
||||
let context = &mut Context::default();
|
||||
|
||||
// This create an array buffer of byte length 4
|
||||
let array_buffer = JsArrayBuffer::new(4, context)?;
|
||||
|
||||
// We can now create an typed array to access the data.
|
||||
let uint32_typed_array = JsUint32Array::from_array_buffer(array_buffer, context)?;
|
||||
|
||||
let value = 0x12345678u32;
|
||||
uint32_typed_array.set(0_u64, value, true, context)?;
|
||||
|
||||
assert_eq!(uint32_typed_array.get(0_u64, context)?, JsValue::new(value));
|
||||
|
||||
// We can also create array buffers from a user defined block of data.
|
||||
//
|
||||
// NOTE: The block data will not be cloned.
|
||||
let blob_of_data: Vec<u8> = (0..=255).collect();
|
||||
let array_buffer = JsArrayBuffer::from_byte_block(blob_of_data, context)?;
|
||||
|
||||
// This the byte length of the new array buffer will be the length of block of data.
|
||||
let byte_length = array_buffer.byte_length(context);
|
||||
assert_eq!(byte_length, 256);
|
||||
|
||||
// We can now create an typed array to access the data.
|
||||
let uint8_typed_array = JsUint8Array::from_array_buffer(array_buffer.clone(), context)?;
|
||||
|
||||
for i in 0..byte_length {
|
||||
assert_eq!(uint8_typed_array.get(i, context)?, JsValue::new(i));
|
||||
}
|
||||
|
||||
// We can create a Dataview from a JsArrayBuffer
|
||||
let dataview = JsDataView::from_js_array_buffer(&array_buffer, None, Some(100_u64), context)?;
|
||||
|
||||
let dataview_length = dataview.byte_length(context)?;
|
||||
|
||||
assert_eq!(dataview_length, 100);
|
||||
|
||||
let second_byte = dataview.get_uint8(2, true, context)?;
|
||||
|
||||
assert_eq!(second_byte, 2_u8);
|
||||
|
||||
// We can also register it as a global property
|
||||
context.register_global_property(
|
||||
"myArrayBuffer",
|
||||
array_buffer,
|
||||
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
);
|
||||
|
||||
// We can also take the inner data from a JsArrayBuffer
|
||||
let data_block: Vec<u8> = (0..5).collect();
|
||||
let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
|
||||
|
||||
let internal_buffer = array_buffer.take()?;
|
||||
|
||||
assert_eq!(internal_buffer, (0..5).collect::<Vec<u8>>());
|
||||
let detached_err = array_buffer.take();
|
||||
assert!(detached_err.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
78
javascript-engine/external/boa/boa_examples/src/bin/jsdate.rs
vendored
Normal file
78
javascript-engine/external/boa/boa_examples/src/bin/jsdate.rs
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
use boa_engine::{object::builtins::JsDate, Context, JsResult, JsValue};
|
||||
|
||||
fn main() -> JsResult<()> {
|
||||
let context = &mut Context::default();
|
||||
|
||||
let date = JsDate::new(context);
|
||||
|
||||
// 823230245000.0
|
||||
JsDate::utc(
|
||||
&[
|
||||
JsValue::new(96),
|
||||
JsValue::new(1),
|
||||
JsValue::new(2),
|
||||
JsValue::new(3),
|
||||
JsValue::new(4),
|
||||
JsValue::new(5),
|
||||
],
|
||||
context,
|
||||
)?;
|
||||
// reference date: 2022-07-16T06:27:32.087241439
|
||||
|
||||
// sets day of the month to 24
|
||||
date.set_date(24, context)?;
|
||||
// 2022-07-24T06:27:11.567
|
||||
|
||||
// sets date to 1st of January 2000
|
||||
date.set_full_year(&[2000.into(), 0.into(), 1.into()], context)?;
|
||||
// 2000-01-01T06:26:53.984
|
||||
|
||||
// sets time to 10H:10M:10S:10mS
|
||||
date.set_hours(&[23.into(), 23.into(), 23.into(), 23.into()], context)?;
|
||||
// Is 2000-01-01T17:53:23.023
|
||||
// Should be 2000-01-01T23:23:23.023
|
||||
|
||||
// sets milliseconds to 999
|
||||
date.set_milliseconds(999, context)?;
|
||||
// 2000-01-01T17:40:10.999
|
||||
|
||||
// sets time to 12M:12S:12ms
|
||||
date.set_minutes(&[12.into(), 12.into(), 12.into()], context)?;
|
||||
// Is 2000-01-01T17:42:12.012
|
||||
// Should be 2000-01-01T17:12:12:012
|
||||
|
||||
// sets month to 9 and day to 9
|
||||
date.set_month(&[9.into(), 9.into()], context)?;
|
||||
// 2000-10-09T04:42:12.012
|
||||
|
||||
// set seconds to 59 and ms to 59
|
||||
date.set_seconds(&[59.into(), 59.into()], context)?;
|
||||
// 2000-10-09T04:42:59.059
|
||||
|
||||
assert_eq!(
|
||||
date.to_json(context)?,
|
||||
JsValue::from("2000-10-09T17:42:59.059Z")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
date.to_date_string(context)?,
|
||||
JsValue::from("Mon Oct 09 2000")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
date.to_iso_string(context)?,
|
||||
JsValue::from("2000-10-09T17:42:59.059Z")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
date.to_time_string(context)?,
|
||||
JsValue::from("23:12:59 GMT+0530")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
date.to_string(context)?,
|
||||
JsValue::from("Mon Oct 09 2000 23:12:59 GMT+0530")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
58
javascript-engine/external/boa/boa_examples/src/bin/jsmap.rs
vendored
Normal file
58
javascript-engine/external/boa/boa_examples/src/bin/jsmap.rs
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
use boa_engine::{
|
||||
object::{builtins::JsArray, builtins::JsMap},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
fn main() -> JsResult<()> {
|
||||
// Create a `Context` for the Javascript executor.
|
||||
let context = &mut Context::default();
|
||||
|
||||
// Create a new empty map.
|
||||
let map = JsMap::new(context);
|
||||
|
||||
// Set a key-value for the map.
|
||||
map.set("Key-1", "Value-1", context)?;
|
||||
|
||||
let map_check = map.has("Key-1", context)?;
|
||||
assert_eq!(map_check, true.into()); // true
|
||||
|
||||
// Set a second key-value to the same map.
|
||||
map.set(2, 4, context)?;
|
||||
|
||||
assert_eq!(map.get_size(context)?, 2.into()); //true
|
||||
|
||||
assert_eq!(map.get("Key-1", context)?, "Value-1".into());
|
||||
assert_eq!(map.get(2, context)?, 4.into());
|
||||
// Delete an entry with a provided key.
|
||||
map.delete("Key-1", context)?;
|
||||
assert_eq!(map.get_size(context)?, 1.into());
|
||||
|
||||
let deleted_key_one = map.get("Key-1", context)?;
|
||||
|
||||
assert_eq!(deleted_key_one, JsValue::undefined());
|
||||
|
||||
// Retrieve a MapIterator for all entries in the Map.
|
||||
let entries = map.entries(context)?;
|
||||
|
||||
let _first_value = entries.next(context)?;
|
||||
|
||||
// Create a multidimensional array with key value pairs -> [[first-key, first-value], [second-key, second-value]]
|
||||
let js_array = JsArray::new(context);
|
||||
|
||||
let vec_one = vec![JsValue::new("first-key"), JsValue::new("first-value")];
|
||||
let vec_two = vec![JsValue::new("second-key"), JsValue::new("second-value")];
|
||||
|
||||
js_array.push(JsArray::from_iter(vec_one, context), context)?;
|
||||
js_array.push(JsArray::from_iter(vec_two, context), context)?;
|
||||
|
||||
// Create a map from the JsArray using it's iterable property.
|
||||
let iter_map = JsMap::from_js_iterable(&js_array.into(), context)?;
|
||||
|
||||
assert_eq!(iter_map.get("first-key", context)?, "first-value".into());
|
||||
|
||||
iter_map.set("third-key", "third-value", context)?;
|
||||
|
||||
assert_eq!(iter_map.get_size(context)?, JsValue::new(3));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
21
javascript-engine/external/boa/boa_examples/src/bin/jsregexp.rs
vendored
Normal file
21
javascript-engine/external/boa/boa_examples/src/bin/jsregexp.rs
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
use boa_engine::{object::builtins::JsRegExp, Context, JsResult};
|
||||
|
||||
fn main() -> JsResult<()> {
|
||||
let context = &mut Context::default();
|
||||
|
||||
let regexp = JsRegExp::new("foo", "gi", context)?;
|
||||
|
||||
let test_result = regexp.test("football", context)?;
|
||||
assert!(test_result);
|
||||
|
||||
let flags = regexp.flags(context)?;
|
||||
assert_eq!(flags, String::from("gi"));
|
||||
|
||||
let src = regexp.source(context)?;
|
||||
assert_eq!(src, String::from("foo"));
|
||||
|
||||
let to_string = regexp.to_string(context)?;
|
||||
assert_eq!(to_string, String::from("/foo/gi"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
53
javascript-engine/external/boa/boa_examples/src/bin/jsset.rs
vendored
Normal file
53
javascript-engine/external/boa/boa_examples/src/bin/jsset.rs
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// This example shows how to manipulate a Javascript Set using Rust code.
|
||||
#![allow(clippy::bool_assert_comparison)]
|
||||
use boa_engine::{object::builtins::JsSet, Context, JsError, JsValue};
|
||||
|
||||
fn main() -> Result<(), JsError> {
|
||||
// New `Context` for a new Javascript executor.
|
||||
let context = &mut Context::default();
|
||||
|
||||
// Create an empty set.
|
||||
let set = JsSet::new(context);
|
||||
|
||||
assert_eq!(set.size()?, 0);
|
||||
set.add(5, context)?;
|
||||
assert_eq!(set.size()?, 1);
|
||||
set.add(10, context)?;
|
||||
assert_eq!(set.size()?, 2);
|
||||
set.clear(context)?;
|
||||
assert_eq!(set.size()?, 0);
|
||||
|
||||
set.add("one", context)?;
|
||||
set.add("two", context)?;
|
||||
set.add("three", context)?;
|
||||
|
||||
assert!(set.has("one", context)?);
|
||||
assert_eq!(set.has("One", context)?, false);
|
||||
|
||||
set.delete("two", context)?;
|
||||
|
||||
assert_eq!(set.has("two", context)?, false);
|
||||
|
||||
set.clear(context)?;
|
||||
|
||||
assert_eq!(set.has("one", context)?, false);
|
||||
assert_eq!(set.has("three", context)?, false);
|
||||
assert_eq!(set.size()?, 0);
|
||||
|
||||
// Add a slice into a set;
|
||||
set.add_items(
|
||||
&[JsValue::new(1), JsValue::new(2), JsValue::new(3)],
|
||||
context,
|
||||
)?;
|
||||
// Will return 1, as one slice was added.
|
||||
assert_eq!(set.size()?, 1);
|
||||
|
||||
// Make a new set from a slice
|
||||
let slice_set = JsSet::from_iter([JsValue::new(1), JsValue::new(2), JsValue::new(3)], context);
|
||||
// Will return 3, as each element of slice was added into the set.
|
||||
assert_eq!(slice_set.size()?, 3);
|
||||
|
||||
set.clear(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
50
javascript-engine/external/boa/boa_examples/src/bin/jstypedarray.rs
vendored
Normal file
50
javascript-engine/external/boa/boa_examples/src/bin/jstypedarray.rs
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// This example shows how to manipulate a Javascript array using Rust code.
|
||||
|
||||
use boa_engine::{
|
||||
native_function::NativeFunction,
|
||||
object::{builtins::JsUint8Array, FunctionObjectBuilder},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
fn main() -> JsResult<()> {
|
||||
// We create a new `Context` to create a new Javascript executor.
|
||||
let context = &mut Context::default();
|
||||
|
||||
let data: Vec<u8> = (0..=255).collect();
|
||||
|
||||
let array = JsUint8Array::from_iter(data, context)?;
|
||||
|
||||
assert_eq!(array.get(0, context)?, JsValue::new(0));
|
||||
|
||||
let mut sum = 0;
|
||||
|
||||
for i in 0..=255 {
|
||||
assert_eq!(array.at(i, context)?, JsValue::new(i));
|
||||
sum += i;
|
||||
}
|
||||
|
||||
let callback = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(|_this, args, context| {
|
||||
let accumulator = args.get(0).cloned().unwrap_or_default();
|
||||
let value = args.get(1).cloned().unwrap_or_default();
|
||||
|
||||
accumulator.add(&value, context)
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
array.reduce(callback, Some(JsValue::new(0)), context)?,
|
||||
JsValue::new(sum)
|
||||
);
|
||||
|
||||
context.register_global_property(
|
||||
"myUint8Array",
|
||||
array,
|
||||
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
31
javascript-engine/external/boa/boa_examples/src/bin/loadfile.rs
vendored
Normal file
31
javascript-engine/external/boa/boa_examples/src/bin/loadfile.rs
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// This example shows how to load, parse and execute JS code from a source file
|
||||
// (./scripts/helloworld.js)
|
||||
|
||||
use std::fs;
|
||||
|
||||
use boa_engine::Context;
|
||||
|
||||
fn main() {
|
||||
let js_file_path = "./scripts/helloworld.js";
|
||||
|
||||
match fs::read(js_file_path) {
|
||||
Ok(src) => {
|
||||
// Instantiate the execution context
|
||||
let mut context = Context::default();
|
||||
// Parse the source code
|
||||
match context.eval(src) {
|
||||
Ok(res) => {
|
||||
println!(
|
||||
"{}",
|
||||
res.to_string(&mut context).unwrap().to_std_string_escaped()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Pretty print the error
|
||||
eprintln!("Uncaught {e}");
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(msg) => eprintln!("Error: {}", msg),
|
||||
}
|
||||
}
|
||||
24
javascript-engine/external/boa/boa_examples/src/bin/loadstring.rs
vendored
Normal file
24
javascript-engine/external/boa/boa_examples/src/bin/loadstring.rs
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// This example loads, parses and executes a JS code string
|
||||
|
||||
use boa_engine::Context;
|
||||
|
||||
fn main() {
|
||||
let js_code = "console.log('Hello World from a JS code string!')";
|
||||
|
||||
// Instantiate the execution context
|
||||
let mut context = Context::default();
|
||||
|
||||
// Parse the source code
|
||||
match context.eval(js_code) {
|
||||
Ok(res) => {
|
||||
println!(
|
||||
"{}",
|
||||
res.to_string(&mut context).unwrap().to_std_string_escaped()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Pretty print the error
|
||||
eprintln!("Uncaught {e}");
|
||||
}
|
||||
};
|
||||
}
|
||||
62
javascript-engine/external/boa/boa_examples/src/bin/modulehandler.rs
vendored
Normal file
62
javascript-engine/external/boa/boa_examples/src/bin/modulehandler.rs
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// This example implements a custom module handler which mimics
|
||||
// the require/module.exports pattern
|
||||
|
||||
use boa_engine::{
|
||||
native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsResult,
|
||||
JsValue,
|
||||
};
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn main() {
|
||||
let js_file_path = "./scripts/calctest.js";
|
||||
let buffer = read_to_string(js_file_path);
|
||||
|
||||
if buffer.is_err() {
|
||||
println!("Error: {}", buffer.unwrap_err());
|
||||
return;
|
||||
}
|
||||
|
||||
// Creating the execution context
|
||||
let mut ctx = Context::default();
|
||||
|
||||
// Adding custom implementation that mimics 'require'
|
||||
ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require));
|
||||
|
||||
// Adding custom object that mimics 'module.exports'
|
||||
let moduleobj = JsObject::default();
|
||||
moduleobj
|
||||
.set("exports", JsValue::from(" "), false, &mut ctx)
|
||||
.unwrap();
|
||||
ctx.register_global_property("module", JsValue::from(moduleobj), Attribute::default());
|
||||
|
||||
// Instantiating the engine with the execution context
|
||||
// Loading, parsing and executing the JS code from the source file
|
||||
ctx.eval(&buffer.unwrap()).unwrap();
|
||||
}
|
||||
|
||||
// Custom implementation that mimics the 'require' module loader
|
||||
fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
let arg = args.get(0).unwrap();
|
||||
|
||||
// BUG: Dev branch seems to be passing string arguments along with quotes
|
||||
let libfile = arg
|
||||
.to_string(ctx)
|
||||
.expect("Failed to convert to string")
|
||||
.to_std_string_escaped();
|
||||
|
||||
// Read the module source file
|
||||
println!("Loading: {}", libfile);
|
||||
let buffer = read_to_string(libfile);
|
||||
if let Err(..) = buffer {
|
||||
println!("Error: {}", buffer.unwrap_err());
|
||||
Ok(JsValue::Rational(-1.0))
|
||||
} else {
|
||||
// Load and parse the module source
|
||||
ctx.eval(&buffer.unwrap()).unwrap();
|
||||
|
||||
// Access module.exports and return as ResultValue
|
||||
let global_obj = ctx.global_object().to_owned();
|
||||
let module = global_obj.get("module", ctx).unwrap();
|
||||
module.as_object().unwrap().get("exports", ctx)
|
||||
}
|
||||
}
|
||||
48
javascript-engine/external/boa/boa_examples/src/bin/symbol_visitor.rs
vendored
Normal file
48
javascript-engine/external/boa/boa_examples/src/bin/symbol_visitor.rs
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// This example demonstrates how to use a visitor to perform simple operations over the Javascript
|
||||
// AST, namely: finding all the `Sym`s present in a script. See commuter_visitor.rs for an example
|
||||
// which mutates the AST.
|
||||
|
||||
use boa_ast::visitor::Visitor;
|
||||
use boa_engine::Context;
|
||||
use boa_interner::Sym;
|
||||
use boa_parser::Parser;
|
||||
use core::ops::ControlFlow;
|
||||
use std::{collections::HashSet, convert::Infallible, fs::File, io::BufReader};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct SymbolVisitor {
|
||||
observed: HashSet<Sym>,
|
||||
}
|
||||
|
||||
impl<'ast> Visitor<'ast> for SymbolVisitor {
|
||||
type BreakTy = Infallible;
|
||||
|
||||
fn visit_sym(&mut self, node: &'ast Sym) -> ControlFlow<Self::BreakTy> {
|
||||
self.observed.insert(*node);
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut parser = Parser::new(BufReader::new(
|
||||
File::open("boa_examples/scripts/calc.js").unwrap(),
|
||||
));
|
||||
let mut ctx = Context::default();
|
||||
|
||||
let statements = parser.parse_all(ctx.interner_mut()).unwrap();
|
||||
|
||||
let mut visitor = SymbolVisitor::default();
|
||||
|
||||
assert!(matches!(
|
||||
visitor.visit_statement_list(&statements),
|
||||
ControlFlow::Continue(_)
|
||||
));
|
||||
|
||||
println!(
|
||||
"Observed {} unique strings/symbols:",
|
||||
visitor.observed.len()
|
||||
);
|
||||
for sym in visitor.observed {
|
||||
println!(" - {}", ctx.interner().resolve(sym).unwrap());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user