From 394f6277d6dcbfcd6588fd8a8c157200cd2aa443 Mon Sep 17 00:00:00 2001 From: Thomas Antony Date: Tue, 7 Sep 2021 20:11:14 -0500 Subject: [PATCH 1/4] Add missing newlines --- .gitignore | 2 +- Cargo.toml | 2 +- examples/guess_number.clj | 2 +- examples/what_is_your_name.clj | 2 +- src/clojure/string.clj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 24c0633..bcbce10 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,4 @@ Session.vim tags .idea -Cargo.lock \ No newline at end of file +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 64adde0..149dfb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,4 @@ itertools= "0.9" url = "2.1.1" regex = "1.3.7" if_chain = "1.0" -reqwest = { version = "0.10.4", features = ["blocking"] } \ No newline at end of file +reqwest = { version = "0.10.4", features = ["blocking"] } diff --git a/examples/guess_number.clj b/examples/guess_number.clj index 72a78e1..f47f740 100644 --- a/examples/guess_number.clj +++ b/examples/guess_number.clj @@ -6,4 +6,4 @@ (if (= answer (str number)) (println "correct!") - (println "wrong, the correct was " number))) \ No newline at end of file + (println "wrong, the correct was " number))) diff --git a/examples/what_is_your_name.clj b/examples/what_is_your_name.clj index 01fd03c..0abea7e 100644 --- a/examples/what_is_your_name.clj +++ b/examples/what_is_your_name.clj @@ -1,4 +1,4 @@ (print "What is your name? " ) (flush) (let [answer (read-line)] - (println "Hello, " answer "!")) \ No newline at end of file + (println "Hello, " answer "!")) diff --git a/src/clojure/string.clj b/src/clojure/string.clj index 718ff66..15cc115 100644 --- a/src/clojure/string.clj +++ b/src/clojure/string.clj @@ -4,4 +4,4 @@ (def clojure.string/split-lines (fn [s] - (clojure.string/split s #"\r?\n"))) \ No newline at end of file + (clojure.string/split s #"\r?\n"))) From 789210110ef3ce2f4e6daccc070bc51090560b83 Mon Sep 17 00:00:00 2001 From: Thomas Antony Date: Tue, 7 Sep 2021 20:12:10 -0500 Subject: [PATCH 2/4] Fix macros to use $crate --- src/persistent_list.rs | 4 ++-- src/symbol.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/persistent_list.rs b/src/persistent_list.rs index 0dd6396..7b75488 100644 --- a/src/persistent_list.rs +++ b/src/persistent_list.rs @@ -25,7 +25,7 @@ macro_rules! list { $( temp_list_as_vec.push($val.to_rc_value()); )* - temp_list_as_vec.into_iter().collect::() + temp_list_as_vec.into_iter().collect::<$crate::persistent_list::PersistentList>() } }; } @@ -40,7 +40,7 @@ macro_rules! list_val { $( temp_list_as_vec.push($val.to_rc_value()); )* - temp_list_as_vec.into_iter().collect::().to_value() + temp_list_as_vec.into_iter().collect::<$crate::persistent_list::PersistentList>().to_value() } }; } diff --git a/src/symbol.rs b/src/symbol.rs index 4e54ee3..edd4e33 100644 --- a/src/symbol.rs +++ b/src/symbol.rs @@ -15,9 +15,10 @@ pub struct Symbol { pub ns: String, pub meta: PersistentListMap, } +#[macro_export] macro_rules! sym { ($x:expr) => { - Symbol::intern($x) + $crate::symbol::Symbol::intern($x) } } impl Hash for Symbol { From 5b85ad80ea7dda8fdac6785f9681996403e48cf9 Mon Sep 17 00:00:00 2001 From: Thomas Antony Date: Tue, 7 Sep 2021 20:12:26 -0500 Subject: [PATCH 3/4] Add lib.rs for example programs --- src/lib.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b58431b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,34 @@ +#[macro_use] +extern crate nom; +extern crate itertools; + +#[macro_use] +pub mod persistent_list_map; +#[macro_use] +pub mod persistent_list; +#[macro_use] +pub mod protocol; +#[macro_use] +pub mod symbol; +#[macro_use] +pub mod var; +pub mod clojure_std; +pub mod clojure_string; +pub mod environment; +pub mod error_message; +pub mod ifn; +pub mod iterable; +pub mod keyword; +pub mod lambda; +pub mod maps; +pub mod namespace; +pub mod persistent_vector; +pub mod reader; +pub mod repl; +pub mod rust_core; +pub mod type_tag; +pub mod user_action; +pub mod util; +pub mod value; +pub mod protocols; +pub mod traits; From eabc03e6c3d57915df53465ccc1a329803fb3946 Mon Sep 17 00:00:00 2001 From: Thomas Antony Date: Tue, 7 Sep 2021 20:21:45 -0500 Subject: [PATCH 4/4] Add barebones version of transpiler - Can create working rust code for hello_world.clj - Generated code for strings.clj compiles but fails at runtime - Most notably the IFn values are currently not supported --- .gitignore | 1 + examples/transpiler/codegen.rs.tmpl | 17 +++ examples/transpiler/main.rs | 169 ++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 examples/transpiler/codegen.rs.tmpl create mode 100644 examples/transpiler/main.rs diff --git a/.gitignore b/.gitignore index bcbce10..a45b79b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/examples/codegen_* /target **/*.rs.bk diff --git a/examples/transpiler/codegen.rs.tmpl b/examples/transpiler/codegen.rs.tmpl new file mode 100644 index 0000000..5a6394d --- /dev/null +++ b/examples/transpiler/codegen.rs.tmpl @@ -0,0 +1,17 @@ +#[macro_use] +extern crate rust_clojure; + +use rust_clojure::environment::Environment; +use rust_clojure::value::{Evaluable, ToValue, Value}; +use std::rc::Rc; + +use rust_clojure::persistent_vector::{PersistentVector}; + +fn main() { + let v = %%; + + let environment = Rc::new(Environment::clojure_core_environment()); + for val in v.into_iter() { + val.eval(Rc::clone(&environment)); + } +} diff --git a/examples/transpiler/main.rs b/examples/transpiler/main.rs new file mode 100644 index 0000000..db8f568 --- /dev/null +++ b/examples/transpiler/main.rs @@ -0,0 +1,169 @@ +#[macro_use] +extern crate rust_clojure; + +use std::fs::File; +use std::path::PathBuf; +use std::fs; +use std::io::{self, Write}; +use std::io::BufRead; +use std::io::BufReader; + +use rust_clojure::environment; +use rust_clojure::environment::Environment; +use rust_clojure::reader; +use rust_clojure::value::{Evaluable, ToValue, Value}; +use std::rc::Rc; +use std::process::{Command, Stdio}; + +use rust_clojure::symbol::*; +use rust_clojure::persistent_list::ToPersistentListIter; +// use rust_clojure::persistent_list_map::*; +use rust_clojure::persistent_vector::{PersistentVector, ToPersistentVectorIter}; + + +fn codegen_value (value: Rc) -> String { + match value.as_ref() { + Value::Symbol(s) => format!("sym!(\"{}\").to_rc_value()", s.name), + Value::PersistentList(l) => { + let vals: String = Rc::new(l.clone()).iter() + .map(codegen_value) + .collect::>() + .join(" \n"); + + format!("list_val!({})", vals) + }, + Value::PersistentVector(pv) => { + let vals: String = Rc::new(pv.clone()).iter() + .map(codegen_value) + .collect::>() + .join(","); + format!("PersistentVector{{ vals: vec![{}] }}", vals) + }, + Value::String(s) => format!("String::from(\"{}\").to_rc_value()", s), + Value::Boolean(b) => format!("{}.to_rc_value()", b), + Value::I32(i) => format!("{}i32.to_rc_value()", i), + Value::F64(f)=> format!("{}f64.to_rc_value()", f), + Value::Nil => { + "Value::Nil".into() + } + _ => "".into() + } +} + +fn apply_rustfmt(source: String) -> io::Result +{ + let rustfmt = rustfmt_path(); + let mut cmd = Command::new(&rustfmt); + cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); + let mut child = cmd.spawn().expect("Error running rustfmt"); + let mut child_stdin = child.stdin.take().unwrap(); + let mut child_stdout = child.stdout.take().unwrap(); + + // Write to stdin in a new thread, so that we can read from stdout on this + // thread. This keeps the child from blocking on writing to its stdout which + // might block us from writing to its stdin. + let stdin_handle = ::std::thread::spawn(move || { + let _ = child_stdin.write_all(source.as_bytes()); + source + }); + + let mut output = vec![]; + io::copy(&mut child_stdout, &mut output)?; + + let status = child.wait()?; + let source = stdin_handle.join().expect( + "The thread writing to rustfmt's stdin doesn't do \ + anything that could panic", + ); + match String::from_utf8(output) { + Ok(bindings) => match status.code() { + Some(0) => Ok(bindings), + Some(2) => Err(io::Error::new( + io::ErrorKind::Other, + "Rustfmt parsing errors.".to_string(), + )), + Some(3) => { + println!("Rustfmt could not format some lines."); + Ok(bindings) + } + _ => Err(io::Error::new( + io::ErrorKind::Other, + "Internal rustfmt error".to_string(), + )), + }, + _ => Ok(source) + } +} +fn read_source(mut reader: B) -> Vec +{ + let mut last_val = reader::read(&mut reader); + + let mut values = Vec::new(); + values.push(last_val.clone()); + let value = loop { + // @TODO this is hardcoded until we refactor Conditions to have keys, so that + // we can properly identify them + // @FIXME + if let Value::Condition(cond) = &last_val { + if cond != "Tried to read empty stream; unexpected EOF" { + println!("Error reading file: {}", cond); + } + + break last_val; + } + + last_val = reader::read(&mut reader); + if let Value::Condition(cond) = last_val.clone() { + }else{ + values.push(last_val.clone()); + } + }; + values +} + +/// Gets the rustfmt path to rustfmt the generated bindings. +fn rustfmt_path() -> std::path::PathBuf { + if let Ok(rustfmt) = std::env::var("RUSTFMT") { + rustfmt.into() + }else{ + "rustfmt".into() + } +} + +fn main() -> io::Result<()> +{ + let args = std::env::args().collect::>(); + if args.len() < 3 { + eprintln!("A Clojure-to-Rust transpiler\n\n\ + Usage: {} [output-filename]\n\n\ + [output-filename] defaults to main.rs\n", args[0]); + std::process::exit(-1); + } + let output_folder: PathBuf = (&args[2]).into(); + let source_file: String = (&args[1]).into(); + + let output_filename = args.get(3).cloned().unwrap_or("main.rs".into()); + + let output_file: PathBuf = output_folder.join(output_filename); + + let codegen_template = include_str!("codegen.rs.tmpl"); + + let core = File::open(source_file).unwrap(); + let reader = BufReader::new(core); + let values = read_source(reader); + let v_str = values.clone().into_iter() + .map(|v| codegen_value(Rc::new(v))) + .collect::>() + .join(",\n"); + + + let value_vec_code= format!("vec![{}];", v_str); + let codegen_output = codegen_template.replace("%%", &value_vec_code); + let codegen_output = apply_rustfmt(codegen_output)?; + + fs::create_dir_all(&output_folder)?; + let mut fp = File::create(&output_file)?; + write!(fp, "{}", codegen_output)?; + println!("Code written to {:?}", output_file); + Ok(()) +}