~tim/scheme-vm

04d738c7bb4afd67aef3edb2df04d940a20af0c4 — Tim Morgan 3 years ago d1eac91
Switch back to rust-peg and speed it up!

I was doing some stupid work repeatedly every time atom() was called.
Computing that work ahead-of-time puts rust-peg back on par with pest,
and I like rust-peg much better!
6 files changed, 106 insertions(+), 60 deletions(-)

M Cargo.toml
A build.rs
M src/atom.rs
M src/lib.rs
A src/lisp.rustpeg
M vm/atom.rb
M Cargo.toml => Cargo.toml +4 -2
@@ 2,6 2,7 @@
name = "scheme-vm"
version = "0.1.0"
authors = ["Tim Morgan <tim@timmorgan.org>"]
build = "build.rs"

[lib]
crate-type = ["dylib"]


@@ 10,5 11,6 @@ crate-type = ["dylib"]
libc = "0.2.37"
ruby-sys = "0.3.0"
lazy_static = "1.0.0"
pest = "^1.0"
pest_derive = "^1.0"

[build-dependencies]
peg = { git = "https://github.com/kevinmehall/rust-peg", branch = "master" }

A build.rs => build.rs +5 -0
@@ 0,0 1,5 @@
extern crate peg;

fn main() {
    peg::cargo_build("src/lisp.rustpeg");
}

M src/atom.rs => src/atom.rs +9 -7
@@ 1,11 1,13 @@
use rb;
use rb::{Value, RB_NIL};

pub fn atom(name: &str, filename: &str, offset: usize, line: Value, column: Value) -> Value {
    let name_str = rb::str_new(&name.to_string());
    let filename_str = rb::str_new(&filename.to_string());
    let offset_num = int2rbnum!(offset);
    let vm_class = rb::const_get("VM", &RB_NIL);
    let atom_class = rb::const_get("Atom", &vm_class);
    rb::class_new_instance(&atom_class, vec![name_str, filename_str, offset_num, line, column])
pub fn atom(name: &str, filename: &str, offset: usize, newlines: &Vec<usize>) -> Value {
    let lines_before: Vec<usize> = newlines.iter().take_while(|i| *i < &offset).map(|i| *i).collect();
    let line = int2rbnum!(lines_before.len() + 1);
    let column = int2rbnum!(offset - lines_before.last().unwrap_or(&0));
    let name = rb::str_new(&name.to_string());
    let filename = rb::str_new(&filename.to_string());
    let vm = rb::const_get("VM", &RB_NIL);
    let atom = rb::const_get("Atom", &vm);
    rb::class_new_instance(&atom, vec![name, filename, line, column])
}

M src/lib.rs => src/lib.rs +26 -48
@@ 2,9 2,6 @@ extern crate libc;
extern crate ruby_sys;
#[macro_use]
extern crate lazy_static;
extern crate pest;
#[macro_use]
extern crate pest_derive;

#[macro_use] mod rb;
use rb::{CallbackPtr, Value, RB_NIL};


@@ 12,57 9,38 @@ use rb::{CallbackPtr, Value, RB_NIL};
mod atom;
mod quotes;

use pest::Parser;
use atom::atom;
use quotes::QUOTES;

pub mod lisp {
    #[derive(Parser)]
    #[grammar = "lisp.pest"]
    pub struct LispParser;
}

use lisp::*;

fn build_ast(pair: pest::iterators::Pair<lisp::Rule>, filename: &str, newlines: &Vec<usize>) -> Option<Value> {
    match pair.as_rule() {
        lisp::Rule::program | lisp::Rule::simple_sexp | lisp::Rule::quoted_sexp | lisp::Rule::quoted_atom => {
            let mut array = rb::ary_new();
            for p in pair.into_inner() {
                match build_ast(p, filename, newlines) {
                    Some(ast) => array = rb::ary_push(array, ast),
                    None => {}
                }
            }
            Some(array)
        }
        lisp::Rule::simple_atom | lisp::Rule::delimited_identifier_inner => {
            let span = pair.into_span();
            let before: Vec<usize> = newlines.iter().take_while(|i| *i < &span.start()).map(|i| *i).collect();
            let line = int2rbnum!(before.len() + 1);
            let column = int2rbnum!(span.start() - before.last().unwrap_or(&0));
            Some(atom(span.as_str(), filename, span.start(), line, column))
        }
        lisp::Rule::string => Some(rb::str_new(pair.into_span().as_str())),
        lisp::Rule::quote => {
            let q = pair.into_span().as_str();
            Some(rb::str_new(&QUOTES.get(&q).unwrap().to_string()))
        }
        lisp::Rule::comment => None,
        _ => {
            println!("{:?} is unknown", pair.as_rule());
            unreachable!()
        }
    }
mod lisp {
    include!(concat!(env!("OUT_DIR"), "/lisp.rs"));
}

fn parse_native(rself: Value) -> Value {
    let code = rbstr2str!(&rb::ivar_get(&rself, "@code"));
    let filename = rbstr2str!(&rb::ivar_get(&rself, "@filename"));
    let newlines = code.match_indices("\n").map(|(i, s)| i).collect();
    let newlines: Vec<usize> = code.match_indices("\n").map(|(i, _s)| i).collect();
    rb::gc_disable();
    let pairs = LispParser::parse(lisp::Rule::program, code).unwrap_or_else(|e| panic!("{}", e));
    build_ast(pairs.into_iter().next().unwrap(), filename, &newlines).expect("error parsing")
    match lisp::program(&code, &filename, &newlines) {
        Ok(ast) => {
            rb::gc_enable();
            ast
        },
        Err(err) => {
            rb::gc_enable();
            //let expected = rb::vec2rbarr(
                //err.expected.iter().cloned().map(|e| rb::str_new(&e.to_string())).collect()
            //);
            println!("{}", err.line);
            println!("{}", err.column);
            println!("{:?}", err.expected);
            println!("{:?}", &code);
            println!("{:?}", &code[err.column..]);
            let c_parser = rb::const_get("Parser", &RB_NIL);
            let c_parse_error = rb::const_get("ParseError", &c_parser);
            let line = int2rbnum!(err.line);
            let error = rb::class_new_instance(&c_parse_error, vec![line]);
            rb::raise_instance(&error);
            RB_NIL
        }
    }
}

#[no_mangle]

A src/lisp.rustpeg => src/lisp.rustpeg +60 -0
@@ 0,0 1,60 @@
#![arguments(filename: &str, newlines: &Vec<usize>)]

use rb;
use rb::Value;
use atom::atom;
use quotes::QUOTES;

whitespace
	= [ \t\n]*

escape
	= "\\" .

string -> Option<Value>
	= s:$("\"" (escape / [^"])* "\"") { Some(rb::str_new(&s.to_string())) }

delimited_identifier -> &'input str
	= "|" i:$([^|]+) "|" {i}

simple_atom -> Value
	= p:#position a:(delimited_identifier / $([^\(\) \t\n\[\]\{\}\|"]+)) { atom(&a, &filename, p, newlines) }

quoted_atom -> Value
	= q:quote a:simple_atom { rb::vec2rbarr(vec![q, a]) }

atom -> Option<Value>
	= a:(quoted_atom / simple_atom) { Some(a) }

sexp -> Option<Value>
	= n:(quoted_sexp / simple_sexp) { Some(n) }

comment -> Option<Value>
	= (block_comment / line_comment / datum_comment) { None }

expression -> Option<Value>
	= string / comment / sexp / atom

quote -> Value
	= q:$("'" / ",@" / "," / "`") { rb::str_new(&QUOTES.get(&q).unwrap().to_string()) }

quoted_sexp -> Value
	= q:quote s:simple_sexp { rb::vec2rbarr(vec![q, s]) }

simple_sexp -> Value
	= "(" s:expressions ")" {s}

block_comment
	= "#|" (!"|#" .)* "|#"

line_comment
	= ";" [^\n]*

datum_comment
	= "#;" " "? (atom / sexp)

expressions -> Value
  = whitespace s:(expression ** whitespace) whitespace { rb::vec2rbarr(s.into_iter().filter_map(|i| i).collect()) }

pub program -> Value
	= expressions

M vm/atom.rb => vm/atom.rb +2 -3
@@ 2,10 2,9 @@ class VM
  class Atom < String
    attr_reader :filename, :offset, :line, :column

    def initialize(name, filename = nil, offset = nil, line = nil, column = nil)
    def initialize(name, filename = nil, line = nil, column = nil)
      super(name.to_s)
      @filename = filename
      @offset = offset
      @line = line
      @column = column
    end


@@ 22,7 21,7 @@ class VM

    def mangle(version)
      name = "##{self}.v#{version}"
      self.class.new(name, filename, offset, line, column)
      self.class.new(name, filename, line, column)
    end
  end
end