mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user