~tim/scheme-vm

9ad2519add6e15dc7b977b30a6fb12166c3d4b18 — Tim Morgan 4 years ago 82e799d
Return ruby object (ast) from rust parser
6 files changed, 119 insertions(+), 72 deletions(-)

M src/lib.rs
M src/lisp.rustpeg
M src/rb.rs
M src/tests.rs
D src/values.rs
M test.rb
M src/lib.rs => src/lib.rs +15 -10
@@ 1,27 1,32 @@
extern crate libc;
extern crate ruby_sys;

mod values;
mod tests;

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

mod tests;

mod lisp {
    include!(concat!(env!("OUT_DIR"), "/lisp.rs"));
}

fn is_ok(_rself: Value, program: Value) -> Value {
fn parse(_rself: Value, program: Value) -> Value {
    let program_str = rbstr2str!(&program);
    if lisp::program(&program_str).is_ok() {
        RB_TRUE
    } else {
        RB_FALSE
    match lisp::program(&program_str) {
        Ok(ast) => ast,
        Err(err) => {
            let expected = rb::vec2rbarr(
                err.expected.iter().cloned().map(|e| rb::str_new(&e.to_string())).collect()
            );
            // TODO: pass expected and other error info back in ruby exception
            rb::raise("foo".to_owned());
            RB_NIL
        }
    }
}

#[no_mangle]
pub extern fn init_parser() {
  let m_parser = rb::define_module("Parser");
  rb::define_singleton_method(&m_parser, "ok?", is_ok as CallbackPtr, 1);
  rb::define_singleton_method(&m_parser, "parse", parse as CallbackPtr, 1);
}

M src/lisp.rustpeg => src/lisp.rustpeg +17 -16
@@ 1,4 1,5 @@
use values::*;
use rb;
use rb::Value;

whitespace
	= [ \t\n]+


@@ 6,28 7,28 @@ whitespace
escape
	= "\\" .

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

atom -> Option<Box<Val>>
	= n:$([^\(\) \t\n\[\]\{\}"]+) { Some(Box::new(Val::Atom { name: n.to_string() })) }
atom -> Option<Value>
	= n:$([^\(\) \t\n\[\]\{\}"]+) { Some(rb::str_new(&n.to_string())) }

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

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

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

quote -> Box<Val>
	= n:$("'" / ",@" / "," / "`") { Box::new(Val::Atom { name: n.to_string() }) }
quote -> Value
	= n:$("'" / ",@" / "," / "`") { rb::str_new(&n.to_string()) }

quoted_sexp -> Box<Val>
	= q:quote s:simple_sexp { Box::new(Val::Arr { vals: vec![q, s] }) }
quoted_sexp -> Value
	= q:quote s:simple_sexp { rb::vec2rbarr(vec![q, s]) }

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

block_comment


@@ 39,8 40,8 @@ line_comment
datum_comment
	= "#;" " "? (atom / sexp)

expressions -> Box<Val>
	= whitespace* s:(expression ** whitespace) whitespace* { Box::new(Val::Arr { vals: s.into_iter().filter_map(|i| i).collect() }) }
expressions -> Value
  = whitespace* s:(expression ** whitespace) whitespace* { rb::vec2rbarr(s.into_iter().filter_map(|i| i).collect()) }

pub program -> Box<Val>
pub program -> Value
	= expressions

M src/rb.rs => src/rb.rs +58 -20
@@ 1,12 1,20 @@
// borrowed from https://github.com/ubcsanskrit/sanscript.rb (MIT)

use ruby_sys::class;
use ruby_sys::{class, array, string};
use ruby_sys::types::{c_char, c_long};
use ruby_sys::util::{rb_const_get};
use ruby_sys::vm::{rb_raise};
use ruby_sys::value::RubySpecialConsts::{Nil, True, False};
use ruby_sys::rb_cObject;

pub use ruby_sys::types::{CallbackPtr, Value};

pub const RB_NIL: Value = Value { value: Nil as usize };

#[allow(dead_code)]
pub const RB_TRUE: Value = Value { value: True as usize };

#[allow(dead_code)]
pub const RB_FALSE: Value = Value { value: False as usize };

//


@@ 14,49 22,79 @@ pub const RB_FALSE: Value = Value { value: False as usize };
//

macro_rules! str2cstr {
  ($s:expr) => { ::std::ffi::CString::new($s).unwrap() }
    ($s:expr) => { ::std::ffi::CString::new($s).unwrap() }
}

macro_rules! str2cstrp {
  ($s:expr) => { str2cstr!($s).as_ptr() }
    ($s:expr) => { str2cstr!($s).as_ptr() }
}

macro_rules! rbstr2cstrp {
  ($s:expr) => { ::ruby_sys::string::rb_string_value_cstr($s) }
    ($s:expr) => { ::ruby_sys::string::rb_string_value_cstr($s) }
}

macro_rules! rbstr2str {
  ($s:expr) => {
    unsafe { ::std::ffi::CStr::from_ptr(rbstr2cstrp!($s)).to_str().unwrap() }
  }
    ($s:expr) => {
        unsafe { ::std::ffi::CStr::from_ptr(rbstr2cstrp!($s)).to_str().unwrap() }
    }
}

#[macro_export]
macro_rules! str2rbid {
  ($s:expr) => { ::ruby_sys::util::rb_intern(str2cstrp!($s)) }
    ($s:expr) => { ::ruby_sys::util::rb_intern(str2cstrp!($s)) }
}

macro_rules! str2sym {
  ($s:expr) => {
    unsafe { ::ruby_sys::symbol::rb_id2sym(str2rbid!($s)) }
  }
    ($s:expr) => {
        unsafe { ::ruby_sys::symbol::rb_id2sym(str2rbid!($s)) }
    }
}

pub fn define_module(name: &str) -> Value {
  unsafe { class::rb_define_module(str2cstrp!(name)) }
    unsafe { class::rb_define_module(str2cstrp!(name)) }
}

pub fn define_module_under(parent: &Value, name: &str) -> Value {
  unsafe { class::rb_define_module_under(*parent, str2cstrp!(name)) }
//pub fn define_module_under(parent: &Value, name: &str) -> Value {
    //unsafe { class::rb_define_module_under(*parent, str2cstrp!(name)) }
//}

//pub fn define_method(module: &Value, name: &str, method: CallbackPtr, argc: i32) {
    //unsafe { class::rb_define_method(*module, str2cstrp!(name), method, argc) }
//}

pub fn define_singleton_method(module: &Value, name: &str, method: CallbackPtr, argc: i32) {
    unsafe { class::rb_define_singleton_method(*module, str2cstrp!(name), method, argc) }
}

pub fn define_method(module: &Value, name: &str, method: CallbackPtr, argc: i32) {
  unsafe { class::rb_define_method(*module, str2cstrp!(name), method, argc) }
//pub fn extend_object(object: &Value, module: &Value) {
  //unsafe { class::rb_extend_object(*object, *module) }
//}

pub fn ary_new() -> Value {
    unsafe { array::rb_ary_new() }
}

pub fn define_singleton_method(module: &Value, name: &str, method: CallbackPtr, argc: i32) {
  unsafe { class::rb_define_singleton_method(*module, str2cstrp!(name), method, argc) }
pub fn ary_push(array: &Value, item: &Value) -> Value {
    unsafe { array::rb_ary_push(*array, *item) }
}

pub fn str_new(string: &String) -> Value {
    let str = string.as_ptr() as *const c_char;
    let len = string.len() as c_long;
    unsafe { string::rb_str_new(str, len) }
}

pub fn vec2rbarr(vec: Vec<Value>) -> Value {
    let mut arr = ary_new();
    for item in vec {
        arr = ary_push(&arr, &item);
    }
    arr
}

pub fn extend_object(object: &Value, module: &Value) {
  unsafe { class::rb_extend_object(*object, *module) }
pub fn raise(err: String) {
    unsafe {
        let exception = rb_const_get(rb_cObject, str2rbid!("RuntimeError"));
        rb_raise(exception, str2cstrp!(err));
    }
}

M src/tests.rs => src/tests.rs +28 -19
@@ 1,28 1,37 @@
#[cfg(test)]
mod tests {
    use lisp;
    use values::*;
    use rb;
    use ruby_sys::vm::{ruby_init};
    use ruby_sys::fixnum::{rb_num2int};
    use ruby_sys::util::{rb_funcallv};

    #[test]
    fn program() {
		let program = lisp::program("
			; comment
			'foo
			'(1 2)
			'()
			,foo
			,(foo bar) #; (baz) #;6
			#| this is a
			   multi-line comment |#
			(print |space in identifier|)
			(if (< 1 2) #;(2 3)
				x ; another comment
				(foo (bar (baz \"this is a string\"))))
		");
        assert!(program.is_ok());
        match *program.unwrap() {
            Val::Arr { vals } => assert_eq!(7, vals.len()),
            _                 => panic!()
        unsafe { ruby_init() };
        let program = lisp::program("
            ; comment
            'foo
            '(1 2)
            '()
            ,foo
            ,(foo bar) #; (baz) #;6
            #| this is a
               multi-line comment |#
            (print |space in identifier|)
            (if (< 1 2) #;(2 3)
                x ; another comment
                (foo (bar (baz \"this is a string\"))))
        ");
        match program {
            Ok(value) => {
                let args = rb::ary_new();
                let size = unsafe {
                    rb_num2int(rb_funcallv(value, str2rbid!("size"), 0, &args))
                };
                assert_eq!(7, size);
            },
            Err(_) => panic!()
        }
    }
}

D src/values.rs => src/values.rs +0 -6
@@ 1,6 0,0 @@
pub enum Val {
    Str  { val: String },
    Atom { name: String },
    Arr  { vals: Vec<Box<Val>> },
    Cons { car: Box<Val>, cdr: Box<Val> }
}

M test.rb => test.rb +1 -1
@@ 4,4 4,4 @@ library = Fiddle::dlopen('target/release/libscheme_vm.dylib')
init_parser = Fiddle::Function.new(library['init_parser'], [], Fiddle::TYPE_VOID)
init_parser.call

p Parser.ok?(ARGV.first)
p Parser.parse(ARGV.first)