From f45488b9af06cae44a5b440df63aa7acc91cf3f3 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Fri, 18 Feb 2022 10:37:15 -0800 Subject: [PATCH] Add a pretty printer. --- Cargo.toml | 1 + src/bin.rs | 20 ++++++-- src/syntax/ast.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 40eea36..c881f33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ codespan-reporting = "0.11.1" lalrpop-util = "^0.19.7" lazy_static = "^1.4.0" logos = "^0.12.0" +pretty = { version = "^0.11.2", features = ["termcolor"] } thiserror = "^1.0.30" [build-dependencies] diff --git a/src/bin.rs b/src/bin.rs index 36cc1e5..a7d0aa4 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -5,6 +5,7 @@ use codespan_reporting::term; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use ngr::error::Error; use ngr::syntax::Program; +use pretty::{Arena, Pretty}; use std::fs; #[derive(Parser, Debug)] @@ -34,11 +35,20 @@ fn main() { let mut file_database = SimpleFiles::new(); let initial_file_name = &args.file; - if let Err(e) = compile_file(&mut file_database, initial_file_name) { - let diagnostic = Diagnostic::from(e); - let writer = StandardStream::stderr(ColorChoice::Auto); - let config = codespan_reporting::term::Config::default(); + match compile_file(&mut file_database, initial_file_name) { + Ok(p) => { + let arena = Arena::new(); + p.pretty(&arena) + .into_doc() + .render_colored(72, StandardStream::stdout(ColorChoice::Auto)) + .unwrap() + } + Err(e) => { + let diagnostic = Diagnostic::from(e); + let writer = StandardStream::stderr(ColorChoice::Auto); + let config = codespan_reporting::term::Config::default(); - term::emit(&mut writer.lock(), &config, &file_database, &diagnostic).unwrap(); + term::emit(&mut writer.lock(), &config, &file_database, &diagnostic).unwrap(); + } } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index c821d97..90f7d79 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1,10 +1,32 @@ use crate::syntax::token_stream::Location; +use pretty::{DocAllocator, DocBuilder, Pretty}; + +static BINARY_OPERATORS: &[&str] = &["+", "-", "*", "/"]; #[derive(Debug, PartialEq)] pub struct Program { pub statements: Vec, } +impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + let mut result = allocator.nil(); + + for stmt in self.statements.iter() { + result = result + .append(stmt.pretty(allocator)) + .append(allocator.text(";")) + .append(allocator.hardline()); + } + + result + } +} + #[derive(Debug, PartialEq)] pub enum Statement { Binding(Location, String, Expression), @@ -12,6 +34,28 @@ pub enum Statement { Expr(Location, Expression), } +impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Statement +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + match self { + Statement::Binding(_, var, expr) => allocator + .text(var.to_string()) + .append(allocator.space()) + .append(allocator.text("=")) + .append(allocator.space()) + .append(expr.pretty(allocator)), + Statement::Print(_, var) => allocator + .text("print") + .append(allocator.space()) + .append(allocator.text(var.to_string())), + Statement::Expr(_, expr) => expr.pretty(allocator), + } + } +} + #[derive(Debug, PartialEq)] pub enum Expression { Value(Location, Value), @@ -19,7 +63,79 @@ pub enum Expression { Primitive(Location, String, Vec), } +impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Expression +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + match self { + Expression::Value(_, val) => val.pretty(allocator), + Expression::Reference(_, var) => allocator.text(var.to_string()), + Expression::Primitive(_, op, exprs) if BINARY_OPERATORS.contains(&op.as_ref()) => { + assert_eq!( + exprs.len(), + 2, + "Found binary operator with {} components?", + exprs.len() + ); + + let left = exprs[0].pretty(allocator); + let right = exprs[1].pretty(allocator); + + left.append(allocator.space()) + .append(allocator.text(op.to_string())) + .append(allocator.space()) + .append(right) + .parens() + } + Expression::Primitive(_, op, exprs) => { + let call = allocator.text(op.to_string()); + let args = exprs.iter().map(|x| x.pretty(allocator)); + let comma_sepped_args = allocator.intersperse(args, CommaSep {}); + call.append(comma_sepped_args.parens()) + } + } + } +} + #[derive(Debug, PartialEq)] pub enum Value { Number(Option, i128), } + +impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Value +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + match self { + Value::Number(opt_base, value) => { + let value_str = match opt_base { + None => format!("{}", value), + Some(2) => format!("0b{:b}", value), + Some(8) => format!("0o{:o}", value), + Some(10) => format!("0d{}", value), + Some(16) => format!("0x{:x}", value), + Some(_) => format!("!!{:x}!!", value), + }; + + allocator.text(value_str) + } + } + } +} + +#[derive(Clone, Copy)] +struct CommaSep {} + +impl<'a, 'b, D, A> Pretty<'a, D, A> for CommaSep +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + allocator.text(",").append(allocator.space()) + } +}