Replies: 21 comments
-
yeah we don't need to expose that really. Are there any existing standards/interfaces we should be following? |
Beta Was this translation helpful? Give feedback.
-
Speaking of standard/interfaces we should follow the Rust API Guidelines |
Beta Was this translation helpful? Give feedback.
-
Not that I know of. We could check how V8 and SpiderMonkey interface with browsers, for example. |
Beta Was this translation helpful? Give feedback.
-
Neon crate has a nice API. that's were i got the idea of The |
Beta Was this translation helpful? Give feedback.
-
In the future we might want to have some kind of procedural macro to make it easy to interoperate from native to javascript
#[wasm_bindgen]
#[derive(Copy, Clone)]
pub struct Answer(u32);
#[wasm_bindgen]
impl Answer {
pub fn new() -> Answer {
Answer(41)
}
#[wasm_bindgen(getter)]
pub fn the_answer(self) -> u32 {
self.0 + 1
}
pub fn foo(self) -> u32 {
self.0 + 1
}
} We could have something like this, instead of #[boa]
#[derive(Copy, Clone)]
pub struct Answer(u32);
#[boa]
impl Answer {
pub fn new() -> Answer { // this will be ignored
Answer(Self::helper_function())
}
fn helper_function() -> u32 { // this too
42
}
#[boa(name = "theAnswer", getter)] // we could have the proc macro convert the name of the function to camelCase implicitly, so we don't have to supply it, when we don't want to rename it.
pub fn the_answer(&mut self, args: &[Value], ctx: &mut Interpreter) -> u32 {
self.0 + 1
}
#[boa(name = "bar", prototype)] // Reaname function to 'bar' and add it to the prototype.
pub fn foo(&mut self, args: &[Value], ctx: &mut Interpreter) -> u32 {
self.0 + 1
}
} And the For the function: #[boa(name = "theAnswer", getter)]
pub fn the_answer(&mut self, args: &[Value], ctx: &mut Interpreter) -> u32 {
self.0 + 1
} Where it takes This is for objects and methods but we can have something similar for functions. BTW: I'm just brainstorming. What do you think? |
Beta Was this translation helpful? Give feedback.
-
This would be awesome. I would automatically rename to camelCase, though, since that's the usual pattern in JavaScript, and maybe allow to specify the name if needed. |
Beta Was this translation helpful? Give feedback.
-
This might need to be I like how in the random crate you have an option to just use it directly without much setup https://rust-random.github.io/book/guide-start.html i think using Whats a simple use case? You pass in a string (or path to file) then get a value back.
I think How much of this do we want to follow: Embedding V8 example |
Beta Was this translation helpful? Give feedback.
-
Here is V8 API https://v8.dev/docs/embed (it also has some examples) |
Beta Was this translation helpful? Give feedback.
-
#461 implements Here's a Q |
Beta Was this translation helpful? Give feedback.
-
I think it should have all the builtins, since that should be the "default" behaviour. We can have a |
Beta Was this translation helpful? Give feedback.
-
if I wants to embed boa into some native application. I wants more featured API. so that i can call rust code from javascript and return javascript value from rust code. maybe like nodejs's napi, https://nodejs.org/api/n-api.html |
Beta Was this translation helpful? Give feedback.
-
You currently can do this, if I'm not mistaken, but it's a bit difficult to make it happen. |
Beta Was this translation helpful? Give feedback.
-
Yes. you can do this, I have been looking at making this a bit nicer, how context.register_function("sayHello", |context, (name, lastname, age): (String, String, u8)| {
println!("Hello, {} {}", name, lastname);
// do something with age
Ok(Value::undefined())
}); Right now we pass the interpreter as the context, but this is not what we want, the interpreter is only a implementation detail, we might remove the struct Context<'a> {
executor: &'a mut Interpreter,
// maybe other fields ...
} We could also have different contexts for different things struct CallContext<'a> {
context: Context<'a>,
arguments: &'a [Value],
this: &'a Value,
} Something similar to There will be a trait maybe called pub trait FromValue {
fn from_value(&self, context: Context) -> Result<Self>;
}
pub trait ToValue {
fn to_value(&self, context: Context) -> Result<Value>;
} so we could so something like: context.register_function("sayHello", |context, name: String| {
println!("Hello, {)", name);
Ok(Value::undefined())
}); These methods of passing arguments enables us to have in a way variadic parameters. We can pass also a function it does not have to be a closure: // in boa have a `Result` type def
// in sre/lib.rs
type Result<T> = Result<T, Value>;
/// user code
fn say_hello(_: CallContext, (name, lastname, age): (String, String, u8)) -> boa::Result<()> {
Ok(())
}
context.register_function("sayHello", say_hello); Questions that need answers for the API:
Right now these are the only thing that popped into my mind, but there are probably more. What do you think? |
Beta Was this translation helpful? Give feedback.
-
I would say it should be
Good question. I would just follow whatever happens in JS when you add more arguments to any function. So for example, the var d = new Date(10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10) Then the rest of the parameters are just ignored. So I think we should just ignore them too. There is one especial case. What if we want the Rust function to receive a variable number of arguments? In that case, I would say we need a way of calling that function with a slice of the argument list. So, all Rust functions should have a signature such as: fn say_hello(_context: CallContext, _args: &[Value]) -> boa::Result<()> {
Ok(())
} We can even create a type (
I think this should be solved with the idea above. There is no way in Rust to specify a variable number of arguments, so we will need to make all Rust functions accept a slice of arguments, and then the function itself can check if the number and type of arguments is correct.
Overal, I think this is the way to go, and it looks easy to use. I don't think we can just use any Rust function from JavaScript, but I do think we can allow users to create wrappers with lists of arguments. If we had to create a default wrapper, I would just ignore extra arguments and give a Even if we provide a default wrapper for most native Rust functions, I still think we have to expose this, to allow for customizing the behaviour in case the number or types of the arguments is not the expected one. |
Beta Was this translation helpful? Give feedback.
-
Agreed. this is the way to go if we want to match what js does, in rust. About the variadic arguments, one way of doing is as you said: fn say_hello(_context: CallContext, _args: &[Value]) -> boa::Result<()> {
Ok(())
}
struct CallContext<'a> {
context: Context<'a>,
arguments: &'a [Value],
this: &'a Value,
} and we can implement on fn say_hello(context: CallContext, _: ()) -> boa::Result<()> { // we accept an () unit which is accepting no arguments
context.arguments() // maps the first argument to the last argument
Ok(())
} The equivelent in js is: function sayHello() {
arguments
} This matches nicely with javascript and Rust :) What if we want to have typed arguments with variadic? impl<A, B> FromValueMulti for (A, B, &[Value])
where
A: FromValue,
B: FromValue,
{
// ...
} so we could do: fn say_hello(context: CallContext, (_, _, _): (String, u32, &[Value])) -> boa::Result<()> {
Ok(())
} we would have more overloads no just also pub trait FromValueMulti {
// ...
// Return the number of arguments this function accepts
fn length() -> usize;
} so for Also forgot to mention if |
Beta Was this translation helpful? Give feedback.
-
FWIW the current internal API is pretty awesome. I've used the ChakraCore integration API in the past, which is designed to be usable for integration (unlike V8), and Boa feels way more ergonomic than even that. The effort involved for a large public interface ( IMO simply adding a public |
Beta Was this translation helpful? Give feedback.
-
My experience with overloading stuff is that you have to decide where to stop, and it will increase compile times a lot (I'm looking at you, It's not easy to define a good and only way of going from Rust values to JS values and vice-versa. We can just use
What would this do? We were talking about adding functions, but something similar could be done to add new objects. |
Beta Was this translation helpful? Give feedback.
-
If I was a user of What do you think? @Razican |
Beta Was this translation helpful? Give feedback.
-
I like the idea of having it behind a feature flag. |
Beta Was this translation helpful? Give feedback.
-
We have the What is your opinion? |
Beta Was this translation helpful? Give feedback.
-
Yes, I think we should stay close to Rust naming conventions. |
Beta Was this translation helpful? Give feedback.
-
Related to #440.
Current situation
The current public API of Boa, that can be found here for Boa 0.8.0 and can be seen offline running
cargo doc --open
for themaster
branch, is far from optimal. We have 3 global functions:exec()
: That creates a new lexer, parser, Realm and interpreter, and isolates the execution of a given JavaScript string, and return a string with the result.forward()
: That will receive an already initialized interpreter and a JavaScript string and will lex, parse and execute this code in the initialized interpreter, and return the result as a string.forward_val()
: That will work the same way asforward()
but will return aResultValue
, which will indicate if the execution was successful or not.Then, we have the following modules public:
syntax
: which includes:ast
: the AST implementation (nodes, constants, keywords, builtin types, positions, spans, tokens, operations, punctuators...), with some re-exports to make life easier.lexer
: The tokenizer/lexer implementation, that turns a string into a vector of tokens. (this is changing in Started with the new lexer implementation #432)parser
: The parser implementation, with its error and a couple of parsers (Script
andScriptBody
), which turns a vector of tokens into aStatementList
, that can then be executed with an interpreter.realm
, which includes the implementation of aRealm
, an interesting helper function to register a global function, and makes public the global object, environment, and the lexical environment. This in theory allows anyone to add stuff to the global environment, but it's a bit cumbersome.builtins
: Where we have the builtin objects such asArray
,Number
,JSON
and more, and also the implementations ofValue
and object properties. And the initialization function.environment
, which has modules for each of the environments and the global trait, everything exposed because therealm
has public members so that they can be modified.exec
: We only contain theInterpreter
, with just two public functions:new()
andto_string()
. The first creates a new interpreter with a givenRealm
, and the second one converts aValue
to a string. It also contains theExecutable
trait, that allows callingrun()
on any AST node passing the interpreter so that it runs it.Finally, we also have some re-exports to make things easier to use.
Problem description
When a user wants to use Boa for their projects, I can imagine the following use cases:
Read
interface and get the result by streaming the bytes.global
object, for example, because they want it to be thewindow
or aframe
.I cannot think of anything else, but there might be others. In this context, our current API is very poor. It does allow most of this (except for the
Read
interface, that is being worked on in #432). But, it is very difficult to do it if you are not an expert in Boa.Proposed solution
I think we should go for a global
Boa
structure. This structure should have adefault()
function (implementing thedefault()
trait, that would setup a clean environment for the execution. Then ainterpret()
function that would receive either a&str
or au8
(Read
) stream (we might need two functions) and return aResultValue
. We could then callto_string()
on this if we needed to show it, for example, or on theOk
/Err
variants.It should also have a
new()
orwith_global_obj()
function that would require a custom global object. Note that we don't need to expose all the Gc stuff here, as we would be owning the global object.One option is for it to receive the full
Realm
, but I think it's best if we didn't expose the complexity of aRealm
. In any case, that global object should be wrapped, to provide an easy to use API:register_global_object()
function, that would just receive a name and aNativeObject
. The currentregister_global_func()
should be kept (maybe renamining toregister_global_function()
.The
NativeObject
should just implement atrait
, and should easily be boxable, or we could use dynamic dispatch. This trait should allow adding native methods to the object, and should implement a way of retrieving and setting fields. I want to be vague about this, because #419 should give us a clear path.So, what do you think? What could we improve?
Beta Was this translation helpful? Give feedback.
All reactions