├── compiler-interface ├── tests │ ├── data │ │ ├── empty │ │ │ └── empty │ │ │ │ └── main.js │ │ └── stmts │ │ │ └── for-loop │ │ │ ├── init.json │ │ │ ├── expected.json │ │ │ └── test.js │ ├── .compiled │ │ └── readme │ └── integration.rs ├── Cargo.toml └── src │ ├── errors.rs │ ├── options.rs │ ├── main.rs │ └── composer.rs ├── playground ├── minimal │ └── unobfuscated │ │ ├── code.js │ │ ├── cmd.txt │ │ └── index.html └── snake │ ├── unobfuscated │ ├── make.sh │ ├── README.md │ ├── index.html │ └── snake.js │ └── obfuscated │ ├── bytecode.base64 │ ├── index.html │ └── vm.js ├── Cargo.toml ├── vm ├── tests │ ├── test_helper.js │ └── vm.test.js └── vm.js ├── codecov.yml ├── package.json ├── compiler ├── src │ ├── lib.rs │ ├── jshelper.rs │ ├── error.rs │ ├── scope.rs │ ├── instruction_set.rs │ └── bytecode.rs ├── Cargo.toml └── tests │ └── lib.rs ├── .travis.yml ├── .gitignore ├── README.md ├── LICENSE.md └── Cargo.lock /compiler-interface/tests/data/empty/empty/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/minimal/unobfuscated/code.js: -------------------------------------------------------------------------------- 1 | console.log("Hello World"); 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["compiler", "compiler-interface"] 3 | -------------------------------------------------------------------------------- /compiler-interface/tests/data/stmts/for-loop/init.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 10 3 | } 4 | -------------------------------------------------------------------------------- /compiler-interface/tests/data/stmts/for-loop/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "i": 10, 3 | "a": 20 4 | } 5 | -------------------------------------------------------------------------------- /compiler-interface/tests/data/stmts/for-loop/test.js: -------------------------------------------------------------------------------- 1 | for(var i = 0;i<10;i++) { 2 | a++; 3 | } 4 | -------------------------------------------------------------------------------- /playground/snake/unobfuscated/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo run --target-dir ../../../compiler-interface snake.js ../../../vm/vm.js ../obfuscated -------------------------------------------------------------------------------- /vm/tests/test_helper.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | this.chai = require('chai'); 3 | this.assert = chai.assert; 4 | this.expect = chai.expect; 5 | } 6 | -------------------------------------------------------------------------------- /compiler-interface/tests/.compiled/readme: -------------------------------------------------------------------------------- 1 | This dir contains the compiled bytecode and a vm ready to be used. These testcases will then be 2 | called by the node.js test infrastructure. 3 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "header, diff, tree, changes, files" 3 | behavior: default 4 | require_changes: false 5 | require_base: no 6 | require_head: yes 7 | branches: null 8 | -------------------------------------------------------------------------------- /playground/minimal/unobfuscated/cmd.txt: -------------------------------------------------------------------------------- 1 | cargo run "../playground/minimal/unobfuscated/code.js" "../vm/vm.js" "../playground/minimal/obfuscated" -d 2 | 3 | cargo run "playground/minimal/unobfuscated/code.js" "vm/vm.js" "playground/minimal/obfuscated" "playground/minimal/unobfuscated/index.html" -d 4 | -------------------------------------------------------------------------------- /playground/snake/unobfuscated/README.md: -------------------------------------------------------------------------------- 1 | ### Snake 2 | 3 | This is an implementation of the classic Snake-game. However, it is not an educational 4 | example on how to implement Snake right. As it uses a rather reduced set of features of 5 | JavaScript with the sole purpose to be compliable by the rusty-jsyc compiler. 6 | -------------------------------------------------------------------------------- /playground/minimal/unobfuscated/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Minimal 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /compiler-interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsyc-compiler-interface" 3 | version = "0.1.0" 4 | authors = ["Johannes Willbold "] 5 | edition = "2018" 6 | readme = "../README.md" 7 | license = "LGPL-3.0-only" 8 | 9 | [dependencies.jsyc-compiler] 10 | path = "../compiler" 11 | 12 | [dev-dependencies] 13 | assert_cmd = "0.11.1" 14 | 15 | [dependencies] 16 | resw = "~0.2.2" 17 | ressa = "~0.5.2" 18 | resast = "~0.2.1" 19 | structopt = "0.3" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rusty-jsyc", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "Johannes Willbold", 6 | "scripts": { 7 | "test": "_mocha --recursive ./vm/tests/*.test.js" 8 | }, 9 | "dependencies": { 10 | "atob": "^2.1.2", 11 | "chai": "~4.2.0", 12 | "mocha": "^6.2.1" 13 | }, 14 | "main": "index.js", 15 | "repository": { 16 | "type": "git", 17 | "url": "git@git.noc.ruhr-uni-bochum.de:willbjy6/jsob-blogpost.git" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /playground/snake/unobfuscated/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Snake 6 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /compiler/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate ressa; 2 | extern crate resast; 3 | extern crate base64; 4 | 5 | pub mod error; 6 | pub mod bytecode; 7 | pub mod jshelper; 8 | pub mod compiler; 9 | pub mod scope; 10 | pub mod instruction_set; 11 | 12 | pub use crate::bytecode::{Bytecode, BytecodeElement, Operation, Instruction, Operand, ToBytes}; 13 | pub use crate::compiler::{BytecodeCompiler, DeclDepencies}; 14 | pub use crate::error::{CompilerResult, CompilerError}; 15 | pub use crate::instruction_set::{InstructionSet}; 16 | pub use crate::jshelper::{JSSourceCode, JSAst}; 17 | pub use crate::scope::{Register}; 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: rust 3 | cache: cargo 4 | rust: 5 | - stable 6 | - beta 7 | - nightly 8 | # Required for tarpaulin (code coverage) 9 | addons: 10 | apt: 11 | packages: 12 | - libssl-dev 13 | os: 14 | - linux 15 | matrix: 16 | allow_failures: 17 | - rust: nightly 18 | fast_finish: true 19 | branches: 20 | only: 21 | - master 22 | 23 | # Add code coverage with tarpaulin 24 | before_cache: | 25 | if [[ "$TRAVIS_OS_NAME" == linux && "$TRAVIS_RUST_VERSION" == stable ]]; then 26 | cargo install cargo-tarpaulin -f 27 | fi 28 | 29 | script: 30 | - cargo build --verbose --all 31 | - cargo test --verbose --all 32 | 33 | # Save the code coverage result 34 | after_success: | 35 | if [[ "$TRAVIS_OS_NAME" == linux && "$TRAVIS_RUST_VERSION" == stable ]]; then 36 | cargo tarpaulin --out Xml 37 | bash <(curl -s https://codecov.io/bash) 38 | fi 39 | -------------------------------------------------------------------------------- /compiler-interface/tests/integration.rs: -------------------------------------------------------------------------------- 1 | extern crate assert_cmd; 2 | 3 | use std::process::Command; 4 | use assert_cmd::prelude::*; 5 | 6 | const DEFAULT_OUTPUT: &str = "Starting to compile bytecode...\nFinished bytecode compilation\nStarting to compose VM and bytecode...\n"; 7 | 8 | #[test] 9 | fn test_empty_bc_std_vm() { 10 | let cmd = Command::cargo_bin("jsyc-compiler-interface").unwrap() 11 | .args(&["tests/data/empty/empty/main.js", "../vm/vm.js", "tests/.compiled/empty/empty"]) 12 | .output().unwrap(); 13 | 14 | cmd.assert().success().stdout(DEFAULT_OUTPUT); 15 | } 16 | 17 | #[test] 18 | fn test_compose_snake() { 19 | let cmd = Command::cargo_bin("jsyc-compiler-interface").unwrap() 20 | .args(&["../playground/snake/unobfuscated/snake.js", "../vm/vm.js", "../playground/snake/obfuscated"]) 21 | .output().unwrap(); 22 | 23 | cmd.assert().success(); 24 | } 25 | -------------------------------------------------------------------------------- /compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsyc-compiler" 3 | version = "0.1.0" 4 | authors = ["Johannes Willbold "] 5 | edition = "2018" 6 | readme = "../README.md" 7 | license = "LGPL-3.0-only" 8 | 9 | description = "Rusty-JSYC (JavaScript bYtecode Compiler) is a JavaScript-To-Bytecode compiler written in Rust. The bytecode is meant to be used in conjunction with the provided virtual machine (https://github.com/jwillbold/rusty-jsyc/blob/master/vm/vm.js) written in JavaScript. In combination they form the components for a virtualization obfuscation." 10 | 11 | repository = "https://github.com/jwillbold/rusty-jsyc" 12 | 13 | keywords = ["javascript", "obfuscation", "virtualization", "compiler"] 14 | categories = [] 15 | 16 | [badges] 17 | travis-ci = { repository = "jwillbold/rusty-jsyc", branch = "master" } 18 | codecov = { repository = "jwillbold/rusty-jsyc", branch = "master", service = "github" } 19 | 20 | [dependencies] 21 | ressa = "~0.5.2" 22 | # resast = "~0.4" 23 | resast = "0.2.1" 24 | base64 = "~0.10" 25 | -------------------------------------------------------------------------------- /compiler/src/jshelper.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{CompilerError}; 2 | 3 | use ressa::{Parser}; 4 | 5 | /// A wrapper for JavaScript source code 6 | /// 7 | /// ``` 8 | /// use jsyc_compiler::JSSourceCode; 9 | /// 10 | /// let js_code = jsyc_compiler::JSSourceCode::new("console.log('Hello World')".into()); 11 | /// ``` 12 | /// 13 | pub struct JSSourceCode { 14 | source_code: String 15 | } 16 | 17 | impl JSSourceCode { 18 | pub fn new(source_code: String) -> Self { 19 | JSSourceCode { source_code } 20 | } 21 | 22 | pub fn from_str(js_code: &str) -> Self { 23 | JSSourceCode::new(js_code.into()) 24 | } 25 | } 26 | 27 | /// A wrapper for the AST of the provided JavaScript code 28 | /// 29 | /// ``` 30 | /// use jsyc_compiler::{JSSourceCode, JSAst}; 31 | /// 32 | /// let js_code = JSSourceCode::new("console.log('Hello World')".into()); 33 | /// let js_ast = JSAst::parse(&js_code).expect("Failed to parse input code"); 34 | /// ``` 35 | pub struct JSAst { 36 | pub ast: resast::Program 37 | } 38 | 39 | impl JSAst { 40 | pub fn parse(source: &JSSourceCode) -> Result { 41 | let mut parser = match Parser::new(&source.source_code) { 42 | Ok(parser) => parser, 43 | Err(e) => { return Err(CompilerError::Parser(e)); } 44 | }; 45 | 46 | match parser.parse() { 47 | Ok(ast) => Ok(JSAst{ ast }), 48 | Err(e) => Err(CompilerError::Parser(e)) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /compiler-interface/src/errors.rs: -------------------------------------------------------------------------------- 1 | use jsyc_compiler::{CompilerError}; 2 | use std::{error}; 3 | 4 | pub type CompositionResult = Result; 5 | 6 | #[derive(Debug)] 7 | pub enum CompositionError { 8 | Compiler(CompilerError), 9 | IoError(std::io::Error), 10 | Custom(String) 11 | } 12 | 13 | impl CompositionError { 14 | pub fn from_str(msg: &str) -> CompositionError { 15 | CompositionError::Custom(msg.into()) 16 | } 17 | } 18 | 19 | impl std::fmt::Display for CompositionError { 20 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 21 | write!(f, "TO{0}", "DO") 22 | } 23 | } 24 | 25 | impl std::error::Error for CompositionError { 26 | fn description(&self) -> &str { 27 | match *self { 28 | CompositionError::Compiler(ref e) => e.description(), 29 | CompositionError::IoError(ref e) => e.description(), 30 | CompositionError::Custom(ref s) => s.as_str(), 31 | } 32 | } 33 | 34 | fn cause(&self) -> Option<&dyn error::Error> { 35 | match *self { 36 | _ => None 37 | } 38 | } 39 | } 40 | 41 | impl From for CompositionError { 42 | fn from(err: CompilerError) -> CompositionError { 43 | CompositionError::Compiler(err) 44 | } 45 | } 46 | 47 | impl From for CompositionError { 48 | fn from(err: std::io::Error) -> CompositionError { 49 | CompositionError::IoError(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless/ 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | 78 | #DynamoDB Local files 79 | .dynamodb/ 80 | 81 | build 82 | .DS_Store 83 | 84 | .directory 85 | 86 | *.vscode* 87 | 88 | # Rust 89 | /target/ 90 | Cargo.lock 91 | **/*.rs.bk 92 | 93 | # For this project 94 | **/.compiled/* 95 | !**/.compiled/readme 96 | *.local.* 97 | 98 | 99 | .jshintrc 100 | .idea 101 | -------------------------------------------------------------------------------- /compiler/src/error.rs: -------------------------------------------------------------------------------- 1 | use ressa::Error as RessaError; 2 | use std::{error}; 3 | 4 | 5 | #[derive(Debug)] 6 | pub enum CompilerError { 7 | Parser(RessaError), 8 | Unsupported(String), 9 | Custom(String) 10 | } 11 | 12 | pub type CompilerResult = Result; 13 | 14 | impl CompilerError { 15 | pub fn is_unsupported(error: &str, unsuppoted: D) -> Self 16 | where D: std::fmt::Debug 17 | { 18 | CompilerError::Unsupported(format!("{} '{:?}' is not supported", error, unsuppoted)) 19 | } 20 | 21 | pub fn are_unsupported(error: &str) -> Self { 22 | CompilerError::Unsupported(format!("'{}' are not supported", error)) 23 | } 24 | 25 | pub fn is_unsupported_feature(&self) -> bool { 26 | match self { 27 | CompilerError::Parser(_) | 28 | CompilerError::Custom(_) => false, 29 | CompilerError::Unsupported(_) => true 30 | } 31 | } 32 | } 33 | 34 | impl std::fmt::Display for CompilerError { 35 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 36 | match self { 37 | CompilerError::Parser(ressa_error) => write!(f, "{}", ressa_error), 38 | CompilerError::Unsupported(string) | 39 | CompilerError::Custom(string) => write!(f, "{}", string) 40 | } 41 | } 42 | } 43 | 44 | impl std::error::Error for CompilerError { 45 | fn description(&self) -> &str { 46 | match *self { 47 | CompilerError::Parser(_) => "An error during the parsing process", 48 | CompilerError::Unsupported(ref s) | 49 | CompilerError::Custom(ref s) => s.as_str(), 50 | } 51 | } 52 | 53 | fn cause(&self) -> Option<&dyn error::Error> { 54 | match self { 55 | // CompilerError::Parser(ressa_error) => Some(&ressa_error) 56 | _ => None 57 | } 58 | } 59 | } 60 | 61 | impl From for CompilerError { 62 | fn from(err: RessaError) -> CompilerError { 63 | CompilerError::Parser(err) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /compiler-interface/src/options.rs: -------------------------------------------------------------------------------- 1 | use structopt::StructOpt; 2 | 3 | use crate::errors::{CompositionError, CompositionResult}; 4 | 5 | 6 | #[derive(StructOpt)] 7 | #[structopt(name = "Rusty JSYC bytecode compiler", 8 | about = "A tool to compile JavaScript code into bytecode to be used in virtualization obfuscation.", 9 | author = "Johannes Willbold ", 10 | rename_all = "verbatim")] 11 | pub struct Options { 12 | #[structopt(parse(from_os_str), name = "/path/to/javascript.js")] 13 | pub input_path: std::path::PathBuf, 14 | 15 | #[structopt(parse(from_os_str), name = "/path/to/vm-template.js")] 16 | pub vm_template_path: std::path::PathBuf, 17 | 18 | #[structopt(parse(from_os_str), name = "/output/dir")] 19 | pub output_dir: std::path::PathBuf, 20 | 21 | #[structopt(parse(from_os_str), name = "/path/to/index.html")] 22 | pub index_html_path: Option, 23 | 24 | #[structopt(short = "s", long = "show-bytecode")] 25 | pub show_bytecode: bool, 26 | 27 | #[structopt(short = "d", long = "show-depedencies")] 28 | pub show_dependencies: bool, 29 | 30 | #[structopt(short = "v", long = "verbose")] 31 | pub verbose: bool, 32 | 33 | #[structopt(flatten)] 34 | pub vm_options: VMOptions 35 | } 36 | 37 | #[derive(StructOpt)] 38 | pub enum VMExportType { 39 | ES6, 40 | NodeJS 41 | } 42 | 43 | impl std::str::FromStr for VMExportType { 44 | type Err = CompositionError; 45 | 46 | fn from_str(s: &str) -> CompositionResult { 47 | match s { 48 | "ES6" => Ok(VMExportType::ES6), 49 | "NodeJS" => Ok(VMExportType::NodeJS), 50 | _ => Err(CompositionError::Custom("".into())) 51 | } 52 | } 53 | } 54 | 55 | #[derive(StructOpt)] 56 | #[structopt(rename_all = "verbatim")] 57 | pub struct VMOptions { 58 | #[structopt(long="vm-export-type", name="ES6/NodeJS")] 59 | pub export_type: Option, 60 | 61 | // #[structopt(long)] 62 | // keep_unused_instructions: bool 63 | } -------------------------------------------------------------------------------- /playground/snake/obfuscated/bytecode.base64: -------------------------------------------------------------------------------- 1 | AQQABmNhbnZhcwEDAA5nZXRFbGVtZW50QnlJZAoBAgMLAAECAQQBCAACMmQBBwAKZ2V0Q29udGV4dAoGAAcLBQYAAQgBCgAGaGVpZ2h0CgkACgEMAAV3aWR0aAoLAAwCDTICDihnDwkODxD9DxH9DQAAAcrKAA8S/w8T/gIVAgIWAwIaAmcZDRoCHAJnGw4cBRgDGRsVAiACZx8NIGQeH/4CIgJnIQ4iBR0DHiEVAiYCZyUNJgInAmQkJScCKQJnKA4pBSMDJCgVBRcDGB0jDyr/DysVAixkDy39ATIAB2tleWRvd24UMwAABcEBMAI0AAExABBhZGRFdmVudExpc3RlbmVyCjACMQvKMAIDMjM0DQAABYjKABABGgAGcmFuZG9tChkUGgsYGRQAZRcYEWQWFxABFQAFZmxvb3IKExQVCxITFAEWDhIAARUACWJlZ2luUGF0aAoTBRULyhMFAGUZEA9kGBn+ZRsRD2QaG/5mHA/+Zh0P/gEXAARyZWN0ChYFFwvKFgUEGBocHQEeAAlmaWxsU3R5bGUVBR4SASAABGZpbGwKHwUgC8ofBQABIgAJY2xvc2VQYXRoCiEFIgvKIQUADv0CBQ9mEg3+DQAAARkQBBD/ERJmEw7+DQAAARkRBBD/ERMO/QQQDQ4RAS8ACWJlZ2luUGF0aAouBS8Lyi4FAA8wDzgxMAsTMQAAAmEDNj/gAAAAAAAAZDU2MAE0AAZtb3ZlVG8KMwU0C8ozBQI1/wM6P+AAAAAAAABkOTowATgABmxpbmVUbwo3BTgLyjcFAjkJZDAwDxIAAAIIDzsPODw7CRM8AAACvQNBP+AAAAAAAABkQEE7AT8ABm1vdmVUbwo+BT8Lyj4FAv9AA0U/4AAAAAAAAGRERTsBQwAGbGluZVRvCkIFQwvKQgUCC0RkOzsPEgAAAmQBRgALc3Ryb2tlU3R5bGUBRwAEZ3JleRUFRkcBSQAGc3Ryb2tlCkgFSQvKSAUAAUsACWNsb3NlUGF0aApKBUsLykoFAA79Bg8wCQs7BQExAAV3aWR0aAowADEBMwAGaGVpZ2h0CjIAMwEvAAljbGVhclJlY3QKLgUvC8ouBQT//zAyDQAAAe/KAA80/wE3AAZsZW5ndGgKNhc3NjU0NhM1AAADjQo5FzQKOjn/Cjs5/gE8AAZvcmFuZ2UNAAABSsoGEDoROxI8ZDQ0/hIAAANJAT0ABWdyZWVuDQAAAUrKBhAQERESPQ79BgARBTQQF2Yv//4FLgT//y/+ZjH//gUwBDH+//8PMisPM/0PNP8BNwAGbGVuZ3RoCjYXNzY1NDYTNQAABVEKORc0AjwCCjs5PDM6OzITOgAABAkCPgIKPTk+Aj8CFTk/Mg8yPQpBOf8CRAIKQzlECkIuQ2RAQUIKRjn+AkkCCkg5SQpHMEhkRUZHMkpAEBNKAAAEPTJKRRETSgAABHwFTgABUgAGbGVuZ3RoClEXUmZQUf4KTxdQAU0ABmFzc2lnbgpLTE0LM0tMAk5PDQAAAcrKAGQqKv4PU/8PVP8BVwAGbGVuZ3RoClYXVzZVVFYTVQAABM8KWRdUCltZ/wpcOf80WltcE1oAAAS8Cl1Z/gpeOf40Wl1eE1oAAATGZFNT/mRUVP4SAAAEgjZfQP8RXwAABN05X0ANEV8AAATnNl9F/xFfAAAE8TlfRQ4RXwAABP4CYAI5X1NgE18AAAU3AWMACGxvY2F0aW9uCmICYwFkAAZyZWxvYWQKYWJkAWYACGxvY2F0aW9uCmUCZgvKYWUAAmkCCmg5aQVnA0BFaBUXNGdkNDT+EgAAA8sTMwAABWkBawAEcHVzaApqF2sLymoXATMUbQAAAwoAC8ps/QFtDv0PFzMOERA5KzIwDS5TKlQ0FC8AAAOsAAstLv0CLywNAAADCsoADv0CLSwTLQAABbYLyi/9AS0PLf0SAAAFvQ0AAAWIygAO/QEtDzH9ATUAA2tleQo0MDUBNgAJQXJyb3dMZWZ0MjM0NhMzAAAF8Q8yFQ8xFhIAAAZyATkAA2tleQo4MDkBOgAKQXJyb3dSaWdodDI3ODoTNwAABh8PMhYPMRUSAAAGcgE9AANrZXkKPDA9AT4AB0Fycm93VXAyOzw+EzsAAAZKDzISDzETEgAABnIBQQADa2V5CkAwQQFCAAlBcnJvd0Rvd24yP0BCEz8AAAZyDzITDzESD0T9NUMxRBNDAAAGgzNDMSsTQwAABpEPKzISAAAHAgFHAANrZXkKRjBHAUgAASAyRUZIE0UAAAa3DQAABaLKABIAAAcCAUsAA2tleQpKMEsBTQABKzJJSk0TSQAABwICUApmTyxQAlEUOU5PURFOAAAG8QJSCmYsLFISAAAG9AIsFA0AAAWiygANAAAFosoADv0IEzESFhUsMis= -------------------------------------------------------------------------------- /playground/snake/obfuscated/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Snake 6 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /compiler-interface/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate jsyc_compiler; 2 | extern crate resw; 3 | extern crate resast; 4 | extern crate ressa; 5 | extern crate structopt; 6 | 7 | mod composer; 8 | mod errors; 9 | mod options; 10 | 11 | use std::io::Read; 12 | use std::fs; 13 | use std::path::PathBuf; 14 | use jsyc_compiler::{JSSourceCode, BytecodeCompiler}; 15 | 16 | use crate::errors::{CompositionResult}; 17 | use crate::composer::{Composer, VM}; 18 | use crate::options::{Options}; 19 | use crate::structopt::StructOpt; 20 | 21 | 22 | fn load_js_from_file(path: &PathBuf) -> CompositionResult { 23 | let mut f = fs::File::open(path)?; 24 | let mut string = String::new(); 25 | f.read_to_string(&mut string)?; 26 | 27 | Ok(JSSourceCode::new(string)) 28 | } 29 | 30 | fn main() -> CompositionResult<()> { 31 | let options = Options::from_args(); 32 | 33 | if options.verbose { 34 | println!("Using input file: {}", options.input_path.to_str().unwrap()); 35 | println!("Using vm template file: {}", options.vm_template_path.to_str().unwrap()); 36 | println!("Using output dir: {}", options.output_dir.to_str().unwrap()); 37 | } 38 | 39 | let output_dir = std::path::Path::new(&options.output_dir); 40 | 41 | if !output_dir.exists() { 42 | if let Some(paren_dir) = output_dir.parent() { 43 | if !paren_dir.exists() { 44 | if options.verbose { 45 | println!("Creating output parent dir..."); 46 | } 47 | fs::create_dir(paren_dir)?; 48 | } 49 | } 50 | 51 | if options.verbose { 52 | println!("Creating output dir..."); 53 | } 54 | fs::create_dir(output_dir)?; 55 | } 56 | 57 | let js_code = load_js_from_file(&options.input_path)?; 58 | let vm = VM::from_js_code(load_js_from_file(&options.vm_template_path)?)?; 59 | 60 | 61 | println!("Starting to compile bytecode..."); 62 | 63 | let mut compiler = BytecodeCompiler::new(); 64 | let bytecode = compiler.compile(&js_code)?; 65 | 66 | println!("Finished bytecode compilation"); 67 | 68 | if options.show_dependencies { 69 | println!("Dependencies: {:?}", &compiler.decl_dependencies()); 70 | } 71 | 72 | if options.show_bytecode { 73 | println!("Bytecode:\n{}", &bytecode); 74 | } 75 | 76 | if let Some(index_html_template) = &options.index_html_path { 77 | let index_html_template_path = std::path::Path::new(&index_html_template); 78 | println!("Using html template {}", index_html_template_path.display()); 79 | let html_template = fs::read_to_string(index_html_template_path)?; 80 | let index_html = html_template.replace("Base64EncodedBytecode", &bytecode.encode_base64()); 81 | 82 | fs::write(output_dir.join(index_html_template_path.file_name() 83 | .expect(&format!("{} is not a valid file path", index_html_template_path.display()))), 84 | index_html)?; 85 | } 86 | 87 | println!("Starting to compose VM and bytecode..."); 88 | let composer = Composer::new(vm, bytecode, &options); 89 | 90 | let (vm, bytecode) = composer.compose(compiler.decl_dependencies())?; 91 | vm.save_to_file(output_dir.join("vm.js"))?; 92 | 93 | let base64_bytecode = bytecode.encode_base64(); 94 | fs::write(output_dir.join("bytecode.base64"), base64_bytecode)?; 95 | 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /playground/snake/unobfuscated/snake.js: -------------------------------------------------------------------------------- 1 | var canvas = document.getElementById("canvas"); 2 | var ctx = canvas.getContext("2d"); 3 | 4 | const bh = canvas.height; 5 | const bw = canvas.width; 6 | 7 | const hFieldCount = 50; 8 | const vFieldCount = 40; 9 | const fieldLength = bh/vFieldCount; 10 | 11 | function rand(min, max) { 12 | return Math.floor((Math.random() * max) + min); 13 | } 14 | 15 | function fillField(x, y, color) { 16 | ctx.beginPath(); 17 | ctx.rect(x*fieldLength+1, y*fieldLength+1, fieldLength-1, fieldLength-1); 18 | ctx.fillStyle = color; 19 | ctx.fill(); 20 | ctx.closePath(); 21 | } 22 | 23 | var apple_x = void 0; 24 | var apple_y = void 0; 25 | 26 | function spawnNewApple() { 27 | apple_x = rand(0, hFieldCount-1); 28 | apple_y = rand(0, vFieldCount-1); 29 | } 30 | 31 | spawnNewApple(); 32 | 33 | const DIRECTION_UP = 0; 34 | const DIRECTION_DOWN = 1; 35 | const DIRECTION_LEFT = 2; 36 | const DIRECTION_RIGHT = 3; 37 | 38 | var snake_fields = [[hFieldCount/2, vFieldCount/2, DIRECTION_LEFT], 39 | [hFieldCount/2+1, vFieldCount/2, DIRECTION_LEFT], 40 | [hFieldCount/2+2, vFieldCount/2, DIRECTION_LEFT]]; 41 | 42 | var score = 0; 43 | var snake_direction = DIRECTION_LEFT; 44 | var updateSpeed = 100; 45 | var updater = void 0; 46 | 47 | function drawGrid() { 48 | ctx.beginPath(); 49 | for (var x = fieldLength; x <= bw; x += fieldLength) { 50 | ctx.moveTo(0.5 + x, 0); 51 | ctx.lineTo(0.5 + x, bh); 52 | } 53 | for (var y = fieldLength; y <= bh; y += fieldLength) { 54 | ctx.moveTo(0, 0.5 + y); 55 | ctx.lineTo(bw, 0.5 + y); 56 | } 57 | ctx.strokeStyle = "grey"; 58 | ctx.stroke(); 59 | ctx.closePath(); 60 | } 61 | 62 | function drawFrame() { 63 | ctx.clearRect(0, 0, canvas.width, canvas.height); 64 | drawGrid(); 65 | 66 | for(var i = 0; i= hFieldCount || y < 0 || y >= vFieldCount || collisionCounter >= 2) { 107 | // Game Over 108 | document.location.reload(); 109 | } 110 | 111 | snake_fields[i] = [x, y, field[2]]; 112 | } 113 | 114 | if(newField) { 115 | snake_fields.push(newField); 116 | } 117 | 118 | requestAnimationFrame(drawFrame); 119 | } 120 | 121 | function startOrContinue() { 122 | updater = setInterval(updateTick, updateSpeed); 123 | drawFrame(); 124 | } 125 | 126 | function toggleUpdateLoop() { 127 | if(updater) { 128 | clearInterval(updater); 129 | updater = void 0; 130 | } else { 131 | startOrContinue(); 132 | } 133 | } 134 | 135 | function eventHandler(event) { 136 | var maybe_current_dir = void 0, new_dir; 137 | 138 | if(event.key == "ArrowLeft") { 139 | new_dir = DIRECTION_LEFT; 140 | maybe_current_dir = DIRECTION_RIGHT; 141 | } else if(event.key == "ArrowRight") { 142 | new_dir = DIRECTION_RIGHT; 143 | maybe_current_dir = DIRECTION_LEFT; 144 | } else if(event.key == "ArrowUp") { 145 | new_dir = DIRECTION_UP; 146 | maybe_current_dir = DIRECTION_DOWN; 147 | } else if(event.key == "ArrowDown") { 148 | new_dir = DIRECTION_DOWN; 149 | maybe_current_dir = DIRECTION_UP; 150 | } 151 | 152 | if((maybe_current_dir !== void 0) && (maybe_current_dir != snake_direction)) { 153 | snake_direction = new_dir; 154 | } else if(event.key == " ") { 155 | toggleUpdateLoop(); 156 | } else if(event.key == "+") { 157 | updateSpeed = updateSpeed-10 >= 20 ? updateSpeed-10 : 20; 158 | toggleUpdateLoop(); 159 | toggleUpdateLoop(); 160 | } 161 | } 162 | 163 | document.addEventListener("keydown", eventHandler, false); 164 | 165 | startOrContinue(); 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/jwillbold/rusty-jsyc.svg?token=hPh87VpFt3MQPwdySdkS&branch=master)](https://travis-ci.com/jwillbold/rusty-jsyc) 2 | [![codecov](https://codecov.io/gh/jwillbold/rusty-jsyc/branch/master/graph/badge.svg?token=puTrXEsmcx)](https://codecov.io/gh/jwillbold/rusty-jsyc) 3 | 4 | 5 | # Rusty-JSYC 6 | 7 | Rusty-JSYC (JavaScript bYtecode Compiler) is a JavaScript-To-Bytecode compiler written in Rust. The bytecode is meant to be used in conjunction with the provided [virtual machine](https://github.com/jwillbold/rusty-jsyc/blob/master/vm/vm.js) written in JavaScript. In combination they form the components for a virtualization obfuscation. 8 | 9 | There is also a [blogpost](https://jwillbold.com/posts/obfuscation/2019-06-16-the-secret-guide-to-virtualization-obfuscation-in-javascript/) explaining this project and virtualization obfuscation in general. 10 | 11 | ## How to use this 12 | You must first compile the given JavaScript code. After that you can execute it with the provided virtual machine. 13 | 14 | #### Compile your JavaScript code 15 | 16 | You can either use the provided command line tool: 17 | 18 | ```Bash 19 | cargo run -d 20 | ``` 21 | 22 | or use the compiler as a library and call it from your own rust code: 23 | 24 | ```Rust 25 | extern crate jsyc_compiler; 26 | 27 | use jsyc_compiler::{JSSourceCode, BytecodeCompiler}; 28 | 29 | fn main() { 30 | let js_code = JSSourceCode::new("console.log('Hello World');".into()); 31 | let mut compiler = BytecodeCompiler::new(); 32 | 33 | let bytecode = compiler.compile(&js_code).expect("Failed to compile code"); 34 | println!("Bytecode: {}", bytecode); 35 | 36 | let depedencies = compiler.decl_dependencies(); 37 | println!("Depedencies: {:?}", depedencies); 38 | 39 | let base64_bytecode = bytecode.encode_base64(); 40 | println!("Base64-encoded bytecode: {}", base64_bytecode); 41 | } 42 | ``` 43 | 44 | In your Cargo.Toml: 45 | ```Toml 46 | [dependencies] 47 | jsyc_compiler = "~0.1" 48 | ``` 49 | 50 | #### Run the virtual machine 51 | ```JavaScript 52 | // include vm.js 53 | // ... 54 | var vm = new VM(); 55 | vm.init(Base64EncodedBytecode); 56 | requestIdleCallback(() => vm.run()); 57 | // ... 58 | ``` 59 | Replace ``Base64EncodedBytecode`` with the actual base64 encoded bytecode. 60 | 61 | #### Playground example 62 | 63 | An example demonstrating both the compiler and the virtual machine can be found in ``playground/snake``. It features a small Snake game (snake.js). 64 | You can compile this with: 65 | ```Bash 66 | cargo run "playground/snake/unobfuscated/snake.js" "vm/vm.js" "playground/snake/obfuscated" "playground/snake/unobfuscated/index.html" 67 | ``` 68 | After compilation, open the index.html file in your browser. 69 | ``` 70 | /path/to/rusty-jsyc/playground/snake/obfuscated/index.html 71 | ``` 72 | This was tested in Chrome 74 and Firefox 67. However, any ES6 capable browser should be compatible. 73 | 74 | ## Virtualization Obfuscation 75 | Virtualization obfuscation is a state-of-the-art obfuscation scheme. It obfuscates the code by compiling it into bytecode which is then executed by a virtual machine (VM). Thus, the VM gets distributed along with the compiled bytecode. It is then called with this bytecode and executes it and is thereby executing the actual code. 76 | 77 | Since the bytecode is executed instruction by instruction, the original code is never restored anywhere. So, any potential attacker must first reverse engineer the VM, which may be heavily obfuscated. One must then understand the underlying architecture and instruction-set before being able to analyze the actual bytecode. Since any two virtualization obfuscations are potentially different, the use of automated tools is limited.[[1](1)][[2](2)] 78 | 79 | ### Compatibility 80 | 81 | #### Interactions between the virtual and real JavaScript context 82 | It is possible to provide the functions defined in the virtual JavaScript context to the real JavaScript context. 83 | ```JavaScript 84 | // Compiled JavaScript 85 | function secret_function(a, b, c) { return a*b+c; } 86 | window.secret_function = secret_function; 87 | ``` 88 | 89 | ```JavaScript 90 | // Non-Compiled JavaScript 91 | var secret_function = window.secret_function; 92 | secret_function(10, 20, 1337); 93 | ``` 94 | 95 | It does not need to be ``window``, any object instance know to both contexts will work. When calling ``secret_function`` the virtual machine will start the execution of the corresponding bytecode chunk. Thus, calling a function this way does not reveal any more information on the implementation than just calling it inside the compiled JavaScript. 96 | 97 | #### Current unsound properties 98 | These are the properties that are not reflected by the bytecode as they would be in real JavaScript. 99 | - the 'this' pointer for external non-member functions is simply 'void 0' 100 | - Assignment expressions do not return a value, and thus are not really expressions 101 | - If you declare a variable without assignment it's value will be unknown. Thus it might or might not be undefined (void 0). (It will be undefined but not JavaScript's undefined (void 0)) 102 | - ``let`` and ``const`` declarations are treated as ``var`` declarations 103 | 104 | #### Unsupported JavaScript syntaxes 105 | This compiler currently only supports a subset of JavaScript features. Currently missing are 106 | - Object related notations ({}, new, this, super, class) 107 | - for-of and for-in loops 108 | - async and await keywords 109 | - with, and switch keywords 110 | - ~~try and throw structures~~ 111 | - ~~break, continue, labels~~ 112 | - function expressions and arrow function (Regular functions are allowed) 113 | - function expressions and arrow functions can be realized with: 114 | ```JavaScript 115 | var func_expr = eval("0, function(x) {return x*x;}"); 116 | ``` 117 | However, they do not support references to variables defined in the compiled JavaScript. 118 | - tagged template expressions 119 | - spread, rest and sequence notations 120 | 121 | ### How to run tests 122 | There are several test sets in this project: 123 | 1. Cargo tests: ``cargo test`` 124 | 2. Node (mocha) tests:``npm install && npm test`` 125 | 126 | _____________________________________ 127 | [1]: http://static.usenix.org/event/woot09/tech/full_papers/rolles.pdf 128 | *1*: Rolf Rolles. Unpacking virtualization obfuscators. USENIX Workshop on Offensive Technologies (WOOT), 2009. 129 | 130 | [2]: https://dslab.epfl.ch/pubs/staticVirtObf.pdf 131 | *2*: Johannes Kinder. Towards static analysis of virtualization-obfuscated binaries. Reverse Engineering (WCRE), 2012 19th Working Conference. 132 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /vm/tests/vm.test.js: -------------------------------------------------------------------------------- 1 | require("./test_helper.js")(); 2 | require("../vm.js")(); 3 | // var btoa = require('btoa'); 4 | 5 | ///! HELPERS 6 | 7 | function encodeBytecode(nonEncodedBytecode) 8 | { 9 | return Buffer.from(nonEncodedBytecode).toString('base64') 10 | } 11 | 12 | function encodeString(string) 13 | { 14 | var stringLength = string.length; 15 | var bytecode = [stringLength & 0xff00, stringLength & 0xff]; 16 | 17 | for(var i = 0;i> 24) & 0xff, (num >> 16) & 0xff, (num >> 8) & 0xff, (num >> 0) & 0xff] 37 | } 38 | 39 | var window = { 40 | document: {}, 41 | String: String 42 | } 43 | 44 | const testDataSet = [ 45 | { 46 | name: "Empty Bytecode", 47 | bytecode: [], 48 | expected_registers: [], 49 | }, 50 | { 51 | name: "Load short num", 52 | bytecode: [ 53 | OP.LOAD_NUM, 150 , 66, // LOAD NUM 66 INTO REGISTER 150 54 | ], 55 | expected_registers: [ 56 | [150, 66] 57 | ], 58 | }, 59 | { 60 | name: "Load float (12.5)", 61 | bytecode: [ 62 | OP.LOAD_FLOAT, 150, ...[0x40, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] 63 | ], 64 | expected_registers: [ 65 | [150, 12.5] 66 | ], 67 | }, 68 | { 69 | name: "Load float (0.5)", 70 | bytecode: [ 71 | OP.LOAD_FLOAT, 150, ...[0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] 72 | ], 73 | expected_registers: [ 74 | [150, 0.5] 75 | ], 76 | }, 77 | { 78 | name: "Load string", 79 | bytecode: [ 80 | OP.LOAD_STRING, 150, ...encodeString("Hello World") 81 | ], 82 | expected_registers: [ 83 | [150, "Hello World"] 84 | ], 85 | }, 86 | { 87 | name: "Load array", 88 | bytecode: [ 89 | OP.LOAD_STRING, 150, ...encodeString("Hello World"), 90 | OP.LOAD_NUM, 151, 120, 91 | OP.LOAD_FLOAT, 152, ...[0x40, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 92 | OP.LOAD_ARRAY, 153, ...encodeRegistersArray([150, 151, 152]) 93 | ], 94 | expected_registers: [ 95 | [150, "Hello World"], 96 | [151, 120], 97 | [152, 12.5], 98 | [153, ["Hello World", 120, 12.5]] 99 | ], 100 | }, 101 | { 102 | name: "Load long num positiv", 103 | bytecode: [ 104 | OP.LOAD_LONG_NUM, 150, 0x0f, 0x00, 0xff, 0x1f 105 | ], 106 | expected_registers: [ 107 | [150, 0x0f00ff1f], 108 | ], 109 | }, 110 | { 111 | name: "Load long num negative", 112 | bytecode: [ 113 | OP.LOAD_LONG_NUM, 150, 0xff, 0x00, 0xff, 0x1f 114 | ], 115 | expected_registers: [ 116 | [150, -0x00ff00e1], 117 | ], 118 | }, 119 | { 120 | name: "Multiply two registers", 121 | bytecode: [ 122 | OP.LOAD_NUM, 150, 3, // LOAD NUM 3 INTO REGISTER 100 123 | OP.LOAD_NUM, 151, 2, // LOAD NUM 2 INTO REGISTER 101 124 | OP.MUL, 150, 150, 151, // MULTIPLY NUM IN REG 100 WITH NUM IN REG 101 125 | ], 126 | expected_registers: [ 127 | [150, 6] 128 | ], 129 | }, 130 | { 131 | name: "Call member function", 132 | init: function() { 133 | window.testFunc = function() { return 66; }; 134 | }, 135 | bytecode: [ 136 | OP.LOAD_STRING, 150, ...encodeString("testFunc"), 137 | OP.PROPACCESS, 151, REGS.WINDOW, 150, 138 | OP.FUNC_CALL, 152, 151, REGS.WINDOW, ...encodeRegistersArray([]) 139 | ], 140 | expected_registers: [ 141 | [150, "testFunc"], 142 | [152, 66] 143 | ], 144 | }, 145 | { 146 | name: "Call member function with arguments", 147 | init: function() { 148 | window.testFunc = function(a ,b) { return a + b; }; 149 | }, 150 | bytecode: [ 151 | OP.LOAD_STRING, 150, ...encodeString("testFunc"), 152 | OP.PROPACCESS, 151, REGS.WINDOW, 150, 153 | OP.LOAD_NUM, 160, 60, 154 | OP.LOAD_NUM, 161, 6, 155 | // Cal the function 156 | OP.FUNC_CALL, 152, 151, REGS.WINDOW, ...encodeRegistersArray([160, 161]) 157 | ], 158 | expected_registers: [ 159 | [150, "testFunc"], 160 | [152, 66] 161 | ], 162 | }, 163 | { 164 | name: "Create object", 165 | bytecode: [ 166 | OP.LOAD_STRING, 150, ...encodeString("String"), 167 | OP.PROPACCESS, 151, REGS.WINDOW, 150, 168 | OP.FUNC_CALL, 152, 151, REGS.WINDOW, ...encodeRegistersArray([]) 169 | ], 170 | expected_registers: [ 171 | [150, "String"], 172 | [152, ""] 173 | ] 174 | }, 175 | { 176 | name: "Call bytecode function", 177 | bytecode: [ 178 | OP.LOAD_NUM, 150, 60, 179 | OP.LOAD_NUM, 151, 6, 180 | // 22 is the offset of the bytecode function below 181 | OP.CALL_BCFUNC, ...encodeLongNum(22), 160, ...encodeRegistersArray([152, 150, 153, 151]), 182 | OP.ADD, 160, 160, 150, 183 | OP.EXIT, 184 | 185 | // The function: function(a, b) { return (a+b)^2; } 186 | // a: 152 187 | // b: 153 188 | OP.ADD, 152, 152, 153, 189 | OP.MUL, 152, 152, 152, 190 | OP.RETURN_BCFUNC, 152, ...encodeRegistersArray([]) 191 | ], 192 | expected_registers: [ 193 | [150, 60], 194 | [151, 6], 195 | [160, 4416] 196 | ] 197 | }, 198 | { 199 | name: "Load and call custom function", 200 | bytecode: [ 201 | OP.LOAD_STRING, 150, ...encodeString("0,function(){return 66;}"), 202 | OP.EVAL, 150, 150, 203 | OP.FUNC_CALL, 151, 150, 252, ...encodeRegistersArray([]), 204 | ], 205 | expected_registers: [ 206 | [151, 66], 207 | ] 208 | }, 209 | // { // TODO: This should work, but it requires 'null' to work 210 | // // currently, 'null' is recognized as identifier by ressa 211 | // name: "Try-Throw", 212 | // init_regeisters: [ 213 | // [0, console], 214 | // [1, JSON], 215 | // [2, Object] 216 | // ], 217 | // bytecode: [ 218 | // OP.TRY, 7, ...encodeLongNum(49), ...encodeLongNum(72), 219 | // OP.LOAD_STRING, 4, ...encodeString('{"x": 100}'), 220 | // OP.LOAD_STRING, 6, ...encodeString("parse"), 221 | // OP.PROPACCESS, 5, 1, 6, 222 | // OP.FUNC_CALL, 3, 5, 1, ...encodeRegistersArray([4]), 223 | // OP.LOAD_LONG_NUM, 200, ...encodeLongNum(98), 224 | // OP.LOAD_STRING, 9, ...encodeString("log"), 225 | // OP.PROPACCESS, 8, 0, 9, 226 | // OP.FUNC_CALL, 202, 8, 0, ...encodeRegistersArray([7]), 227 | // OP.LOAD_LONG_NUM, 200, ...encodeLongNum(98), 228 | // OP.LOAD_STRING, 8, ...encodeString("create"), 229 | // OP.PROPACCESS, 7, 2, 8, 230 | // OP.FUNC_CALL, 3, 7, 2, ...encodeRegistersArray([9]), 231 | // OP.LOAD_LONG_NUM, 200, ...encodeLongNum(98) 232 | // ], 233 | // expected_registers: [ 234 | // [2, {x: 100}] 235 | // ] 236 | // } 237 | { 238 | name: "Try-Throw (no-throw)", 239 | init_regeisters: [ 240 | [0, console], 241 | [1, JSON], 242 | [2, {}] 243 | ], 244 | bytecode: [ 245 | OP.TRY, 7, ...encodeLongNum(49), ...encodeLongNum(58), 246 | OP.LOAD_STRING, 4, ...encodeString('{"x": 100}'), 247 | OP.LOAD_STRING, 6, ...encodeString("parse"), 248 | OP.PROPACCESS, 5, 1, 6, 249 | OP.FUNC_CALL, 3, 5, 1, ...encodeRegistersArray([4]), 250 | OP.LOAD_LONG_NUM, 200, ...encodeLongNum(81), 251 | OP.COPY, 3, 2, 252 | OP.LOAD_LONG_NUM, 200, ...encodeLongNum(81), 253 | OP.LOAD_STRING, 8, ...encodeString("log"), 254 | OP.PROPACCESS, 7, 0, 8, 255 | OP.FUNC_CALL, 202, 7, 0, ...encodeRegistersArray([3]), 256 | OP.LOAD_LONG_NUM, 200, ...encodeLongNum(81) 257 | ], 258 | expected_registers: [ 259 | [3, {x: 100}] 260 | ] 261 | }, 262 | { 263 | name: "Try-Throw (throw)", 264 | init_regeisters: [ 265 | [0, console], 266 | [1, JSON], 267 | [2, {}] 268 | ], 269 | bytecode: [ 270 | OP.TRY, 7, ...encodeLongNum(57), ...encodeLongNum(66), 271 | OP.LOAD_STRING, 4, ...encodeString('{invalid, invalid}'), 272 | OP.LOAD_STRING, 6, ...encodeString("parse"), 273 | OP.PROPACCESS, 5, 1, 6, 274 | OP.FUNC_CALL, 3, 5, 1, ...encodeRegistersArray([4]), 275 | OP.LOAD_LONG_NUM, 200, ...encodeLongNum(89), 276 | OP.COPY, 3, 2, 277 | OP.LOAD_LONG_NUM, 200, ...encodeLongNum(89), 278 | OP.LOAD_STRING, 8, ...encodeString("log"), 279 | OP.PROPACCESS, 7, 0, 8, 280 | OP.FUNC_CALL, 202, 7, 0, ...encodeRegistersArray([3]), 281 | OP.LOAD_LONG_NUM, 200, ...encodeLongNum(89) 282 | ], 283 | expected_registers: [ 284 | [3, {}] 285 | ] 286 | } 287 | ] 288 | 289 | function runVMTests(testData) { 290 | var encodedBytecode = encodeBytecode(testData.bytecode); 291 | 292 | if(testData.init instanceof Function) { 293 | testData.init(); 294 | } 295 | 296 | var vm = new VM(); 297 | 298 | if(typeof testData.init_regeisters !== "undefined") { 299 | // console.log(testData.init_regeisters[1]); 300 | for(let reg_init of testData.init_regeisters) { 301 | // console.log(reg_init); 302 | vm.setReg(reg_init[0], reg_init[1]) 303 | } 304 | } 305 | 306 | vm.atob = require("atob"); 307 | vm.setReg(REGS.WINDOW, window); 308 | 309 | vm.init(encodedBytecode); 310 | 311 | const result = vm.run(); 312 | assert.equal(result, 0); 313 | 314 | for(let regData of testData.expected_registers) { 315 | assert.deepEqual(vm.getReg(regData[0]), regData[1], 316 | "Expected register " + regData[0] + " to be " + regData[1] + 317 | " but it is " + vm.getReg(regData[0])); 318 | } 319 | } 320 | 321 | 322 | describe("VM Tests", function() { 323 | describe("Instructions Tests", function() { 324 | for(let testData of testDataSet) { 325 | it(testData.name, () => runVMTests(testData)); 326 | } 327 | }); 328 | }); 329 | -------------------------------------------------------------------------------- /compiler/src/scope.rs: -------------------------------------------------------------------------------- 1 | use std::collections::*; 2 | use resast::prelude::*; 3 | 4 | use crate::bytecode::{BytecodeLiteral}; 5 | use crate::error::{CompilerError, CompilerResult}; 6 | 7 | pub type Register = u8; 8 | pub type Reg = Register; 9 | 10 | /// A reimplementantion of resast::prelude::VaribaleKind 11 | /// 12 | /// This reimplementantion of resast::prelude::VaribaleKind is done to derive the HashMap, 13 | /// PartialEq and Eq traits. 14 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 15 | pub enum MyVariableKind { 16 | Var, 17 | Let, 18 | Const 19 | } 20 | 21 | impl From<&VariableKind> for MyVariableKind { 22 | fn from(var_kind: &VariableKind) -> Self { 23 | match var_kind { 24 | VariableKind::Var => MyVariableKind::Var, 25 | VariableKind::Let => MyVariableKind::Let, 26 | VariableKind::Const => MyVariableKind::Const, 27 | } 28 | } 29 | } 30 | 31 | 32 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 33 | pub enum DeclarationType { 34 | Variable(MyVariableKind), 35 | Function, 36 | Literal, 37 | // Intermediate 38 | } 39 | 40 | pub type DeclType = DeclarationType; 41 | 42 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 43 | pub struct Declaration 44 | { 45 | pub register: Register, 46 | pub decl_type: DeclarationType, 47 | } 48 | 49 | 50 | #[derive(Debug, Clone)] 51 | pub struct Scope 52 | { 53 | decls: HashMap, 54 | new_decls: HashSet, 55 | /// Is always sorted 56 | unused_register: VecDeque, 57 | pub used_decls: HashSet 58 | } 59 | 60 | impl Scope { 61 | pub fn new() -> Self { 62 | Scope { 63 | decls: HashMap::new(), 64 | new_decls: HashSet::new(), 65 | unused_register: (0..(Register::max_value() as u16 + 1)).map(|reg: u16| reg as u8).collect(), 66 | used_decls: HashSet::new() 67 | } 68 | } 69 | 70 | pub fn derive_scope(parent_scope: &Scope) -> CompilerResult { 71 | Ok(Scope { 72 | decls: parent_scope.decls.clone(), 73 | new_decls: HashSet::new(), 74 | unused_register: parent_scope.unused_register.clone(), 75 | used_decls: HashSet::new() 76 | }) 77 | } 78 | 79 | pub fn get_unused_register(&mut self) -> CompilerResult { 80 | self.unused_register.pop_front().ok_or( 81 | CompilerError::Custom("All registers are in use. Free up some registers".into()) 82 | ) 83 | } 84 | 85 | pub fn get_unused_register_back(&mut self) -> CompilerResult { 86 | self.unused_register.pop_back().ok_or( 87 | CompilerError::Custom("All registers are in use. Free up some registers".into()) 88 | ) 89 | } 90 | 91 | pub fn try_reserve_specific_reg(&mut self, specific_reg: Register) -> CompilerResult { 92 | let maybe_idx = self.unused_register.iter().enumerate().find_map(|(i, ®)| { 93 | if reg == specific_reg { Some(i) } else { None } 94 | }); 95 | 96 | match maybe_idx { 97 | Some(idx) => { 98 | self.unused_register.remove(idx); 99 | Ok(specific_reg) 100 | }, 101 | None => Err(CompilerError::Custom(format!("Failed to reserve specific reg {}", specific_reg))) 102 | } 103 | } 104 | 105 | pub fn add_decl(&mut self, decl_name: String, decl_type: DeclarationType) -> CompilerResult { 106 | let unused_reg = self.get_unused_register()?; 107 | self.decls.insert(decl_name.clone(), Declaration { 108 | register: unused_reg, 109 | decl_type: decl_type 110 | }); 111 | self.new_decls.insert(decl_name); 112 | Ok(unused_reg) 113 | } 114 | 115 | pub fn get_decl(&mut self, decl_name: &str) -> CompilerResult<&Declaration> { 116 | let decl = self.decls.get(decl_name).ok_or( 117 | CompilerError::Custom(format!("The declaration '{}' does not exist", decl_name)) 118 | )?; 119 | 120 | if !self.new_decls.contains(decl_name) { 121 | self.used_decls.insert(decl.clone()); 122 | } 123 | Ok(decl) 124 | } 125 | 126 | pub fn reserve_register(&mut self) -> CompilerResult { 127 | self.get_unused_register() 128 | } 129 | 130 | pub fn reserve_register_back(&mut self) -> CompilerResult { 131 | self.get_unused_register_back() 132 | } 133 | } 134 | 135 | 136 | #[derive(Debug, Clone)] 137 | pub struct Scopes 138 | { 139 | // TODO: It is not trivial to derive the Hash trait from BytecodeLiteral (because of f64), 140 | // and thus, it cannot be easily used in HashMap. However, this would be 141 | // a better choice. 142 | // pub literals_cache: HashMap, 143 | pub literals: Vec<(BytecodeLiteral, Declaration)>, 144 | pub scopes: Vec, 145 | } 146 | 147 | impl Scopes 148 | { 149 | pub fn new() -> Scopes { 150 | Scopes { 151 | literals: vec![], 152 | scopes: vec![ Scope::new() ], 153 | } 154 | } 155 | 156 | pub fn add_lit_decl(&mut self, literal: BytecodeLiteral, reg: Reg) -> CompilerResult<()> { 157 | // self.literals.insert(lit, Declaration{ 158 | // register: reg, 159 | // is_function: false 160 | // }).ok_or( 161 | // Err(CompilerError::Custom("Failed to insert literal to hashmap".into())) 162 | // ) 163 | 164 | self.literals.push((literal, 165 | Declaration { 166 | register: reg, 167 | decl_type: DeclarationType::Literal 168 | } 169 | )); 170 | 171 | Ok(()) 172 | } 173 | 174 | pub fn add_var_decl(&mut self, decl: String) -> CompilerResult { 175 | self.add_decl(decl, DeclarationType::Variable(MyVariableKind::Var)) 176 | } 177 | 178 | pub fn add_decl(&mut self, decl: String, decl_type: DeclarationType) -> CompilerResult { 179 | self.current_scope_mut()?.add_decl(decl, decl_type) 180 | } 181 | 182 | pub fn reserve_register(&mut self) -> CompilerResult { 183 | self.current_scope_mut()?.reserve_register() 184 | } 185 | 186 | pub fn reserve_register_back(&mut self) -> CompilerResult { 187 | self.current_scope_mut()?.reserve_register_back() 188 | } 189 | 190 | pub fn get_var(&mut self, var_name: &str) -> CompilerResult<&Declaration> { 191 | self.current_scope_mut()?.get_decl(var_name) 192 | } 193 | 194 | pub fn get_lit_decl(&self, literal: &BytecodeLiteral) -> CompilerResult<&Declaration> { 195 | // self.literals.get(literal).ok_or( 196 | // Err(CompilerError::Custom("The requested literal does not exist".into())) 197 | // ) 198 | match self.literals.iter().find(|&lit| lit.0 == *literal) { 199 | Some(lit_and_decl) => Ok(&lit_and_decl.1), 200 | None => Err(CompilerError::Custom("The requested literal does not exist".into())) 201 | } 202 | } 203 | 204 | pub fn enter_new_scope(&mut self) -> CompilerResult<()> { 205 | self.scopes.push(Scope::derive_scope(self.current_scope()?)?); 206 | Ok(()) 207 | } 208 | 209 | pub fn enter_new_block_scope(&mut self) -> CompilerResult<()> { 210 | self.scopes.push(Scope::derive_scope(self.current_scope()?)?); 211 | Ok(()) 212 | } 213 | 214 | pub fn current_scope(&self) -> CompilerResult<&Scope> { 215 | self.scopes.last().ok_or( 216 | CompilerError::Custom("No current scope".into()) 217 | ) 218 | } 219 | 220 | pub fn current_scope_mut(&mut self) -> CompilerResult<&mut Scope> { 221 | self.scopes.last_mut().ok_or( 222 | CompilerError::Custom("No current (mut) scope".into()) 223 | ) 224 | } 225 | 226 | pub fn leave_current_scope(&mut self) -> CompilerResult { 227 | let scope = self.scopes.pop().ok_or( 228 | CompilerError::Custom("Cannot leave inexisting scope".into()) 229 | )?; 230 | 231 | if let Ok(current_scope) = self.current_scope_mut() { 232 | current_scope.used_decls.extend(scope.used_decls.iter().cloned()); 233 | } 234 | 235 | Ok(scope) 236 | } 237 | 238 | pub fn leave_current_block_scope(&mut self) -> CompilerResult { 239 | let scope = self.scopes.pop().ok_or( 240 | CompilerError::Custom("Cannot leave inexisting scope".into()) 241 | )?; 242 | 243 | if let Ok(current_scope) = self.current_scope_mut() { 244 | current_scope.unused_register = scope.unused_register.clone(); 245 | current_scope.used_decls.extend(scope.used_decls.iter().cloned()); 246 | } 247 | 248 | Ok(scope) 249 | } 250 | } 251 | 252 | #[test] 253 | fn test_scopes() { 254 | let mut scopes = Scopes::new(); 255 | 256 | let r0 = scopes.add_var_decl("globalVar".into()).unwrap(); 257 | 258 | scopes.enter_new_scope().unwrap(); 259 | let r1 = scopes.add_var_decl("testVar".into()).unwrap(); 260 | let r2 = scopes.add_var_decl("anotherVar".into()).unwrap(); 261 | let rx = scopes.current_scope_mut().unwrap().get_unused_register().unwrap(); 262 | let rxb = scopes.current_scope_mut().unwrap().get_unused_register_back().unwrap(); 263 | assert_ne!(r0, r1); 264 | assert_ne!(r1, r2); 265 | assert_eq!(rx, 3); 266 | assert_eq!(rxb, 255); 267 | assert_eq!(scopes.get_var("testVar").unwrap().register, r1); 268 | assert_eq!(scopes.get_var("anotherVar").unwrap().register, r2); 269 | assert!(scopes.leave_current_scope().is_ok()); 270 | 271 | assert_eq!(scopes.get_var("globalVar").unwrap().register, r0); 272 | assert!(scopes.get_var("testVar").is_err()); 273 | assert!(scopes.get_var("anotherVar").is_err()); 274 | 275 | assert!(scopes.leave_current_scope().is_ok()); 276 | 277 | assert!(scopes.current_scope().is_err()); 278 | } 279 | -------------------------------------------------------------------------------- /compiler-interface/src/composer.rs: -------------------------------------------------------------------------------- 1 | use jsyc_compiler::{Bytecode, JSSourceCode, JSAst, DeclDepencies}; 2 | use resast::prelude::*; 3 | 4 | use crate::errors::{CompositionError, CompositionResult}; 5 | use crate::options::{Options, VMExportType}; 6 | 7 | 8 | #[derive(Debug, PartialEq)] 9 | pub struct VM { 10 | vm_template: Vec 11 | } 12 | 13 | impl VM { 14 | pub fn from_js_code(vm_code: JSSourceCode) -> CompositionResult { 15 | match JSAst::parse(&vm_code) { 16 | Ok(ast_helper) => match ast_helper.ast { 17 | Program::Mod(parts) | 18 | Program::Script(parts) => Ok(VM { 19 | vm_template: parts 20 | }) 21 | }, 22 | Err(_) => Err(CompositionError::from_str("failed to parse vm code")) 23 | } 24 | } 25 | 26 | pub fn save_to_file

(self, filepath: P) -> CompositionResult<()> 27 | where P: AsRef 28 | { 29 | let f = std::fs::File::create(filepath)?; 30 | let mut writer = resw::Writer::new(f); 31 | 32 | for part in self.vm_template.iter() { 33 | writer.write_part(part).expect(&format!("failed to write part {:?}", part)); 34 | } 35 | 36 | Ok(()) 37 | } 38 | 39 | pub fn inject_decleration_dependencies(self, decls_deps: &DeclDepencies) -> CompositionResult { 40 | // The first part of this (until the next big comment) is to find function declerations 41 | // inside the class "VM" 42 | Ok(VM{ 43 | vm_template: self.vm_template.into_iter().map(|part| { 44 | match part { 45 | ProgramPart::Decl(Decl::Class(mut class)) => ProgramPart::Decl(Decl::Class(match &class.id { 46 | Some(id) => match id.as_str() == "VM" { 47 | true => { 48 | class.body = class.body.into_iter().map(|mut property| { 49 | property.value = match property.value { 50 | PropertyValue::Expr(Expr::Function(mut function)) => { 51 | 52 | // In this part it gets checked whether any of the functions 53 | // function calls contains the idetifier argument 54 | // FutureDeclerationsPlaceHolder. If this is the case, 55 | // This call is used as template. It's arguments get 56 | // replaced by the detected dependencies 57 | let maybe_template_call = function.body.iter().enumerate().find_map(|(i, part)| { 58 | match part { 59 | ProgramPart::Stmt(Stmt::Expr(Expr::Call(call_expr))) => { 60 | call_expr.arguments.iter().find(|&arg_expr| { 61 | arg_expr == &Expr::Ident("FutureDeclerationsPlaceHolder".into()) 62 | }).map(|_| (i, call_expr)) 63 | }, 64 | _ => None 65 | } 66 | }); 67 | 68 | if let Some((i, template_call)) = maybe_template_call { 69 | let dep_stmts = decls_deps.decls_decps.iter().map(|(ident, reg)| { 70 | let mut call = template_call.clone(); 71 | 72 | call.arguments = vec![ 73 | Expr::Literal(Literal::Number(reg.to_string())), 74 | Expr::Ident(ident.to_string()), 75 | ]; 76 | 77 | ProgramPart::Stmt(Stmt::Expr(Expr::Call(call))) 78 | }).collect::>(); 79 | 80 | function.body.extend(dep_stmts); 81 | function.body.remove(i); 82 | } 83 | 84 | PropertyValue::Expr(Expr::Function(function)) 85 | }, 86 | _ => property.value 87 | }; 88 | property 89 | }).collect(); 90 | class 91 | }, 92 | false => class 93 | }, 94 | None => class, 95 | })), 96 | _ => part 97 | } 98 | }).collect() 99 | }) 100 | } 101 | 102 | pub fn strip_unneeded(self) -> CompositionResult { 103 | Ok(VM { 104 | vm_template: self.vm_template.into_iter().filter(|part| { 105 | match part { 106 | ProgramPart::Decl(decl) => match decl { 107 | Decl::Class(class) => match &class.id { 108 | Some(id) => id == "VM", 109 | None => false, 110 | }, 111 | Decl::Variable(kind, _) => kind == &VariableKind::Const, 112 | _ => false 113 | }, 114 | _ => false 115 | } 116 | }).collect() 117 | }) 118 | } 119 | 120 | pub fn append_export_stmt(&mut self, export_types: &VMExportType) { 121 | use resast::*; 122 | self.vm_template.push(match export_types { 123 | VMExportType::NodeJS => { 124 | ProgramPart::Stmt(Stmt::Expr(Expr::Assignment(AssignmentExpr{ 125 | operator: AssignmentOperator::Equal, 126 | left: AssignmentLeft::Expr(Box::new(Expr::Member(MemberExpr{ 127 | object: Box::new(Expr::Ident("module".into())), 128 | property: Box::new(Expr::Ident("exports".into())), 129 | computed: false 130 | }))), 131 | right: Box::new(Expr::Ident("VM".into())) 132 | }))) 133 | }, 134 | VMExportType::ES6 => { 135 | ProgramPart::Decl(Decl::Export(Box::new(ModExport::Named(NamedExportDecl::Specifier( 136 | vec![ExportSpecifier::new("VM".into(), None)], 137 | None 138 | ))))) 139 | } 140 | }); 141 | } 142 | 143 | 144 | } 145 | 146 | pub struct Composer<'a> { 147 | vm: VM, 148 | bytecode: Bytecode, 149 | options: &'a Options 150 | } 151 | 152 | impl<'a> Composer<'a> { 153 | pub fn new(vm: VM, bytecode: Bytecode, options: &'a Options) -> Self { 154 | Composer { 155 | vm, 156 | bytecode, 157 | options 158 | } 159 | } 160 | 161 | pub fn compose(self, decls_deps: &DeclDepencies) -> CompositionResult<(VM, Bytecode)> { 162 | let mut vm = self.vm.inject_decleration_dependencies(decls_deps)?.strip_unneeded()?; 163 | 164 | if let Some(export_type) = &self.options.vm_options.export_type { 165 | vm.append_export_stmt(export_type); 166 | } 167 | 168 | Ok((vm, self.bytecode)) 169 | } 170 | } 171 | 172 | 173 | #[test] 174 | fn test_vm_strip_strip_unneeded() { 175 | let vm = VM::from_js_code(JSSourceCode::from_str( 176 | "if(typeof window == \"undefined\") {\ 177 | var window = {};\ 178 | }\ 179 | const REGS = {}; 180 | const OP = {}; 181 | class VM {}\ 182 | module.exports = function() {\ 183 | this.REGS = REGS;\ 184 | }")).unwrap(); 185 | 186 | let clean_vm = vm.strip_unneeded().unwrap(); 187 | let expected_vm = VM::from_js_code(JSSourceCode::from_str(" 188 | const REGS = {};\ 189 | const OP = {};\ 190 | class VM {}")).unwrap(); 191 | 192 | assert_eq!(clean_vm, expected_vm); 193 | } 194 | 195 | #[test] 196 | fn test_vm_inject_decleration_dependencies() { 197 | let vm = VM::from_js_code(JSSourceCode::from_str( 198 | "class VM {\ 199 | init(bytecode) { 200 | this.setReg(255, 0); 201 | 202 | this.setReg(FutureDeclerationsPlaceHolder, 0); 203 | } 204 | }")).unwrap(); 205 | 206 | let mut decl_deps = DeclDepencies::new(); 207 | decl_deps.add_decl_dep("document".into(), 2); 208 | decl_deps.add_decl_dep("window".into(), 10); 209 | 210 | let injected_vm = vm.inject_decleration_dependencies(&decl_deps).unwrap(); 211 | 212 | // Both varinats expected_vm_0 and expected_vm_1 are possible since 213 | // DeclDepencies uses a HashMap internally so the order of the elements is unpredictable 214 | let expected_vm_0 = VM::from_js_code(JSSourceCode::from_str( 215 | "class VM {\ 216 | init(bytecode) { 217 | this.setReg(255, 0); 218 | 219 | this.setReg(2, document); 220 | this.setReg(10, window); 221 | } 222 | }")).unwrap(); 223 | 224 | let expected_vm_1 = VM::from_js_code(JSSourceCode::from_str( 225 | "class VM {\ 226 | init(bytecode) { 227 | this.setReg(255, 0); 228 | 229 | this.setReg(10, window); 230 | this.setReg(2, document); 231 | } 232 | }")).unwrap(); 233 | 234 | if injected_vm == expected_vm_0 { 235 | assert_eq!(injected_vm, expected_vm_0); 236 | } else { 237 | assert_eq!(injected_vm, expected_vm_1); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /compiler/src/instruction_set.rs: -------------------------------------------------------------------------------- 1 | use crate::bytecode::*; 2 | use crate::scope::{Reg, Register, Scope, Scopes}; 3 | use crate::error::{CompilerError, CompilerResult}; 4 | 5 | use resast::prelude::*; 6 | 7 | 8 | macro_rules! make_enum_helper{ 9 | (enum $name:ident { $($variants:ident),* }) => 10 | ( 11 | #[derive(Clone, PartialEq, Debug)] 12 | pub enum $name { 13 | $($variants,)* 14 | __VarinatsCountHelper__ 15 | } 16 | impl $name { 17 | pub const fn enum_size() -> usize { 18 | $name::__VarinatsCountHelper__ as usize 19 | } 20 | 21 | pub fn enum_iterator() -> std::slice::Iter<'static, $name> { 22 | const VARINTS: [$name; $name::enum_size()] = [$($name::$variants,)*]; 23 | VARINTS.into_iter() 24 | } 25 | 26 | pub fn variant_index(&self) -> usize { 27 | self.clone() as usize 28 | } 29 | } 30 | ) 31 | } 32 | 33 | #[test] 34 | fn test_make_enum_helper() { 35 | make_enum_helper!( 36 | enum TestEnumA { 37 | A, 38 | B, 39 | C 40 | } 41 | ); 42 | 43 | assert_eq!(TestEnumA::enum_size(), 3); 44 | 45 | let mut iter = TestEnumA::enum_iterator(); 46 | 47 | assert_eq!(iter.next(), Some(&TestEnumA::A)); 48 | assert_eq!(iter.next(), Some(&TestEnumA::B)); 49 | assert_eq!(iter.next(), Some(&TestEnumA::C)); 50 | assert_eq!(iter.next(), None); 51 | 52 | assert_eq!(TestEnumA::A.variant_index(), 0); 53 | assert_eq!(TestEnumA::B.variant_index(), 1); 54 | assert_eq!(TestEnumA::C.variant_index(), 2); 55 | 56 | make_enum_helper!( 57 | enum TestEnumB { 58 | A 59 | } 60 | ); 61 | 62 | assert_eq!(TestEnumB::enum_size(), 1); 63 | 64 | let mut iter = TestEnumB::enum_iterator(); 65 | 66 | assert_eq!(iter.next(), Some(&TestEnumB::A)); 67 | assert_eq!(iter.next(), None); 68 | 69 | assert_eq!(TestEnumB::A.variant_index(), 0); 70 | } 71 | 72 | 73 | make_enum_helper!( 74 | enum CommonLiteral 75 | { 76 | Num0, 77 | Num1, 78 | Void0 // Undefined 79 | // EmptyString 80 | }); 81 | 82 | impl CommonLiteral { 83 | pub fn to_literal(&self) -> BytecodeLiteral { 84 | match &self { 85 | CommonLiteral::Num0 => BytecodeLiteral::IntNumber(0), 86 | CommonLiteral::Num1 => BytecodeLiteral::IntNumber(1), 87 | CommonLiteral::Void0 => BytecodeLiteral::Null, 88 | _ => panic!("") 89 | } 90 | } 91 | } 92 | 93 | /// Represents common literals 94 | /// 95 | /// Common literals are literals such as ``0``, ``1`` or ``undefined``. 96 | #[derive(Clone)] 97 | pub struct CommonLiteralRegs 98 | { 99 | regs: Vec 100 | } 101 | 102 | impl CommonLiteralRegs { 103 | pub fn new(scope: &mut Scope) -> CompilerResult { 104 | Ok(CommonLiteralRegs { 105 | regs: (0..CommonLiteral::enum_size()).map(|_| scope.reserve_register_back()).collect::>>()? 106 | }) 107 | } 108 | 109 | pub fn add_to_lit_cache(&self, scopes: &mut Scopes) -> CompilerResult<()> { 110 | for common_lit in CommonLiteral::enum_iterator() { 111 | scopes.add_lit_decl(common_lit.to_literal(), self.regs[common_lit.variant_index()])?; 112 | } 113 | 114 | Ok(()) 115 | } 116 | 117 | pub fn reg(&self, common_lit: &CommonLiteral) -> Reg { 118 | self.regs[common_lit.variant_index()] 119 | } 120 | } 121 | 122 | 123 | make_enum_helper!( 124 | enum ReservedeRegister { 125 | BytecodePointer, 126 | BytecodeFuncReturn, 127 | TrashRegister 128 | }); 129 | 130 | /// Represents a set of reserved registers 131 | /// 132 | /// Reserved register are registers used by the virtual machine for special purposes. 133 | /// For example [ReservedeRegister::BytecodePointer](enum.ReservedeRegister.html#ReservedeRegister::BytecodePointer) 134 | /// represents the register where the during the execution the current bytecode pointer is stored. 135 | #[derive(Clone)] 136 | pub struct ReservedeRegisters { 137 | regs: Vec 138 | } 139 | 140 | impl ReservedeRegisters { 141 | pub fn new(scope: &mut Scope) -> CompilerResult { 142 | Ok(ReservedeRegisters { 143 | regs: ReservedeRegister::enum_iterator().map(|mp_reg| { 144 | match mp_reg { 145 | ReservedeRegister::BytecodePointer => scope.try_reserve_specific_reg(200), 146 | ReservedeRegister::BytecodeFuncReturn => scope.try_reserve_specific_reg(201), 147 | ReservedeRegister::TrashRegister => scope.try_reserve_specific_reg(202), 148 | ReservedeRegister::__VarinatsCountHelper__ => panic!("ReservedeRegister::__VarinatsCountHelper__") 149 | } 150 | }).collect::>>()? 151 | }) 152 | } 153 | 154 | /// Returns the actual register of a reserved register type 155 | pub fn reg(&self, mp_reg: &ReservedeRegister) -> Reg { 156 | self.regs[mp_reg.variant_index()] 157 | } 158 | } 159 | 160 | 161 | #[derive(Clone)] 162 | pub struct InstructionSet 163 | { 164 | common_literal_regs: CommonLiteralRegs, 165 | reserved_regs: ReservedeRegisters, 166 | } 167 | 168 | impl InstructionSet { 169 | pub fn default(scope: &mut Scope) -> Self { 170 | InstructionSet { 171 | common_literal_regs: CommonLiteralRegs::new(scope).unwrap(), 172 | reserved_regs: ReservedeRegisters::new(scope).unwrap() 173 | } 174 | } 175 | 176 | pub fn common_lits(&self) -> &CommonLiteralRegs { 177 | &self.common_literal_regs 178 | } 179 | 180 | pub fn common_literal_reg(&self, common_lit: &CommonLiteral) -> Reg { 181 | self.common_literal_regs.reg(common_lit) 182 | } 183 | 184 | pub fn reserved_reg(&self, common_lit: &ReservedeRegister) -> Reg { 185 | self.reserved_regs.reg(common_lit) 186 | } 187 | 188 | pub fn load_op(&self, left: Reg, right: Operand) -> Operation { 189 | let instruction = match right { 190 | Operand::String(_) => Instruction::LoadString, 191 | Operand::FloatNum(_) => Instruction::LoadFloatNum, 192 | Operand::LongNum(_) => Instruction::LoadLongNum, 193 | Operand::ShortNum(_) => Instruction::LoadNum, 194 | Operand::Reg(_) => Instruction::Copy, 195 | Operand::RegistersArray(_) => unimplemented!("Register Arrays are not yet implement as seperte load operation"), 196 | Operand::FunctionAddr(_) | 197 | Operand::BranchAddr(_) | 198 | Operand::FunctionArguments(_) | 199 | Operand::BytecodeEnd => unimplemented!("...") 200 | }; 201 | 202 | Operation::new(instruction, vec![Operand::Reg(left), right]) 203 | } 204 | 205 | pub fn assignment_op(&self, op: &AssignmentOperator, rd: Reg, rs: Reg) -> Operation { 206 | let instr = match op { 207 | AssignmentOperator::Equal => Instruction::Copy, 208 | AssignmentOperator::PlusEqual => Instruction::Add, 209 | AssignmentOperator::MinusEqual => Instruction::Minus, 210 | AssignmentOperator::TimesEqual => Instruction::Mul, 211 | AssignmentOperator::DivEqual => Instruction::Div, 212 | // ModEqual, 213 | // LeftShiftEqual, 214 | // RightShiftEqual, 215 | // UnsignedRightShiftEqual, 216 | // OrEqual, 217 | // XOrEqual, 218 | // AndEqual, 219 | // PowerOfEqual, 220 | _ => unimplemented!("The correct branch for the assignment op ist not yet implemented") 221 | }; 222 | 223 | Operation::new(instr, vec![Operand::Reg(rd), Operand::Reg(rd), Operand::Reg(rs)]) 224 | } 225 | 226 | pub fn update_op(&self, op: &UpdateOperator, rd: Reg) -> Operation { 227 | let instr = match op { 228 | UpdateOperator::Increment => Instruction::Add, 229 | UpdateOperator::Decrement => Instruction::Minus, 230 | }; 231 | 232 | Operation::new(instr, vec![ 233 | Operand::Reg(rd), 234 | Operand::Reg(rd), 235 | Operand::Reg(self.common_literal_reg(&CommonLiteral::Num1)) 236 | ] 237 | ) 238 | } 239 | 240 | pub fn unary_op(&self, op: &UnaryOperator, rd: Reg, rs: Reg) -> CompilerResult { 241 | Ok(match op { 242 | UnaryOperator::Minus => Operation::new(Instruction::Minus, vec![ 243 | Operand::Reg(rd), 244 | Operand::Reg(self.common_literal_reg(&CommonLiteral::Num0)), 245 | Operand::Reg(rs) 246 | ] 247 | ), 248 | UnaryOperator::Plus => Operation::new(Instruction::Add, vec![ 249 | Operand::Reg(rd), 250 | Operand::Reg(self.common_literal_reg(&CommonLiteral::Num0)), 251 | Operand::Reg(rs) 252 | ] 253 | ), 254 | // Not, 255 | // Tilde, 256 | // TypeOf, 257 | UnaryOperator::Void => { return Err(CompilerError::Custom("The 'void' must be handled on compiler-level".into())); }, 258 | // Delete, 259 | _ => { return Err(CompilerError::is_unsupported("Unary operation", op)); } 260 | }) 261 | } 262 | 263 | pub fn binary_op(&self, op: &BinaryOperator, rd: Reg, r0: Reg, r1: Reg) -> CompilerResult { 264 | let instr = match op { 265 | BinaryOperator::Equal => Instruction::CompEqual, 266 | BinaryOperator::NotEqual => Instruction::CompNotEqual, 267 | BinaryOperator::StrictEqual => Instruction::CompStrictEqual, 268 | BinaryOperator::StrictNotEqual => Instruction::CompStrictNotEqual, 269 | BinaryOperator::LessThan => Instruction::CompLessThan, 270 | BinaryOperator::GreaterThan => Instruction::CompGreaterThan, 271 | BinaryOperator::LessThanEqual => Instruction::CompLessThanEqual, 272 | BinaryOperator::GreaterThanEqual => Instruction::CompGreaterThanEqual, 273 | // BinaryOperator::LeftShift => Instruction::Sh, 274 | // BinaryOperator::RightShift, 275 | // BinaryOperator::UnsignedRightShift, 276 | BinaryOperator::Plus => Instruction::Add, 277 | BinaryOperator::Minus => Instruction::Minus, 278 | BinaryOperator::Times => Instruction::Mul, 279 | BinaryOperator::Over => Instruction::Div, 280 | // Mod, 281 | // Or, 282 | // XOr, 283 | // And, 284 | // In, 285 | // InstanceOf, 286 | // PowerOf, 287 | _ => { return Err(CompilerError::is_unsupported("Binary operation", op)); } 288 | }; 289 | 290 | Ok(Operation::new(instr, vec![Operand::Reg(rd), Operand::Reg(r0), Operand::Reg(r1)])) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /vm/vm.js: -------------------------------------------------------------------------------- 1 | var FutureDeclerationsPlaceHolder = {} 2 | 3 | const REGS = { 4 | // External dependencies 5 | WINDOW: 100, // This is only for testing 6 | 7 | // Reserved registers 8 | BYTECODE_PTR: 200, 9 | BCFUNC_RETURN: 201, 10 | TRASH_REG: 202, 11 | 12 | // Common literals 13 | // EMPTY_OBJ: 252, 14 | VOID: 253, 15 | NUM_1: 254, 16 | NUM_0: 255, 17 | }; 18 | 19 | const OP = { 20 | // Loaders 21 | LOAD_STRING: 1, 22 | LOAD_NUM: 2, 23 | LOAD_FLOAT: 3, 24 | LOAD_LONG_NUM: 4, 25 | LOAD_ARRAY: 5, 26 | 27 | // Misc 28 | PROPACCESS: 10, 29 | FUNC_CALL: 11, 30 | EVAL: 12, 31 | CALL_BCFUNC: 13, 32 | RETURN_BCFUNC: 14, 33 | COPY: 15, 34 | EXIT: 16, 35 | COND_JUMP: 17, 36 | JUMP: 18, 37 | JUMP_COND_NEG: 19, 38 | BCFUNC_CALLBACK: 20, 39 | PROPSET: 21, 40 | TRY: 22, 41 | THROW: 23, 42 | 43 | // Comparisons 44 | COMP_EQUAL: 50, 45 | COMP_NOT_EQUAL: 51, 46 | COMP_STRICT_EQUAL: 52, 47 | COMP_STRICT_NOT_EQUAL: 53, 48 | COMP_LESS_THAN: 54, 49 | COMP_GREATHER_THAN: 55, 50 | COMP_LESS_THAN_EQUAL: 56, 51 | COMP_GREATHER_THAN_EQUAL: 57, 52 | 53 | // Math 54 | ADD: 100, 55 | MUL: 101, 56 | MINUS: 102, 57 | DIV: 103 58 | }; 59 | 60 | class VM { 61 | constructor() { 62 | this.regs = []; 63 | this.bytecode = []; 64 | this.ops = []; 65 | this.reg_backups = []; 66 | this.modified_regs = []; 67 | try { 68 | this.atob = window.atob; 69 | } catch(e) {} 70 | 71 | this.ops[OP.LOAD_STRING] = function(vm) { 72 | var dst = vm.getByte(), str = vm._loadString(); 73 | vm.setReg(dst, str); 74 | }; 75 | 76 | this.ops[OP.LOAD_NUM] = function(vm) { 77 | var dst = vm.getByte(), val = vm.getByte(); 78 | vm.setReg(dst, val); 79 | }; 80 | 81 | this.ops[OP.LOAD_FLOAT] = function(vm) { 82 | var dst = vm.getByte(), val = vm._loadFloat(); 83 | vm.setReg(dst, val); 84 | }; 85 | 86 | this.ops[OP.LOAD_LONG_NUM] = function(vm) { 87 | var dst = vm.getByte(), val = vm._loadLongNum(); 88 | vm.setReg(dst, val); 89 | }; 90 | 91 | this.ops[OP.LOAD_ARRAY] = function(vm) { 92 | var dst = vm.getByte(), array = vm._loadArrayFromRegister(); 93 | vm.setReg(dst, array); 94 | }; 95 | 96 | this.ops[OP.PROPACCESS] = function(vm) { 97 | var dst = vm.getByte(), obj = vm.getByte(), prop = vm.getByte(); 98 | obj = vm.getReg(obj); prop = vm.getReg(prop); 99 | 100 | vm.setReg(dst, obj[prop]); 101 | }; 102 | 103 | this.ops[OP.PROPSET] = function(vm) { 104 | var dstObj = vm.getByte(), dstProp = vm.getByte(), val = vm.getByte(); 105 | dstObj = vm.getReg(dstObj); 106 | dstProp = vm.getReg(dstProp); 107 | val = vm.getReg(val); 108 | 109 | dstObj[dstProp] = val; 110 | }; 111 | 112 | this.ops[OP.TRY] = function(vm) { 113 | var catchBlockExceptReg = vm.getByte(); 114 | var catchBlockOffset = vm._loadLongNum(); 115 | var finallyBlockOffset = vm._loadLongNum(); 116 | 117 | try { 118 | vm.run(); 119 | } catch(e) { 120 | vm.setReg(catchBlockExceptReg, e); 121 | vm.runAt(catchBlockOffset); 122 | } finally { 123 | vm.runAt(finallyBlockOffset); 124 | } 125 | } 126 | 127 | this.ops[OP.THROW] = function(vm) { 128 | var reg = vm.getByte(); 129 | throw vm.getReg(reg); 130 | } 131 | 132 | this.ops[OP.FUNC_CALL] = function(vm) { 133 | var dst = vm.getByte(), func = vm.getByte(), funcThis = vm.getByte(), 134 | args = vm._loadArrayFromRegister(); 135 | func = vm.getReg(func); 136 | funcThis = vm.getReg(funcThis); 137 | 138 | vm.setReg(dst, func.apply(funcThis, args)); 139 | } 140 | 141 | this.ops[OP.EVAL] = function(vm) { 142 | var dst = vm.getByte(), str = vm.getByte(); 143 | str = vm.getReg(str); 144 | 145 | vm.setReg(dst, eval(str)); 146 | } 147 | 148 | this.ops[OP.CALL_BCFUNC] = function(vm) { 149 | var funcOffset = vm._loadLongNum(); 150 | var returnReg = vm.getByte(); 151 | var argsArray = vm._loadRegistersArray(); 152 | vm.reg_backups.push([vm.regs.slice(), returnReg]); 153 | 154 | for(let i = 0; i < argsArray.length; i+=2) { 155 | vm.setReg(argsArray[i], vm.getReg(argsArray[i+1])); 156 | } 157 | 158 | vm.setReg(REGS.BYTECODE_PTR, funcOffset); 159 | } 160 | 161 | this.ops[OP.RETURN_BCFUNC] = function(vm) { 162 | var returnFromReg = vm.getByte(); 163 | var exceptedRegs = vm._loadRegistersArray(); 164 | var returnData = vm.reg_backups.pop(); 165 | var regBackups = returnData[0]; 166 | let returnToReg = returnData[1]; 167 | 168 | vm.modified_regs = [...new Set([...vm.modified_regs, ...exceptedRegs])]; 169 | 170 | regBackups[returnToReg] = vm.getReg(returnFromReg); 171 | 172 | for(let exceptedReg of vm.modified_regs) { 173 | regBackups[exceptedReg] = vm.getReg(exceptedReg); 174 | } 175 | 176 | if(!vm.reg_backups.length) { 177 | vm.modified_regs = []; 178 | } 179 | 180 | vm.regs = regBackups; 181 | } 182 | 183 | this.ops[OP.COPY] = function(vm) { 184 | var dst = vm.getByte(), src = vm.getByte(); 185 | vm.setReg(dst, vm.getReg(src)); 186 | } 187 | 188 | this.ops[OP.EXIT] = function(vm) { 189 | vm.setReg(REGS.BYTECODE_PTR, vm.bytecode.length); 190 | } 191 | 192 | this.ops[OP.COND_JUMP] = function(vm) { 193 | var cond = vm.getByte(); 194 | var offset = vm._loadLongNum(); 195 | cond = vm.getReg(cond); 196 | 197 | if(cond) { 198 | vm.setReg(REGS.BYTECODE_PTR, offset); 199 | } 200 | } 201 | 202 | this.ops[OP.JUMP] = function(vm) { 203 | var offset = vm._loadLongNum(); 204 | vm.setReg(REGS.BYTECODE_PTR, offset); 205 | } 206 | 207 | this.ops[OP.JUMP_COND_NEG] = function(vm) { 208 | var cond = vm.getByte(); 209 | var offset = vm._loadLongNum(); 210 | cond = vm.getReg(cond); 211 | 212 | if(!cond) { 213 | vm.setReg(REGS.BYTECODE_PTR, offset); 214 | } 215 | } 216 | 217 | this.ops[OP.BCFUNC_CALLBACK] = function(vm) { 218 | var dst = vm.getByte(), func_offset = vm._loadLongNum(), arg_regs = vm._loadRegistersArray(); 219 | vm.setReg(dst, function() { 220 | for(let i = 0; i right); 273 | } 274 | 275 | this.ops[OP.COMP_LESS_THAN_EQUAL] = function(vm) { 276 | var dst = vm.getByte(), left = vm.getByte(), right = vm.getByte(); 277 | left = vm.getReg(left); 278 | right = vm.getReg(right); 279 | 280 | vm.setReg(dst, left <= right); 281 | } 282 | 283 | this.ops[OP.COMP_GREATHER_THAN_EQUAL] = function(vm) { 284 | var dst = vm.getByte(), left = vm.getByte(), right = vm.getByte(); 285 | left = vm.getReg(left); 286 | right = vm.getReg(right); 287 | 288 | vm.setReg(dst, left >= right); 289 | } 290 | 291 | this.ops[OP.ADD] = function(vm) { 292 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 293 | vm.setReg(dst, vm.getReg(src0) + vm.getReg(src1)); 294 | } 295 | 296 | this.ops[OP.MUL] = function(vm) { 297 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 298 | vm.setReg(dst, vm.regs[src0] * vm.regs[src1]); 299 | } 300 | 301 | this.ops[OP.MINUS] = function(vm) { 302 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 303 | vm.setReg(dst, vm.regs[src0] - vm.regs[src1]); 304 | } 305 | 306 | this.ops[OP.DIV] = function(vm) { 307 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 308 | vm.setReg(dst, vm.regs[src0] / vm.regs[src1]); 309 | } 310 | 311 | } 312 | 313 | setReg(reg, value) { 314 | this.regs[reg] = value; 315 | } 316 | 317 | getReg(reg) { 318 | return this.regs[reg]; 319 | } 320 | 321 | getByte() { 322 | return this.bytecode[this.regs[REGS.BYTECODE_PTR]++]; 323 | } 324 | 325 | run() { 326 | while(this.regs[REGS.BYTECODE_PTR] < this.bytecode.length) { 327 | var op_code = this.getByte(); 328 | var op = this.ops[op_code]; 329 | 330 | try { 331 | op(this); 332 | } catch(e) { 333 | console.log("Current stack ptr: ", this.regs[REGS.BYTECODE_PTR], "op code: ", op_code); 334 | throw e; 335 | } 336 | } 337 | return 0; 338 | } 339 | 340 | runFuncAt(offset) { 341 | this.reg_backups.push([this.regs.slice(), REGS.BCFUNC_RETURN]); 342 | this.runAt(offset); 343 | } 344 | 345 | runAt(offset) { 346 | this.setReg(REGS.BYTECODE_PTR, offset); 347 | this.run(); 348 | } 349 | 350 | init(bytecode) { 351 | this.bytecode = this._decodeBytecode(bytecode); 352 | this.setReg(REGS.BYTECODE_PTR, 0); 353 | 354 | this.setReg(REGS.NUM_0, 0); 355 | this.setReg(REGS.NUM_1, 1); 356 | this.setReg(REGS.VOID, void 0); 357 | 358 | this.setReg(FutureDeclerationsPlaceHolder, 0); 359 | } 360 | 361 | _decodeBytecode(encodedBytecode) { 362 | var bytecode = this.atob(encodedBytecode); 363 | var bytes = []; 364 | var byteCounter = 0; 365 | for (var i = 0; i < bytecode.length; i++){ 366 | var b = bytecode.charCodeAt(i); 367 | if (b > 255) { 368 | bytes[byteCounter++] = b & 255; 369 | b >>= 8; 370 | } 371 | bytes[byteCounter++] = b; 372 | } 373 | 374 | return bytes; 375 | } 376 | 377 | _loadString() { 378 | // With a 1 byte string length it would only be possible to load 379 | // string up to a length of 256. However, this might be to short 380 | // load functions or so. 2 bytes and thus a maximal length of 65536 381 | // should be sufficient. 382 | var stringLength = (this.getByte() << 8) || this.getByte(); 383 | var string = ""; 384 | 385 | for(var i = 0;i right); 172 | }; 173 | this.ops[OP.COMP_LESS_THAN_EQUAL] = function(vm) { 174 | var dst = vm.getByte(), left = vm.getByte(), right = vm.getByte(); 175 | left = vm.getReg(left); 176 | right = vm.getReg(right); 177 | vm.setReg(dst, left <= right); 178 | }; 179 | this.ops[OP.COMP_GREATHER_THAN_EQUAL] = function(vm) { 180 | var dst = vm.getByte(), left = vm.getByte(), right = vm.getByte(); 181 | left = vm.getReg(left); 182 | right = vm.getReg(right); 183 | vm.setReg(dst, left >= right); 184 | }; 185 | this.ops[OP.ADD] = function(vm) { 186 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 187 | vm.setReg(dst, vm.getReg(src0) + vm.getReg(src1)); 188 | }; 189 | this.ops[OP.MUL] = function(vm) { 190 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 191 | vm.setReg(dst, vm.regs[src0] * vm.regs[src1]); 192 | }; 193 | this.ops[OP.MINUS] = function(vm) { 194 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 195 | vm.setReg(dst, vm.regs[src0] - vm.regs[src1]); 196 | }; 197 | this.ops[OP.DIV] = function(vm) { 198 | var dst = vm.getByte(), src0 = vm.getByte(), src1 = vm.getByte(); 199 | vm.setReg(dst, vm.regs[src0] / vm.regs[src1]); 200 | }; 201 | } 202 | 203 | setReg(reg, value){ 204 | this.regs[reg] = value; 205 | } 206 | 207 | getReg(reg){ 208 | return this.regs[reg]; 209 | } 210 | 211 | getByte(){ 212 | return this.bytecode[this.regs[REGS.BYTECODE_PTR]++]; 213 | } 214 | 215 | run(){ 216 | while (this.regs[REGS.BYTECODE_PTR] < this.bytecode.length) { 217 | var op_code = this.getByte(); 218 | 219 | var op = this.ops[op_code]; 220 | 221 | try { 222 | op(this); 223 | } catch (e) { 224 | console.log("Current stack ptr: ", this.regs[REGS.BYTECODE_PTR], "op code: ", op_code); 225 | 226 | throw e; 227 | } 228 | } 229 | return 0; 230 | } 231 | 232 | runFuncAt(offset){ 233 | this.reg_backups.push([this.regs.slice(), REGS.BCFUNC_RETURN]); 234 | this.runAt(offset); 235 | } 236 | 237 | runAt(offset){ 238 | this.setReg(REGS.BYTECODE_PTR, offset); 239 | this.run(); 240 | } 241 | 242 | init(bytecode){ 243 | this.bytecode = this._decodeBytecode(bytecode); 244 | this.setReg(REGS.BYTECODE_PTR, 0); 245 | this.setReg(REGS.WINDOW, window); 246 | this.setReg(REGS.NUM_0, 0); 247 | this.setReg(REGS.NUM_1, 1); 248 | this.setReg(REGS.VOID, void 0); 249 | this.setReg(47, clearInterval); 250 | this.setReg(2, document); 251 | this.setReg(76, Object); 252 | this.setReg(46, setInterval); 253 | this.setReg(108, requestAnimationFrame); 254 | this.setReg(20, Math); 255 | } 256 | 257 | _decodeBytecode(encodedBytecode){ 258 | var bytecode = window.atob(encodedBytecode); 259 | var bytes = []; 260 | var byteCounter = 0; 261 | for (var i = 0;i < bytecode.length;i++) { 262 | var b = bytecode.charCodeAt(i); 263 | 264 | if (b > 255) { 265 | bytes[byteCounter++] = b & 255; 266 | 267 | b >>= 8; 268 | } 269 | 270 | bytes[byteCounter++] = b; 271 | } 272 | return bytes; 273 | } 274 | 275 | _loadString(){ 276 | var stringLength = this.getByte() << 8 || this.getByte(); 277 | var string = ""; 278 | for (var i = 0;i < stringLength;i++) { 279 | string += String.fromCharCode(this.getByte()); 280 | } 281 | return string; 282 | } 283 | 284 | _loadArrayFromRegister(){ 285 | var arrayLength = this.getByte(); 286 | var array = []; 287 | for (var i = 0;i < arrayLength;i++) { 288 | array.push(this.getReg(this.getByte())); 289 | } 290 | return array; 291 | } 292 | 293 | _loadFloat(){ 294 | var binary = ""; 295 | for (let i = 0;i < 8;++i) { 296 | binary += this.getByte().toString(2).padStart(8, '0'); 297 | } 298 | var sign = binary.charAt(0) == '1' ? -1 : 1; 299 | var exponent = parseInt(binary.substr(1, 11), 2); 300 | var significandBase = binary.substr(12); 301 | var significandBin; 302 | if (exponent == 0) { 303 | if (significandBase.indexOf('1') == -1) { 304 | return 0; 305 | } else { 306 | exponent = -0x3fe; 307 | 308 | significandBin = '0' + significandBase; 309 | } 310 | } else { 311 | exponent -= 0x3ff; 312 | 313 | significandBin = '1' + significandBase; 314 | } 315 | var significand = 0; 316 | for (let i = 0, val = 1;i < significandBin.length;(++i, val /= 2)) { 317 | significand += val * parseInt(significandBin.charAt(i)); 318 | } 319 | return sign * significand * Math.pow(2, exponent); 320 | } 321 | 322 | _loadLongNum(){ 323 | var num = this.getByte() << 24 | this.getByte() << 16 | this.getByte() << 8 | this.getByte(); 324 | return num; 325 | } 326 | 327 | _loadRegistersArray(){ 328 | var arrayLength = this.getByte(); 329 | var registers_array = []; 330 | for (var i = 0;i < arrayLength;i++) { 331 | registers_array.push(this.getByte()); 332 | } 333 | return registers_array; 334 | } 335 | } 336 | 337 | -------------------------------------------------------------------------------- /compiler/src/bytecode.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{CompilerError, CompilerResult}; 2 | use crate::scope::Register; 3 | 4 | use std::{u16}; 5 | use std::iter::FromIterator; 6 | use resast::prelude::*; 7 | 8 | 9 | pub type BytecodeResult = Result; 10 | 11 | /// Labels are used as targets of jumps 12 | pub type Label = u32; 13 | 14 | /// This trait is implemented by elements that are part of the final bytecode 15 | pub trait ToBytes { 16 | fn to_bytes(&self) -> Vec; 17 | 18 | fn length_in_bytes(&self) -> usize { 19 | self.to_bytes().len() 20 | } 21 | } 22 | 23 | /// Represents the basics instructions known to this compiler 24 | #[derive(Debug, PartialEq, Clone)] 25 | pub enum Instruction 26 | { 27 | LoadString, 28 | LoadFloatNum, 29 | LoadLongNum, 30 | LoadNum, 31 | LoadArray, 32 | 33 | PropAccess, 34 | CallFunc, 35 | Eval, 36 | CallBytecodeFunc, 37 | ReturnBytecodeFunc, 38 | Copy, 39 | Exit, 40 | BytecodeFuncCallback, 41 | PropertySet, 42 | Try, 43 | Throw, 44 | 45 | JumpCond, 46 | Jump, 47 | JumpCondNeg, 48 | 49 | CompEqual, 50 | CompNotEqual, 51 | CompStrictEqual, 52 | CompStrictNotEqual, 53 | CompLessThan, 54 | CompGreaterThan, 55 | CompLessThanEqual, 56 | CompGreaterThanEqual, 57 | 58 | Add, 59 | Minus, 60 | Mul, 61 | Div, 62 | // LeftShift 63 | // RightShift 64 | // Mod, 65 | // Or, 66 | // XOr, 67 | // And, 68 | // In, 69 | } 70 | 71 | impl Instruction { 72 | fn to_byte(&self) -> u8 { 73 | match self { 74 | Instruction::LoadString => 1, 75 | Instruction::LoadNum => 2, 76 | Instruction::LoadFloatNum => 3, 77 | Instruction::LoadLongNum => 4, 78 | Instruction::LoadArray => 5, 79 | 80 | Instruction::PropAccess => 10, 81 | Instruction::CallFunc => 11, 82 | Instruction::Eval => 12, 83 | Instruction::CallBytecodeFunc => 13, 84 | Instruction::ReturnBytecodeFunc => 14, 85 | Instruction::Copy => 15, 86 | Instruction::Exit => 16, 87 | Instruction::JumpCond => 17, 88 | Instruction::Jump => 18, 89 | Instruction::JumpCondNeg => 19, 90 | Instruction::BytecodeFuncCallback => 20, 91 | Instruction::PropertySet => 21, 92 | Instruction::Try => 22, 93 | Instruction::Throw => 23, 94 | 95 | Instruction::CompEqual => 50, 96 | Instruction::CompNotEqual => 51, 97 | Instruction::CompStrictEqual => 52, 98 | Instruction::CompStrictNotEqual => 53, 99 | Instruction::CompLessThan => 54, 100 | Instruction::CompGreaterThan => 55, 101 | Instruction::CompLessThanEqual => 56, 102 | Instruction::CompGreaterThanEqual => 57, 103 | 104 | Instruction::Add => 100, 105 | Instruction::Minus => 102, 106 | Instruction::Mul => 101, 107 | Instruction::Div => 103, 108 | } 109 | } 110 | 111 | pub fn to_str(&self) -> &str { 112 | match self { 113 | Instruction::LoadString => "LoadString", 114 | Instruction::LoadNum => "LoadNum", 115 | Instruction::LoadFloatNum => "LoadFloatNum", 116 | Instruction::LoadLongNum => "LoadLongNum", 117 | Instruction::LoadArray => "LoadArray", 118 | 119 | Instruction::PropAccess => "PropAccess", 120 | Instruction::CallFunc => "CallFunc", 121 | Instruction::Eval => "Eval", 122 | Instruction::CallBytecodeFunc => "CallBytecodeFunc", 123 | Instruction::ReturnBytecodeFunc => "ReturnBytecodeFunc", 124 | Instruction::Copy => "Copy", 125 | Instruction::Exit => "Exit", 126 | Instruction::JumpCond => "JumpCond", 127 | Instruction::Jump => "Jump", 128 | Instruction::JumpCondNeg => "JumpCondNeg", 129 | Instruction::BytecodeFuncCallback => "BytecodeFuncCallback", 130 | Instruction::PropertySet => "PropertySet", 131 | Instruction::Try => "Try", 132 | Instruction::Throw => "Throw", 133 | 134 | Instruction::CompEqual => "CompEqual", 135 | Instruction::CompNotEqual => "CompNotEqual", 136 | Instruction::CompStrictEqual => "CompStrictEqual", 137 | Instruction::CompStrictNotEqual => "CompStrictNotEqual", 138 | Instruction::CompLessThan => "CompLessThan", 139 | Instruction::CompGreaterThan => "CompGreaterThan", 140 | Instruction::CompLessThanEqual => "CompLessThanEqual", 141 | Instruction::CompGreaterThanEqual => "CompGreaterThanEqual", 142 | 143 | Instruction::Add => "Add", 144 | Instruction::Minus => "Minus", 145 | Instruction::Mul => "Mul", 146 | Instruction::Div => "Div", 147 | } 148 | } 149 | } 150 | 151 | 152 | #[derive(Debug, PartialEq, Clone)] 153 | pub struct BytecodeAddrToken { 154 | pub ident: String 155 | } 156 | 157 | impl ToBytes for BytecodeAddrToken { 158 | fn to_bytes(&self) -> Vec { 159 | vec![0; 4] 160 | } 161 | 162 | fn length_in_bytes(&self) -> usize { 163 | 4 164 | } 165 | } 166 | 167 | #[derive(Debug, PartialEq, Clone)] 168 | pub struct LabelAddrToken { 169 | pub label: Label 170 | } 171 | 172 | impl ToBytes for LabelAddrToken { 173 | fn to_bytes(&self) -> Vec { 174 | vec![0; 4] 175 | } 176 | 177 | fn length_in_bytes(&self) -> usize { 178 | 4 179 | } 180 | } 181 | 182 | #[derive(Debug, PartialEq, Clone)] 183 | pub struct FunctionArguments { 184 | pub args: Vec 185 | } 186 | 187 | impl ToBytes for FunctionArguments { 188 | fn to_bytes(&self) -> Vec { 189 | vec![0; self.args.len()] 190 | } 191 | 192 | fn length_in_bytes(&self) -> usize { 193 | 1 + 2*self.args.len() 194 | } 195 | } 196 | 197 | /// Represents all literal types of JavaScript 198 | /// 199 | /// ``var a = 1.5;`` => ``a => BytecodeLiteral::FloatNum(1.5)`` 200 | /// 201 | /// ``var b = 100;`` => ``b => BytecodeLiteral::IntNumber(100)`` 202 | /// 203 | /// # Note 204 | /// JavaScript regex literals are not yet supported. 205 | #[derive(Clone, Debug, PartialEq)] 206 | pub enum BytecodeLiteral 207 | { 208 | Null, 209 | String(String), 210 | FloatNum(f64), 211 | IntNumber(i64), 212 | Bool(bool), 213 | // RegEx(ressa::expr::RegEx) 214 | } 215 | 216 | impl BytecodeLiteral { 217 | pub fn from_lit(lit: Literal) -> CompilerResult { 218 | match lit { 219 | Literal::Null => Ok(BytecodeLiteral::Null), 220 | Literal::String(string) => Ok({ 221 | if string.len() > 0 { 222 | BytecodeLiteral::String(string[1..string.len()-1].to_string()) 223 | } else { 224 | BytecodeLiteral::String(string) 225 | } 226 | }), 227 | Literal::Number(num_string) => { 228 | if let Ok(dec_num) = num_string.parse::() { 229 | Ok(BytecodeLiteral::IntNumber(dec_num)) 230 | } else if let Ok(float_num) = num_string.parse::() { 231 | Ok(BytecodeLiteral::FloatNum(float_num)) 232 | } else if num_string.len() > 2 { 233 | if &num_string[..2] == "0x" { 234 | if let Ok(hex_num) = i64::from_str_radix(&num_string[2..], 16) { 235 | Ok(BytecodeLiteral::IntNumber(hex_num)) 236 | } else { 237 | Err(CompilerError::Custom(format!("Failed to parse hex-numeric literal '{}'", num_string))) 238 | } 239 | } else if &num_string[..2] == "0o" { 240 | if let Ok(oct_num) = i64::from_str_radix(&num_string[2..], 8) { 241 | Ok(BytecodeLiteral::IntNumber(oct_num)) 242 | } else { 243 | Err(CompilerError::Custom(format!("Failed to parse oct-numeric literal '{}'", num_string))) 244 | } 245 | } else if &num_string[..2] == "0b" { 246 | if let Ok(oct_num) = i64::from_str_radix(&num_string[2..], 2) { 247 | Ok(BytecodeLiteral::IntNumber(oct_num)) 248 | } else { 249 | Err(CompilerError::Custom(format!("Failed to parse bin-numeric literal '{}'", num_string))) 250 | } 251 | } else { 252 | Err(CompilerError::Custom(format!("Failed to parse numeric literal '{}'", num_string))) 253 | } 254 | } else { 255 | Err(CompilerError::Custom(format!("Failed to parse numeric literal '{}'", num_string))) 256 | } 257 | }, 258 | Literal::Boolean(b) => Ok(BytecodeLiteral::Bool(b)), 259 | Literal::RegEx(_) | 260 | Literal::Template(_) => Err(CompilerError::are_unsupported("regex and template literals")) 261 | } 262 | } 263 | } 264 | 265 | impl std::fmt::Display for BytecodeLiteral { 266 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 267 | match self { 268 | BytecodeLiteral::Null => write!(f, "Null"), 269 | BytecodeLiteral::String(string) => write!(f, "\"{}\"", string), 270 | BytecodeLiteral::FloatNum(float) => write!(f, "Float(){})", float), 271 | BytecodeLiteral::IntNumber(signed_int) => write!(f, "SignedInt({})", signed_int), 272 | BytecodeLiteral::Bool(bool) => write!(f, "Bool({})", bool), 273 | } 274 | } 275 | } 276 | 277 | /// Represents variants of bytecode operands 278 | /// 279 | /// There are two types of operands. Regular operands(numbers, strings or registers) and token operands. 280 | /// Token operands are only used by the compiler to express a dependency that cannot be calculated 281 | /// at the point of declaration. For example, the target of a jump is an address. However, an address 282 | /// can only be calculated when the entire bytecode is known. Thus, all jump instructions contain a token 283 | /// operand [BranchAddr](enum.Operand.html#Operand::FunctionAddr) which holds the target [label](type.Label.html) 284 | /// of the jump. After the compilation of this, these tokens are then replaced. 285 | /// Thus, token operands cannot be part of a final bytecode. 286 | #[derive(Debug, PartialEq, Clone)] 287 | pub enum Operand 288 | { 289 | String(String), 290 | FloatNum(f64), 291 | LongNum(i32), 292 | ShortNum(u8), 293 | Reg(u8), 294 | RegistersArray(Vec), 295 | 296 | FunctionAddr(BytecodeAddrToken), 297 | BranchAddr(LabelAddrToken), 298 | FunctionArguments(FunctionArguments), 299 | BytecodeEnd 300 | } 301 | 302 | impl Operand { 303 | pub fn from_literal(literal: BytecodeLiteral) -> CompilerResult { 304 | match literal { 305 | BytecodeLiteral::Null => Ok(Operand::Reg(253)), //TODO: Register of predefined void 0, 306 | BytecodeLiteral::String(string) => Ok(Operand::String(string)), 307 | BytecodeLiteral::FloatNum(float) => Ok(Operand::FloatNum(float)), 308 | BytecodeLiteral::IntNumber(int) => { 309 | if int <= 255 && int >= 0 { 310 | Ok(Operand::ShortNum(int as u8)) 311 | } else if int <= std::i32::MAX.into() && int >= std::i32::MIN.into() { 312 | Ok(Operand::LongNum(int as i32)) 313 | } else { 314 | Err(CompilerError::Custom( 315 | format!("Only integers from {} to {} are allowed. Consider using a float instead", 316 | std::i32::MIN, std::i32::MAX))) 317 | } 318 | }, 319 | BytecodeLiteral::Bool(bool) => Ok(Operand::ShortNum(bool as u8)), 320 | } 321 | } 322 | 323 | pub fn str(string: String) -> Self { 324 | Operand::String(string.to_string()) 325 | } 326 | 327 | pub fn function_addr(ident: String) -> Self { 328 | Operand::FunctionAddr(BytecodeAddrToken{ ident }) 329 | } 330 | 331 | pub fn branch_addr(label: Label) -> Self { 332 | Operand::BranchAddr(LabelAddrToken{ label }) 333 | } 334 | 335 | pub fn bc_func_args(arg_regs: Vec) -> Self { 336 | Operand::FunctionArguments(FunctionArguments{ args: arg_regs }) 337 | } 338 | 339 | pub fn is_worth_caching(&self) -> bool { 340 | match *self { 341 | Operand::String(_) | 342 | Operand::FloatNum(_) | 343 | Operand::LongNum(_) | 344 | Operand::RegistersArray(_) => true, 345 | _ => false 346 | } 347 | } 348 | 349 | fn encode_string(string: String) -> Vec { 350 | if string.len() > u16::max_value() as usize { 351 | panic!("The string '{}' is too long. Encoded string may only have 65536 charachters."); 352 | } 353 | 354 | let bytes = string.as_bytes(); 355 | 356 | let mut encoded = vec![(bytes.len() & 0xff00) as u8, (bytes.len() & 0xff) as u8]; 357 | encoded.extend_from_slice(bytes); 358 | encoded 359 | } 360 | 361 | fn encode_registers_array(regs: &[Register]) -> Vec { 362 | if regs.len() > u8::max_value() as usize { 363 | panic!("Too long registers array. Encoded byte arrays may only have 256 elements."); 364 | } 365 | 366 | let mut encoded = vec![regs.len() as u8]; 367 | encoded.extend_from_slice(regs); 368 | encoded 369 | } 370 | 371 | fn encode_num(num: u32) -> Vec { 372 | vec![(((num & 0xff000000) >> 24) as u8), 373 | (((num & 0x00ff0000) >> 16) as u8), 374 | (((num & 0x0000ff00) >> 8) as u8), 375 | (((num & 0x000000ff) >> 0) as u8)] 376 | } 377 | 378 | fn encode_long_num(num: u64) -> Vec { 379 | vec![(((num & 0xff000000_00000000) >> 56) as u8), 380 | (((num & 0x00ff0000_00000000) >> 48) as u8), 381 | (((num & 0x0000ff00_00000000) >> 40) as u8), 382 | (((num & 0x000000ff_00000000) >> 32) as u8), 383 | (((num & 0x00000000_ff000000) >> 24) as u8), 384 | (((num & 0x00000000_00ff0000) >> 16) as u8), 385 | (((num & 0x00000000_0000ff00) >> 8) as u8), 386 | (((num & 0x00000000_000000ff) >> 0) as u8)] 387 | } 388 | 389 | fn encode_float_num(num: f64) -> Vec { 390 | Operand::encode_long_num(num.to_bits()) 391 | } 392 | } 393 | 394 | impl ToBytes for Operand { 395 | fn to_bytes(&self) -> Vec { 396 | match self { 397 | Operand::String(string) => Operand::encode_string(string.to_string()), 398 | Operand::FloatNum(float_num) => Operand::encode_float_num(float_num.clone()), 399 | Operand::LongNum(long_num) => Operand::encode_num(long_num.clone() as u32), 400 | Operand::ShortNum(num) | 401 | Operand::Reg(num) => vec![*num], 402 | Operand::RegistersArray(regs) => Operand::encode_registers_array(®s), 403 | Operand::FunctionAddr(token) => token.to_bytes(), 404 | Operand::BranchAddr(token) => token.to_bytes(), 405 | Operand::FunctionArguments(args) => args.to_bytes(), 406 | Operand::BytecodeEnd => vec![0; 4] 407 | } 408 | } 409 | 410 | fn length_in_bytes(&self) -> usize { 411 | match self { 412 | Operand::String(string) => 2 + string.len(), 413 | Operand::FloatNum(_) => 8, 414 | Operand::LongNum(_) => 4, 415 | Operand::ShortNum(_) | 416 | Operand::Reg(_) => 1, 417 | Operand::RegistersArray(regs) => 1 + regs.len(), 418 | Operand::FunctionAddr(token) => token.length_in_bytes(), 419 | Operand::BranchAddr(token) => token.length_in_bytes(), 420 | Operand::FunctionArguments(args) => args.length_in_bytes(), 421 | Operand::BytecodeEnd => 4 422 | } 423 | } 424 | } 425 | 426 | impl std::fmt::Display for Operand { 427 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 428 | match self { 429 | Operand::String(string) => write!(f, "String(\"{}\")", string), 430 | Operand::FloatNum(float) => write!(f, "Float({})", float), 431 | Operand::LongNum(long_num) => write!(f, "LongNum({})", long_num), 432 | Operand::ShortNum(short_num) => write!(f, "ShortNum({})", short_num), 433 | Operand::Reg(reg) => write!(f, "Reg({})", reg), 434 | Operand::RegistersArray(reg_array) => write!(f, "RegArray({:?})", reg_array), 435 | 436 | Operand::FunctionAddr(bc_addr_token) => write!(f, "FunctionAddr({:?})", bc_addr_token), 437 | Operand::BranchAddr(label_addr_token) => write!(f, "BranchAddr({:?})", label_addr_token), 438 | Operand::FunctionArguments(args) => write!(f, "FunctionArguments({:?})", args), 439 | Operand::BytecodeEnd => write!(f, "BytecodeEnd"), 440 | } 441 | } 442 | } 443 | 444 | /// Contains an instruction and its operands 445 | /// 446 | /// Every operation consists of one [instruction](enum.Instruction.html) and zero or more [operands](enum.Operand.html). 447 | /// 448 | ///``` 449 | /// use jsyc_compiler::{Operation, Instruction, Operand}; 450 | /// 451 | /// let cmd = Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(100)]); 452 | ///``` 453 | #[derive(Debug, PartialEq, Clone)] 454 | pub struct Operation 455 | { 456 | pub instruction: Instruction, 457 | pub operands: Vec 458 | } 459 | 460 | impl Operation { 461 | pub fn new(instruction: Instruction, operands: Vec) -> Self { 462 | Operation { 463 | instruction, 464 | operands 465 | } 466 | } 467 | } 468 | 469 | impl std::fmt::Display for Operation { 470 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 471 | write!(f, "{}", self.instruction.to_str())?; 472 | for operand in self.operands.iter() { 473 | write!(f, " {}", operand)?; 474 | } 475 | Ok(()) 476 | } 477 | } 478 | 479 | impl ToBytes for Operation { 480 | fn to_bytes(&self) -> Vec { 481 | let mut line = vec![self.instruction.to_byte()]; 482 | line.append(&mut self.operands.iter().map(|operand| operand.to_bytes()).flatten().collect::>()); 483 | line 484 | } 485 | 486 | fn length_in_bytes(&self) -> usize { 487 | 1 + self.operands.iter().fold(0, |acc, x| acc + x.length_in_bytes()) 488 | } 489 | } 490 | 491 | 492 | #[derive(Debug, PartialEq, Clone)] 493 | pub enum BytecodeElement 494 | { 495 | Operation(Operation), 496 | Label(Label) 497 | } 498 | 499 | impl ToBytes for BytecodeElement { 500 | fn to_bytes(&self) -> Vec { 501 | match self { 502 | BytecodeElement::Operation(cmd) => cmd.to_bytes(), 503 | BytecodeElement::Label(_) => vec![] 504 | } 505 | } 506 | 507 | fn length_in_bytes(&self) -> usize { 508 | match self { 509 | BytecodeElement::Operation(cmd) => cmd.length_in_bytes(), 510 | BytecodeElement::Label(_) => 0 511 | } 512 | } 513 | } 514 | 515 | /// Represents the bytecode produced by the [compiler](struct.BytecodeCompiler.html). 516 | /// 517 | /// Bytecode is a wrapper for a list of [bytecode elements](enum.BytecodeElement.html). It offers 518 | /// an API to extend it by other [bytecode](struct.Bytecode.html), [commands](struct.Operation.html) or [labels](type.Label.html). 519 | /// ``` 520 | /// use jsyc_compiler::{Bytecode, Operation, Instruction, Operand}; 521 | /// 522 | /// let bytecode = Bytecode::new() 523 | /// .add(Operation::new(Instruction::LoadNum, 524 | /// vec![Operand::Reg(10), Operand::ShortNum(10)])) 525 | /// .add(Operation::new(Instruction::Add, 526 | /// vec![Operand::Reg(10), Operand::Reg(9)])); 527 | /// ``` 528 | #[derive(Debug, PartialEq, Clone)] 529 | pub struct Bytecode { 530 | pub elements: Vec, 531 | } 532 | 533 | impl std::fmt::Display for Bytecode { 534 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 535 | for element in self.elements.iter() { 536 | match element { 537 | BytecodeElement::Label(label) => { write!(f, "\nlabel_{}:\n", label)?; } 538 | BytecodeElement::Operation(cmd) => { 539 | 540 | write!(f, "{}\n", cmd)?; 541 | 542 | if Instruction::ReturnBytecodeFunc == cmd.instruction { 543 | write!(f, "\n\n")?; 544 | } 545 | 546 | } 547 | } 548 | } 549 | Ok(()) 550 | } 551 | } 552 | 553 | impl Bytecode { 554 | pub fn new() -> Self { 555 | Bytecode { 556 | elements: vec![] 557 | } 558 | } 559 | 560 | pub fn add(mut self, command: Operation) -> Self { 561 | self.elements.push(BytecodeElement::Operation(command)); 562 | self 563 | } 564 | 565 | /// Appends a [label](type.Label.html) as [bytecode element](enum.BytecodeElement.html). 566 | pub fn add_label(mut self, label: Label) -> Self { 567 | self.elements.push(BytecodeElement::Label(label)); 568 | self 569 | } 570 | 571 | /// Appends another bytecode onto this bytecode. 572 | pub fn add_bytecode(mut self, mut other: Bytecode) -> Self { 573 | self.elements.append(&mut other.elements); 574 | self 575 | } 576 | 577 | /// Returns the base64-encoded bytecode as string. 578 | pub fn encode_base64(&self) -> String { 579 | base64::encode(&self.to_bytes()) 580 | } 581 | 582 | /// Checks whether the last element is a [return instruction](enum.Instruction.html#Instruction::ReturnBytecodeFunc). 583 | pub fn last_op_is_return(&self) -> bool { 584 | match self.elements.last() { 585 | Some(last_element) => match last_element { 586 | BytecodeElement::Operation(cmd) => (cmd.instruction == Instruction::ReturnBytecodeFunc), 587 | _ => false 588 | }, 589 | None => false 590 | } 591 | } 592 | 593 | /// Returns an iterator over all [commands](struct.Operation.html) in the bytecode. 594 | pub fn commands_iter_mut(&mut self) -> impl std::iter::Iterator { 595 | self.elements.iter_mut().filter_map(|element| match element { 596 | BytecodeElement::Operation(cmd) => Some(cmd), 597 | BytecodeElement::Label(_) => None 598 | }) 599 | } 600 | } 601 | 602 | impl FromIterator for Bytecode { 603 | fn from_iter>(iter: I) -> Self { 604 | Bytecode { 605 | elements: iter.into_iter().flat_map(|bc| bc.elements).collect() 606 | } 607 | } 608 | } 609 | 610 | impl ToBytes for Bytecode { 611 | fn to_bytes(&self) -> Vec { 612 | self.elements.iter().map(|element| element.to_bytes()).flatten().collect() 613 | } 614 | 615 | fn length_in_bytes(&self) -> usize { 616 | self.elements.iter().fold(0, |acc, element| acc + element.length_in_bytes()) 617 | } 618 | } 619 | 620 | 621 | #[test] 622 | fn test_instrution_to_byte() { 623 | assert_eq!(Instruction::Add.to_byte(), 100); 624 | } 625 | 626 | #[test] 627 | fn test_bytecode_literal_from_literal() { 628 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("0".into())).unwrap(), 629 | BytecodeLiteral::IntNumber(0)); 630 | 631 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("1".into())).unwrap(), 632 | BytecodeLiteral::IntNumber(1)); 633 | 634 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("0x10".into())).unwrap(), 635 | BytecodeLiteral::IntNumber(16)); 636 | 637 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("0b10".into())).unwrap(), 638 | BytecodeLiteral::IntNumber(2)); 639 | 640 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("0o10".into())).unwrap(), 641 | BytecodeLiteral::IntNumber(8)); 642 | 643 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("0.0".into())).unwrap(), 644 | BytecodeLiteral::FloatNum(0.0)); 645 | 646 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("1.1".into())).unwrap(), 647 | BytecodeLiteral::FloatNum(1.1)); 648 | 649 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number(".0".into())).unwrap(), 650 | BytecodeLiteral::FloatNum(0.0)); 651 | 652 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number(".1".into())).unwrap(), 653 | BytecodeLiteral::FloatNum(0.1)); 654 | 655 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("0.0e0".into())).unwrap(), 656 | BytecodeLiteral::FloatNum(0.0)); 657 | 658 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number("1.1e2".into())).unwrap(), 659 | BytecodeLiteral::FloatNum(110.0)); 660 | 661 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number(".1E0".into())).unwrap(), 662 | BytecodeLiteral::FloatNum(0.1)); 663 | 664 | assert_eq!(BytecodeLiteral::from_lit(Literal::Number(".1E2".into())).unwrap(), 665 | BytecodeLiteral::FloatNum(10.0)); 666 | } 667 | 668 | #[test] 669 | fn test_encode_string() { 670 | assert_eq!(Operand::String("Hello World".into()).to_bytes(), 671 | vec![0, 11, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]); 672 | } 673 | 674 | #[test] 675 | fn test_encode_registers_array() { 676 | assert_eq!(Operand::RegistersArray(vec![]).to_bytes(), 677 | vec![0]); 678 | assert_eq!(Operand::RegistersArray(vec![1, 2, 200]).to_bytes(), 679 | vec![3, 1, 2, 200]); 680 | } 681 | 682 | #[test] 683 | fn test_encode_long_num() { 684 | assert_eq!(Operand::LongNum(1_234_567_891).to_bytes(), 685 | vec![0x49, 0x96, 0x02, 0xD3]); 686 | 687 | assert_eq!(Operand::LongNum(-1_234_567_891 as i32).to_bytes(), 688 | vec![0xB6, 0x69, 0xFD, 0x2D]) 689 | } 690 | 691 | #[test] 692 | fn test_encode_float_num() { 693 | assert_eq!(Operand::FloatNum(0.12345).to_bytes(), 694 | vec![63, 191, 154, 107, 80, 176, 242, 124]); 695 | 696 | assert_eq!(Operand::FloatNum(0.5).to_bytes(), 697 | vec![0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 698 | 699 | assert_eq!(Operand::FloatNum(-1.1234).to_bytes(), 700 | vec![191, 241, 249, 114, 71, 69, 56, 239]) 701 | } 702 | 703 | #[test] 704 | fn test_command() { 705 | assert_eq!(Operation{ 706 | instruction: Instruction::Add, 707 | operands:vec![ 708 | Operand::Reg(150), 709 | Operand::Reg(151), 710 | ] 711 | }.to_bytes(), 712 | vec![100, 150, 151]); 713 | } 714 | 715 | #[test] 716 | fn test_bytecode_to_bytes() { 717 | assert_eq!(Bytecode::new().to_bytes().len(), 0); 718 | assert_eq!(Bytecode{ elements: vec![ 719 | BytecodeElement::Operation(Operation{ 720 | instruction: Instruction::LoadNum, 721 | operands: vec![ 722 | Operand::Reg(151), 723 | Operand::ShortNum(2), 724 | ] 725 | }), 726 | BytecodeElement::Operation(Operation{ 727 | instruction: Instruction::LoadNum, 728 | operands: vec![ 729 | Operand::Reg(150), 730 | Operand::ShortNum(3), 731 | ] 732 | }), 733 | BytecodeElement::Operation(Operation{ 734 | instruction: Instruction::Mul, 735 | operands: vec![ 736 | Operand::Reg(150), 737 | Operand::Reg(151), 738 | ] 739 | }), 740 | ] 741 | }.to_bytes(), vec![2, 151, 2, 2, 150, 3,101, 150, 151]); 742 | } 743 | 744 | #[test] 745 | fn test_last_op_is_return() { 746 | assert_eq!(Bytecode::new().last_op_is_return(), false); 747 | assert_eq!(Bytecode::new().add(Operation::new(Instruction::ReturnBytecodeFunc, vec![])).last_op_is_return(), true); 748 | assert_eq!(Bytecode::new() 749 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(0), Operand::Reg(1)])) 750 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![])).last_op_is_return(), true); 751 | assert_eq!(Bytecode::new().add( 752 | Operation::new(Instruction::Copy, vec![Operand::Reg(0), Operand::Reg(1)]) 753 | ).last_op_is_return(), false); 754 | } 755 | 756 | #[test] 757 | fn test_format_print() { 758 | let fmt = format!("{}", Bytecode::new() 759 | .add(Operation::new(Instruction::LoadFloatNum, vec![Operand::Reg(1), Operand::FloatNum(12.5)])) 760 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(2), Operand::String("String".into())])) 761 | .add(Operation::new(Instruction::LoadArray, vec![Operand::Reg(0), Operand::RegistersArray(vec![255, 1, 2])])) 762 | ); 763 | 764 | assert_eq!(fmt, "LoadFloatNum Reg(1) Float(12.5)\n\ 765 | LoadString Reg(2) String(\"String\")\n\ 766 | LoadArray Reg(0) RegArray([255, 1, 2])\n".to_string()); 767 | } 768 | -------------------------------------------------------------------------------- /compiler/tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate jsyc_compiler; 2 | 3 | use jsyc_compiler::*; 4 | use jshelper::{JSSourceCode}; 5 | 6 | macro_rules! reg { 7 | ($r:expr) => { Operand::Reg($r) }; 8 | } 9 | 10 | macro_rules! reg_arr { 11 | ( $($x:expr),* ) => { Operand::RegistersArray(vec![$($x),*]) }; 12 | } 13 | 14 | macro_rules! string { 15 | ($s:expr) => { Operand::String($s.into()) }; 16 | } 17 | 18 | // macro_rules! float_num { 19 | // ($f:expr) => { Operand::FloatNum($f) }; 20 | // } 21 | 22 | macro_rules! short_num { 23 | ($n:expr) => { Operand::ShortNum($n) }; 24 | } 25 | 26 | macro_rules! long_num { 27 | ($n:expr) => { Operand::LongNum($n) }; 28 | } 29 | 30 | macro_rules! addr { 31 | ($n:expr) => { Operand::LongNum($n) }; 32 | } 33 | 34 | macro_rules! op { 35 | ($instr:ident, $($operands:expr),*) => { Operation::new(Instruction::$instr, vec![$($operands),*]) }; 36 | } 37 | 38 | 39 | #[cfg(test)] 40 | fn run_test(js_code: &str, mut compiler: compiler::BytecodeCompiler, expected_bc: Bytecode) { 41 | let js_source = JSSourceCode::new(js_code.to_string()); 42 | 43 | assert_eq!(compiler.compile(&js_source).unwrap(), expected_bc); 44 | } 45 | 46 | #[cfg(test)] 47 | fn run_test_deps(js_code: &str, expected_decl_deps: &[&str], expected_bc: Bytecode) { 48 | let mut compiler = BytecodeCompiler::new(); 49 | let js_source = JSSourceCode::new(js_code.to_string()); 50 | 51 | assert_eq!(compiler.compile(&js_source).unwrap(), expected_bc); 52 | 53 | for (decl_dep, expected_decl_dep) in compiler.decl_dependencies().decls_decps.keys().zip(expected_decl_deps) { 54 | assert_eq!(&decl_dep.as_str(), expected_decl_dep); 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | fn check_is_unsupported_error(js_code: &str, mut compiler: compiler::BytecodeCompiler) { 60 | let js_source = JSSourceCode::new(js_code.to_string()); 61 | 62 | assert!(compiler.compile(&js_source).is_err()); 63 | let error = compiler.compile(&js_source).err().unwrap(); 64 | 65 | println!("{:?}", error); 66 | 67 | assert!(error.is_unsupported_feature()); 68 | } 69 | 70 | 71 | #[test] 72 | fn test_compiler_api() { 73 | let mut compiler = BytecodeCompiler::new(); 74 | let js_code = JSSourceCode::from_str("var a = 10"); 75 | 76 | let bytecode = compiler.compile(&js_code).unwrap(); 77 | assert_eq!(bytecode.encode_base64(), "AgAK"); 78 | } 79 | 80 | #[test] 81 | fn test_compile_empty_js() { 82 | run_test("", BytecodeCompiler::new(), Bytecode::new()); 83 | } 84 | 85 | #[test] 86 | fn test_compile_js_decls() { 87 | run_test("var a = 5;", BytecodeCompiler::new(), Bytecode::new() 88 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(5)])) 89 | ); 90 | run_test("var a = 5, b = 6;", BytecodeCompiler::new(), Bytecode::new() 91 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(5)])) 92 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(1), Operand::ShortNum(6)])) 93 | ); 94 | 95 | run_test("var s = \"Hello World\";", BytecodeCompiler::new(), Bytecode::new() 96 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(0), Operand::String("Hello World".into())])) 97 | ); 98 | 99 | // TODO: null !== undefined 100 | // run_test("var xxx = null;", BytecodeCompiler::new(), Bytecode::new() 101 | // .add(op!(LoadNum, reg!(0), reg!(253))) 102 | // ); 103 | 104 | run_test("function foo() {}", BytecodeCompiler::new(), Bytecode::new() 105 | .add(Operation::new(Instruction::Exit, vec![])) 106 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 107 | ); 108 | run_test("function foo(a) {}", BytecodeCompiler::new(), Bytecode::new() 109 | .add(Operation::new(Instruction::Exit, vec![])) 110 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 111 | ); 112 | run_test("function foo(a, b) {}", BytecodeCompiler::new(), Bytecode::new() 113 | .add(Operation::new(Instruction::Exit, vec![])) 114 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 115 | ); 116 | run_test("function foo(a) {return a;}", BytecodeCompiler::new(), Bytecode::new() 117 | .add(Operation::new(Instruction::Exit, vec![])) 118 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(0), Operand::RegistersArray(vec![])])) 119 | ); 120 | run_test("function foo(a, b) {a+=b; return a;}", BytecodeCompiler::new(), Bytecode::new() 121 | .add(Operation::new(Instruction::Exit, vec![])) 122 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(1)])) 123 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(0), Operand::RegistersArray(vec![])])) 124 | ); 125 | 126 | run_test_deps("var a = document.cookie;", &["document"], Bytecode::new() 127 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(2), Operand::String("cookie".into())])) 128 | .add(Operation::new(Instruction::PropAccess, vec![Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)])) 129 | ); 130 | 131 | check_is_unsupported_error("class C {}", BytecodeCompiler::new()); 132 | 133 | check_is_unsupported_error("import foo from \"bar.js;\"", BytecodeCompiler::new()); 134 | check_is_unsupported_error("export {foo}", BytecodeCompiler::new()); 135 | } 136 | 137 | #[test] 138 | fn test_bytecode_func_calls() { 139 | run_test("function test() {}; test();", BytecodeCompiler::new(), Bytecode::new() 140 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(8), Operand::Reg(202), Operand::RegistersArray(vec![])])) 141 | .add(Operation::new(Instruction::Exit, vec![])) 142 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 143 | ); 144 | 145 | run_test("function foo() {}; function bar() {}; foo();bar();", BytecodeCompiler::new(), Bytecode::new() 146 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(15), Operand::Reg(202), Operand::RegistersArray(vec![])])) 147 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(18), Operand::Reg(202), Operand::RegistersArray(vec![])])) 148 | .add(Operation::new(Instruction::Exit, vec![])) 149 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 150 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 151 | ); 152 | 153 | run_test("function foo() {var a = 5;}; function bar() {}; foo();bar();", BytecodeCompiler::new(), Bytecode::new() 154 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(15), Operand::Reg(202), Operand::RegistersArray(vec![])])) 155 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(21), Operand::Reg(202), Operand::RegistersArray(vec![])])) 156 | .add(Operation::new(Instruction::Exit, vec![])) 157 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(5)])) 158 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 159 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 160 | ); 161 | 162 | run_test("var a = 5; function foo() {a = 10;}; foo();", BytecodeCompiler::new(), Bytecode::new() 163 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(5)])) 164 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(11), Operand::Reg(202), Operand::RegistersArray(vec![])])) 165 | .add(Operation::new(Instruction::Exit, vec![])) 166 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(10)])) 167 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![0])])) 168 | ); 169 | 170 | run_test("function testy(a) {} testy(10);", BytecodeCompiler::new(), Bytecode::new() 171 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(10)])) 172 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(13), Operand::Reg(202), Operand::RegistersArray(vec![0, 0])])) 173 | .add(Operation::new(Instruction::Exit, vec![])) 174 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(253), Operand::RegistersArray(vec![])])) 175 | ); 176 | 177 | run_test("function testy(a) {return a;} testy(10);", BytecodeCompiler::new(), Bytecode::new() 178 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(10)])) 179 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(13), Operand::Reg(202), Operand::RegistersArray(vec![0, 0])])) 180 | .add(Operation::new(Instruction::Exit, vec![])) 181 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(0), Operand::RegistersArray(vec![])])) 182 | ); 183 | 184 | run_test("var x = 10; function testy(a) {return a;} testy(x);", BytecodeCompiler::new(), Bytecode::new() 185 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(10)])) 186 | .add(Operation::new(Instruction::CallBytecodeFunc, vec![Operand::LongNum(13), Operand::Reg(202), Operand::RegistersArray(vec![1, 0])])) 187 | .add(Operation::new(Instruction::Exit, vec![])) 188 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(1), Operand::RegistersArray(vec![])])) 189 | ); 190 | 191 | run_test_deps("function testy(a) {return a;}; var interval = setInterval(testy, 60);", &["setInterval"], Bytecode::new() 192 | .add(Operation::new(Instruction::BytecodeFuncCallback, vec![Operand::Reg(2), Operand::LongNum(19), Operand::RegistersArray(vec![0])])) 193 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(3), Operand::ShortNum(60)])) 194 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(0), Operand::Reg(1), 195 | Operand::Reg(253), Operand::RegistersArray(vec![2, 3])])) 196 | .add(Operation::new(Instruction::Exit, vec![])) 197 | .add(Operation::new(Instruction::ReturnBytecodeFunc, vec![Operand::Reg(0), Operand::RegistersArray(vec![])])) 198 | ); 199 | } 200 | 201 | #[test] 202 | fn test_jump_stmts() { 203 | run_test("var a = false; if(a){a+=a;}", BytecodeCompiler::new(), Bytecode::new() 204 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(0)])) 205 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(0), Operand::LongNum(13)])) 206 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(0)])) 207 | .add_label(0) 208 | ); 209 | 210 | run_test("var a = false; if(a){a+=a;}else{a+=2}", BytecodeCompiler::new(), Bytecode::new() 211 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(0)])) 212 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(0), Operand::LongNum(18)])) 213 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(0)])) 214 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(25)])) 215 | .add_label(0) 216 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(1), Operand::ShortNum(2)])) 217 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(1)])) 218 | .add_label(1) 219 | ); 220 | 221 | run_test("var a = true; while(a){a=false;}", BytecodeCompiler::new(), Bytecode::new() 222 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(1)])) 223 | .add_label(0) 224 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(0), Operand::LongNum(17)])) 225 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(0)])) 226 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(3)])) 227 | .add_label(1) 228 | ); 229 | 230 | run_test("var a = true; do{a=false;}while(a)", BytecodeCompiler::new(), Bytecode::new() 231 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(1)])) 232 | .add_label(0) 233 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(0)])) 234 | .add(Operation::new(Instruction::JumpCond, vec![Operand::Reg(0), Operand::LongNum(3)])) 235 | .add_label(1) 236 | ); 237 | 238 | run_test("var a = 10; for(var i = 0; i < 10; ++i){++a} --i;", BytecodeCompiler::new(), Bytecode::new() 239 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(10)])) 240 | // Init 241 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(1), Operand::Reg(255)])) 242 | .add_label(0) 243 | // Comp 244 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(3), Operand::ShortNum(10)])) 245 | .add(Operation::new(Instruction::CompLessThan, vec![Operand::Reg(2), Operand::Reg(1), Operand::Reg(3)])) 246 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(2), Operand::LongNum(32)])) 247 | // Body 248 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(254)])) 249 | // Update 250 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(1), Operand::Reg(1), Operand::Reg(254)])) 251 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(6)])) 252 | .add_label(1) 253 | // Check that i still exists 254 | .add(Operation::new(Instruction::Minus, vec![Operand::Reg(1), Operand::Reg(1), Operand::Reg(254)])) 255 | ); 256 | 257 | run_test("var a = 10; var i = 0; for(; i < 10; ++i){++a} --i;", BytecodeCompiler::new(), Bytecode::new() 258 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(10)])) 259 | // Init 260 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(1), Operand::Reg(255)])) 261 | .add_label(0) 262 | // Comp 263 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(3), Operand::ShortNum(10)])) 264 | .add(Operation::new(Instruction::CompLessThan, vec![Operand::Reg(2), Operand::Reg(1), Operand::Reg(3)])) 265 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(2), Operand::LongNum(32)])) 266 | // Body 267 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(254)])) 268 | // Update 269 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(1), Operand::Reg(1), Operand::Reg(254)])) 270 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(6)])) 271 | .add_label(1) 272 | // Check that i still exists 273 | .add(Operation::new(Instruction::Minus, vec![Operand::Reg(1), Operand::Reg(1), Operand::Reg(254)])) 274 | ); 275 | 276 | run_test("var a = 10; var i = 0; for(;;){++a} --i;", BytecodeCompiler::new(), Bytecode::new() 277 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(10)])) 278 | // Init 279 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(1), Operand::Reg(255)])) 280 | .add_label(0) 281 | // Body 282 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(254)])) 283 | // Update 284 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(6)])) 285 | .add_label(1) 286 | // Check that i still exists 287 | .add(Operation::new(Instruction::Minus, vec![Operand::Reg(1), Operand::Reg(1), Operand::Reg(254)])) 288 | ); 289 | 290 | run_test("var i = 0; for(;;){++i}", BytecodeCompiler::new(), Bytecode::new() 291 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(0), Operand::Reg(255)])) 292 | // Init 293 | .add_label(0) 294 | // Body 295 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(254)])) 296 | // Update 297 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(3)])) 298 | .add_label(1) 299 | ); 300 | 301 | let break_testcase_bytecode = Bytecode::new() 302 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(1)])) 303 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(1), Operand::ShortNum(1)])) 304 | .add_label(0) 305 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(1), Operand::LongNum(28)])) 306 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(0), Operand::LongNum(23)])) 307 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(28)])) 308 | .add_label(2) // If block end label 309 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(6)])) 310 | .add_label(1); // while block end label 311 | 312 | // Test 'break' 313 | run_test("var b = true; while(true) { if(b) {break;} }", BytecodeCompiler::new(), 314 | break_testcase_bytecode.clone()); 315 | 316 | // Test labeled 'break', should be equal to last testcase 317 | run_test("var b = true; foo: while(true) { if(b) {break;} }", BytecodeCompiler::new(), 318 | break_testcase_bytecode); 319 | 320 | // Test 'continue' 321 | run_test("var b = true; while(true) { if(b) {continue;} }", BytecodeCompiler::new(), Bytecode::new() 322 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(1)])) 323 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(1), Operand::ShortNum(1)])) 324 | .add_label(0) 325 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(1), Operand::LongNum(28)])) 326 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(0), Operand::LongNum(23)])) 327 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(6)])) 328 | .add_label(2) // If block end label 329 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(6)])) 330 | .add_label(1) // while block end label 331 | ); 332 | 333 | // Test labeled 'continue' 334 | run_test("var b = true; foo: while(true) { var x = 0; for(;;) { if(b) {continue;} } }", BytecodeCompiler::new(), Bytecode::new() 335 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(1)])) 336 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(1), Operand::ShortNum(1)])) 337 | .add_label(0) 338 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(1), Operand::LongNum(36)])) 339 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(2), Operand::Reg(255)])) 340 | .add_label(2) 341 | .add(Operation::new(Instruction::JumpCondNeg, vec![Operand::Reg(0), Operand::LongNum(26)])) 342 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(15)])) 343 | .add_label(4) // If block end label 344 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(15)])) 345 | .add_label(3) 346 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(6)])) 347 | .add_label(1) // while block end label 348 | ); 349 | 350 | run_test("outer: for(;;){ for(;;) {break outer;} }", BytecodeCompiler::new(), Bytecode::new() 351 | .add_label(0) 352 | .add_label(2) 353 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(10)])) // break jump 354 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(0)])) 355 | .add_label(3) 356 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(0)])) 357 | .add_label(1) 358 | ); 359 | } 360 | 361 | #[test] 362 | fn test_assigmnet_expr() { 363 | let mut compiler = BytecodeCompiler::new(); 364 | assert!(compiler.add_var_decl("a".into()).is_ok()); 365 | assert!(compiler.add_var_decl("b".into()).is_ok()); 366 | 367 | run_test("a+=b;", compiler.clone(), Bytecode::new() 368 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(1)])) 369 | ); 370 | 371 | run_test("a*=b;", compiler.clone(), Bytecode::new() 372 | .add(Operation::new(Instruction::Mul, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(1)])) 373 | ); 374 | } 375 | 376 | #[test] 377 | fn test_member_expr() { 378 | let mut compiler = BytecodeCompiler::new(); 379 | assert!(compiler.add_var_decl("document".into()).is_ok()); 380 | 381 | run_test("var t = document.test", compiler.clone(), Bytecode::new() 382 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(2), Operand::String("test".into())])) 383 | .add(Operation::new(Instruction::PropAccess, vec![Operand::Reg(1), Operand::Reg(0), Operand::Reg(2)]))); 384 | 385 | run_test("var t = document.test; var a = document.test", compiler.clone(), Bytecode::new() 386 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(2), Operand::String("test".into())])) 387 | .add(Operation::new(Instruction::PropAccess, vec![Operand::Reg(1), Operand::Reg(0), Operand::Reg(2)])) 388 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(4), Operand::String("test".into())])) 389 | .add(Operation::new(Instruction::PropAccess, vec![Operand::Reg(3), Operand::Reg(0), Operand::Reg(4)]))); 390 | 391 | // Assignment expression 'equal' 392 | let mut assignments_compiler = BytecodeCompiler::new(); 393 | assert!(assignments_compiler.add_var_decl("test".into()).is_ok()); 394 | assert!(assignments_compiler.add_var_decl("foo".into()).is_ok()); 395 | 396 | run_test("test = 0;", assignments_compiler.clone(), Bytecode::new() 397 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(0), Operand::ShortNum(0)]))); 398 | run_test("test = foo;", assignments_compiler.clone(), Bytecode::new() 399 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(0), Operand::Reg(1)]))); 400 | } 401 | 402 | #[test] 403 | fn test_cond_expr() { 404 | let mut compiler = BytecodeCompiler::new(); 405 | compiler.add_var_decl("test".into()).unwrap(); 406 | compiler.add_var_decl("a".into()).unwrap(); 407 | compiler.add_var_decl("b".into()).unwrap(); 408 | 409 | run_test("var result = (test > 0) ? a : b;", compiler, Bytecode::new() 410 | .add(Operation::new(Instruction::CompGreaterThan, vec![Operand::Reg(4), Operand::Reg(0), Operand::Reg(255)])) 411 | .add(Operation::new(Instruction::JumpCond, vec![Operand::Reg(4), Operand::LongNum(18)])) 412 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(3), Operand::Reg(1)])) 413 | .add(Operation::new(Instruction::Jump, vec![Operand::LongNum(21)])) 414 | .add_label(0) 415 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(3), Operand::Reg(2)])) 416 | .add_label(1) 417 | ); 418 | } 419 | 420 | #[test] 421 | fn test_unary_expr() { 422 | run_test("var a = void 0", BytecodeCompiler::new(), Bytecode::new() 423 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(0), Operand::Reg(253)])) 424 | ); 425 | 426 | run_test("var a = 0; ++a;", BytecodeCompiler::new(), Bytecode::new() 427 | .add(Operation::new(Instruction::Copy, vec![Operand::Reg(0), Operand::Reg(255)])) 428 | .add(Operation::new(Instruction::Add, vec![Operand::Reg(0), Operand::Reg(0), Operand::Reg(254)])) 429 | ); 430 | 431 | // Suffix update expressions 432 | check_is_unsupported_error("a++;", BytecodeCompiler::new()); 433 | } 434 | 435 | #[test] 436 | fn test_array_expr() { 437 | run_test("var a = [0, 12.5, \"String\"]", BytecodeCompiler::new(), Bytecode::new() 438 | .add(Operation::new(Instruction::LoadFloatNum, vec![Operand::Reg(1), Operand::FloatNum(12.5)])) 439 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(2), Operand::String("String".into())])) 440 | .add(Operation::new(Instruction::LoadArray, vec![Operand::Reg(0), Operand::RegistersArray(vec![255, 1, 2])])) 441 | ); 442 | } 443 | 444 | #[test] 445 | fn test_compile_js_func_call() { 446 | let mut compiler = BytecodeCompiler::new(); 447 | assert!(compiler.add_var_decl("test".into()).is_ok()); 448 | 449 | run_test("test();", compiler.clone(), Bytecode::new() 450 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(202), Operand::Reg(0), 451 | Operand::Reg(253), Operand::RegistersArray(vec![])])) 452 | ); 453 | 454 | run_test("test(1);", compiler.clone(), Bytecode::new() 455 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(202), Operand::Reg(0), 456 | Operand::Reg(253),Operand::RegistersArray(vec![254])])) 457 | ); 458 | 459 | run_test("test(10);test(10);", compiler.clone(), Bytecode::new() 460 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(1), Operand::ShortNum(10)])) 461 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(202), Operand::Reg(0), 462 | Operand::Reg(253),Operand::RegistersArray(vec![1])])) 463 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(2), Operand::ShortNum(10)])) 464 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(202), Operand::Reg(0), 465 | Operand::Reg(253),Operand::RegistersArray(vec![2])])) 466 | ); 467 | 468 | run_test("test(1, 20);", compiler.clone(), Bytecode::new() 469 | .add(Operation::new(Instruction::LoadNum, vec![Operand::Reg(1), Operand::ShortNum(20)])) 470 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(202), Operand::Reg(0), 471 | Operand::Reg(253),Operand::RegistersArray(vec![254, 1])])) 472 | ); 473 | 474 | run_test("var a = test(1);", compiler.clone(), Bytecode::new() 475 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(1), Operand::Reg(0), 476 | Operand::Reg(253),Operand::RegistersArray(vec![254])])) 477 | ); 478 | 479 | 480 | 481 | let mut compiler_doc = BytecodeCompiler::new(); 482 | assert!(compiler_doc.add_var_decl("document".into()).is_ok()); 483 | // assert!(compiler1.add_var_decl("document.test".into()).is_ok()); 484 | 485 | run_test("document.test();", compiler_doc, Bytecode::new() 486 | .add(Operation::new(Instruction::LoadString, vec![Operand::Reg(2), Operand::String("test".into())])) 487 | .add(Operation::new(Instruction::PropAccess, vec![Operand::Reg(1), Operand::Reg(0), Operand::Reg(2)])) 488 | .add(Operation::new(Instruction::CallFunc, vec![Operand::Reg(202), Operand::Reg(1), Operand::Reg(0), Operand::RegistersArray(vec![])])) 489 | ); 490 | } 491 | 492 | #[test] 493 | fn test_try_throw() { 494 | // let mut compiler_with_json = BytecodeCompiler::new(); 495 | // assert!(compiler_with_json.add_var_decl("console".into()).is_ok()); 496 | // assert!(compiler_with_json.add_var_decl("JSON".into()).is_ok()); 497 | // assert!(compiler_with_json.add_var_decl("Object".into()).is_ok()); 498 | // 499 | // run_test("var j; try{ var s = '{}'; j = JSON.parse(s); }\ 500 | // catch(e){ console.log(e); }\ 501 | // finally{ j = Object.create(null); }", 502 | // compiler_with_json.clone(), 503 | // Bytecode::new() 504 | // .add(op!(Try, reg!(7), long_num!(41), long_num!(64))) 505 | // .add(op!(LoadString, reg!(4), string!("{}"))) 506 | // .add(op!(LoadString, reg!(6), string!("parse"))) 507 | // .add(op!(PropAccess, reg!(5), reg!(1), reg!(6))) 508 | // .add(op!(CallFunc, reg!(3), reg!(5), reg!(1), reg_arr!(4))) 509 | // .add(op!(LoadNum, reg!(200), long_num!(90))) 510 | // .add_label(0) 511 | // .add(op!(LoadString, reg!(9), string!("log"))) 512 | // .add(op!(PropAccess, reg!(8), reg!(0), reg!(9))) 513 | // .add(op!(CallFunc, reg!(202), reg!(8), reg!(0), reg_arr!(7))) 514 | // .add(op!(LoadNum, reg!(200), long_num!(90))) 515 | // .add_label(1) 516 | // .add(op!(LoadString, reg!(8), string!("create"))) 517 | // .add(op!(PropAccess, reg!(7), reg!(2), reg!(8))) 518 | // .add(op!(CallFunc, reg!(3), reg!(7), reg!(2), reg_arr!(9))) 519 | // .add(op!(LoadNum, reg!(200), long_num!(90))) 520 | // ); 521 | 522 | let mut compiler_with_json = BytecodeCompiler::new(); 523 | assert!(compiler_with_json.add_var_decl("console".into()).is_ok()); 524 | assert!(compiler_with_json.add_var_decl("JSON".into()).is_ok()); 525 | assert!(compiler_with_json.add_var_decl("empty_object".into()).is_ok()); 526 | 527 | run_test("var x; try{ var s = '{\"x\": 100}'; x = JSON.parse(s); }\ 528 | catch(e){ x = empty_object; }\ 529 | finally{ console.log(x); }", 530 | compiler_with_json.clone(), 531 | Bytecode::new() 532 | .add(op!(Try, reg!(7), long_num!(49), long_num!(58))) 533 | .add(op!(LoadString, reg!(4), string!("{\"x\": 100}"))) 534 | .add(op!(LoadString, reg!(6), string!("parse"))) 535 | .add(op!(PropAccess, reg!(5), reg!(1), reg!(6))) 536 | .add(op!(CallFunc, reg!(3), reg!(5), reg!(1), reg_arr!(4))) 537 | .add(op!(LoadLongNum, reg!(200), long_num!(81))) 538 | .add_label(0) 539 | .add(op!(Copy, reg!(3), reg!(2))) 540 | .add(op!(LoadLongNum, reg!(200), long_num!(81))) 541 | .add_label(1) 542 | .add(op!(LoadString, reg!(8), string!("log"))) 543 | .add(op!(PropAccess, reg!(7), reg!(0), reg!(8))) 544 | .add(op!(CallFunc, reg!(202), reg!(7), reg!(0), reg_arr!(3))) 545 | .add(op!(LoadLongNum, reg!(200), long_num!(81))) 546 | ); 547 | 548 | run_test("var x = 10; try { throw x*2; }", BytecodeCompiler::new(), Bytecode::new() 549 | .add(op!(LoadNum, reg!(0), short_num!(10))) 550 | .add(op!(Try, reg!(202), addr!(28), addr!(34))) 551 | .add(op!(LoadNum, reg!(2), short_num!(2))) 552 | .add(op!(Mul, reg!(1), reg!(0), reg!(2))) 553 | .add(op!(Throw, reg!(1))) 554 | .add(op!(LoadLongNum, reg!(200), long_num!(40))) 555 | .add_label(0) 556 | .add(op!(LoadLongNum, reg!(200), long_num!(40))) 557 | .add_label(1) 558 | .add(op!(LoadLongNum, reg!(200), long_num!(40))) 559 | ); 560 | } 561 | 562 | #[test] 563 | fn test_unsupported_exprs() { 564 | // Arrow functions 565 | check_is_unsupported_error("() => 0;", BytecodeCompiler::new()); 566 | // Arrow function placeholder 567 | check_is_unsupported_error("_ => 0;", BytecodeCompiler::new()); 568 | 569 | // Await, FIXME, this seems to be buggy in RESSA 570 | // check_is_unsupported_error("var x = await something();", BytecodeCompiler::new()); 571 | 572 | // Class expressions 573 | check_is_unsupported_error("var x = class X {};", BytecodeCompiler::new()); 574 | // Function expressions 575 | check_is_unsupported_error("var x = function X() {};", BytecodeCompiler::new()); 576 | 577 | // Object related stuff 578 | check_is_unsupported_error("var x = new X();", BytecodeCompiler::new()); 579 | check_is_unsupported_error("var x = {};", BytecodeCompiler::new()); 580 | check_is_unsupported_error("var x = this;", BytecodeCompiler::new()); 581 | 582 | // yield, FIXME 583 | // check_is_unsupported_error("var index; while (index < 2) { yield index++; }", BytecodeCompiler::new()); 584 | 585 | // Spread 586 | check_is_unsupported_error("const nums = [1, 2, 3]; func(...nums);", BytecodeCompiler::new()); 587 | 588 | // Seqeunce 589 | check_is_unsupported_error("var x = 10; var b = (x+=10, x==20);", BytecodeCompiler::new()); 590 | 591 | // TaggedTemplate, super, meta properties 592 | } 593 | 594 | #[test] 595 | fn test_unsupported_stmts() { 596 | check_is_unsupported_error("switch (x) { case 0: ;}", BytecodeCompiler::new()); 597 | 598 | check_is_unsupported_error("for (x in X) {}", BytecodeCompiler::new()); 599 | check_is_unsupported_error("for (x of X) {}", BytecodeCompiler::new()); 600 | 601 | check_is_unsupported_error("with(x) {}", BytecodeCompiler::new()); 602 | check_is_unsupported_error("debugger;", BytecodeCompiler::new()); 603 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "ansi_term" 13 | version = "0.11.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "ascii" 21 | version = "0.9.1" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "assert_cmd" 26 | version = "0.11.1" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | dependencies = [ 29 | "escargot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "predicates 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [[package]] 36 | name = "atty" 37 | version = "0.2.11" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | dependencies = [ 40 | "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 43 | ] 44 | 45 | [[package]] 46 | name = "autocfg" 47 | version = "0.1.4" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | 50 | [[package]] 51 | name = "backtrace" 52 | version = "0.3.20" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | dependencies = [ 55 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "backtrace-sys" 64 | version = "0.1.28" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", 69 | ] 70 | 71 | [[package]] 72 | name = "base64" 73 | version = "0.10.1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | dependencies = [ 76 | "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "bitflags" 81 | version = "1.0.4" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | 84 | [[package]] 85 | name = "byteorder" 86 | version = "1.3.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | 89 | [[package]] 90 | name = "cc" 91 | version = "1.0.37" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | 94 | [[package]] 95 | name = "cfg-if" 96 | version = "0.1.9" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | 99 | [[package]] 100 | name = "chrono" 101 | version = "0.4.6" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | dependencies = [ 104 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "clap" 111 | version = "2.33.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 121 | ] 122 | 123 | [[package]] 124 | name = "combine" 125 | version = "3.8.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | dependencies = [ 128 | "ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 133 | ] 134 | 135 | [[package]] 136 | name = "difference" 137 | version = "2.0.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | 140 | [[package]] 141 | name = "either" 142 | version = "1.5.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | 145 | [[package]] 146 | name = "env_logger" 147 | version = "0.6.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | dependencies = [ 150 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "escargot" 159 | version = "0.4.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 166 | ] 167 | 168 | [[package]] 169 | name = "heck" 170 | version = "0.3.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | dependencies = [ 173 | "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "humantime" 178 | version = "1.2.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | dependencies = [ 181 | "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 182 | ] 183 | 184 | [[package]] 185 | name = "itoa" 186 | version = "0.4.4" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | 189 | [[package]] 190 | name = "jsyc-compiler" 191 | version = "0.1.0" 192 | dependencies = [ 193 | "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 194 | "resast 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 195 | "ressa 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 196 | ] 197 | 198 | [[package]] 199 | name = "jsyc-compiler-interface" 200 | version = "0.1.0" 201 | dependencies = [ 202 | "assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "jsyc-compiler 0.1.0", 204 | "resast 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "ressa 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 206 | "resw 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "structopt 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 208 | ] 209 | 210 | [[package]] 211 | name = "lazy_static" 212 | version = "1.3.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | 215 | [[package]] 216 | name = "libc" 217 | version = "0.2.55" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | 220 | [[package]] 221 | name = "log" 222 | version = "0.4.6" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 226 | ] 227 | 228 | [[package]] 229 | name = "memchr" 230 | version = "2.2.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | 233 | [[package]] 234 | name = "num-integer" 235 | version = "0.1.41" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | dependencies = [ 238 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 240 | ] 241 | 242 | [[package]] 243 | name = "num-traits" 244 | version = "0.2.8" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | dependencies = [ 247 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 248 | ] 249 | 250 | [[package]] 251 | name = "numtoa" 252 | version = "0.1.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | 255 | [[package]] 256 | name = "predicates" 257 | version = "1.0.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | dependencies = [ 260 | "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 262 | ] 263 | 264 | [[package]] 265 | name = "predicates-core" 266 | version = "1.0.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | 269 | [[package]] 270 | name = "predicates-tree" 271 | version = "1.0.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | dependencies = [ 274 | "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 275 | "treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 276 | ] 277 | 278 | [[package]] 279 | name = "pretty_env_logger" 280 | version = "0.3.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | dependencies = [ 283 | "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 284 | "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 285 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 286 | ] 287 | 288 | [[package]] 289 | name = "proc-macro-error" 290 | version = "0.2.6" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | dependencies = [ 293 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 295 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 296 | ] 297 | 298 | [[package]] 299 | name = "proc-macro2" 300 | version = "0.4.30" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | dependencies = [ 303 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 304 | ] 305 | 306 | [[package]] 307 | name = "proc-macro2" 308 | version = "1.0.6" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | dependencies = [ 311 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 312 | ] 313 | 314 | [[package]] 315 | name = "quick-error" 316 | version = "1.2.2" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | 319 | [[package]] 320 | name = "quote" 321 | version = "0.6.12" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | dependencies = [ 324 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 325 | ] 326 | 327 | [[package]] 328 | name = "quote" 329 | version = "1.0.2" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | dependencies = [ 332 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 333 | ] 334 | 335 | [[package]] 336 | name = "redox_syscall" 337 | version = "0.1.54" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | 340 | [[package]] 341 | name = "redox_termios" 342 | version = "0.1.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 346 | ] 347 | 348 | [[package]] 349 | name = "regex" 350 | version = "1.1.6" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | dependencies = [ 353 | "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 354 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 355 | "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 356 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 358 | ] 359 | 360 | [[package]] 361 | name = "regex-syntax" 362 | version = "0.6.6" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | dependencies = [ 365 | "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 366 | ] 367 | 368 | [[package]] 369 | name = "resast" 370 | version = "0.2.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | 373 | [[package]] 374 | name = "ress" 375 | version = "0.6.3" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | dependencies = [ 378 | "combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 379 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 380 | "unic-ucd-ident 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 381 | ] 382 | 383 | [[package]] 384 | name = "ressa" 385 | version = "0.5.2" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | dependencies = [ 388 | "backtrace 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 391 | "resast 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "ress 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 393 | ] 394 | 395 | [[package]] 396 | name = "resw" 397 | version = "0.2.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | dependencies = [ 400 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 401 | "pretty_env_logger 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 402 | "resast 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 403 | "ress 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 404 | "ressa 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 405 | ] 406 | 407 | [[package]] 408 | name = "rustc-demangle" 409 | version = "0.1.14" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | 412 | [[package]] 413 | name = "ryu" 414 | version = "0.2.8" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | 417 | [[package]] 418 | name = "serde" 419 | version = "1.0.92" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | dependencies = [ 422 | "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 423 | ] 424 | 425 | [[package]] 426 | name = "serde_derive" 427 | version = "1.0.92" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | dependencies = [ 430 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 431 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 432 | "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", 433 | ] 434 | 435 | [[package]] 436 | name = "serde_json" 437 | version = "1.0.39" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | dependencies = [ 440 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 441 | "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 442 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 443 | ] 444 | 445 | [[package]] 446 | name = "strsim" 447 | version = "0.8.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | 450 | [[package]] 451 | name = "structopt" 452 | version = "0.3.3" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | dependencies = [ 455 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 456 | "structopt-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 457 | ] 458 | 459 | [[package]] 460 | name = "structopt-derive" 461 | version = "0.3.3" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | dependencies = [ 464 | "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 465 | "proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 466 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 467 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 468 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 469 | ] 470 | 471 | [[package]] 472 | name = "syn" 473 | version = "0.15.35" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | dependencies = [ 476 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 477 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 478 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 479 | ] 480 | 481 | [[package]] 482 | name = "syn" 483 | version = "1.0.5" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | dependencies = [ 486 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 487 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 488 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 489 | ] 490 | 491 | [[package]] 492 | name = "termcolor" 493 | version = "1.0.4" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | dependencies = [ 496 | "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 497 | ] 498 | 499 | [[package]] 500 | name = "termion" 501 | version = "1.5.2" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | dependencies = [ 504 | "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", 505 | "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 506 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 508 | ] 509 | 510 | [[package]] 511 | name = "textwrap" 512 | version = "0.11.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | dependencies = [ 515 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 516 | ] 517 | 518 | [[package]] 519 | name = "thread_local" 520 | version = "0.3.6" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | dependencies = [ 523 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 524 | ] 525 | 526 | [[package]] 527 | name = "time" 528 | version = "0.1.42" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | dependencies = [ 531 | "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", 532 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 533 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 534 | ] 535 | 536 | [[package]] 537 | name = "treeline" 538 | version = "0.1.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | 541 | [[package]] 542 | name = "ucd-util" 543 | version = "0.1.3" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | 546 | [[package]] 547 | name = "unic-char-property" 548 | version = "0.7.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | dependencies = [ 551 | "unic-char-range 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 552 | ] 553 | 554 | [[package]] 555 | name = "unic-char-range" 556 | version = "0.7.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | 559 | [[package]] 560 | name = "unic-common" 561 | version = "0.7.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | 564 | [[package]] 565 | name = "unic-ucd-ident" 566 | version = "0.7.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | dependencies = [ 569 | "unic-char-property 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 570 | "unic-char-range 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 571 | "unic-ucd-version 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 572 | ] 573 | 574 | [[package]] 575 | name = "unic-ucd-version" 576 | version = "0.7.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | dependencies = [ 579 | "unic-common 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 580 | ] 581 | 582 | [[package]] 583 | name = "unicode-segmentation" 584 | version = "1.3.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | 587 | [[package]] 588 | name = "unicode-width" 589 | version = "0.1.5" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | 592 | [[package]] 593 | name = "unicode-xid" 594 | version = "0.1.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | 597 | [[package]] 598 | name = "unicode-xid" 599 | version = "0.2.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | 602 | [[package]] 603 | name = "unreachable" 604 | version = "1.0.0" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | dependencies = [ 607 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 608 | ] 609 | 610 | [[package]] 611 | name = "utf8-ranges" 612 | version = "1.0.2" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | 615 | [[package]] 616 | name = "vec_map" 617 | version = "0.8.1" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | 620 | [[package]] 621 | name = "void" 622 | version = "1.0.2" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | 625 | [[package]] 626 | name = "winapi" 627 | version = "0.3.7" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | dependencies = [ 630 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 631 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 632 | ] 633 | 634 | [[package]] 635 | name = "winapi-i686-pc-windows-gnu" 636 | version = "0.4.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | 639 | [[package]] 640 | name = "winapi-util" 641 | version = "0.1.2" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | dependencies = [ 644 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 645 | ] 646 | 647 | [[package]] 648 | name = "winapi-x86_64-pc-windows-gnu" 649 | version = "0.4.0" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | 652 | [[package]] 653 | name = "wincolor" 654 | version = "1.0.1" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | dependencies = [ 657 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 658 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 659 | ] 660 | 661 | [metadata] 662 | "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" 663 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 664 | "checksum ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5fc969a8ce2c9c0c4b0429bb8431544f6658283c8326ba5ff8c762b75369335" 665 | "checksum assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2dc477793bd82ec39799b6f6b3df64938532fdf2ab0d49ef817eac65856a5a1e" 666 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 667 | "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" 668 | "checksum backtrace 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "45934a579eff9fd0ff637ac376a4bd134f47f8fc603f0b211d696b54d61e35f1" 669 | "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" 670 | "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 671 | "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 672 | "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" 673 | "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" 674 | "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 675 | "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" 676 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 677 | "checksum combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" 678 | "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 679 | "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" 680 | "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" 681 | "checksum escargot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ceb9adbf9874d5d028b5e4c5739d22b71988252b25c9c98fe7cf9738bee84597" 682 | "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 683 | "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" 684 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 685 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 686 | "checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880" 687 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 688 | "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 689 | "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" 690 | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 691 | "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 692 | "checksum predicates 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53e09015b0d3f5a0ec2d4428f7559bb7b3fff341b4e159fedd1d57fac8b939ff" 693 | "checksum predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" 694 | "checksum predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" 695 | "checksum pretty_env_logger 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8b3f4e0475def7d9c2e5de8e5a1306949849761e107b360d03e98eafaffd61" 696 | "checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097" 697 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 698 | "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 699 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 700 | "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" 701 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 702 | "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" 703 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 704 | "checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" 705 | "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" 706 | "checksum resast 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8a1817d9e740d2e358eb51045866a27dcbc2da5b4d700cb8e54bce480f16eb90" 707 | "checksum ress 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c18e721cbf55ba0d9581221c32c13671c140a8dd6e7ac97f57bf64d5f8c7f3a6" 708 | "checksum ressa 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5748fec0524ba9f1a8a75a39a49616b07cba388caaa60149dfab1bbce3ddbd5f" 709 | "checksum resw 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c94696aef255cad101a15affdb88d5e097eb996f2d0a15e12a43df4472f945d7" 710 | "checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288" 711 | "checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" 712 | "checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" 713 | "checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" 714 | "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" 715 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 716 | "checksum structopt 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4f66a4c0ddf7aee4677995697366de0749b0139057342eccbb609b12d0affc" 717 | "checksum structopt-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe0c13e476b4e21ff7f5c4ace3818b6d7bdc16897c31c73862471bc1663acae" 718 | "checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" 719 | "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" 720 | "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" 721 | "checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" 722 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 723 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 724 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 725 | "checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" 726 | "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 727 | "checksum unic-char-property 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce36d3f7ce754afdbccccf8ff0dd0134e50fb44aaae579f96218856e9e5dbd1e" 728 | "checksum unic-char-range 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9ab85fab42ad1b26cafc03bf891f69cb4d6e15f491030e89a0122197baa8ae8" 729 | "checksum unic-common 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff8d4a7ade929ef7d971e16ced21a8cd56a63869aa6032dfb8cb083cf7d077bf" 730 | "checksum unic-ucd-ident 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e197067fc6822c146043faaa5dc54a31e5b6c53e65f06407a95b78ad52758f6" 731 | "checksum unic-ucd-version 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1f5e6c6c53c2d0ece4a5964bc55fcff8602153063cb4fab20958ff32998ff6" 732 | "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" 733 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 734 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 735 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 736 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 737 | "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" 738 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 739 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 740 | "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 741 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 742 | "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" 743 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 744 | "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" 745 | --------------------------------------------------------------------------------