1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-11 13:55:55 +03:00

Rust: step0_repl and step1_read_print

This commit is contained in:
Joel Martin 2014-10-25 11:42:07 -05:00
parent f41866dbe9
commit abdd56ebc0
11 changed files with 416 additions and 33 deletions

14
.gitignore vendored
View File

@ -27,14 +27,6 @@ go/step*
go/mal
java/target/
java/dependency-reduced-pom.xml
rust/step0_repl
rust/step1_read_print
rust/step2_eval
rust/step3_env
rust/step4_if_fn_do
rust/step5_tco
rust/step6_file
rust/step7_quote
rust/step8_macros
rust/step9_try
rust/stepA_interop
rust/target/
rust/Cargo.lock
rust/.cargo

View File

@ -10,7 +10,7 @@ PYTHON = python
# Settings
#
IMPLS = bash c clojure cs go java js make mal perl php ps python ruby
IMPLS = bash c clojure cs go java js make mal perl php ps python ruby rust
step0 = step0_repl
step1 = step1_read_print
@ -60,6 +60,7 @@ php_STEP_TO_PROG = php/$($(1)).php
ps_STEP_TO_PROG = ps/$($(1)).ps
python_STEP_TO_PROG = python/$($(1)).py
ruby_STEP_TO_PROG = ruby/$($(1)).rb
rust_STEP_TO_PROG = rust/target/$($(1))
bash_RUNSTEP = bash ../$(2) $(3)
@ -76,6 +77,7 @@ php_RUNSTEP = php ../$(2) $(3)
ps_RUNSTEP = $(4)gs -q -I./ -dNODISPLAY -- ../$(2) $(3)$(4)
python_RUNSTEP = $(PYTHON) ../$(2) $(3)
ruby_RUNSTEP = ruby ../$(2) $(3)
rust_RUNSTEP = ../$(2) $(3)
# Extra options to pass to runtest.py
cs_TEST_OPTS = --redirect

View File

@ -3,7 +3,7 @@
## Description
Mal is an interpreter for a subset of the Clojure programming
language. Mal is implemented from scratch in 14 different languages:
language. Mal is implemented from scratch in 15 different languages:
* Bash shell
* C
@ -19,6 +19,7 @@ language. Mal is implemented from scratch in 14 different languages:
* Postscript
* Python
* Ruby
* Rust
Mal is also a learning tool. Each implentation of mal is separated
@ -179,6 +180,17 @@ cd ruby
ruby stepX_YYY.rb
```
### Rust (0.13)
The rust implementation of mal requires the rust compiler and build
tool (cargo) to build.
```
cd rust
cargo build
./target/stepX_YYY
```
## Running tests
The are nearly 400 generic Mal tests (for all implementations) in the

View File

@ -73,24 +73,18 @@ Ruby:
Future Implementations:
- Rust:
- http://doc.rust-lang.org/index.html
- http://doc.rust-lang.org/intro.html
- http://doc.rust-lang.org/guide.html
- http://rustbyexample.com/index.html
- http://www.rustforrubyists.com/book/index.html
- http://static.rust-lang.org/doc/0.9/complement-cheatsheet.html
- http://pzol.github.io/getting_rusty/
- release notes:
- https://github.com/mozilla/rust/wiki/Doc-detailed-release-notes
- this week in rust:
- http://cmr.github.io/
- readline:
- http://redbrain.co.uk/2013/11/09/rust-and-readline-c-ffi/
- http://www.reddit.com/r/rust/comments/1q9pqc/rust_cffi_and_readline/
- https://github.com/dbp/rustrepl
- hash-map:
- http://static.rust-lang.org/doc/master/std/hashmap/index.html
- http://static.rust-lang.org/doc/master/std/hashmap/struct.HashMap.html
- vector/list:
- http://static.rust-lang.org/doc/master/std/vec/index.html
- example code:
- https://github.com/dradtke/rust-dominion/blob/master/dominion/mod.rs
- http://blog.thiago.me/notes-about-rust-modules/
- http://doc.rust-lang.org/std/io/io
- https://github.com/shaleh/rust-readline/blob/master/src/lib.rs
- http://stackoverflow.com/questions/23942627/does-rust-0-10-have-a-rl-package
- http://blog.skylight.io/rust-means-never-having-to-close-a-socket/
- Redmonk languages from Jan 2014:
http://sogrady-media.redmonk.com/sogrady/files/2014/01/lang-rank-114-wm.png

22
rust/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "Mal"
version = "0.0.1"
authors = [ "Your name <you@example.com>" ]
[dependencies.cadencemarseille-pcre]
git = "https://github.com/kanaka/rust-pcre"
[[bin]]
name = "exp"
[[bin]]
name = "step0_repl"
[[bin]]
name = "step1_read_print"

126
rust/src/reader.rs Normal file
View File

@ -0,0 +1,126 @@
//#![feature(phase)]
//#[phase(plugin)]
//extern crate regex_macros;
//extern crate regex;
extern crate pcre;
use std::rc::Rc;
use types::{MalVal,Nil,True,False,Int,Strn,Sym,List};
use self::pcre::Pcre;
use super::printer::unescape_str;
#[deriving(Show, Clone)]
struct Reader {
tokens : Vec<String>,
position : uint,
}
impl Reader {
fn next(&mut self) -> Option<String> {
if self.position < self.tokens.len() {
self.position += 1;
Some(self.tokens[self.position-1].to_string())
} else {
None
}
}
fn peek(&self) -> Option<String> {
if self.position < self.tokens.len() {
Some(self.tokens[self.position].to_string())
} else {
None
}
}
}
fn tokenize(str :String) -> Vec<String> {
let mut results = vec![];
let re = match Pcre::compile(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"###) {
Err(_) => { fail!("failed to compile regex") },
Ok(re) => re
};
let mut it = re.matches(str.as_slice());
loop {
let opt_m = it.next();
if opt_m.is_none() { break; }
let m = opt_m.unwrap();
if m.group(1) == "" { break; }
results.push((*m.group(1)).to_string());
}
results
}
fn read_atom(rdr : &mut Reader) -> Result<MalVal,String> {
let otoken = rdr.next();
//println!("read_atom: {}", otoken);
if otoken.is_none() { return Err("read_atom underflow".to_string()); }
let stoken = otoken.unwrap();
let token = stoken.as_slice();
if regex!(r"^-?[0-9]+$").is_match(token) {
let num : Option<int> = from_str(token);
Ok(Rc::new(Int(num.unwrap())))
} else if regex!(r#"^".*"$"#).is_match(token) {
let new_str = token.slice(1,token.len()-1);
Ok(Rc::new(Strn(unescape_str(new_str))))
} else if token == "nil" {
Ok(Rc::new(Nil))
} else if token == "true" {
Ok(Rc::new(True))
} else if token == "false" {
Ok(Rc::new(False))
} else {
Ok(Rc::new(Sym(String::from_str(token))))
}
}
fn read_list(rdr : &mut Reader) -> Result<MalVal,String> {
let otoken = rdr.next();
if otoken.is_none() { return Err("read_atom underflow".to_string()); }
let stoken = otoken.unwrap();
let token = stoken.as_slice();
if token != "(" { return Err("expected '('".to_string()); }
let mut ast_vec : Vec<MalVal> = vec![];
loop {
let otoken = rdr.peek();
if otoken.is_none() { return Err("expected ')', got EOF".to_string()); }
let stoken = otoken.unwrap();
let token = stoken.as_slice();
if token == ")" { break; }
match read_form(rdr) {
Ok(mv) => ast_vec.push(mv),
Err(e) => return Err(e),
}
}
rdr.next();
//ast_vec.push(Rc::new(Nil));
Ok(Rc::new(List(ast_vec)))
}
fn read_form(rdr : &mut Reader) -> Result<MalVal,String> {
let otoken = rdr.peek();
//println!("read_form: {}", otoken);
let stoken = otoken.unwrap();
let token = stoken.as_slice();
match token {
")" => Err("unexected ')'".to_string()),
"(" => read_list(rdr),
_ => read_atom(rdr)
}
}
pub fn read_str(str :String) -> Result<MalVal,String> {
let tokens = tokenize(str);
if tokens.len() == 0 {
return Err("<empty line>".to_string());
}
//println!("tokens: {}", tokens);
let rdr = &mut Reader{tokens: tokens, position: 0};
read_form(rdr)
}

76
rust/src/readline.rs Normal file
View File

@ -0,0 +1,76 @@
// Based on: https://github.com/shaleh/rust-readline (MIT)
extern crate libc;
use std::c_str;
use std::io::{File, Append, Write};
use std::io::BufferedReader;
mod ext_readline {
extern crate libc;
use self::libc::c_char;
#[link(name = "readline")]
extern {
pub fn add_history(line: *const c_char);
pub fn readline(p: *const c_char) -> *const c_char;
}
}
pub fn add_history(line: &str) {
unsafe {
ext_readline::add_history(line.to_c_str().as_ptr());
}
}
pub fn readline(prompt: &str) -> Option<String> {
let cprmt = prompt.to_c_str();
unsafe {
let ret = ext_readline::readline(cprmt.as_ptr());
if ret.is_null() { // user pressed Ctrl-D
None
}
else {
c_str::CString::new(ret, true).as_str().map(|ret| ret.to_string())
}
}
}
// --------------------------------------------
static mut history_loaded : bool = false;
static HISTORY_FILE : &'static str = "/home/joelm/.mal-history";
fn load_history() {
unsafe {
if history_loaded { return; }
history_loaded = true;
}
let path = Path::new(HISTORY_FILE);
let mut file = BufferedReader::new(File::open(&path));
for line in file.lines() {
let rt: &[_] = &['\r', '\n'];
let line2 = line.unwrap();
let line3 = line2.as_slice().trim_right_chars(rt);
add_history(line3);
}
}
fn append_to_history(line: &str) {
let path = Path::new("/home/joelm/.mal-history");
let mut file = File::open_mode(&path, Append, Write);
let _ = file.write_line(line);
}
pub fn mal_readline (prompt: &str) -> Option<String> {
load_history();
let line = readline(prompt);
match line {
None => None,
_ => {
add_history(line.clone().unwrap().as_slice());
append_to_history(line.clone().unwrap().as_slice());
line
}
}
}

25
rust/src/step0_repl.rs Normal file
View File

@ -0,0 +1,25 @@
use readline::mal_readline;
mod readline;
// read
fn read(str: String) -> String {
str
}
// eval
fn eval(ast: String) -> String {
ast
}
// print
fn print(exp: String) -> String {
exp
}
fn main() {
loop {
let line = mal_readline("user> ");
match line { None => break, _ => () }
println!("{}", print(eval(read(line.unwrap()))));
}
}

View File

@ -0,0 +1,51 @@
// support precompiled regexes in reader.rs
#![feature(phase)]
#[phase(plugin)]
extern crate regex_macros;
extern crate regex;
use std::rc::Rc;
use types::{MalVal,List,Vector,Int,Nil};
mod readline;
mod types;
mod reader;
mod printer;
// read
fn read(str: String) -> Result<MalVal,String> {
reader::read_str(str)
}
// eval
fn eval(ast: MalVal) -> Result<MalVal,String> {
Ok(ast)
}
// print
fn print(exp: MalVal) -> String {
exp.pr_str(true)
}
fn rep(str: String) -> Result<String,String> {
match read(str) {
Err(e) => Err(e),
Ok(ast) => {
//println!("read: {}", ast);
match eval(ast) {
Err(e) => Err(e),
Ok(exp) => Ok(print(exp)),
}
}
}
}
fn main() {
loop {
let line = readline::mal_readline("user> ");
match line { None => break, _ => () }
match rep(line.unwrap()) {
Ok(str) => println!("{}", str),
Err(str) => println!("Error: {}", str),
}
}
}

83
rust/src/types.rs Normal file
View File

@ -0,0 +1,83 @@
use std::rc::Rc;
use std::collections;
use std::fmt;
use super::printer::escape_str;
#[deriving(Clone)]
pub enum MalType {
Nil,
True,
False,
Int(int),
Strn(String),
Sym(String),
List(Vec<MalVal>),
Vector(Vec<MalVal>),
HashMap(collections::HashMap<String, MalVal>),
}
pub type MalVal = Rc<MalType>;
impl MalType {
pub fn pr_str(&self, print_readably: bool) -> String {
let _r = print_readably;
let mut res = String::new();
match *self {
Nil => res.push_str("nil"),
True => res.push_str("true"),
False => res.push_str("false"),
Int(v) => res.push_str(v.to_string().as_slice()),
Sym(ref v) => res.push_str((*v).as_slice()),
Strn(ref v) => {
if print_readably {
res.push_str(escape_str((*v).as_slice()).as_slice())
} else {
res.push_str(v.as_slice())
}
}
List(ref v) => {
let mut first = true;
res.push_str("(");
for item in v.iter() {
if first { first = false; } else { res.push_str(" "); }
res.push_str(item.pr_str(_r).as_slice());
}
res.push_str(")")
}
/*
*/
/*
Vector(ref v) => {
let mut first = true;
write!(f, "[");
for item in v.iter() {
if first { first = false; } else { write!(f, " ") }
item.fmt(f);
}
write!(f, "]");
}
Hash_Map(ref v) => {
let mut first = true;
write!(f, "{}", "{");
for (key, value) in v.iter() {
if first { first = false; } else { write!(f, " ") }
write!(f, "\"{}\"", *key);
write!(f, " ");
value.fmt(f);
}
write!(f, "{}", "}");
}
// Atom(ref v) => v.fmt(f),
*/
_ => { res.push_str("#<unknown type>") }
};
res
}
}
impl fmt::Show for MalType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.pr_str(true))
}
}

View File

@ -56,6 +56,10 @@ abc-def
(** 1 2)
;=>(** 1 2)
;; Test commas as whitespace
(1 2, 3,,,,),,
;=>(1 2 3)
;; Testing read of vectors
[+ 1 2]
;=>[+ 1 2]
@ -76,10 +80,6 @@ abc-def
{ "a" {"b" { "cde" 3 } }}
;=>{"a" {"b" {"cde" 3}}}
;; Test commas as whitespace
(1 2, 3,,,,),,
;=>(1 2 3)
;;
;; Testing reader errors
;;; TODO: fix these so they fail correctly