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:
parent
f41866dbe9
commit
abdd56ebc0
14
.gitignore
vendored
14
.gitignore
vendored
@ -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
|
||||
|
4
Makefile
4
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
|
||||
|
14
README.md
14
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
|
||||
|
26
docs/TODO
26
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
|
||||
|
22
rust/Cargo.toml
Normal file
22
rust/Cargo.toml
Normal 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
126
rust/src/reader.rs
Normal 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
76
rust/src/readline.rs
Normal 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
25
rust/src/step0_repl.rs
Normal 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()))));
|
||||
}
|
||||
}
|
51
rust/src/step1_read_print.rs
Normal file
51
rust/src/step1_read_print.rs
Normal 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
83
rust/src/types.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user