Coder Social home page Coder Social logo

boa-dev / boa Goto Github PK

View Code? Open in Web Editor NEW
3.7K 46.0 324.0 706.4 MB

Boa is an embeddable and experimental Javascript engine written in Rust. Currently, it has support for some of the language.

License: MIT License

Rust 99.72% JavaScript 0.25% HTML 0.03%
rust javascript-engine interpreter javascript-interpreter ecmascript hacktoberfest javascript parser wasm webassembly

boa's People

Contributors

0x7d2b avatar 54k1 avatar adumbidiot avatar annikacodes avatar callumquick avatar croraf avatar dependabot[bot] avatar domparfitt avatar evomassiny avatar georgeroman avatar halidodat avatar iovoslaviovchev avatar jasonwilliams avatar jedel1043 avatar jevancc avatar johndoneth avatar joshwd36 avatar lan2u avatar letmutx avatar lupd avatar n14little avatar neeldug avatar nekevss avatar norbertgarfield avatar rageknify avatar raskad avatar razican avatar tofpie avatar tunz avatar vtcakavsmoace avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

boa's Issues

UnexpectedKeyword(Else)

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.

empty `const` declaration should fail

Specification:

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.

Expected

const a = 5, b, c = 6;"

Should fail, binding b is not initialised

Related to:

https://github.com/jasonwilliams/boa/pull/42/files#diff-9f9158af1c9e00d4a93319c5918d9b97R924

Parser const decl location

https://github.com/jasonwilliams/boa/blob/master/src/lib/syntax/parser.rs#L79-L133

Contributing

https://github.com/jasonwilliams/boa/blob/master/CONTRIBUTING.md

Implement missing assign operators

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

Implement Array prototype methods

Similar to #13 but for the Array

  • Array.of() (#1127)
  • Array.prototype.concat()
  • Array.prototype.copyWithin()
  • Array.prototype.entries()
  • Array.prototype.every()
  • Array.prototype.fill()
  • Array.prototype.filter()
  • Array.prototype.find()
  • Array.prototype.findIndex()
  • Array.prototype.flat()
  • Array.prototype.flatMap()
  • Array.prototype.forEach()
  • Array.prototype.includes()
  • Array.prototype.indexOf()
  • Array.prototype.join()
  • Array.prototype.keys()
  • Array.prototype.lastIndexOf()
  • Array.prototype.map()
  • Array.prototype.pop()
  • Array.prototype.push()
  • Array.prototype.reduce()
  • Array.prototype.reduceRight()
  • Array.prototype.reverse()
  • Array.prototype.shift()
  • Array.prototype.slice()
  • Array.prototype.some()
  • Array.prototype.sort()
  • Array.prototype.splice()
  • Array.prototype.toLocaleString()
  • Array.prototype.toString()
  • Array.prototype.unshift()
  • Array.prototype.values()
  • Array.prototype@@iterator

Implementation happens here: https://github.com/boa-dev/boa/blob/master/boa/src/builtins/array/mod.rs

updating wasm-pack breaks with the latest version

What's happening:

undefined is returned

What should happen?:

`"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

Steps to replicate:

  • 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

Implementing Symbol (Braindump)

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

Hashing

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.

More performance

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.

Can we just increment a number?

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

References:

Implement Symbols: https://bugzilla.mozilla.org/show_bug.cgi?id=645416

Change all r#try! to ?

warning: unused manifest key: package.edition

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?

implement exponentiation operator

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.

Add [[Call]] to String

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]]

Spec

https://tc39.es/ecma262/#sec-string-constructor-string-value

Tests for parser.rs

Similar to the tests for the lexer.rs we would like tests for parser.rs

Creating test scenarios

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

Generating tokens

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.

Implement block scopes for let and const

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..

  • Have a separate branch for let, which creates a LetDeclExpr in the parser.
  • change scopes to function_scope so and have a new scopes for block scope, once we can differentiate between these we should be able to put 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 versa
    Actually we could just bump the scope for every block we come across instead, things should still work as normal. Start off by seeing how var works

Extra 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

Feature Request: Boa Shell

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:

  1. output written to stdout
    1. only outputs the result of the last evaluation.
  2. errors written to stderr

Things needed in the shell runtime itself:

  1. 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").

  2. globalThis or similar mechanism
  3. Some way to create a new realm and global object, eg. jsshell's newGlobal() or d8's Realm.global(realmId) (which requires some way of making a realmId, ie. Realm.create() or Realm.createAllowCrossRealmAccess())
  4. Some way to evaluate a string of source code, eg. jsshell's evaluate(code) or d8's Realm.eval(realmId, code)

Implement String prototype methods

String prototype methods

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

  • charAt()
  • codePointAt()
  • charCodeAt()
  • concat()
  • endsWith()
  • includes()
  • indexOf()
  • lastIndexOf()
  • localeCompare()
  • match()
  • matchAll()
  • normalize()
  • padEnd()
  • padStart()
  • repeat()
  • replace()
  • replaceAll()
  • search()
  • slice()
  • split()
  • startsWith()
  • substring()
  • toLocaleLowerCase()
  • toLocaleUpperCase()
  • toLowerCase()
  • toString()
  • toUpperCase()
  • trim()
  • trimEnd()
  • trimStart()
  • toString()
  • valueOf()
  • @@iterator

Implementing native returns for constructor functions

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:

[Webassembly] - chrono Local::now() time does not work

Steps

  • clone project & build
  • npm/yarn install
  • npm/yarn serve
  • Navigate to http://localhost:8080/
  • try console.log() something, there will be an error below in the console

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

Regular expressions does not work

Hi @jasonwilliams

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.

Create new binary which has print() available

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").

Should check for existence of binding before creating a new one

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.

What does the spec say?

https://tc39.github.io/ecma262/#sec-assignment-operators-runtime-semantics-evaluation
https://tc39.github.io/ecma262/#sec-putvalue

Block scope not being used on variable allocation

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"
}

What does the specification say?

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

high level task

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.

low level

  • new_declaration_environment is already exposed here. Bring it into exec.rs so that it can be used similar to how new_function_environment is used.
  • lexical_environment.rs will need a way of setting bindings to the nearest function environment, rather than the current environment (to support var)

implement toString for [built-in] objects

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

Use as a rust crate

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 😊

[Optimisation] - Zero cost arguments, create argument objects lazily

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.

Before and after benchmarks of 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%

add Property::isPropertyKey static method

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

Order of precendence on binary operators potentially wrong

Setup

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

Expected

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

implement in operator

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.