Rust: step0_repl and step1_read_print
authorJoel Martin <github@martintribe.org>
Sat, 25 Oct 2014 16:42:07 +0000 (11:42 -0500)
committerJoel Martin <github@martintribe.org>
Wed, 7 Jan 2015 03:58:35 +0000 (21:58 -0600)
.gitignore
Makefile
README.md
docs/TODO
rust/Cargo.toml [new file with mode: 0644]
rust/src/reader.rs [new file with mode: 0644]
rust/src/readline.rs [new file with mode: 0644]
rust/src/step0_repl.rs [new file with mode: 0644]
rust/src/step1_read_print.rs [new file with mode: 0644]
rust/src/types.rs [new file with mode: 0644]
tests/step1_read_print.mal

index cded525..2e73e52 100644 (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
index 6c1bf12..1cc08d3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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
index 1156135..55b1216 100644 (file)
--- a/README.md
+++ b/README.md
@@ -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
index 6f0b2d0..00928a4 100644 (file)
--- a/docs/TODO
+++ b/docs/TODO
@@ -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
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644 (file)
index 0000000..60cdf34
--- /dev/null
@@ -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"
diff --git a/rust/src/reader.rs b/rust/src/reader.rs
new file mode 100644 (file)
index 0000000..9ad129b
--- /dev/null
@@ -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)
+}
diff --git a/rust/src/readline.rs b/rust/src/readline.rs
new file mode 100644 (file)
index 0000000..17d1ed9
--- /dev/null
@@ -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
+        }
+    }
+}
diff --git a/rust/src/step0_repl.rs b/rust/src/step0_repl.rs
new file mode 100644 (file)
index 0000000..ac9cf24
--- /dev/null
@@ -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()))));
+    }
+}
diff --git a/rust/src/step1_read_print.rs b/rust/src/step1_read_print.rs
new file mode 100644 (file)
index 0000000..c6f87d0
--- /dev/null
@@ -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),
+        }
+    }
+}
diff --git a/rust/src/types.rs b/rust/src/types.rs
new file mode 100644 (file)
index 0000000..3e0e027
--- /dev/null
@@ -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))
+    }
+}
index 43e7931..1714a56 100644 (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