diff --git a/eg/fib.irl.dot b/eg/test.dot similarity index 97% rename from eg/fib.irl.dot rename to eg/test.dot index 23ae2cc..5cbc9b0 100644 --- a/eg/fib.irl.dot +++ b/eg/test.dot @@ -1,4 +1,4 @@ -digraph "./eg/fib.irl.dot" { +digraph "./eg/test.dot" { subgraph cluster_fib { label="fib"; graph [style=filled]; diff --git a/eg/fib.irl.dot.svg b/eg/test.dot.svg similarity index 99% rename from eg/fib.irl.dot.svg rename to eg/test.dot.svg index f5c4bb7..b6d1668 100644 --- a/eg/fib.irl.dot.svg +++ b/eg/test.dot.svg @@ -3,11 +3,11 @@ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> - + -eg/fib.irl.dot +./eg/test.dot cluster_fib diff --git a/eg/test.irl.dot b/eg/test.irl.dot deleted file mode 100644 index 3a92f4e..0000000 --- a/eg/test.irl.dot +++ /dev/null @@ -1,33 +0,0 @@ -digraph "./eg/test.irl.dot" { - subgraph cluster_fib { - label="fib"; - graph [style=filled]; - fib_ENTRY [label="ENTRY"]; - fib_EXIT [label="EXIT"]; - fib_ENTRY -> fib_BB0; - fib_BB0 [shape=record label="[0]\n"]; - fib_BB0 -> fib_BB1; - fib_BB1 [shape=record label="[1]\na = 0\lb = 1\li = 1\l"]; - fib_BB1 -> fib_BB2; - fib_BB2 [shape=record label="[2 - begin]\n"]; - fib_BB2 -> fib_BB3; - fib_BB2 -> fib_BB4; - fib_BB3 [shape=record label="[3 - end]\nret b\l"]; - fib_BB3 -> fib_EXIT; - fib_BB4 [shape=record label="[4]\nt = b\lb = a + b\la = t\li = i + 1\l"]; - fib_BB4 -> fib_BB2; - } - - subgraph cluster_main { - label="main"; - graph [style=filled]; - main_ENTRY [label="ENTRY"]; - main_EXIT [label="EXIT"]; - main_ENTRY -> main_BB0; - main_BB0 [shape=record label="[0]\n"]; - main_BB0 -> main_BB1; - main_BB1 [shape=record label="[1]\nparam 3\la = call fib, 1\lret a\l"]; - main_BB1 -> main_EXIT; - } - -} diff --git a/eg/test.wasm b/eg/test.wasm new file mode 100644 index 0000000..a94039f Binary files /dev/null and b/eg/test.wasm differ diff --git a/eg/test.wat b/eg/test.wat new file mode 100644 index 0000000..3b4f574 --- /dev/null +++ b/eg/test.wat @@ -0,0 +1,20 @@ +(module + (func $fib (export "fib") (param $n i32) (result i32) + (block $1 + ) + (block $begin + ) + (block $end + ) + (block $4 + br $begin + ) + i32.const 0 + ) + (func $main (export "main") (result i32) + br $1 + (block $1 + ) + i32.const 0 + ) +) diff --git a/src/cli.rs b/src/cli.rs index be13d1f..1f9ab43 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,6 +5,8 @@ pub struct CliOptions { pub cfg: bool, pub debug: bool, pub verbose: bool, + pub wat: bool, + pub wasm: bool, } impl CliOptions { @@ -16,15 +18,37 @@ impl CliOptions { cfg: *compile_args.unwrap().get_one::("cfg").unwrap(), debug: *compile_args.unwrap().get_one::("debug").unwrap(), verbose: *compile_args.unwrap().get_one::("verbose").unwrap(), + wat: *compile_args.unwrap().get_one::("wat").unwrap(), + wasm: *compile_args.unwrap().get_one::("wasm").unwrap(), } } pub fn verbose_message(&self, message: String) { if self.verbose {println!("{}: info: {}", self.filepath, message);} } + pub fn verbose_error(&self, message: String) { + if self.verbose {println!("{}: error: {}", self.filepath, message);} + } + pub fn run_command(&self, args: &[&str]) { + if args.is_empty() {return;} + + let binpath = args.get(0).cloned().unwrap_or_default(); + let mut command = std::process::Command::new(binpath); + for arg in &args[1..] { + command.arg(arg); + } + + let output = command.output().expect("failed to execute command"); + if !output.status.success() { + let stderr = std::str::from_utf8(&output.stderr).expect("Invalid UTF-8 in stderr"); + self.verbose_message(format!("error: {}", stderr)); + } + } } pub fn cli() -> Command { Command::new("irl") + .version("1.0") + .author("Hyouteki") .about("Intermediate Representation Language: Minimal implementation of LLVM") .subcommand_required(true) .arg_required_else_help(true) @@ -57,5 +81,15 @@ pub fn cli() -> Command { .required(false) .action(ArgAction::SetTrue) .help("Sets info level to verbose")) + .arg(Arg::new("wat") + .long("wat") + .required(false) + .action(ArgAction::SetTrue) + .help("Generates WAT")) + .arg(Arg::new("wasm") + .long("wasm") + .required(false) + .action(ArgAction::SetTrue) + .help("Generates WASM")) ) } diff --git a/src/main.rs b/src/main.rs index e6069eb..95de649 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,13 @@ use crate::fe::{lexer::Lexer, parser::Parser, ast::AstNode}; use crate::mw::default_ast_pass_manager::*; use crate::opt::{default_compiler_pass_manager::*, cfg::*}; +use crate::trn::transpiler::*; use crate::cli::CliOptions; -use std::process::Command; pub mod fe; pub mod mw; pub mod opt; +pub mod trn; pub mod cli; fn main() { @@ -42,10 +43,10 @@ fn main() { let mut cfg_table: Vec = cfg_table_from_program(&ast); run_default_compiler_pass_manager(&mut cfg_table); if options.cfg { - let dot_filepath: String = format!("{}.dot", options.filepath.clone()); - dump_cfg_table_to_svg(&cfg_table, dot_filepath.clone()); - Command::new("dot").arg("-Tsvg").arg("-O").arg(dot_filepath.clone()); - options.verbose_message(format!("created control flow graph svg '{}'", dot_filepath)); + let dot_filepath: String = replace_extension(options.filepath.clone(), "irl", "dot"); + dump_cfg_table_to_svg(&cfg_table, dot_filepath.to_string()); + options.run_command(&["dot", "-Tsvg", "-O", dot_filepath.as_str()]); + options.verbose_error(format!("created control flow graph svg '{}.svg'", dot_filepath)); } options.verbose_message(format!("OPT over")); @@ -60,4 +61,6 @@ fn main() { println!("{}", node); } } + + transpile(&options, &ast, options.filepath.clone()); } diff --git a/src/trn/mod.rs b/src/trn/mod.rs new file mode 100644 index 0000000..155f2a8 --- /dev/null +++ b/src/trn/mod.rs @@ -0,0 +1,2 @@ +pub mod transpiler; +pub mod wat_transpiler; diff --git a/src/trn/transpiler.rs b/src/trn/transpiler.rs new file mode 100644 index 0000000..42a52b4 --- /dev/null +++ b/src/trn/transpiler.rs @@ -0,0 +1,34 @@ +use std::{fs::File, io::Write}; +use crate::{fe::ast::*, cli::*}; +use crate::trn::wat_transpiler::WatTranspiler; + +pub trait Transpiler { + fn transpile(&self, nodes: &Vec) -> Vec; +} + +fn remove_extension(filepath: String, ext: &str) -> String { + filepath.as_str().trim_end_matches(ext).to_string() +} + +pub fn replace_extension(filepath: String, old_ext: &str, new_ext: &str) -> String { + format!("{}{}", remove_extension(filepath, old_ext), new_ext) +} + +pub fn transpilation_mode(transpiler: T, nodes: &Vec, + output_filepath: String) { + let mut file = File::create(output_filepath).expect("could not create file"); + for line in transpiler.transpile(nodes).iter() { + file.write_all(line.as_bytes()).expect("could not write line"); + file.write_all(b"\n").expect("could not write new line"); + } +} + +pub fn transpile(options: &CliOptions, nodes: &Vec, filepath: String) { + if options.wat || options.wasm { + let wat_filepath: String = replace_extension(filepath.clone(), "irl", "wat"); + transpilation_mode(WatTranspiler{}, nodes, wat_filepath.clone()); + if !options.wasm {return;} + let wasm_filepath: String = replace_extension(filepath.clone(), "irl", "wasm"); + options.run_command(&["wat2wasm", wat_filepath.as_str(), "-o", wasm_filepath.as_str()]); + } +} diff --git a/src/trn/wat_transpiler.rs b/src/trn/wat_transpiler.rs new file mode 100644 index 0000000..29c9bd5 --- /dev/null +++ b/src/trn/wat_transpiler.rs @@ -0,0 +1,62 @@ +use std::collections::HashSet; +use crate::fe::ast::*; +use crate::trn::transpiler::Transpiler; + +pub struct WatTranspiler; + +impl Transpiler for WatTranspiler { + fn transpile(&self, nodes: &Vec) -> Vec { + let mut lines: Vec = Vec::new(); + lines.push(String::from("(module")); + lines.append(&mut transpile_nodes_to_wat(nodes, 1, &mut HashSet::new())); + lines.push(String::from(")")); + lines + } +} + +fn transpile_nodes_to_wat(nodes: &Vec, indent_sz: usize, + vis_labels: &mut HashSet) -> Vec { + + let mut lines: Vec = Vec::new(); + for node in nodes.iter() { + lines.append(&mut transpile_node_to_wat(node, indent_sz, vis_labels)); + } + lines +} + +fn make_line(indent_sz: usize, text: String) -> String {let mut line: String = String::new(); + for _ in 0..indent_sz {line.push_str(" ");} + line.push_str(&text); + line +} + +fn transpile_node_to_wat(node: &AstNode, indent_sz: usize, vis_labels: &mut HashSet) -> Vec { + let mut lines: Vec = Vec::new(); + match node { + AstNode::Function(function_node) => { + let mut line: String = make_line(indent_sz, format!("(func ${} (export \"{}\")", + function_node.name, function_node.name)); + for arg_name in function_node.args.iter() { + line += &format!(" (param ${} i32)", arg_name); + } + line += " (result i32)"; + lines.push(line); + lines.append(&mut transpile_nodes_to_wat(&function_node.body, indent_sz+1, vis_labels)); + lines.push(make_line(indent_sz+1, String::from("i32.const 0"))); + lines.push(make_line(indent_sz, String::from(" )"))); + } + AstNode::Label(label_node) => { + lines.push(make_line(indent_sz, format!("(block ${}", label_node.name))); + vis_labels.insert(label_node.name.clone()); + lines.append(&mut transpile_nodes_to_wat(&label_node.body, indent_sz+1, vis_labels)); + lines.push(make_line(indent_sz, String::from(")"))); + } + AstNode::Goto(goto_node) => { + if vis_labels.contains(&goto_node.name) { + lines.push(make_line(indent_sz, format!("br ${}", goto_node.name))); + } + } + _ => {} + } + lines +}