Add pretty-printing of the parsed expression

Adds `--unpretty` unstable flag, which currently accepts two arguments,
`dot` and `graphdotviz`, which are synonymous, and which prints the
expression we parsed in a graphdotviz-compatible format, i.e. in a form
of a directed graph.
This commit is contained in:
Aodhnait Étaín 2021-05-22 21:47:39 +01:00
parent ba409b9be0
commit 3d26398ac7

View file

@ -1,9 +1,16 @@
#![feature(box_syntax)]
// Try to keep this string updated with the argument parsing, otherwise it will // Try to keep this string updated with the argument parsing, otherwise it will
// get confusing for users. // get confusing for users.
static USAGE: &'static str = "usage: pine [options] input static USAGE: &'static str = "usage: pine [options] input
options: 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() { fn main() {
// Throw away the first argument, which usually is the executable name. // 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 path: Option<&str> = None;
let mut output_pretty: Option<&str> = None;
// Handle command-line arguments. // Handle command-line arguments.
let mut i = 0; let mut i = 0;
loop { loop {
@ -35,6 +44,22 @@ fn main() {
println!("{}\n", USAGE); println!("{}\n", USAGE);
return; 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); eprintln!("pine: \x1b[1;31merror\x1b[0m: unknown argument '{}'", arg);
std::process::exit(1); std::process::exit(1);
@ -70,6 +95,32 @@ fn main() {
let mut tokens = TokenStream::from(source); let mut tokens = TokenStream::from(source);
let expr = parse_expression(&mut tokens); let expr = parse_expression(&mut tokens);
eprintln!("{:?}", expr); 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<T> {
fn then<F>(&self, f: F) where F: FnOnce(&T);
}
impl<T> WithContinuation<T> for std::option::Option<T> {
fn then<F>(&self, f: F) where F: FnOnce(&T) {
match self {
None => {},
Some(v) => f(v),
};
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -78,6 +129,46 @@ enum Expression {
Binary(Token, Box<Expression>, Box<Expression>), Binary(Token, Box<Expression>, Box<Expression>),
} }
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<Expression> { fn parse_expression<'a>(tokens: &'a mut TokenStream<'a>) -> Option<Expression> {
let lhs = match tokens.next()? { let lhs = match tokens.next()? {
token @ Token::IntegerLiteral(_) => Expression::Literal(token), token @ Token::IntegerLiteral(_) => Expression::Literal(token),