Skip to content

Commit f60f661

Browse files
authored
Merge pull request #423 from yeslogic/wezm/pretty-core
Add pretty printer for core language
2 parents 1393404 + bc830e6 commit f60f661

File tree

6 files changed

+418
-4
lines changed

6 files changed

+418
-4
lines changed

fathom/src/core.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::env::{Index, Level};
44
use crate::source::{Span, StringId};
55

66
pub mod binary;
7+
pub mod pretty;
78
pub mod prim;
89
pub mod semantics;
910

fathom/src/core/pretty.rs

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
//! A pretty printer for the core language
2+
//!
3+
//! This is mainly intended for debugging.
4+
//!
5+
//! Example:
6+
//!
7+
//! ```
8+
//! use codespan_reporting::term::termcolor::{BufferedStandardStream, ColorChoice};
9+
//! use fathom::core::pretty::Context;
10+
//! use fathom::core::Module;
11+
//! use fathom::source::StringInterner;
12+
//! use std::cell::RefCell;
13+
//! use std::io::Write;
14+
//!
15+
//! // These are created for demonstration
16+
//! let interner = RefCell::new(StringInterner::new());
17+
//! let module = Module { items: &[] };
18+
//!
19+
//! let pp = Context::new(&interner);
20+
//! let doc = pp.module(&module);
21+
//! let mut stream = BufferedStandardStream::stdout(ColorChoice::Auto);
22+
//! let emit_width = 100;
23+
//! writeln!(stream, "{}", doc.pretty(emit_width)).unwrap();
24+
//! stream.flush().unwrap();
25+
//! ```
26+
27+
use pretty::RcDoc;
28+
use std::cell::RefCell;
29+
30+
use crate::core::{Item, Module, Term};
31+
use crate::source::{StringId, StringInterner};
32+
33+
/// Term precedences
34+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
35+
enum Prec {
36+
Top = 0,
37+
Let,
38+
Group,
39+
Fun,
40+
App,
41+
Proj,
42+
Atomic,
43+
}
44+
45+
const INDENT: isize = 4;
46+
47+
pub struct Context<'interner> {
48+
interner: &'interner RefCell<StringInterner>,
49+
}
50+
51+
impl<'interner, 'arena> Context<'interner> {
52+
pub fn new(interner: &'interner RefCell<StringInterner>) -> Context<'interner> {
53+
Context { interner }
54+
}
55+
56+
fn string_id(&'arena self, name: StringId) -> RcDoc {
57+
match self.interner.borrow().resolve(name) {
58+
Some(name) => RcDoc::text(name.to_owned()),
59+
None => RcDoc::text("#error"),
60+
}
61+
}
62+
63+
pub fn module(&'arena self, module: &Module<'arena>) -> RcDoc {
64+
RcDoc::intersperse(
65+
module.items.iter().map(|item| self.item(item)),
66+
RcDoc::hardline(),
67+
)
68+
}
69+
70+
fn item(&'arena self, item: &Item<'arena>) -> RcDoc {
71+
match item {
72+
Item::Def {
73+
label,
74+
r#type,
75+
expr,
76+
} => RcDoc::concat([
77+
RcDoc::text("def"),
78+
RcDoc::space(),
79+
self.ann_pattern(Prec::Top, Some(*label), r#type),
80+
RcDoc::space(),
81+
RcDoc::text("="),
82+
RcDoc::softline(),
83+
self.term_prec(Prec::Group, expr),
84+
RcDoc::text(";"),
85+
RcDoc::hardline(),
86+
])
87+
.group(),
88+
}
89+
}
90+
91+
fn pattern(&'arena self, pattern: Option<StringId>) -> RcDoc {
92+
match pattern {
93+
Some(name) => self.string_id(name),
94+
None => RcDoc::text("_"),
95+
}
96+
}
97+
98+
fn ann_pattern(
99+
&'arena self,
100+
prec: Prec,
101+
pattern: Option<StringId>,
102+
r#type: &Term<'arena>,
103+
) -> RcDoc {
104+
self.paren(
105+
prec > Prec::Top,
106+
RcDoc::concat([
107+
RcDoc::concat([self.pattern(pattern), RcDoc::space(), RcDoc::text(":")]).group(),
108+
RcDoc::softline(),
109+
self.term_prec(Prec::Top, r#type),
110+
]),
111+
)
112+
}
113+
114+
pub fn term(&'arena self, term: &Term<'arena>) -> RcDoc {
115+
self.term_prec(Prec::Top, term)
116+
}
117+
118+
fn term_prec(&'arena self, prec: Prec, term: &Term<'arena>) -> RcDoc {
119+
// FIXME: indentation and grouping
120+
121+
match term {
122+
Term::ItemVar(_, level) => RcDoc::text(format!("Item({})", level)),
123+
Term::LocalVar(_, index) => RcDoc::text(format!("Local({})", index)),
124+
Term::MetaVar(_, index) => RcDoc::text(format!("Meta({})", index)),
125+
Term::InsertedMeta(_, level, info) => {
126+
RcDoc::text(format!("InsertedMeta({:?}, {:?})", level, info))
127+
}
128+
Term::Ann(_, expr, r#type) => self.paren(
129+
prec > Prec::Top,
130+
RcDoc::concat([
131+
RcDoc::concat([
132+
self.term_prec(Prec::Let, expr),
133+
RcDoc::space(),
134+
RcDoc::text(":"),
135+
])
136+
.group(),
137+
RcDoc::softline(),
138+
self.term_prec(Prec::Top, r#type),
139+
]),
140+
),
141+
Term::Let(_, def_pattern, def_type, def_expr, body_expr) => self.paren(
142+
prec > Prec::Let,
143+
RcDoc::concat([
144+
RcDoc::concat([
145+
RcDoc::text("let"),
146+
RcDoc::space(),
147+
self.ann_pattern(Prec::Top, *def_pattern, *def_type),
148+
RcDoc::space(),
149+
RcDoc::text("="),
150+
RcDoc::softline(),
151+
self.term_prec(Prec::Let, def_expr),
152+
RcDoc::text(";"),
153+
])
154+
.group(),
155+
RcDoc::line(),
156+
self.term_prec(Prec::Let, body_expr),
157+
]),
158+
),
159+
Term::Universe(_) => RcDoc::text("Type"),
160+
Term::FunType(_, param_name, param_type, body_type) => self.paren(
161+
prec > Prec::Fun,
162+
RcDoc::concat([
163+
RcDoc::concat([
164+
RcDoc::text("fun"),
165+
// TODO: Share with Term::Ann
166+
self.paren(
167+
prec > Prec::Top,
168+
RcDoc::concat([
169+
RcDoc::concat([
170+
if let Some(name) = param_name {
171+
self.string_id(*name)
172+
} else {
173+
RcDoc::nil()
174+
},
175+
RcDoc::space(),
176+
RcDoc::text(":"),
177+
])
178+
.group(),
179+
RcDoc::softline(),
180+
self.term_prec(Prec::Top, param_type),
181+
]),
182+
),
183+
RcDoc::space(),
184+
RcDoc::text("->"),
185+
])
186+
.group(),
187+
RcDoc::softline(),
188+
self.term_prec(Prec::Fun, body_type),
189+
]),
190+
),
191+
Term::FunLit(_, param_name, body_expr) => self.paren(
192+
prec > Prec::Fun,
193+
RcDoc::concat([
194+
RcDoc::concat([
195+
RcDoc::text("fun"),
196+
if let Some(name) = param_name {
197+
self.string_id(*name)
198+
} else {
199+
RcDoc::nil()
200+
},
201+
RcDoc::space(),
202+
RcDoc::text("=>"),
203+
])
204+
.group(),
205+
RcDoc::space(),
206+
self.term_prec(Prec::Let, body_expr),
207+
]),
208+
),
209+
Term::FunApp(_, head_expr, arg_expr) => self.paren(
210+
prec > Prec::App,
211+
RcDoc::concat([
212+
self.term_prec(Prec::Proj, head_expr),
213+
RcDoc::space(),
214+
self.term_prec(Prec::Proj, arg_expr),
215+
]),
216+
),
217+
Term::RecordType(_, labels, types) => self.sequence(
218+
RcDoc::text("{"),
219+
labels.iter().zip(types.iter()).map(|(&label, type_)| {
220+
RcDoc::concat([
221+
self.string_id(label),
222+
RcDoc::space(),
223+
RcDoc::text(":"),
224+
RcDoc::space(),
225+
self.term_prec(Prec::Top, type_),
226+
])
227+
}),
228+
RcDoc::text(","),
229+
RcDoc::text("}"),
230+
),
231+
Term::RecordLit(_, labels, exprs) => self.sequence(
232+
RcDoc::text("{"),
233+
labels.iter().zip(exprs.iter()).map(|(&label, expr)| {
234+
RcDoc::concat([
235+
self.string_id(label),
236+
RcDoc::space(),
237+
RcDoc::text("="),
238+
RcDoc::space(),
239+
self.term_prec(Prec::Top, expr),
240+
])
241+
}),
242+
RcDoc::text(","),
243+
RcDoc::text("}"),
244+
),
245+
// Term::UnitLiteral(_) => RcDoc::text("{}"),
246+
Term::RecordProj(_, head_expr, label) => RcDoc::concat([
247+
self.term_prec(Prec::Atomic, head_expr),
248+
RcDoc::text(".").append(self.string_id(*label)),
249+
]),
250+
Term::ArrayLit(_, exprs) => self.sequence(
251+
RcDoc::text("["),
252+
exprs.iter().map(|expr| self.term_prec(Prec::Top, expr)),
253+
RcDoc::text(","),
254+
RcDoc::text("]"),
255+
),
256+
Term::ConstLit(_, const_) => RcDoc::text(format!("{:?}", const_)),
257+
Term::FormatRecord(_, labels, formats) => self.sequence(
258+
RcDoc::text("{"),
259+
labels
260+
.iter()
261+
.zip(formats.iter())
262+
.map(|(&label, format)| self.format_field(label, format)),
263+
RcDoc::text(","),
264+
RcDoc::text("}"),
265+
),
266+
Term::FormatCond(_, label, format, cond) => RcDoc::concat([
267+
RcDoc::text("{"),
268+
RcDoc::space(),
269+
self.string_id(*label),
270+
RcDoc::space(),
271+
RcDoc::text("<-"),
272+
RcDoc::space(),
273+
self.term_prec(Prec::Top, format),
274+
RcDoc::space(),
275+
RcDoc::text("|"),
276+
RcDoc::space(),
277+
self.term_prec(Prec::Top, cond),
278+
RcDoc::space(),
279+
RcDoc::text("}"),
280+
]),
281+
Term::FormatOverlap(_, labels, formats) => self.sequence(
282+
RcDoc::concat([RcDoc::text("overlap"), RcDoc::space(), RcDoc::text("{")]),
283+
labels
284+
.iter()
285+
.zip(formats.iter())
286+
.map(|(&label, format)| self.format_field(label, format)),
287+
RcDoc::text(","),
288+
RcDoc::text("}"),
289+
),
290+
Term::Prim(_, prim) => RcDoc::text(format!("{:?}", prim)),
291+
Term::ConstMatch(_, scrutinee, branches, default_expr) => self.sequence(
292+
RcDoc::concat([
293+
RcDoc::text("match"),
294+
RcDoc::space(),
295+
self.term_prec(Prec::Proj, scrutinee),
296+
RcDoc::space(),
297+
RcDoc::text("{"),
298+
]),
299+
branches
300+
.iter()
301+
.map(|(pattern, body_expr)| {
302+
RcDoc::concat([
303+
RcDoc::text(format!("{:?}", pattern)),
304+
RcDoc::space(),
305+
RcDoc::text("=>"),
306+
RcDoc::space(),
307+
self.term_prec(Prec::Top, body_expr),
308+
])
309+
})
310+
.chain(default_expr.iter().map(|&(name, default)| {
311+
RcDoc::concat([
312+
match name {
313+
Some(name) => self.string_id(name),
314+
None => RcDoc::text("_"),
315+
},
316+
RcDoc::space(),
317+
RcDoc::text("=>"),
318+
RcDoc::space(),
319+
self.term_prec(Prec::Top, default),
320+
])
321+
}))
322+
.collect::<Vec<_>>()
323+
.into_iter(),
324+
RcDoc::text(","),
325+
RcDoc::text("}"),
326+
),
327+
}
328+
}
329+
330+
fn format_field(&'arena self, label: StringId, format: &Term<'arena>) -> RcDoc {
331+
RcDoc::concat([
332+
self.string_id(label),
333+
RcDoc::space(),
334+
RcDoc::text("<-"),
335+
RcDoc::space(),
336+
self.term_prec(Prec::Top, format),
337+
])
338+
}
339+
340+
/// Wrap a document in parens.
341+
fn paren(&'arena self, wrap: bool, doc: RcDoc<'arena>) -> RcDoc {
342+
if wrap {
343+
RcDoc::concat([RcDoc::text("("), doc, RcDoc::text(")")])
344+
} else {
345+
doc
346+
}
347+
}
348+
349+
/// Pretty prints a delimited sequence of documents with a trailing
350+
/// separator if it is formatted over multiple lines.
351+
pub fn sequence(
352+
&'arena self,
353+
start_delim: RcDoc<'arena>,
354+
docs: impl ExactSizeIterator<Item = RcDoc<'arena, ()>> + Clone,
355+
separator: RcDoc<'arena>,
356+
end_delim: RcDoc<'arena>,
357+
) -> RcDoc {
358+
if docs.len() == 0 {
359+
RcDoc::concat([start_delim, end_delim])
360+
} else {
361+
RcDoc::flat_alt(
362+
RcDoc::concat([
363+
start_delim.clone(),
364+
RcDoc::concat(
365+
docs.clone()
366+
.map(|doc| RcDoc::concat([RcDoc::hardline(), doc, separator.clone()])),
367+
)
368+
.nest(INDENT),
369+
RcDoc::hardline(),
370+
end_delim.clone(),
371+
]),
372+
RcDoc::concat([
373+
start_delim,
374+
RcDoc::space(),
375+
RcDoc::intersperse(docs, RcDoc::concat([separator, RcDoc::space()])),
376+
RcDoc::space(),
377+
end_delim,
378+
]),
379+
)
380+
.group()
381+
}
382+
}
383+
}

0 commit comments

Comments
 (0)