[rust] Pretty-printer for HIR

Implements a pretty-printer for the HIR and switches the fixture tests to use 
this instead of the debug format. It's much more readable now! 

Note that not all types are properly printed, I only implemented the 
instructions and terminals used in the example. For others we fall back to the 
Debug impl so we at least print something.
This commit is contained in:
Joe Savona
2023-07-06 09:24:47 +09:00
parent d3b9948b5e
commit 08e51d3fd7
7 changed files with 192 additions and 557 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
function test() {
[true, false, null, 1, 3.14, "hello world!"];
[true, false, null, 1, 3.14, ...["hello world!"]];
return 2;
}
@@ -2,7 +2,7 @@ use build_hir::build;
use bumpalo::Bump;
use estree::{ModuleItem, Statement};
use estree_swc::parse;
use hir::{Environment, Registry};
use hir::{Environment, Print, Registry};
use insta::{assert_snapshot, glob};
#[test]
@@ -11,9 +11,9 @@ fn fixtures() {
let input = std::fs::read_to_string(path).unwrap();
let ast = parse(&input, path.to_str().unwrap()).unwrap();
let mut output = Vec::new();
let mut output = String::new();
for item in ast.body {
for (ix, item) in ast.body.into_iter().enumerate() {
if let ModuleItem::Statement(stmt) = item {
if let Statement::FunctionDeclaration(fun) = *stmt {
let allocator = Bump::new();
@@ -25,12 +25,15 @@ fn fixtures() {
Registry,
));
let hir = build(&environment, *fun).unwrap();
output.push(format!("{hir:#?}"));
if ix != 0 {
output.push_str("\n\n");
}
hir.print(&mut output).unwrap();
}
}
}
let joined = output.join("\n\n");
assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{joined}"));
assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{output}"));
});
}
@@ -1,560 +1,26 @@
---
source: crates/fixtures/tests/fixtures_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{joined}\")"
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
input_file: crates/fixtures/tests/fixtures/simple.js
---
Input:
function test() {
[true, false, null, 1, 3.14, "hello world!"];
[true, false, null, 1, 3.14, ...["hello world!"]];
return 2;
}
Output:
Function {
body: HIR {
entry: BlockId(
0,
),
blocks: {
BlockId(
0,
): BasicBlock {
id: BlockId(
0,
),
kind: Block,
instructions: [
Instruction {
id: InstructionId(
0,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
0,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
0,
),
),
},
},
},
effect: None,
},
value: Primitive(
Primitive {
value: Boolean(
true,
),
},
),
},
Instruction {
id: InstructionId(
1,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
1,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
1,
),
),
},
},
},
effect: None,
},
value: Primitive(
Primitive {
value: Boolean(
false,
),
},
),
},
Instruction {
id: InstructionId(
2,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
2,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
2,
),
),
},
},
},
effect: None,
},
value: Primitive(
Primitive {
value: Null,
},
),
},
Instruction {
id: InstructionId(
3,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
3,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
3,
),
),
},
},
},
effect: None,
},
value: Primitive(
Primitive {
value: Number(
Number(
4607182418800017408,
),
),
},
),
},
Instruction {
id: InstructionId(
4,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
4,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
4,
),
),
},
},
},
effect: None,
},
value: Primitive(
Primitive {
value: Number(
Number(
4614253070214989087,
),
),
},
),
},
Instruction {
id: InstructionId(
5,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
5,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
5,
),
),
},
},
},
effect: None,
},
value: Primitive(
Primitive {
value: String(
"hello world!",
),
},
),
},
Instruction {
id: InstructionId(
6,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
6,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
6,
),
),
},
},
},
effect: None,
},
value: Array(
Array {
elements: [
Place(
Place {
identifier: Identifier {
id: IdentifierId(
0,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
0,
),
),
},
},
},
effect: None,
},
),
Place(
Place {
identifier: Identifier {
id: IdentifierId(
1,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
1,
),
),
},
},
},
effect: None,
},
),
Place(
Place {
identifier: Identifier {
id: IdentifierId(
2,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
2,
),
),
},
},
},
effect: None,
},
),
Place(
Place {
identifier: Identifier {
id: IdentifierId(
3,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
3,
),
),
},
},
},
effect: None,
},
),
Place(
Place {
identifier: Identifier {
id: IdentifierId(
4,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
4,
),
),
},
},
},
effect: None,
},
),
Place(
Place {
identifier: Identifier {
id: IdentifierId(
5,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
5,
),
),
},
},
},
effect: None,
},
),
],
},
),
},
Instruction {
id: InstructionId(
7,
),
lvalue: Place {
identifier: Identifier {
id: IdentifierId(
7,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
7,
),
),
},
},
},
effect: None,
},
value: Primitive(
Primitive {
value: Number(
Number(
4611686018427387904,
),
),
},
),
},
],
terminal: Terminal {
id: InstructionId(
8,
),
value: ReturnTerminal(
ReturnTerminal {
value: Place {
identifier: Identifier {
id: IdentifierId(
7,
),
name: None,
data: RefCell {
value: IdentifierData {
mutable_range: MutableRange {
start: InstructionId(
0,
),
end: InstructionId(
0,
),
},
scope: None,
type_: Var(
TypeVarId(
7,
),
),
},
},
},
effect: None,
},
},
),
},
predecessors: {},
},
},
},
is_async: false,
is_generator: false,
}
entry bb0
bb0
[0] true
[1] false
[2] null
[3] 1
[4] 3.14
[5] "hello world!"
[6] Array [unknown $5]
[7] Array [unknown $0, unknown $1, unknown $2, unknown $3, unknown $4, ...unknown $6]
[8] 2
[9] Return unknown $8
@@ -1,3 +1,5 @@
use std::fmt::Display;
/// Unique identifier for a basic block. Values are unique only with respect to
/// a single top-level function, and may be reused across different top-level
/// functions. Notably, ids *are* unique across the basic blocks of a function
@@ -12,6 +14,12 @@ impl BlockId {
}
}
impl Display for BlockId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("bb{}", self.0))
}
}
/// Unique identifier for a variable within a program. This is used to distinguish
/// different instances of a variable with the same name in different scopes, or
/// even the same named identifier across reassignments (when in SSA form).
@@ -24,6 +32,12 @@ impl IdentifierId {
}
}
impl Display for IdentifierId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("${}", self.0))
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
pub struct TypeVarId(pub(crate) u32);
@@ -42,6 +56,12 @@ impl TypeVarId {
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
pub struct InstructionId(pub(crate) u32);
impl Display for InstructionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("[{}]", self.0))
}
}
pub struct InstructionIdGenerator(u32);
impl InstructionIdGenerator {
+14 -1
View File
@@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, fmt::Display, rc::Rc};
use bumpalo::collections::{String, Vec};
@@ -176,6 +176,19 @@ impl Effect {
}
}
impl Display for Effect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Effect::Capture => "capture",
Effect::ConditionallyMutate => "mutate?",
Effect::Freeze => "freeze",
Effect::Mutate => "mutate",
Effect::Read => "read",
Effect::Store => "store",
})
}
}
#[derive(Clone, Debug)]
pub struct Identifier<'a> {
/// Uniquely identifiers this identifier
+2
View File
@@ -4,6 +4,7 @@ mod features;
mod function;
mod id_types;
mod instruction;
mod print;
mod registry;
mod terminal;
mod types;
@@ -14,6 +15,7 @@ pub use features::*;
pub use function::*;
pub use id_types::*;
pub use instruction::*;
pub use print::Print;
pub use registry::Registry;
pub use terminal::*;
pub use types::*;
+131
View File
@@ -0,0 +1,131 @@
use std::fmt::{Result, Write};
use crate::{
ArrayElement, BasicBlock, Function, Instruction, InstructionValue, Place, PrimitiveValue,
Terminal, TerminalValue,
};
/// Trait for HIR types to describe how they print themselves.
/// Eventually we should add a higher-level abstraction for printing to
/// handle things like indentation and maybe wrapping long lines. The
/// `pretty` crate seems to have a lot of usage but the type signatures
/// are pretty tedious, we can make something much simpler.
pub trait Print {
fn print(&self, out: &mut impl Write) -> Result;
}
impl<'a> Print for Function<'a> {
fn print(&self, out: &mut impl Write) -> Result {
writeln!(out, "entry {}", self.body.entry)?;
for block in self.body.blocks.values() {
block.print(out)?;
}
Ok(())
}
}
impl<'a> Print for BasicBlock<'a> {
fn print(&self, out: &mut impl Write) -> Result {
writeln!(out, "{}", self.id)?;
for instr in &self.instructions {
instr.print(out)?;
}
self.terminal.print(out)?;
Ok(())
}
}
impl<'a> Print for Instruction<'a> {
fn print(&self, out: &mut impl Write) -> Result {
write!(out, " {} ", self.id)?;
self.value.print(out)?;
writeln!(out, "")?;
Ok(())
}
}
impl<'a> Print for InstructionValue<'a> {
fn print(&self, out: &mut impl Write) -> Result {
match self {
InstructionValue::Array(value) => {
write!(out, "Array [")?;
for (ix, item) in value.elements.iter().enumerate() {
if ix != 0 {
write!(out, ", ")?;
}
item.print(out)?;
}
write!(out, "]")?;
}
InstructionValue::Primitive(value) => {
// Unlike other variants we don't print the variant name ("Primitive") since it's
// obvious
match &value.value {
PrimitiveValue::Boolean(value) => write!(out, "{}", value)?,
PrimitiveValue::Null => write!(out, "null")?,
PrimitiveValue::Number(value) => write!(out, "{}", f64::from(*value))?,
// TODO: quote the string itself (JS version uses JSON.stringify())
PrimitiveValue::String(value) => write!(out, "\"{}\"", value.as_str())?,
PrimitiveValue::Undefined => write!(out, "<undefined>")?,
};
}
_ => write!(out, "{:?}", self)?,
}
Ok(())
}
}
impl<'a> Print for ArrayElement<'a> {
fn print(&self, out: &mut impl Write) -> Result {
match self {
ArrayElement::Place(place) => place.print(out),
ArrayElement::Spread(place) => {
write!(out, "...")?;
place.print(out)?;
Ok(())
}
}
}
}
impl<'a> Print for Place<'a> {
fn print(&self, out: &mut impl Write) -> Result {
write!(
out,
"{} {}{}",
match self.effect {
Some(effect) => format!("{}", effect),
None => "unknown".to_string(),
},
match &self.identifier.name {
Some(name) => name.to_string(),
None => "".to_string(),
},
self.identifier.id
)
}
}
impl<'a> Print for Terminal<'a> {
fn print(&self, out: &mut impl Write) -> Result {
write!(out, " {} ", self.id)?;
self.value.print(out)?;
writeln!(out, "")?;
Ok(())
}
}
impl<'a> Print for TerminalValue<'a> {
fn print(&self, out: &mut impl Write) -> Result {
match self {
TerminalValue::ReturnTerminal(terminal) => {
write!(out, "Return ")?;
terminal.value.print(out)?;
}
_ => write!(out, "{:?}", self)?,
}
Ok(())
}
}