boa-dev / boa Goto Github PK
View Code? Open in Web Editor NEWBoa is an embeddable and experimental Javascript engine written in Rust. Currently, it has support for some of the language.
License: MIT License
Boa is an embeddable and experimental Javascript engine written in Rust. Currently, it has support for some of the language.
License: MIT License
Calling new String()
currently works however calling String() without new
will fail.
You will be returned the global object which has everything in it and causes a stack overflow. This would be the equivalent of traversing every object and property of window
in the browser.
The reason this happens is because String() is currently written to take the this
value, and attach a primitiveValue field to the slice which is set to the native string. Because we're calling String() without new, there is no this
passed in, and so this
becomes the global object rather than a new object we pass
The pattern here is this
new Number()
returns an instance of Number
Number()
returns a primitive number
new String()
returns an instance of String
String()
returns a primitive string
Notes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
Sub issues:
The javascript built-in in
operator is missing in the implementation of the lexer and also in the later stages. It is used to test if a property is in an object or its prototype chain.
It should probably be added to the lexer in this method, somewhere in the big match statement https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/lexer.rs#L207-L533
The operator's syntax is defined here https://tc39.es/ecma262/#sec-relational-operators in the spec and its semantics here below https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
undefined
is returned
`"1" should be returned
Ive also noticed boa_bg no longer gets outputed in pkg but index_bg instead.
Seems like wasm-pack has had quite a few changes recently
clone project
yarn install
yarn serve
navigate to localhost:8080
Open up console and type evaluate("1")
You should get "1" back, plus typing in the big box should work.
Now try updating wasm-pack to the latest version here
Similar to SpiderMonkey's jsshell
, or V8's d8
, this is needed to create an eshost
agent.
./target/debug/bin
is almost that, but needs:
Things needed in the shell runtime itself:
print()
function in the global scope (or similar). SpiderMonkey's JavaScript shell defines this as:
Evaluates the expression(s) and displays the result(s) on
stdout
, separated by spaces (" ") and terminated by a newline ("\n").
globalThis
or similar mechanismnewGlobal()
or d8's Realm.global(realmId)
(which requires some way of making a realmId
, ie. Realm.create()
or Realm.createAllowCrossRealmAccess()
)evaluate(code)
or d8's Realm.eval(realmId, code)
let
has already been added to the Keywords enum which means if the lexer comes across it, it knows to create a new Token for let.
The problem comes with the parser, as right now the parser doens't looks for let, so it fails at this point.
The easy option to make things work would be to just add a | Keyword::let
here but this means let
would have the same semantics as var
.
What is different about let?
Let is block scope, so it exist and then be popped off at the end of the closing scope. Right now we have scopes but they're only used when functions are created. Examples here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L168
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L352
So the fix is..
LetDeclExpr
in the parser.let
declerations in the right place. This may be easier said than done, as block scopes will need to inherit from function scopes and vice versavar
worksExtra Info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
https://tc39.github.io/ecma262/#sec-let-and-const-declarations
Research
https://searchfox.org/mozilla-central/source/js/src/vm/EnvironmentObject.h#43
Thank you for starting this project!
I tried to run this JS code
var re2 = /abc/
and got
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Expected([], Token { data: Punctuator(Div), pos: Position { column_number: 11, line_number: 1 } }, "script")', src/libcore/result.rs:997:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
This should be an easy one..
String has a [[Construct]] method which returns a string instance. This is working fine.
The [[Call]] version should return a native string.
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/string.rs#L529-L532
For inspiration, see how Boolean works:
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/boolean.rs#L53-L54
It might be hard to test for now, as the executor currently doesn't use [[Call]]
https://tc39.es/ecma262/#sec-string-constructor-string-value
Similar to the tests for the lexer.rs we would like tests for parser.rs
The parser is used here: https://github.com/jasonwilliams/boa/blob/master/src/lib/lib.rs#L33 it accepts a vector of tokens. We then want to make sure the expr it returns matches what we want
I often write the JS i want to test here: https://github.com/jasonwilliams/boa/blob/master/tests/js/test.js
Then i can output the tokens, by adding dbg!(&tokens) here: https://github.com/jasonwilliams/boa/blob/master/src/lib/lib.rs#L31
You can now pass these into the parser and test the output.
Currently javascript without semi colons break
We basically need to implement https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion
d * (b - 3) + 1;
generates the Expr:
BlockExpr(
[
Expr {
def: BinOpExpr(
Num(
Add,
),
Expr {
def: ConstExpr(
Num(
1.0,
),
),
},
Expr {
def: BinOpExpr(
Num(
Mul,
),
Expr {
def: LocalExpr(
"d",
),
},
Expr {
def: BinOpExpr(
Num(
Sub,
),
Expr {
def: LocalExpr(
"b",
),
},
Expr {
def: ConstExpr(
Num(
3.0,
),
),
},
),
},
),
},
),
},
],
),
The 1.0
should be after the d * (b - 3)
.
op.rs shows addition and subtraction to be lower than multiplication/Div/Mod
https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/ast/op.rs#L191-L210 should match up with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
Followed the instructions in the README.md and CONTRIBUTING.md, this is what I ran into:
$ cargo build --verbose
warning: unused manifest key: package.edition
error: failed to parse lock file at: /Users/rwaldron/clonez/boa/Cargo.lock
Caused by:
expected a section for the key `root`
Any tips?
the time crate is deprecated and Chrono seems to be the replacement. We should move to this
Time
https://crates.io/crates/time
https://docs.rs/time/0.1.40/time/
Chrono
https://crates.io/crates/chrono
https://docs.rs/chrono/0.4.6/chrono/
More Info
time-rs/time#136
The following statements both print null
instead of undefined
.
var x;
x;
and
let x;
x;
Testing locally, it seems like it's enough to change the defaults at https://github.com/jasonwilliams/boa/blob/8851eba27d1df86e139e101f2b0ce26dbf3a1920/src/lib/exec.rs#L395 and https://github.com/jasonwilliams/boa/blob/8851eba27d1df86e139e101f2b0ce26dbf3a1920/src/lib/exec.rs#L407
to ValueData::Undefined
.
We generate Gc::new(ValueData::Undefined)
everywhere.
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L109
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L131
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L322
It would be good to generate 1 undefined Value and reference this, it would live throughout the lifetime of the whole program, so could it be a static value?
Hi 😃, great work and great crate, i would like to use this crate in my projects to provide some sort of dynamic logic for my application, currently checking out this crate in crates.io i found it uses other dependencies that could be optional for example the wasm-bindgen
.
I mean it would be great to split it up into two crates for example boa-core
and boa-wasm
or maybe just make the wasm-bindgen
for example an optional dependency behind a feature flag, also to avoid break the world it could be enabled by default, that also would be great.
Thank you 😊
When we step into a new function, we create a new_function_environment
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L110
Because we don't do the same for blocks (if,while etc) any let values we create in blocks are added to the current environment, which will be the outer function most likely. This means the following JS works..
function outer() {
var foo = "foo";
if (true) {
let bar = "bar";
}
return bar; // "bar"
}
When a Block or CaseBlock is evaluated a new declarative Environment Record is created and bindings for each block scoped variable, constant, function, or class declared in the block are instantiated in the Environment Record.
https://tc39.es/ecma262/#sec-blockdeclarationinstantiation
The process here is to create a new_declarative_environment
when we hit a block and set that to the current environment, popping it off when we reach the end of our block. A potential bug this will cause is putting var
declarations into the same scope, so we need to make sure vars
still go into the function scope above.
new_function_environment
is used.var
)It might be an idea to have this, so some PR's are formatting other PR's, it should make it easier for those reviewing.
https://github.com/rust-lang/rustfmt is here.
cargo fmt
needs to be added to a commit hook. We can add something to CONTRIBUTING.md hinting that rustfmt
needs to be installed by rustup component add rustfmt
Steps
I think its something to do with fetching the time
boa.js:131 Uncaught RuntimeError: unreachable
at std::panicking::rust_panic_with_hook::h12b7239ed4348eae (wasm-function[1218]:298)
at std::panicking::begin_panic::h4c30fd2f16ce25fc (wasm-function[1867]:164)
at time::sys::inner::get_time::ha6751084583d997f (wasm-function[5455]:35)
at time::get_time::hca460158107d2c69 (wasm-function[2293]:37)
at time::now::h6e4a6a5d85944826 (wasm-function[4208]:26)
at chrono::offset::local::Local::now::h3faa61eb795dc1ef (wasm-function[4332]:28)
at boa::js::console::log::h028a12ac78490d44 (wasm-function[256]:180)
at <boa::exec::Interpreter as boa::exec::Executor>::run::he59dda6185fb5b63 (wasm-function[8]:31833)
at <boa::exec::Interpreter as boa::exec::Executor>::run::he59dda6185fb5b63 (wasm-function[8]:894)
at boa::evaluate::h7d4c1f9b9e79a883 (wasm-function[34]:2136)
Stack trace is hitting here:
https://github.com/chronotope/chrono/blob/master/src/offset/local.rs#L91
Linked Issue
chronotope/chrono#284
Would there be any tasks for a Rust beginner to do? Would definitely like to contribute some code :)
Running boa will always read from the same file, at some point we will need to read from any file that is pased through.
Parsing arguments example:
https://doc.rust-lang.org/beta/rust-by-example/std_misc/arg/matching.html
https://github.com/jasonwilliams/boa/blob/master/src/bin/bin.rs#L6 can be changed to read from the command line argument (similar to NodeJS)
Maybe tests/js/test.js
can be read as a fallback?
Missing implementation of assign operators:
AssignMul: x *= y
AssignPow: x **= y
AssignAdd: x += y
AssignSub: x -= y
AssignMod: x %= y
AssignOr: x |= y
AssignAnd: x &= y
AssignXor: x ^= y
AssignLeftSh: x <<= y
AssignRightSh: x >>= y
lexer.rs now has a test suite, but testing comments are still missing.
Test would ensure single-line and multi-line comments are skipped passed and don't generate any tokens
https://github.com/jasonwilliams/boa/blob/master/CONTRIBUTING.md
Lines: https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/lexer.rs#L237-L240
It seems that support for Unicode escape sequences is not perfect. Is there a plan to create a complete Lexer and Parser ?
test.js
const fib = n => {
if(n <= 1) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}
console.log(fib(10));
let a = "hello world";
a;
result
Hello
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: UnexpectedKeyword(Else)', src/libcore/result.rs:997:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Currently property access to objects is done directly by calling .properties
you can see here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/value.rs#L216
We should define the method getOwnProperty
for objectData here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/object.rs#L16-L27
It might be worth implementing the function ordinarygetownproperty
first in the same file.
Spidermonkey calls Symbol::new() with ctx, SymbolCode::Unique and description.
https://searchfox.org/mozilla-central/source/js/src/vm/SymbolType.cpp#34
Every symbol holds a SymbolCode enum to say what kind of Symbol it is.
We could make symbol!() a macro, so that description can be dynamically added, or description could be an
Both Spidermonkey and V8 use u32 for their hash code. So there’s no reason we need a u64 here.
Spidermonkey uses XorShift128PlusSeed algorithm to generate random hashes.
https://searchfox.org/mozilla-central/source/js/src/vm/Runtime.cpp#682
This means we should be safe to use the rand crate and generate u32 values here. As that’s all Spidermonkey is doing.
Spidermonkey team say random() should be fine
What is random() powered by?
random() is just a shortcut for thread_rnd().gen() which itself uses rand:rngs::StdRng.
The current algorithm used is the ChaCha block cipher, it is not secure.
rand::rngs::SmallRng is the best choice for small state, cheap initialisation, good statistical quality and good performance. We don’t care so much about security here, so we should go with this.
Technically we don’t ever expose the hash to a Symbol, and our only criteria is that they’re unique.
So we could just increment a number
Implement Symbols: https://bugzilla.mozilla.org/show_bug.cgi?id=645416
The way functions are implemented are as blocks (arrays of expressions the executor works through).
If return happens we want to stop at this point and return the value.
Currently this doesn't happen:
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L61-L70
Arises from: jasonwilliams#57
Benchmarks were added in: jasonwilliams#109
They will introduce a slow down to all function invocations.
It might be an idea to only add an argument object to the function scope when its needed.
arguments
Test | PR Benchmark | Master Benchmark | % |
---|---|---|---|
Hello World (Execution) | 168.2±7.00µs | 173.4±9.18µs | 97% |
Hello World (Lexer) | 1096.3±28.06ns | 1136.3±37.10ns | 96% |
Hello World (Parser) | 1294.0±35.94ns | 1220.6±41.01ns | 106% |
fibonacci (Execution) | 5.4±0.16ms | 2.2±0.08ms | 239% |
Bring in test262 as a submodule.
Add into the tests directory
These methods would be added to the String prototype object which is implemented here:
https://github.com/boa-dev/boa/blob/master/boa/src/builtins/string/mod.rs
Spec: https://tc39.es/ecma262/#sec-properties-of-the-string-prototype-object
Implementation Example?
See charAt() and charCodeAt() for a good example
https://tc39.es/ecma262/#sec-ispropertykey may need to be implemented in the same (object.rs) file also, as that's where the current Property implementation is.
Property struct lives here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/object.rs#L53-L66
The argument would be a JSValue
We currently don't support Symbols, so for now you could just test for String.
Or even make use of: https://github.com/jasonwilliams/boa/blob/master/src/lib/js/value.rs#L123-L128
This method will later be used by jasonwilliams#71
The javascript built-in exponentiation operator (**
) is missing in the implementation of the lexer. It is a shorthand for Math.pow()
built-in.
It should probably be added to the lexer in this method, somewhere in the big match statement https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/lexer.rs#L207-L533
The operator's syntax and semantics is defined in this section here https://tc39.es/ecma262/#sec-exp-operator in the spec and does the same thing at runtime than Math.pow()
, which is already implemented here https://github.com/jasonwilliams/boa/blob/master/src/lib/js/math.rs#L140-L148.
It would be good to start running these https://github.com/tc39/test262/tree/master/test/language
https://github.com/jasonwilliams/boa/projects/2
Things like implement require/import alias.
Take this javascript..
let a = "foo";
a = "bar";
The error message we get is
thread 'main' panicked at 'Binding already exists!', src/lib/environment/global_environment_record.rs:108:13
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
This is because rather than re-using a
the interpreter is attempting to create a new binding called a
which already exists so causes an error.
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L333-L337 needs to be more clever. there needs to be a check here to see if the binding already exists, if it does use that binding and set it to a new value.
https://tc39.github.io/ecma262/#sec-assignment-operators-runtime-semantics-evaluation
https://tc39.github.io/ecma262/#sec-putvalue
let num = 45;
function fib(n) {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
let res = fib(num);
res;
Related to #74
Create a second binary which can be used for debugging similar to d8.
The first step can be for it to have a print function, and output to stdout/stderr.
print() function in the global scope (or similar). SpiderMonkey's JavaScript shell defines this as:
Evaluates the expression(s) and displays the result(s) on stdout, separated by spaces (" ") and terminated by a newline ("\n").
You can look at how String is created, here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/string.rs
boolean
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/boolean.rs
Its then added to the global scope here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/exec.rs#L36
match
function would need to be added to the string object here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/string.rs
It would be very similar to:
https://github.com/jasonwilliams/boa/blob/master/src/lib/js/regexp.rs#L212-L251
As spotted in jasonwilliams#13 (comment) string methods on string literals won't work because automatic Boxing and Unboxing is not supported in Boa yet. This is something we'll need to think about adding, it could be worth seeing how spidermonkey or V8 do this
There's some low hanging fruit in this one, we've now enabled Clippy. Clippy is the Rust linter
This means there's some linting issues that will need fixing. You can see the warnings from each build for e.g here: https://travis-ci.com/jasonwilliams/boa/jobs/213821772
cargo clippy
in the project.yarn serve
or yarn build
should clean out dist
folder before running.
I believe this can be done by using https://github.com/johnagan/clean-webpack-plugin. I couldn't get it working last time i tried it.
https://tc39.es/ecma262/#sec-let-and-const-declarations-static-semantics-early-errors
It is a Syntax Error if Initializer is not present and IsConstantDeclaration of the LexicalDeclaration containing this LexicalBinding is true.
const a = 5, b, c = 6;"
Should fail, binding b
is not initialised
https://github.com/jasonwilliams/boa/pull/42/files#diff-9f9158af1c9e00d4a93319c5918d9b97R924
https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/parser.rs#L79-L133
https://github.com/jasonwilliams/boa/blob/master/CONTRIBUTING.md
Right now objects are currently printing out every single property, if toString
is set, it should use that,
toString
is already set on Object
which other objects should fall back to.
The danger is debugging could be more difficult with [object Object]
so we need a way to still have a debugging view for the developer.
This is the JS
var a = new String("Hello World");
a;
And here is the output
{__proto__: {toString: function() { [native code] }, length: undefined, __proto__: {toString: function() { [native code] }, __proto__: undefined, hasOwnProperty: function() { [native code] }}}}
This isn't too bad, we have an object who's proto
is set to String.prototype
.
We want to retain this information for debugging purposes, but should not be an output for the user
The issue description work in progress
So i had a great response from the creator of Criterion.rs and he gave some advice on some tweaks we can do for Boa.
Very small changes were making significant changes in Boa, although this could be the way LLVM optimises even the slightest differences in Rust code.
His response was here:
https://gist.github.com/jasonwilliams/5325da61a794d8211dcab846d466c4fd
after looking at:
https://boa-pull.jason-williams.co.uk/82/report/
from PR: jasonwilliams#82
A lot of errors happen because preview_next()
gets called and unwraps an error when there's no more characters left.
This isn't really an error, its expected to happen at some point. all self.preview_next()
calls should expect an option, and deal with the case of no characters individually.
https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/lexer.rs
Rust 2018 edition removed the need for the try
macro, functions which return an Option/Result can be used with the ?
operator.
More info here:
https://doc.rust-lang.org/stable/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html
Boa still has try!
all over the place for e.g https://github.com/jasonwilliams/boa/blob/master/src/lib/js/value.rs#L507
These could be converted to use the ? operator instead. e.g https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/lexer.rs#L219
Similar to #13 but for the Array
Implementation happens here: https://github.com/boa-dev/boa/blob/master/boa/src/builtins/array/mod.rs
https://tc39.es/ecma262/#prod-ArrayAssignmentPattern
let foo = [1, 2, 3,];
Parser should ignore the trailing comma
https://github.com/jasonwilliams/boa/pull/42/files#diff-9f9158af1c9e00d4a93319c5918d9b97R816
https://github.com/jasonwilliams/boa/blob/master/CONTRIBUTING.md
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.