aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-10-25 11:42:07 -0500
committerJoel Martin <github@martintribe.org>2015-01-06 21:58:35 -0600
commitabdd56ebc0e01cd92f694ef2bcafcc394453d055 (patch)
treed1ace96ac90e5d888e4d4d05dd4ca0c0445a856e
parentf41866dbe99080f0916512261f0412c5bc65f190 (diff)
downloadmal-abdd56ebc0e01cd92f694ef2bcafcc394453d055.tar.gz
mal-abdd56ebc0e01cd92f694ef2bcafcc394453d055.zip
Rust: step0_repl and step1_read_print
-rw-r--r--.gitignore14
-rw-r--r--Makefile4
-rw-r--r--README.md14
-rw-r--r--docs/TODO26
-rw-r--r--rust/Cargo.toml22
-rw-r--r--rust/src/reader.rs126
-rw-r--r--rust/src/readline.rs76
-rw-r--r--rust/src/step0_repl.rs25
-rw-r--r--rust/src/step1_read_print.rs51
-rw-r--r--rust/src/types.rs83
-rw-r--r--tests/step1_read_print.mal8
11 files changed, 416 insertions, 33 deletions
diff --git a/.gitignore b/.gitignore
index cded525..2e73e52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/Makefile b/Makefile
index 6c1bf12..1cc08d3 100644
--- 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
diff --git a/README.md b/README.md
index 1156135..55b1216 100644
--- 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
diff --git a/docs/TODO b/docs/TODO
index 6f0b2d0..00928a4 100644
--- 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
index 0000000..60cdf34
--- /dev/null
+++ b/rust/Cargo.toml
@@ -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
index 0000000..9ad129b
--- /dev/null
+++ b/rust/src/reader.rs
@@ -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
index 0000000..17d1ed9
--- /dev/null
+++ b/rust/src/readline.rs
@@ -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
index 0000000..ac9cf24
--- /dev/null
+++ b/rust/src/step0_repl.rs
@@ -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
index 0000000..c6f87d0
--- /dev/null
+++ b/rust/src/step1_read_print.rs
@@ -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
index 0000000..3e0e027
--- /dev/null
+++ b/rust/src/types.rs
@@ -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))
+ }
+}
diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal
index 43e7931..1714a56 100644
--- a/tests/step1_read_print.mal
+++ b/tests/step1_read_print.mal
@@ -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