diff --git a/src/main.rs b/src/main.rs index 75bc284..c5f0b47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,16 @@ +#![feature(box_syntax)] + // Try to keep this string updated with the argument parsing, otherwise it will // get confusing for users. static USAGE: &'static str = "usage: pine [options] input options: - --help print all options"; + --help print all options + +unstable options: + --unpretty val print un-prettified representation of the source code + valid options for `val` are: + dot, graphdotviz (dot-compatible graph)"; fn main() { // Throw away the first argument, which usually is the executable name. @@ -20,6 +27,8 @@ fn main() { } let mut path: Option<&str> = None; + let mut output_pretty: Option<&str> = None; + // Handle command-line arguments. let mut i = 0; loop { @@ -35,6 +44,22 @@ fn main() { println!("{}\n", USAGE); return; }, + "unpretty" => { + if i + 1 == args.len() { + eprintln!("pine: \x1b[1;31merror\x1b[0m: expected option to '{}'", arg); + std::process::exit(1); + } + + output_pretty = match args[i + 1].as_str() { + opt @ ("dot" | "graphdotviz") => Some(opt), + opt => { + eprintln!("pine: \x1b[1;31merror\x1b[0m: invalid option '{}' to '{}'", opt, arg); + std::process::exit(1); + }, + }; + + i += 1; + }, _ => { eprintln!("pine: \x1b[1;31merror\x1b[0m: unknown argument '{}'", arg); std::process::exit(1); @@ -70,6 +95,32 @@ fn main() { let mut tokens = TokenStream::from(source); let expr = parse_expression(&mut tokens); eprintln!("{:?}", expr); + + match output_pretty { + Some("dot" | "graphdotviz") => expr.then(|e| { + let graph = e.create_graphviz_graph(unsafe { GLOBAL_COUNTER.next() }); + let graphviz_format = "node [shape = box, style = filled, color = \"#bfd1e5\", fontname = monospace, fontsize = 12]"; + eprintln!("digraph {{\n{}\n{}\n}}", graphviz_format, graph); + }), + // This case is validated at the command-line parsing time, and we reject everything + // not specified there. This is why this can never happen, unless a solar flare changes + // a single bit. + Some(_) => unreachable!(), + None => {}, + } +} + +trait WithContinuation { + fn then(&self, f: F) where F: FnOnce(&T); +} + +impl WithContinuation for std::option::Option { + fn then(&self, f: F) where F: FnOnce(&T) { + match self { + None => {}, + Some(v) => f(v), + }; + } } #[derive(Debug)] @@ -78,6 +129,46 @@ enum Expression { Binary(Token, Box, Box), } +struct Counter { + state: usize, +} + +impl Counter { + pub const fn new() -> Self { + return Self { + state: 0, + }; + } + + pub fn next(&mut self) -> usize { + let last_state = self.state; + self.state += 1; + return last_state; + } +} + +static mut GLOBAL_COUNTER: Counter = Counter::new(); + +impl Expression { + pub fn create_graphviz_graph(&self, id: usize) -> String { + return match self { + Expression::Literal(Token::IntegerLiteral(i)) => { + format!("Node{} [label = \"{}\"]", id, i) + }, + Expression::Literal(_) => unreachable!(), + Expression::Binary(op, left, right) => { + let left_id = unsafe { GLOBAL_COUNTER.next() }; + let right_id = unsafe { GLOBAL_COUNTER.next() }; + + format!("Node{} -> {{ Node{} Node{} }}\nNode{} [label = \"{}\"]\n{}\n{}", + id, left_id, right_id, + id, op, + left.create_graphviz_graph(left_id), right.create_graphviz_graph(right_id)) + }, + }; + } +} + fn parse_expression<'a>(tokens: &'a mut TokenStream<'a>) -> Option { let lhs = match tokens.next()? { token @ Token::IntegerLiteral(_) => Expression::Literal(token),