From b2d9622febcfe178588f655b5c2887c3cf93f0e0 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Thu, 28 Oct 2021 02:03:01 -0700 Subject: [PATCH] Basic visualizer for AST Pretty-prints a representation of the AST --- schala-lang/language/src/ast/mod.rs | 32 ++- schala-lang/language/src/ast/visualize.rs | 268 ++++++++++++++++++++++ 2 files changed, 292 insertions(+), 8 deletions(-) create mode 100644 schala-lang/language/src/ast/visualize.rs diff --git a/schala-lang/language/src/ast/mod.rs b/schala-lang/language/src/ast/mod.rs index fe06699..d5fa0c4 100644 --- a/schala-lang/language/src/ast/mod.rs +++ b/schala-lang/language/src/ast/mod.rs @@ -3,11 +3,13 @@ use std::{ convert::{AsRef, From}, + fmt, rc::Rc, }; mod operators; mod visitor; +mod visualize; pub use operators::{BinOp, PrefixOp}; pub use visitor::*; @@ -20,14 +22,6 @@ use crate::{ define_id_kind!(ASTItem); -/* -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] -pub struct ASTItem; -impl IdKind for ASTItem { - fn tag() -> &'static str { "ASTItem" } -} -*/ - pub type ItemId = Id; #[derive(Derivative, Debug)] @@ -38,6 +32,12 @@ pub struct AST { pub statements: Block, } +impl fmt::Display for AST { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", visualize::render_ast(self)) + } +} + #[derive(Derivative, Debug, Clone)] #[derivative(PartialEq)] pub struct Statement { @@ -89,6 +89,22 @@ pub struct QualifiedName { pub components: Vec>, } +impl fmt::Display for QualifiedName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.components[..] { + [] => write!(f, "[]"), + [name] => write!(f, "{}", name), + [name, rest @ ..] => { + write!(f, "{}", name)?; + for c in rest { + write!(f, "::{}", c)?; + } + Ok(()) + } + } + } +} + #[derive(Debug, PartialEq, Clone)] pub struct FormalParam { pub name: ParamName, diff --git a/schala-lang/language/src/ast/visualize.rs b/schala-lang/language/src/ast/visualize.rs new file mode 100644 index 0000000..a6c312e --- /dev/null +++ b/schala-lang/language/src/ast/visualize.rs @@ -0,0 +1,268 @@ +use super::{ + Block, Declaration, Expression, ExpressionKind, ImportSpecifier, InvocationArgument, ModuleSpecifier, + Signature, Statement, StatementKind, AST, +}; + +const LEVEL: usize = 2; + +fn do_indent(n: usize, buf: &mut String) { + for _ in 0..n { + buf.push(' '); + } +} + +fn newline(buf: &mut String) { + buf.push('\n'); +} + +pub(super) fn render_ast(ast: &AST) -> String { + let AST { statements, .. } = ast; + + let mut buf = format!("(AST\n"); + render_block(&statements, LEVEL, &mut buf); + + buf.push(')'); + + buf +} + +fn render_statement(stmt: &Statement, indent: usize, buf: &mut String) { + use StatementKind::*; + do_indent(indent, buf); + match stmt.kind { + Expression(ref expr) => render_expression(expr, indent, buf), + Declaration(ref decl) => render_declaration(decl, indent, buf), + Import(ref spec) => render_import(spec, indent, buf), + Module(ref spec) => render_module(spec, indent, buf), + } +} + +fn render_expression(expr: &Expression, indent: usize, buf: &mut String) { + use ExpressionKind::*; + + buf.push_str("(Expr "); + match &expr.kind { + NatLiteral(n) => buf.push_str(&format!("(NatLiteral {})", n)), + FloatLiteral(f) => buf.push_str(&format!("(FloatLiteral {})", f)), + StringLiteral(s) => buf.push_str(&format!("(StringLiteral {})", s)), + BoolLiteral(b) => buf.push_str(&format!("(BoolLiteral {})", b)), + BinExp(binop, lhs, rhs) => { + let new_indent = indent + LEVEL; + buf.push_str(&format!("Binop {}\n", binop.sigil())); + + do_indent(new_indent, buf); + render_expression(lhs, new_indent, buf); + newline(buf); + + do_indent(new_indent, buf); + render_expression(rhs, new_indent, buf); + newline(buf); + + do_indent(indent, buf); + } + PrefixExp(prefix, expr) => { + let new_indent = indent + LEVEL; + buf.push_str(&format!("PrefixOp {}\n", prefix.sigil())); + + do_indent(new_indent, buf); + render_expression(expr, new_indent, buf); + newline(buf); + + do_indent(indent, buf); + } + TupleLiteral(..) => (), + Value(name) => { + buf.push_str(&format!("Value {})", name)); + } + NamedStruct { name: _, fields: _ } => (), + Call { f, arguments } => { + let new_indent = indent + LEVEL; + buf.push_str("Call "); + render_expression(f, new_indent, buf); + newline(buf); + + for arg in arguments { + do_indent(new_indent, buf); + match arg { + InvocationArgument::Positional(expr) => render_expression(expr, new_indent, buf), + InvocationArgument::Keyword { .. } => buf.push_str(""), + InvocationArgument::Ignored => buf.push_str(""), + } + newline(buf); + do_indent(indent, buf); + } + } + Index { .. } => buf.push_str(""), + IfExpression { .. } => buf.push_str(""), + WhileExpression { .. } => buf.push_str(""), + ForExpression { .. } => buf.push_str(""), + Lambda { params, type_anno: _, body } => { + let new_indent = indent + LEVEL; + buf.push_str("Lambda "); + newline(buf); + + do_indent(new_indent, buf); + buf.push_str("(Args "); + for p in params { + buf.push_str(&format!("{} ", p.name)); + } + buf.push(')'); + newline(buf); + + do_indent(new_indent, buf); + buf.push_str("(Body "); + newline(buf); + render_block(body, new_indent + LEVEL, buf); + do_indent(new_indent, buf); + buf.push(')'); + + newline(buf); + do_indent(indent, buf); + } + ListLiteral(..) => buf.push_str(""), + } + buf.push(')'); +} +fn render_declaration(decl: &Declaration, indent: usize, buf: &mut String) { + use Declaration::*; + + buf.push_str("(Decl "); + match decl { + FuncSig(ref sig) => render_signature(sig, indent, buf), + FuncDecl(ref sig, ref block) => { + let indent = indent + LEVEL; + buf.push_str("Function"); + newline(buf); + + do_indent(indent, buf); + render_signature(sig, indent, buf); + newline(buf); + + do_indent(indent, buf); + buf.push_str("(Body"); + newline(buf); + + render_block(block, indent + LEVEL, buf); + do_indent(indent, buf); + buf.push_str(")"); + newline(buf); + } + TypeDecl { name: _, body: _, .. } => { + buf.push_str(""); + } + TypeAlias { alias: _, original: _ } => { + buf.push_str(""); + } + Binding { name, constant: _, type_anno: _, expr } => { + let new_indent = indent + LEVEL; + buf.push_str(&format!("Binding {}", name)); + newline(buf); + do_indent(new_indent, buf); + render_expression(expr, new_indent, buf); + newline(buf); + } + _ => (), /* + Impl { type_name: TypeIdentifier, interface_name: Option, block: Vec }, + Interface { name: Rc, signatures: Vec }, + Annotation { name: Rc, arguments: Vec }, + */ + } + do_indent(indent, buf); + buf.push_str(")"); +} + +fn render_block(block: &Block, indent: usize, buf: &mut String) { + for stmt in block.statements.iter() { + render_statement(&stmt, indent, buf); + newline(buf); + } +} + +fn render_signature(sig: &Signature, _indent: usize, buf: &mut String) { + buf.push_str(&format!("(Signature {} )", sig.name)); +} + +fn render_import(_import: &ImportSpecifier, _indent: usize, buf: &mut String) { + buf.push_str("(Import )"); +} +fn render_module(_expr: &ModuleSpecifier, _indent: usize, buf: &mut String) { + buf.push_str("(Module )"); +} + +#[cfg(test)] +mod test { + use super::render_ast; + use crate::util::quick_ast; + + #[test] + fn test_visualization() { + let ast = quick_ast( + r#" + fn test(x) { + let m = 9 + 1 * 4 <> m |> somemod::output(x) + } + + let quincy = \(no, yes, maybe) { + let a = 10 + yes * no + a + } + + let b = 54 + test(b) == 3 + "#, + ); + + let expected_output = r#"(AST + (Decl Function + (Signature test ) + (Body + (Decl Binding m + (Expr (NatLiteral 9)) + ) + (Expr Binop * + (Expr (NatLiteral 1)) + (Expr Binop |> + (Expr Binop <> + (Expr (NatLiteral 4)) + (Expr Value m)) + ) + (Expr Call (Expr Value somemod::output)) + (Expr Value x)) + ) + ) + ) + ) + ) + (Decl Binding quincy + (Expr Lambda + (Args no yes maybe ) + (Body + (Decl Binding a + (Expr (NatLiteral 10)) + ) + (Expr Binop + + (Expr Binop * + (Expr Value yes)) + (Expr Value no)) + ) + (Expr Value a)) + ) + ) + ) + ) + (Decl Binding b + (Expr (NatLiteral 54)) + ) + (Expr Binop == + (Expr Call (Expr Value test)) + (Expr Value b)) + ) + (Expr (NatLiteral 3)) + ) +)"#; + + let rendered = render_ast(&ast); + assert_eq!(rendered, expected_output); + } +}