Code Monkey home page Code Monkey logo

rapidus's Introduction

Rapidus

Build codecov

JavaScript engine implemented in Rust, now aiming at ES5.

Big thanks to boa.

Rapidus on WASM

On this page, you can try rapidus compiled into WASM on your browser. How amazing, isn't it?

The compiled rapidus on the above page is some commits behind this branch.

Features

  • Small
  • Partly support for Tracing-JIT compiling
  • REPL

Building from Source

Building on Linux

  1. Install Rust

Run the command below and follow the onscreen instructions.

curl https://sh.rustup.rs -sSf | sh
  1. Use Rust Nightly
rustup override set nightly
  1. Install dependencies
  • LLVM 6.0
  • (Other packages as necessary...)
# e.g. Ubuntu or Debian
apt-get install llvm-6.0
  1. Test
cargo test
  • If the compilation failed because of LLVM-related errors, the following command may help.
ln -sf /usr/bin/llvm-config-6.0 /usr/bin/llvm-config
  1. Build
cargo run --release
  1. Run
cargo run --release examples/XXX.js
  1. multilined-aware REPL
$ cargo run
> function fact(n) {
... if (n < 2) {     <- recognize multilined input
... return n
... } else {
... return n * fact(n-1)
... }
... }                <- recognize the end of input
undefined
> fact(10)
3628800
  1. Debug mode (tracing bytecode execution)

    use --trace option.

$ cargo run -- --trace
> function fibo(x) { if (x<2) return 1; return fibo(x-1)+fibo(x-2)}
00020m 00000 Return                    <empty>
undefined
> fibo(3)
00009m 00000 PushInt8 3                <empty>
00015m 00002 GetValue 'fibo'           3.0
00066m 00007 Call 1                    Function
--> call function
  module_id:0 func_id:0
00007m 00000 GetValue 'x'              <empty>
00001m 00005 PushInt8 2                3.0
00013m 00007 Lt                        2.0

...

00001m 00007 Lt                        2.0
00000m 00008 JmpIfFalse 00016          true
00000m 00013 PushInt8 1                <empty>
00167m 00015 Return                    1.0
<-- return value(1.0)
  module_id:0 func_id:2
00001m 00052 Add                       1.0
00044m 00053 Return                    3.0
<-- return value(3.0)
  module_id:0 func_id:0
00000m 00012 Return                    3.0
3
> 
   |     |     |                        | 
   |     |     |                        \- value at the top of exec stack
   |     |     \-------------------------- instruction
   |     \-------------------------------- program counter
   \-------------------------------------- execution time per inst. (in microsecs)

Building on other platforms

I don't know.

  • tips: If you are using macOS, you cannot use llvm installed with brew. You should use macports or docker instead. Now it works!

Use DLLs written in Rust

THIS FEATURE IS EXPERIMENTAL

  1. Make a cargo project in the directory rapidus' directory is located
$ cargo new hello --lib
$ ls
rapidus hello
  1. Edit Cargo.toml
$ cd hello
$ <YOUR EDITOR> Cargo.toml
# Add the followings to Cargo.toml

[dependencies]
rapidus = { path = "../rapidus" }
# other dependencies if you want...

[lib]
name = "hello"
crate_type = ["cdylib"] # try 'dylib' if it doesn't work.
  1. Edit src/lib.rs
$ <YOUR EDITOR> src/lib.rs
// src/lib.rs

#[macro_use]
extern crate rapidus;
use rapidus::{
   gc,
   vm::{callobj::CallObject, error::RuntimeError, value::*, vm::VM},
};

#[no_mangle]
fn initialize(vm: &mut VM, _: &Vec<Value>, _: CallObjectRef) -> Result<(), RuntimeError> {
    // make_object!() is useful
    let module_exports = make_object!(
        greet:   Value::default_builtin_function(greet),
        message: Value::String("hello".to_string())
    );

    vm.set_return_value(module_exports); // We have to return module.exports

    Ok(())
}

#[no_mangle]
fn greet(vm: &mut VM, _: &Vec<Value>, _: CallObjectRef) -> Result<(), RuntimeError> {
    println!("Hello World from Rust DLL!");

    vm.set_return_value(Value::Undefined); // Remember to return a value you want

    Ok(())
}
  1. Let's build
$ cargo build # --release as necessary
  1. Copy the generated DLL to rapidus' directory
$ cp ./target/debug/libhello.(so|dll|dylib) ../rapidus
$ cd ../rapidus
$ ls
libhello.(so|dll|dylib) etc...
  1. You're ready to use it from rapidus. Let's try from REPL.
$ cargo run
> var mod = require('hello')
> mod.greet()
Hello World from Rust DLL!
> mod.message
'hello'
  1. Now everything can be possible from Rust!

rapidus's People

Contributors

maekawatoshiki avatar sisshiki1969 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

rapidus's Issues

handling of 'this' in an arrow function is not correct

The binding of 'this' in an arrow function should be 'lexical this'.

test code:

this.a = 50
let f = () => {
  console.log(this.a)
}
let g = function() {
  console.log(this.a)
}
f() // 50
let obj = { a: 70, f: f, g: g }
obj.f() // should be 50, 70 in rapidus
obj.g() // 70

wrong 'this' binding in certain situation

In the example below, 'this' in the execution context of 'adder()' function should be set to global.
But in rapidus, it seems that 'this' takes over the lexical context when 'up' method was declared in 'c'.
This happens in only limited situations using function() syntax.
object.method() syntax work properly.

ex)

count = 100
var c = {
  count: 0,
  up: function() {
    console.log(++this.count)
  }
}
adder = c.up
c.up()  // node: 1, rapidus: 1
adder()  // node: 101, rapidus: 2

comment which ends with multi-bytes unicode character followed by EOF causes panic.

probably due to improperly set next_pos in the second iter.next() of Lexer.skip_char(), when the first iter.next() returned EOF.

ex)
$ cat examples/test.js
console.log(1); // 異常$ cargo run examples/test.js
Compiling rapidus v0.1.1 (~/Documents/GitHub/rapidus)
Finished dev [unoptimized + debuginfo] target(s) in 6.17s
Running target/debug/rapidus examples/test.js
thread 'main' panicked at 'byte index 23 is not a char boundary; it is inside '常' (bytes 22..25) of console.log(1); // 異常', libcore/str/mod.rs:2111:5
note: Run with RUST_BACKTRACE=1 for a backtrace.
thread 'main' panicked at 'exited. status: 101', src/main.rs:104:25
note: Run with RUST_BACKTRACE=1 for a backtrace.

bug in parsing Number literals

found some bug in parsing Number literals.
1: can not parse floating point literals like "5e3" or "5e-3" correctly. (maybe parsed as integer literals -> 53)

may be some problem in Lexer.read_number().
checking floating point syntax in the statement
let is_f = "eEpP".contains(last) && "+-".contains(c);
but is_f seems not properly working.
and, maybe, "eEpP" and "+-" should be "eE" and "+-0123456789".

2: (tiny) problem in parsing integer literals like "0nnn".

0779 -> Decimal (in JS) vs Octal (parsed as octal number "77" in Rapidus)
0777 -> Octal (in JS or Rapidus)
this syntax is very confusing, and deprecated in strict mode.
I think using new Octal syntax in ECMAScript2015 ("0o777") only is better.

$ cat examples/test.js
console.log(0779)
console.log(0777)
console.log(5e3)
console.log(5e+3)
console.log(5e-3)

$ cargo run examples/test.js
Finished dev [unoptimized + debuginfo] target(s) in 0.15s
Running target/debug/rapidus examples/test.js
63
511
53
53
53
$ node examples/test.js
779
511
5000
5000
0.005

Rest parameter must be last formal parameter

function f(...arg, b){
    console.log(arg, b)  //  [ 1, 3, 4, 5 ]   2
}
f(1,2,3,4,5)

Parser accept rest parameter (or even plural number of rest parameters) placed other than last in formal parameter.

An easy way to pass Rust functions into the JavaScript context

Hey there! Incredible work being done here, but I have a question, that can potentially become an important feature.

Is there a way to import functions written in rust (which are defined in the code embedding the rapidus) into the JavaScript context, for the to be used? If not, as for me, it is one of the most important features for an embeddable JavaScript engine. Also, easily operating on JavaScript values and passing callbacks, as well as calling JavaScript functions from Rust as callbacks.

I haven't read though too much code, so I might be wrong in my conclusions, but as for now it seems like the only way to "introduce" some rust to the engine is to declare it in the code with attributes.

Thanks for your amazing work,
Alex.

assignment expression does not return value.

assignment expression should return value (ex. 5 for "a=5") according to ECMA-262 spec.
In rapidus, example codes (listed below) are correctly parsed, but go panic while bytecode execution.
no value seems to push to VM.state.stack in VM.set_name() function.

ex)
console.log(a=5)
a=b=5

parsing problem for post-increment(++) operator getting over line terminator

The sequence of [LeftHandSideExpression][LineTerminator][++ or --] seems to be parsed wrong as [Expression][++ or --].

ex)
$ cat test.js
a = 1
b = a
++b // parsed as "b = a; ++b;" in node, "b = a++; b;" in rapidus
console.log( a, b )
$ node test.js
1 2
$ cargo run test.js
2 1
$ cargo run -- --debug test.js
Finished dev [unoptimized + debuginfo] target(s) in 0.21s
Running target/debug/rapidus --debug test.js
Lexer:
Token { kind: Identifier("a"), pos: 0 }
Token { kind: Symbol(Assign), pos: 2 }
Token { kind: Number(1.0), pos: 4 }
Token { kind: Identifier("b"), pos: 6 }
Token { kind: Symbol(Assign), pos: 8 }
Token { kind: Identifier("a"), pos: 10 }
Token { kind: Symbol(Inc), pos: 13 }
Token { kind: Identifier("b"), pos: 15 }
Token { kind: Identifier("console"), pos: 80 }
Token { kind: Symbol(Point), pos: 87 }
Token { kind: Identifier("log"), pos: 88 }
Token { kind: Symbol(OpeningParen), pos: 91 }
Token { kind: Identifier("a"), pos: 93 }
Token { kind: Symbol(Comma), pos: 94 }
Token { kind: Identifier("b"), pos: 96 }
Token { kind: Symbol(ClosingParen), pos: 98 }
Parser:
Node { base: StatementList([Node { base: Assign(Node { base: Identifier("a"), pos: 0 }, Node { base: Number(1.0), pos: 4 }), pos: 1 }, Node { base: Assign(Node { base: Identifier("b"), pos: 6 }, Node { base: UnaryOp(Node { base: Identifier("a"), pos: 10 }, PoInc), pos: 11 }), pos: 7 }, Node { base: Identifier("b"), pos: 15 }, Node { base: Call(Node { base: Member(Node { base: Identifier("console"), pos: 80 }, "log"), pos: 87 }, [Node { base: Identifier("a"), pos: 93 }, Node { base: Identifier("b"), pos: 96 }]), pos: 87 }]), pos: 0 }

...

Suggestion: TypeScript support instead?

I see that using ECMAScript's specification is exceedingly difficult. Would it sound more reasonable to use Rapidus for a strictly-typed version of TypeScript? Much like how strictly-typed TypeScript code can be compiled into a WASM module (except the WASM compilation part).

can not use assignment expression in operands for conditional operator.

seems to be parser logic problem.

ConditionalExpression:
LogicalORExpression
LogicalORExpression ? AssignmentExpression : AssignmentExpression
http://www.ecma-international.org/ecma-262/9.0/index.html#sec-conditional-operator

ex)
$ cat test.js
console.log( false ? a = 1 : b = 2 )
$ node test.js
2
$ cargo run test.js
Finished dev [unoptimized + debuginfo] target(s) in 0.15s
Running target/debug/rapidus test.js
thread 'main' panicked at 'assertion failed: (left == right)
left: Symbol(Assign),
right: Symbol(Colon)', src/parser.rs:414:17
thread 'main' panicked at 'exited. status: 101', src/main.rs:104:25

construct() does not pop scope stack.

test code

var a = 0
new f()
console.log(a)

function f() {
  var a = 10
}
$ node test.js
0
$ cargo run test.js
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/rapidus examples/test.js`
10

runtime error occurs when assign value to array with index greater than its length.

It seems a bug in vm.set_member() function.

    ValueBase::Number(n) if n - n.floor() == 0.0 => {
        if n as usize >= map.length as usize {
            map.length = n as usize;
            map.elems.set_len(n as usize);
        }
        map.elems[n as usize] = val;    // <= index out of bounds
    }

ex)

var ary = []
console.log(ary[5] = 4)
$ node array.js
4
$ cargo run array.js
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/rapidus array.js`
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 5', /rustc/96064eb61dc703c289fae61bdf90593d3e7b2449/src/libcore/slice/mod.rs:2454:14
note: Run with `RUST_BACKTRACE=1` for a backtrace.
thread 'main' panicked at 'exited. status: 101', src/main.rs:177:25
note: Run with `RUST_BACKTRACE=1` for a backtrace.

'$' as identifier

Hi, this is a cool work.
It seems that identifiers starting with '$' can not be used.
Is it intentional?

using assignment expression (without "var" keyword, like "i=0") in initialization section of "for" statement cause parsing failure

maybe lexer.skip(Kind::Symbol(Symbol::Semicolon)) is needed after self.read_expression()? in "init" section of parser.read_for_statement().

ex)

for (i = 1; i <= 3; i++) {
    console.log(i);
}
$ cargo run examples/for.js
    Finished dev [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/rapidus examples/for.js`
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `Symbol(Semicolon)`,
 right: `Symbol(ClosingParen)`', src/parser.rs:304:13
note: Run with `RUST_BACKTRACE=1` for a backtrace.
thread 'main' panicked at 'exited. status: 101', src/main.rs:104:25
note: Run with `RUST_BACKTRACE=1` for a backtrace.
$ node examples/for.js
1
2
3

func call incorrectly initialize a var in the parent scope

func call incorrectly initialize a variable in the parent scope which has the same name as the argument.
callobj.clear_args_vals() in vm.call_function() / vm.construct() seem to do this.

(test.js)
var i = 100
console.log("->", i)
f(5)
console.log("<-", i)
function f(i) { }
$ node test.js
-> 100
<- 100
$ cargo run test.js
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
     Running `target/debug/rapidus examples/for.js`
-> 100
<- undefined

parsing error at nested exponentiation(**) operation . ex) 2**2**2

It seems parsing logic for ExponentiationExpression in rapidus is slightly different from that of ECMAScript syntax definition.

ex)
$ cat examples/test.js
console.log( 2 ** 2 ** 2 )
$ node examples/test.js
16
$ cargo run examples/test.js
Finished dev [unoptimized + debuginfo] target(s) in 0.15s
Running target/debug/rapidus examples/test.js
error(4): unexpected token
console.log( 2 ** 2 ** 2 )
^
thread 'main' panicked at 'explicit panic', src/parser.rs:48:9
note: Run with RUST_BACKTRACE=1 for a backtrace.
thread 'main' panicked at 'exited. status: 101', src/main.rs:104:25
note: Run with RUST_BACKTRACE=1 for a backtrace.

holyjit

Hey, this is great project, thanks for your effort!

Maybe you could use https://github.com/nbp/holyjit to get JIT for free. Not sure in which state it is now but at least this was the idea I had when I was thinking about doing js engine in rust myself.

"This" and global object, and module.

This is complexed (and really weird) issue which happens only in non-strict mode, and I may not have understood completely.
And I think this is a problem of implementation, rather than a bug.
First of all, see the test code and result below.

var a = 10
function func(){
    a = 3
    b = 5
    console.log(this.a, this.b) // in function object, this = global object
}
func()
console.log(this.a, this.b) // in module scope, this = {}
$ cargo run test.js
    Finished dev [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/rapidus test.js`
3 5
3 5
$ node test.js
undefined 5
undefined undefined

In the latest ECMA spec, the value of this keyword is resolved in the running execution context, whenever it is referred.
http://www.ecma-international.org/ecma-262/9.0/index.html#sec-this-keyword

And in node.js, every program file read is an independent module, and executed in the scope of each module (even if there is only one file), not the global scope. The global scope exists outer of the module scope. On the other hand, in web browsers, the top-level scope is the global scope.
https://nodejs.org/api/modules.html#modules_the_module_scope

When the func function is invoked in func() manner, this is bound to the Global object inside of the function. (when caller does not exist, this refer to the Global object in non-strict mode and undefined in strict mode)
http://www.ecma-international.org/ecma-262/9.0/index.html#sec-ecmascript-function-objects-call-thisargument-argumentslist

In executing func function, a is a variable declared in the main module scope (not global scope) and b refers to a property of the Global object. Thus, this.a is evaluated as undefined, whereas this.b is 5.
http://www.ecma-international.org/ecma-262/9.0/index.html#sec-global-environment-records

In node.js, this in module scope seems to be bound to an empty object ("{ }") which is not equal to the Global object.
Thus, in the global scope, "this.a" is resolved as "{}.a", which value is undefined as shown in second console.log() output.

To access the Global object directly, node.js provides "global" property of the Global object, which refers to the Global object itself. This means global.global === global.
Therefore, global.b in the module scope, is evaluated as 5.

How to build it by using llvm-rs

I had following errors, but I'm a beginnger in llvm.
I search the LLVM_SYS_60_PREFIX in docs and set it as /usr/lib/llvm-6.0/lib. But it still reported the same error. How to solve it. And can you give me some advises to use llvm and llvm-rs?

image

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.