MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
dump.cpp
Go to the documentation of this file.
1#include <fstream>
2#include <ostream>
3#include <ranges>
4
5#include <fe/assert.h>
6
7#include "mim/driver.h"
8#include "mim/nest.h"
9
10#include "mim/ast/lexer.h"
11#include "mim/ast/tok.h"
12
13using namespace std::literals;
14
15// A dump prints a Def that appears as an operand in one of two ways:
16// * Full: spelled out with all of its operands, e.g. `(1, 2, 3)`; see Full::is_full.
17// * By name (see id): everything else; a `let` - or, for a decl (see isa_decl), a declaration of its own - binds it.
18// A mutable that is no decl is printed like an immutable.
19
20namespace mim {
21
22namespace {
23
24using Srcs = ankerl::unordered_dense::set<const fe::Src*>;
25
26/// An `anx nom` carries its qualified `<file>.<name>` sym, which no identifier can spell; see Annexes::attach.
27bool is_anx_nom(const Def* def) { return def->isa<Nom>() && def->sym().view().contains('.'); }
28
29Def* isa_decl(const Def* def) {
30 if (auto mut = def->isa_mut()) {
31 if (mut->isa<Hole>()) return nullptr; // a Hole stands for what it was set to, or for `?`
32 if (mut->is_external() || mut->isa<Lam>() || (mut->sym() && mut->sym() != '_')) return mut;
33 }
34 return nullptr;
35}
36
37/// Each curried argument of an application and whether its Pi is implicit.
38using Args = fe::Vector<std::pair<DefVec, bool>>;
39
40/// What the running dump decided about the Def%s it emits; `nullptr` where a diagnostic formats a bare Def.
41struct Ctx {
42 /// Names the dump binds itself: a `fun` calls its `ret` continuation `return`, whatever its Def::sym.
43 /// Keyed by Var and index, because the projection itself need not exist as a Def while the World is frozen.
44 DefMap<std::string> names;
45 DefMap<nat_t> ret_projs; ///< Var -> index of the `ret` component within it.
46 DefMap<bool> split; ///< Whether a pattern took a Def apart, so that its components print by name.
47 DefSet bound; ///< Projections of a dependent Sigma's Var a pattern binds in place of a dead component.
48 DefSet whole_vars; ///< Var%s used as a whole; see isa_fun.
49 DefSet arms; ///< Lam%s a `match` spells out as its arms instead of declaring them.
50 DefSet header; ///< Def%s a Lam's header prints inline, as it precedes the block that `let`s them.
51 DefSet inlined; ///< Def%s inside a binder the dump prints *inline* - they have no block to `let` them.
52 /// A Lam's codomain refers to its Pi's Var - or its dependent domain's - but only the Lam's Var is bound by name.
53 DefMap<const Def*> aliases;
54 /// Distinct identifiers picked instead of Def::unique_name, so a re-read dump keeps them as its Def::sym%s.
55 ankerl::unordered_dense::set<std::string> taken;
56 ankerl::unordered_dense::map<std::string, size_t> counters; ///< Next numbered variant to try per base name.
57 fe::Tab tab = fe::Tab::spaces(); ///< Indentation of the line being printed.
58 bool plain = false;
59 bool in_header = false;
60 std::string self; ///< `<world name>.`: the dumped file's own annexes are spelled without it.
61 std::string mod; ///< `<mod>.` while declaring the Axm%s of that `mod`, which does not see its own name.
62 /// What the dumped roots reach; `nullptr` dumps whatever exists.
63 /// A dead projection left behind by normalization must not bind a name a re-read dump would not bind.
64 const DefSet* live = nullptr;
65 /// An instance of a generic `anx nom` from elsewhere: its annex's qualified name and arguments.
66 /// Each curried argument holds either the whole argument or the components a re-read dump tuples up again.
68};
69
70const std::pair<Sym, Args>* isa_inst(const Ctx* ctx, const Def* def) {
71 return ctx && def->isa<Nom>() ? fe::lookup(ctx->insts, def) : nullptr;
72}
73
74/// Does @p def print by name, bound by a declaration of its own - be it a decl or a `nom`?
75bool has_decl(const Ctx* ctx, const Def* def) {
76 return isa_decl(def) || (def->isa<Nom>() && !is_anx_nom(def) && !isa_inst(ctx, def));
77}
78
79/// The name an annex prints by: the dumped file's own annexes drop the `<file>.` - and the `<mod>.` - prefix.
80std::string annex_sym(Ctx* ctx, Sym annex) {
81 auto sym = annex.view(); // a short Sym is stored inline, so this view must not outlive @p annex
82 if (ctx && !ctx->self.empty() && sym.starts_with(ctx->self)) {
83 sym.remove_prefix(ctx->self.size());
84 if (!ctx->mod.empty() && sym.starts_with(ctx->mod)) sym.remove_prefix(ctx->mod.size());
85 }
86 return std::string(sym);
87}
88
89/// @p def's @p i-th of @p n projections, if it exists and the dump reaches it.
90const Def* live_proj(Ctx* ctx, const Def* def, nat_t n, nat_t i) {
91 auto proj = def->proj(n, i);
92 return proj && ctx && ctx->live && !ctx->live->contains(proj) ? nullptr : proj;
93}
94
95/// A numbered variant of @p base that no other name of the dump takes.
96std::string fresh_name(Ctx* ctx, const std::string& base) {
97 auto sep = base == "_" ? "" : "_";
98 auto& i = ctx->counters[base];
99 auto name = std::string();
100 do
101 name = std::format("{}{}{}", base, sep, ++i);
102 while (ctx->taken.contains(name));
103 ctx->taken.emplace(name);
104 return name;
105}
106
107std::string pick_name(Ctx* ctx, const Def* def) {
108 auto sym = def->sym();
109 auto base = sym && sym != '_' && ast::Lexer::is_id(sym.view()) ? sym.str() : "_"s;
110 // A numbered variant of an identifier is an identifier and never a keyword.
111 auto name = base == "_" || def->world().driver().keys().find(sym) || ctx->taken.contains(base)
112 ? fresh_name(ctx, base)
113 : base;
114 ctx->taken.emplace(name);
115 return ctx->names.emplace(def, name).first->second;
116}
117
118/// Did a pattern keep @p def whole?
119bool is_opaque(Ctx* ctx, const Def* def) {
120 auto split = fe::lookup(ctx->split, def);
121 return split && !*split;
122}
123
124/// The projection of the aliased Var that @p def - a projection of a Pi's Var - stands for; see Ctx::aliases.
125const Def* alias_proj(Ctx* ctx, const Def* def) {
126 if (auto ex = def->isa<Extract>())
127 if (auto var = fe::lookup(ctx->aliases, ex->tuple()); var && !is_opaque(ctx, var))
128 if (auto l = Lit::isa(ex->index())) return live_proj(ctx, var, ex->tuple()->num_tprojs(), *l);
129 return nullptr;
130}
131
132/// Whether @p ex prints as a bare name rather than `tuple#index`.
133bool by_name(Ctx* ctx, const Extract* ex) {
134 if (!ex->index()->isa<Lit>()) return false;
135 if (!ctx) return ex->tuple()->isa<Var>();
136 if (ctx->bound.contains(ex)) return true;
137 // A Var's component prints by name - unless the pattern that would have bound that name kept the Var whole.
138 if (is_opaque(ctx, ex->tuple())) return false;
139 // An aliased Var's component the Lam's Var does not hand out is spelled via the Lam's Var instead.
140 if (ctx->aliases.contains(ex->tuple()) && !alias_proj(ctx, ex)) return false;
141 return ex->tuple()->isa<Var>() || ctx->split.contains(ex->tuple());
142}
143
144/// Def::unique_name - or a plain name while a World is dumped or a diagnostic is being formatted, where a gid is noise.
145/// @p ctx is `nullptr` outside a running dump: there are no dump-bound names to consult then.
146std::string name(Ctx* ctx, const Def* def) {
147 if (ctx) {
148 if (auto n = fe::lookup(ctx->names, def)) return *n;
149 if (auto alias = fe::lookup(ctx->aliases, def)) return name(ctx, alias);
150 if (auto proj = alias_proj(ctx, def)) {
151 auto res = name(ctx, proj);
152 return ctx->names.emplace(def, std::move(res)).first->second;
153 }
154 if (auto ex = def->isa<Extract>())
155 if (auto i = fe::lookup(ctx->ret_projs, ex->tuple()); i && Lit::isa(ex->index()) == *i) return "return"s;
156 if (ctx->plain) return pick_name(ctx, def);
157 }
158 if (auto sym = def->sym(); sym && sym != '_' && PlainNames::claim(def->world().driver(), sym, def->gid()))
159 return sym.str();
160 return def->unique_name();
161}
162
163std::string id(Ctx* ctx, const Def* def) {
164 if (def->is_external() || (!def->is_set() && def->isa<Lam>())) return def->sym().str();
165 return name(ctx, def);
166}
167
168std::string_view external(const Def* def) {
169 if (def->is_external()) return "extern "sv;
170 return ""sv;
171}
172
173using Dump = Def::Dump;
174using ast::Assoc;
175using ast::Prec;
176using ast::prec_assoc;
177
178/// The name the surface syntax has for `Idx size`.
179std::optional<std::string_view> idx_name(nat_t size) {
180 // clang-format off
181 switch (size) {
182 case 0x0'0000'0002_n: return "Bool";
183 case 0x0'0000'0100_n: return "I8";
184 case 0x0'0001'0000_n: return "I16";
185 case 0x1'0000'0000_n: return "I32";
186 case 0_n: return "I64";
187 default: return std::nullopt;
188 }
189 // clang-format on
190}
191
192Prec def2prec(Ctx* ctx, const Def* def) {
193 if (auto ex = def->isa<Extract>()) return by_name(ctx, ex) ? Prec::Lit : Prec::Extract;
194 if (def->isa<Insert>()) return Prec::Ins;
195 if (def->isa<Join>()) return Prec::Union;
196 if (auto inj = def->isa<Inj>()) {
197 if (auto variant = inj->type()->isa<Variant>())
198 return variant->op(inj->index()) == def->world().sigma() ? Prec::Extract : Prec::App;
199 return Prec::Inj;
200 }
201 if (def->isa<Name>()) return Prec::Inj;
202 if (def->isa<Struc>()) return Prec::Prefix;
203 if (def->isa<Variant>()) return Prec::Lit; // always parenthesized
204 if (isa_inst(ctx, def) || def->isa<Reform>()) return Prec::App;
205 // `Cn e` parses its domain at Prec::Bot, so it swallows whatever follows and only ever fits a closed context.
206 if (auto pi = def->isa<Pi>()) return Pi::isa_cn(pi) ? Prec::Bot : Prec::Arrow;
207 if (def->isa<Lam>() || def->isa<Match>())
208 return Prec::Bot; // a λ- or match-expression swallows what follows it, too
209 if (auto app = def->isa<App>()) {
210 if (auto size = Idx::isa(app))
211 if (auto l = Lit::isa(size); l && idx_name(*l)) return Prec::Lit;
212 return Prec::App;
213 }
214
215 return Prec::Lit;
216}
217
218/// This is a wrapper to dump a Def.
219class Op {
220public:
221 Op(Ctx* ctx, const Def* def, Prec prec = Prec::Bot, bool is_left = false)
222 : ctx_(ctx)
223 , def_(def)
224 , prec_(prec)
225 , is_left_(is_left) {}
226
227 /// @name Sub-expressions
228 /// They inherit this Op's Ctx - which is how the running dump reaches the whole recursion.
229 ///@{
230 Op op(const Def* def, Prec prec = Prec::Bot, bool is_left = false) const { return {ctx_, def, prec, is_left}; }
231 Op l(const Def* def, Prec prec = Prec::Bot) const { return {ctx_, def, prec, true}; }
232 Op r(const Def* def, Prec prec = Prec::Bot) const { return {ctx_, def, prec, false}; }
233 ///@}
234
235 static auto map(Ctx* ctx, const auto& range, const char* sep = ", ", Prec prec = Prec::Bot) {
236 return fe::Join(range | std::views::transform([ctx, prec](auto op) { return Op(ctx, op, prec); }), sep);
237 }
238
239 /// @name Getters
240 ///@{
241 Ctx* ctx() const { return ctx_; }
242 Prec prec() const { return prec_; }
243 bool is_left() const { return is_left_; }
244 const Def* def() const { return def_; }
245 const Def* operator->() const { return def_; }
246 const Def* operator*() const { return def_; }
247 explicit operator bool() const { return def_ != nullptr; }
248 ///@}
249
250private:
251 Ctx* ctx_;
252 const Def* def_;
253 Prec prec_;
254 bool is_left_;
255
256 /// Streams @p def as an operand: Full if Full::is_full, and by its id otherwise.
257 friend std::ostream& operator<<(std::ostream&, Op);
258};
259
260/// A wrapper that streams a Def with all of its operands.
261class Full : public Op {
262public:
263 Full(Ctx* ctx, const Def* def, Prec prec = Prec::Bot, bool is_left = false)
264 : Op(ctx, def, prec, is_left) {}
265 Full(Op op)
266 : Full(op.ctx(), op.def(), op.prec(), op.is_left()) {}
267
268 explicit operator bool() const { return is_full(); }
269
270 /// Does the Def always print Full - instead of by name?
271 bool is_full() const {
272 // Only a `nom` declaration spells out a Nom; World::dump declares the annex ones.
273 if (def()->isa<Nom>()) return is_anx_nom(def()) || isa_inst(ctx(), def());
274 if (auto ctx = this->ctx()) {
275 if (ctx->inlined.contains(def())) return true;
276 if (ctx->in_header && ctx->header.contains(def())) return true;
277 }
278 if (auto mut = def()->isa_mut()) return !isa_decl(mut);
279
280 if (def()->is_closed()) return true;
281
282 if (auto app = def()->isa<App>()) {
283 if (app->type()->isa<Pi>()) return true; // a curried App prints Full
284 if (app->type()->isa<Type>()) return true;
285 if (app->callee()->isa<Axm>()) return app->callee_type()->num_doms() <= 1;
286 return false;
287 }
288
289 return true;
290 }
291
292 bool needs_parens() const {
293 if (!is_full()) return false;
294
295 auto child_prec = def2prec(ctx(), def());
296 if (child_prec < prec()) return true;
297 if (child_prec > prec()) return false;
298
299 switch (prec_assoc(prec())) {
300 case Assoc::R: return is_left();
301 case Assoc::L: return !is_left();
302 case Assoc::N: return false;
303 }
304 std::unreachable();
305 }
306
307 friend std::ostream& operator<<(std::ostream&, Full);
308};
309
310} // namespace
311} // namespace mim
312
313#ifndef DOXYGEN // clang-format off
314template<> struct std::formatter<mim::Op > : fe::ostream_formatter {};
315template<> struct std::formatter<mim::Full> : fe::ostream_formatter {};
316#endif // clang-format on
317
318namespace mim {
319namespace {
320
321/// Def::num_tprojs, except that a *fused* Seq stays one binder: dumping freezes the World and its sub-Seqs
322/// need not exist as Def%s.
323nat_t num_binders(const Def* type) {
324 if (auto seq = type->isa<Seq>(); seq && seq->shape().is_fused()) return 1;
325 return type->num_tprojs();
326}
327
328/// A Seq's shape the way the surface syntax spells it: one axis per dimension, `2, 3` for a fused `(2, 3)`.
329/// Falls back to the shape as a whole - also a legal spelling - if an axis does not exist as a Def.
330std::string shape(Ctx* ctx, const Seq* seq) {
331 auto s = seq->shape();
332 auto r = s.rank();
333 if (!r || *r <= 1) return std::format("{}", Op(ctx, *s));
334
335 auto axes = s->projs(*r);
336 if (std::ranges::any_of(axes, [](auto a) { return !a; })) return std::format("{}", Op(ctx, *s));
337 return std::format("{}", Op::map(ctx, axes));
338}
339
340/// As above but each axis binds its index - `i: 2, j: 3`; a fused Seq binds one Var per axis.
341std::string shape(Ctx* ctx, const Seq* seq, const Def* var) {
342 if (!var) return shape(ctx, seq);
343 auto s = seq->shape();
344 auto r = s.rank();
345 if (!r || *r <= 1) return std::format("{}: {}", Op(ctx, var), Op(ctx, *s));
346
347 auto res = std::string();
348 for (auto sep = ""; auto i : std::views::iota(nat_t(0), *r)) {
349 auto axis = live_proj(ctx, var, *r, i);
350 auto extent = s[i];
351 if (!extent) return std::format("{}: {}", Op(ctx, var), Op(ctx, *s));
352 // A projection the body never mentions does not exist as a Def and hence has no name of its own.
353 res += std::format("{}{}: {}", sep, axis ? std::format("{}", Op(ctx, axis)) : "_"s, Op(ctx, extent));
354 sep = ", ";
355 }
356 return res;
357}
358
359/// Does @p var occur in @p t only as projections, which a pattern binds by name?
360bool only_projs(const Def* t, const Var* var, DefSet& done) {
361 if (!t->has_free_var(var) || !done.emplace(t).second) return true;
362 if (t == var) return false;
363 if (auto ex = t->isa<Extract>(); ex && ex->tuple() == var) return ex->index()->isa<Lit>();
364 return std::ranges::all_of(t->deps(), [&](const Def* d) { return only_projs(d, var, done); });
365}
366
367/// The projection of @p type's binder that names its dead @p i-th component, if anything refers to it.
368const Def* binder_proj(const Def* type, size_t n, size_t i) {
369 if (auto [_, var] = type->isa_binder<Sigma>(); var) return var->proj(n, i);
370 return nullptr;
371}
372
373/// May @p def of @p type be taken apart into @p n components?
374/// A component the frozen World does not hand out has no name, so it has to fall back to @p type - and that
375/// spells a *dependent* component with the domain's binders instead of this pattern's. That is where
376/// destructuring stops: @p def stays opaque and its components print as `def#i` rather than by a dangling name.
377bool destructible(Ctx* ctx, const Def* def, const Def* type, size_t n) {
378 if (!def || n <= 1) return false;
379 auto var = type->isa_mut<Sigma>() ? type->has_var() : nullptr;
380 auto done = DefSet();
381
382 for (size_t i = 0; i != n; ++i) {
383 if (live_proj(ctx, def, n, i)) continue; // it exists and hence brings its own name and type
384 auto t = type->proj(n, i);
385 if (!t || (var && !only_projs(t, var, done))) return false;
386 }
387 return true;
388}
389
390/// destructible, recorded in Ctx::split so that by_name agrees with the pattern.
391bool split(Ctx* ctx, const Def* def, const Def* type, size_t n) {
392 if (!ctx || !def) return destructible(ctx, def, type, n);
393 if (auto res = fe::lookup(ctx->split, def)) return *res;
394 auto res = destructible(ctx, def, type, n);
395 // Def::num_tprojs keeps a Seq above Flags::scalarize_threshold whole, so its components have no name either.
396 if (res || n > 1 || def->num_projs() > 1) ctx->split.emplace(def, res);
397 return res;
398}
399
400/// Taken apart, a dependent Sigma no longer binds its Var itself; see Ctx::aliases.
401void alias_sigma(Ctx* ctx, const Def* type, const Def* def) {
402 if (auto [_, var] = type->isa_binder<Sigma>(); ctx && var) ctx->aliases.emplace(var, def);
403}
404
405void ptrn(std::ostream&, Ctx*, const Def*, const Def*, bool = false, const Def* = nullptr);
406
407/// The first @p limit of @p def%'s @p n components as sub-patterns; @p def may be `nullptr`.
408void ptrns(std::ostream& os, Ctx* ctx, const Def* def, const Def* type, size_t n, size_t limit, bool brckt) {
409 for (auto sep = ""; auto i : std::views::iota(size_t(0), limit)) {
410 auto proj = def ? live_proj(ctx, def, n, i) : nullptr;
411 os << sep;
412 // A projection's own type is the one where the binder has already been substituted for this Var.
413 ptrn(os, ctx, proj, proj ? proj->type() : type->proj(n, i), brckt, binder_proj(type, n, i));
414 sep = ", ";
415 }
416}
417
418/// @p brckt selects the `[]` sub-patterns of a type over the `()` sub-patterns of a Lam's parameter list.
419/// @p binder names a dead component that a dependent Sigma refers to; see binder_proj.
420void ptrn(std::ostream& os, Ctx* ctx, const Def* def, const Def* type, bool brckt, const Def* binder) {
421 if (!def) {
422 if (!binder || !ctx) return std::print(os, "_: {}", Op(ctx, type));
423 ctx->bound.emplace(binder);
424 return std::print(os, "{}: {}", name(ctx, binder), Op(ctx, type));
425 }
426
427 auto n = def->num_tprojs();
428 // Unless some component exists, nothing refers to one and the whole reads back the same.
429 auto used
430 = std::ranges::any_of(std::views::iota(size_t(0), n), [&](size_t i) { return live_proj(ctx, def, n, i); });
431 if (!used || !split(ctx, def, type, n)) return std::print(os, "{}: {}", name(ctx, def), Op(ctx, type));
432 alias_sigma(ctx, type, def);
433
434 os << (brckt ? '[' : '(');
435 ptrns(os, ctx, def, type, n, n, brckt);
436 // The alias keeps a name for the entity as a whole - which the body may well refer to.
437 std::print(os, "{} as {}", brckt ? ']' : ')', name(ctx, def));
438}
439
440/// A Pi's domain the way the surface syntax binds it - a pattern, once the Var's projections are in use.
441void dom_ptrn(std::ostream& os, Ctx* ctx, const Def* var, const Def* type, bool implicit) {
442 auto l = implicit ? '{' : '[';
443 auto r = implicit ? '}' : ']';
444 auto n = var->num_tprojs();
445 if (!split(ctx, var, type, n)) return std::print(os, "{}{}: {}{}", l, name(ctx, var), Op(ctx, type), r);
446
447 os << l;
448 ptrns(os, ctx, var, type, n, n, true);
449 std::print(os, "{} as {}", r, name(ctx, var));
450}
451
452/// @p num is how many binders the domain has; @p limit how many of them to print - a returning Lam hides its last.
453/// @p split tells whether the parameter list comes apart; see mim::split.
454void curry(std::ostream& os,
455 Ctx* ctx,
456 const Def* def,
457 const Def* type,
458 bool implicit,
459 size_t num,
460 size_t limit,
461 bool split,
462 bool alias) {
463 auto l = implicit ? '{' : '(';
464 auto r = implicit ? '}' : ')';
465
466 if (limit == 0) return (void)(os << l << r);
467 if (!split) {
468 if (def) return std::print(os, "{}{}: {}{}", l, name(ctx, def), Op(ctx, type), r);
469 return std::print(os, "{}_: {}{}", l, Op(ctx, type), r);
470 }
471
472 os << l;
473 ptrns(os, ctx, def, type, num, limit, false);
474 os << r;
475 if (alias && def) std::print(os, " as {}", name(ctx, def));
476}
477
478/// Is @p lam sugar for a `fun`?
479/// That sugar folds the domain into `[<params>, Cn <ret>]`, so a *flat* domain with a trailing Cn stays a `con`.
480bool isa_fun(const Lam* lam) {
481 auto pi = lam->type();
482 if (Lam::isa_returning(lam) && num_binders(pi->dom()) == 2)
483 if (auto ret = pi->dom(2, 1)) return Pi::isa_basicblock(ret) != nullptr; // a frozen World yields no projection
484 return false;
485}
486
487/// A `fun` binds only its parameters, so a Var used as a whole needs the `con` spelling.
488bool isa_fun(Ctx* ctx, const Lam* lam) {
489 auto var = lam->has_var();
490 return isa_fun(lam) && !(ctx && var && ctx->whole_vars.contains(var));
491}
492
493/// A Lam's parameter list, plus its filter where that is not the default for its position.
494/// @p last marks a curried chain's final Lam: only that one hides a `fun`'s `ret` and defaults its filter to `ff`.
495void lam_ptrn(std::ostream& os, Ctx* ctx, Lam* lam, bool fun, bool con, bool last, bool alias) {
496 auto num = num_binders(lam->type()->dom());
497 auto limit = fun && last ? num - 1 : num;
498 auto var = lam->has_var();
499 // A parameter list only comes apart if every parameter is there to be named - unless a `fun` hides its `ret`.
500 auto parts = limit != num || (limit != 0 && split(ctx, var, lam->dom(), num));
501 if (ctx && var) {
502 if (auto pi_var = lam->type()->has_var()) ctx->aliases.emplace(pi_var, var);
503 // Kept whole, the domain prints as a Sigma that binds its Var itself.
504 if (parts) alias_sigma(ctx, lam->dom(), var);
505 }
506 curry(os, ctx, var, lam->type()->dom(), lam->type()->is_implicit(), num, limit, parts, alias);
507 auto& w = lam->world();
508 if (auto dflt = last && (fun || con) ? w.lit_ff() : w.lit_tt(); lam->filter() != dflt)
509 std::print(os, "@({})", Op(ctx, lam->filter()));
510}
511
512/// A Lam's result type; a `con` has none and a `fun` ascribes the domain of its `ret` continuation.
513void lam_codom(std::ostream& os, Ctx* ctx, Lam* lam, bool fun, bool con) {
514 if (fun)
515 std::print(os, ": {}", Op(ctx, lam->ret_dom()));
516 else if (!con)
517 std::print(os, ": {}", Op(ctx, lam->type()->codom()));
518}
519
520/// The names of the constructors live in the frontend, so each case prints under its index.
521std::string ctors(Ctx* ctx, const Variant* variant) {
522 if (variant->num_ops() == 0) return "|";
523 auto res = std::string();
524 for (auto sep = ""; auto i : std::views::iota(size_t(0), variant->num_ops())) {
525 res += std::format("{}| _{}", sep, i);
526 if (auto op = variant->op(i); op != variant->world().sigma()) res += std::format(": {}", Op(ctx, op));
527 sep = " ";
528 }
529 return res;
530}
531
532std::ostream& operator<<(std::ostream& os, Op op) {
533 if (*op == nullptr) return os << "<nullptr>";
534 if (auto d = Full(op)) return os << d;
535 return os << id(op.ctx(), *op);
536}
537
538/// A block's tail has no `let`, so it is spelled out unless it has a declaration of its own.
539std::string tail(Ctx* ctx, const Def* def) {
540 return has_decl(ctx, def) ? std::format("{}", Op(ctx, def)) : std::format("{}", Full(ctx, def));
541}
542
543/// Streams @p d with all of its operands; Full::is_full decides who gets here.
544void full(std::ostream& os, Full d) {
545 if (auto hole = d->isa_mut<Hole>()) {
546 if (hole->is_set()) return std::print(os, "{}", d.op(hole->op()));
547 return std::print(os, "?");
548 }
549 if (auto mut = d->isa_mut(); mut && !mut->is_set()) return std::print(os, "unset");
550 if (d.needs_parens()) return std::print(os, "({})", Full(d.ctx(), *d));
551
552 bool ascii = d->world().flags().ascii;
553 auto arw = ascii ? "->" : "→";
554 auto al = ascii ? "<<" : "«";
555 auto ar = ascii ? ">>" : "»";
556 auto pl = ascii ? "(<" : "‹";
557 auto pr = ascii ? ">)" : "›";
558 auto bot = ascii ? "bot" : "⊥";
559 auto top = ascii ? "top" : "⊤";
560
561 if (auto type = d->isa<Type>()) {
562 if (auto level = Lit::isa(type->level()); level && !ascii) {
563 if (level == 0) return std::print(os, "*");
564 if (level == 1) return std::print(os, "□");
565 }
566 return std::print(os, "Type {}", d.r(type->level(), Prec::App));
567 } else if (auto reform = d->isa<Reform>()) {
568 return std::print(os, "Rule {}", d.r(reform->dom(), Prec::App));
569 } else if (d->isa<Level>()) {
570 return std::print(os, "Level");
571 } else if (d->isa<Nat>()) {
572 return std::print(os, "Nat");
573 } else if (d->isa<Idx>()) {
574 return std::print(os, "Idx");
575 } else if (auto ext = d->isa<Ext>()) {
576 return std::print(os, "{}:{}", ext->isa<Bot>() ? bot : top, d.r(ext->type(), Prec::Lit));
577 } else if (d->isa<Axm>() || is_anx_nom(*d)) {
578 return std::print(os, "{}", annex_sym(d.ctx(), d->sym()));
579 } else if (auto inst = isa_inst(d.ctx(), *d)) {
580 std::print(os, "{}", annex_sym(d.ctx(), inst->first));
581 for (const auto& [arg, implicit] : inst->second) {
582 os << (implicit ? " @ " : " ");
583 if (arg.size() == 1)
584 std::print(os, "{}", d.r(arg.front(), Prec::App));
585 else
586 std::print(os, "({})", Op::map(d.ctx(), arg));
587 }
588 return;
589 } else if (auto lit = d->isa<Lit>()) {
590 if (lit->type()->isa<Nat>()) {
591 // clang-format off
592 switch (lit->get()) {
593 case 0x0'0000'0100_n: return std::print(os, "i8");
594 case 0x0'0001'0000_n: return std::print(os, "i16");
595 case 0x1'0000'0000_n: return std::print(os, "i32");
596 default: return std::print(os, "{}", lit->get());
597 }
598 // clang-format on
599 } else if (auto size = Idx::isa(lit->type())) {
600 if (auto s = Lit::isa(size)) {
601 if (*s == 2) return std::print(os, "{}", lit->get<bool>() ? "tt" : "ff");
602 if (auto suffix = idx_name(*s)) return std::print(os, "{}{}", lit->get(), *suffix);
603 std::print(os, "{}", lit->get());
604 auto digits = std::to_string(*s);
605 if (ascii) {
606 os << '_' << digits;
607 } else {
608 // Raw UTF-8 units of a subscript - bytes to stream, not numbers to format.
609 for (auto d : digits)
610 os << "\xE2\x82" << char(0x80 + d - '0');
611 }
612 return;
613 }
614 }
615 return std::print(os, "{}:{}", lit->get(), d.r(lit->type(), Prec::Lit));
616 } else if (auto ex = d->isa<Extract>()) {
617 if (by_name(d.ctx(), ex)) return std::print(os, "{}", name(d.ctx(), ex));
618 return std::print(os, "{}#{}", d.l(ex->tuple(), Prec::Extract), d.r(ex->index(), Prec::Extract));
619 } else if (auto ins = d->isa<Insert>()) {
620 auto tup = d.l(ins->tuple(), Prec::Extract);
621 // `←` updates the whole `#`-path, so an Extract target needs parens to re-parse.
622 if (auto ex = ins->tuple()->isa<Extract>(); ex && !by_name(d.ctx(), ex))
623 std::print(os, "({})", tup);
624 else
625 std::print(os, "{}", tup);
626 return std::print(os, "#{} ← {}", d.r(ins->index(), Prec::Extract), d.r(ins->value(), Prec::Ins));
627 } else if (auto var = d->isa<Var>()) {
628 return std::print(os, "{}", name(d.ctx(), var));
629 } else if (auto [pi, var] = d->isa_binder<Pi>(); pi && pi->codom()->has_free_var(var)) {
630 dom_ptrn(os, d.ctx(), var, pi->dom(), pi->is_implicit());
631 return std::print(os, " {} {}", arw, d.r(pi->codom(), Prec::Arrow));
632 } else if (auto pi = d->isa<Pi>()) {
633 if (Pi::isa_cn(pi)) return std::print(os, "Cn {}", d.op(pi->dom()));
634 if (pi->is_implicit())
635 return std::print(os, "{{_: {}}} {} {}", d.op(pi->dom()), arw, d.r(pi->codom(), Prec::Arrow));
636 return std::print(os, "{} {} {}", d.l(pi->dom(), Prec::Arrow), arw, d.r(pi->codom(), Prec::Arrow));
637 } else if (auto lam = d->isa_mut<Lam>()) {
638 // A Lam that has no declaration of its own is a λ-expression.
639 auto fun = isa_fun(d.ctx(), lam);
640 auto con = Lam::isa_cn(lam) && !fun;
641 std::print(os, "{}", fun ? "fn " : con ? "cn " : "λ ");
642 lam_ptrn(os, d.ctx(), lam, fun, con, true, true);
643 lam_codom(os, d.ctx(), lam, fun, con);
644 // Like any tail, the body is spelled out - there is no block here that could `let`-bind it.
645 return std::print(os, " = {}", tail(d.ctx(), lam->body()));
646 } else if (auto app = d->isa<App>()) {
647 if (auto size = Idx::isa(app))
648 if (auto l = Lit::isa(size))
649 if (auto name = idx_name(*l)) return std::print(os, "{}", *name);
650
651 if (Pi::isa_implicit(app->callee()->unfold_type())) {
652 // An argument that is still a Hole is what the parser inserts anyway - and `?` has no spelling.
653 if (app->arg()->has_hole()) return std::print(os, "{}", d.op(app->callee(), d.prec(), d.is_left()));
654 // Spelling out an implicit argument needs `@`; juxtaposition would insert a Hole in front of it.
655 return std::print(os, "{} @ {}", d.l(app->callee(), Prec::App), d.r(app->arg(), Prec::App));
656 }
657 return std::print(os, "{} {}", d.l(app->callee(), Prec::App), d.r(app->arg(), Prec::App));
658 } else if (auto sigma = d->isa<Sigma>()) {
659 auto var = sigma->has_var();
660 size_t i = 0;
661 auto elem = fe::StreamFn{[&](std::ostream& os) -> std::ostream& {
662 auto sep = "";
663 for (auto op : sigma->ops()) {
664 os << sep;
665 if (auto v = var ? var->proj(sigma->num_ops(), i) : nullptr)
666 std::print(os, "{}: {}", d.op(v), d.op(op));
667 else
668 os << d.op(op);
669 ++i;
670 sep = ", ";
671 }
672 return os;
673 }};
674
675 return std::print(os, "[{}]", elem);
676 } else if (auto tuple = d->isa<Tuple>()) {
677 return std::print(os, "({})", Op::map(d.ctx(), tuple->ops()));
678 } else if (auto arr = d->isa<Arr>()) {
679 return std::print(os, "{}{}; {}{}", al, shape(d.ctx(), arr, arr->has_var()), d.op(arr->body()), ar);
680 } else if (auto pack = d->isa<Pack>()) {
681 return std::print(os, "{}{}; {}{}", pl, shape(d.ctx(), pack, pack->has_var()), d.op(pack->body()), pr);
682 } else if (auto proxy = d->isa<Proxy>()) {
683 return std::print(os, "(proxy#{} {})", proxy->tag(), Op::map(d.ctx(), proxy->ops()));
684 } else if (auto join = d->isa<Join>()) {
685 return std::print(os, "{}", Op::map(d.ctx(), join->ops(), " ∪ ", Prec::Union));
686 } else if (auto variant = d->isa<Variant>()) {
687 return std::print(os, "({})", ctors(d.ctx(), variant));
688 } else if (auto inj = d->isa<Inj>()) {
689 if (auto variant = inj->type()->isa<Variant>()) {
690 auto ctor = std::format("{}#{}", d.l(variant, Prec::Extract), inj->index());
691 if (variant->op(inj->index()) == d->world().sigma()) return std::print(os, "{}", ctor);
692 return std::print(os, "{} {}", ctor, d.r(inj->value(), Prec::App));
693 }
694 return std::print(os, "{} inj {}", d.l(inj->value(), Prec::Inj), d.r(inj->type(), Prec::Inj));
695 } else if (auto name = d->isa<Name>()) {
696 return std::print(os, "{} inj {}", d.l(name->op(), Prec::Inj), d.r(name->nom(), Prec::Inj));
697 } else if (auto struc = d->isa<Struc>()) {
698 return std::print(os, "#{}", d.r(struc->op(), Prec::Prefix));
699 } else if (auto single = d->isa<Single>()) {
700 return std::print(os, "{}{}{}", al, d.op(single->op()), ar);
701 } else if (auto wrap = d->isa<Wrap>()) {
702 return std::print(os, "{}{}{}", pl, d.op(wrap->op()), pr);
703 } else if (auto match = d->isa<Match>(); match && d.ctx() && match->num_arms() != 0) {
704 std::print(os, "match {} with", d.op(match->scrutinee()));
705 auto variant = match->scrutinee()->type()->isa<Variant>();
706 auto& tab = d.ctx()->tab;
707 ++tab;
708 for (size_t i = 0; auto arm : match->arms()) {
709 auto dom = arm->type()->as<Pi>()->dom();
710 std::print(os, "\n{}| ", tab);
711 // A variant's arms are its constructors, in order; a constructor without payload binds nothing.
712 if (variant) std::print(os, "_{}", i++);
713 auto binds = !variant || dom != d->world().sigma();
714 if (binds && variant) os << ' ';
715 if (d.ctx()->arms.contains(arm)) {
716 auto lam = arm->as_mut<Lam>();
717 if (binds) ptrn(os, d.ctx(), lam->has_var(), dom);
718 // Another `match` would swallow the arms that follow.
719 std::print(os, " => {}", d.op(lam->body(), Prec::Where));
720 } else {
721 auto x = binds ? fresh_name(d.ctx(), "_") : "()"s;
722 if (binds) std::print(os, "{}: {}", x, d.op(dom));
723 std::print(os, " => {} {}", d.l(arm, Prec::App), x);
724 }
725 }
726 --tab;
727 return;
728 }
729
730 // other
731 auto tag = d->flags() == 0 ? std::string(d->node_name()) : std::format("{}#{}", d->node_name(), d->flags());
732 if (d->ops().empty()) return std::print(os, "({})", tag);
733 std::print(os, "({} {})", tag, Op::map(d.ctx(), d->ops(), " "));
734}
735
736std::ostream& operator<<(std::ostream& os, Full d) {
737 full(os, d);
738 return os;
739}
740
741/*
742 * Dumper
743 */
744
745/// The Lam%s a curried declaration folds into one: `lam f (a) (b) = e`.
746fe::Vector<Lam*> curry_chain(Lam* lam) {
747 auto chain = fe::Vector<Lam*>();
748 for (auto* curr = lam;;) {
749 chain.emplace_back(curr);
750 if (auto body = curr->body())
751 if (auto next = body->isa_mut<Lam>(); next && !next->is_external()) {
752 curr = next;
753 continue;
754 }
755 break;
756 }
757 return chain;
758}
759
760/// Recovers the arguments of an instance of a generic Nom by matching it against the generic one.
761/// The generic Nom may live in Driver::library, so even closed parts are compared structurally.
762class Instantiation {
763public:
764 /// @p chain is the curry_chain of the annex Lam whose body is the generic Nom.
765 explicit Instantiation(const fe::Vector<Lam*>& chain)
766 : chain_(chain) {
767 for (auto lam : chain)
768 if (auto var = lam->has_var()) vars_.emplace(var);
769 }
770
771 /// `std::nullopt` if a parameter is not recoverable.
772 std::optional<Args> args(const Nom* inst) {
773 auto generic = chain_.back()->body()->as<Nom>();
774 if (inst == generic || !match(generic->op(), inst->op())) return {};
775
776 auto res = Args();
777 for (auto lam : chain_) {
778 auto var = lam->has_var();
779 auto implicit = lam->type()->is_implicit();
780 if (!var) return {};
781 if (auto whole = fe::lookup(subst_, var)) {
782 res.emplace_back(DefVec{whole}, implicit);
783 continue;
784 }
785 auto n = Lit::isa(var->arity());
786 if (!n || *n < 2) return {};
787 auto comps = DefVec(*n, nullptr);
788 for (auto [g, i] : subst_)
789 if (auto ex = g->isa<Extract>(); ex && ex->tuple() == var) comps[*Lit::isa(ex->index())] = i;
790 if (std::ranges::contains(comps, nullptr)) return {};
791 res.emplace_back(std::move(comps), implicit);
792 }
793 return res;
794 }
795
796private:
797 bool is_param(const Def* def) const {
798 if (auto ex = def->isa<Extract>(); ex && ex->index()->isa<Lit>()) def = ex->tuple();
799 return vars_.contains(def);
800 }
801
802 bool match(const Def* g, const Def* i) {
803 if (g == i) return true;
804 // Only a Level has no type.
805 if (!g || !i) return false;
806 // A substitution maps each generic subterm to exactly one instance subterm.
807 if (auto [j, ins] = subst_.emplace(g, i); !ins) return j->second == i;
808 if (is_param(g)) return true;
809 if (g->node() != i->node() || g->flags() != i->flags() || g->num_ops() != i->num_ops()) return false;
810 if (!match(g->type(), i->type())) return false;
811 for (size_t k = 0, n = g->num_ops(); k != n; ++k)
812 if (!match(g->op(k), i->op(k))) return false;
813 return true;
814 }
815
816 const fe::Vector<Lam*>& chain_;
817 DefSet vars_;
818 DefMap<const Def*> subst_;
819};
820
821/// An annex of the dumped file itself; its name is split into the `mod` it nests in and its own name.
822struct DumpAnnex {
823 std::string mod, name;
824 const Def* def;
825};
826
827/// Emits Def%s as Mim declarations.
828///
829/// Dump::Scope and Dump::All place everything into the block of the innermost binder its free Var%s refer to.
830/// Dump::Expr asks no analysis at all, and neither do the tooltips of World::dot.
831class Dumper {
832public:
833 /// @p srcs are the files an `import` pulls in: they declare their own content, so a dump must not repeat it.
834 Dumper(std::ostream& os, Dump mode, bool typed_let = false, Srcs srcs = {})
835 : os_(os)
836 , mode_(mode)
837 , typed_let_(typed_let)
838 , srcs_(std::move(srcs)) {}
839
840 /// Only @p live Def%s - what the dumped roots reach - count as existing.
841 void live(const DefSet& live) { ctx_.live = &live; }
842
843 void insts(DefMap<std::pair<Sym, Args>> insts) { ctx_.insts = std::move(insts); }
844
845 /// Switches to plain names; @p reserved are names the dump refers to verbatim and hence must not pick.
846 void plain(std::span<const std::string> reserved) {
847 ctx_.plain = true;
848 ctx_.taken.insert(reserved.begin(), reserved.end());
849 }
850
851 /// Declares the annexes of the dumped file itself - Axm%s as `axm`s, Nom%s as `nom`s - grouped into the
852 /// `mod` their names nest them in.
853 void dump_annexes(std::string self, std::span<const DumpAnnex> annexes) {
854 ctx_.self = std::move(self);
855 auto content = [](const Def* def) { return def->isa<Axm>() ? def->type() : def->as<Nom>()->op(); };
856 auto muts = fe::Vector<Def*>();
857 for (const auto& [_, __, annex] : annexes)
858 for (auto mut : content(annex)->local_muts())
859 if (isa_decl(mut)) muts.emplace_back(mut);
860 dump_muts(muts);
861
862 sep(Prev::Decl);
863 auto noms = DefVec();
864 auto names = DefMap<std::string_view>();
865 auto bodies = fe::Vector<Def*>();
866 for (const auto& a : annexes)
867 if (auto nom = a.def->isa<Nom>()) {
868 noms.emplace_back(nom);
869 names[nom] = a.name;
870 for (auto mut : nom->local_muts())
871 bodies.emplace_back(mut);
872 }
873 if (!bodies.empty()) group_noms(Nest(bodies.front()->world(), bodies, true), noms);
874 std::string_view curr_mod;
875 for (const auto& [mod, name, def] : annexes) {
876 if (mod != curr_mod) {
877 if (!curr_mod.empty()) std::println(os_, "{}end", --ctx_.tab);
878 if (!mod.empty()) std::println(os_, "{}mod {}", ctx_.tab, mod), ++ctx_.tab;
879 curr_mod = mod;
880 ctx_.mod = mod.empty() ? std::string() : std::string(mod) + ".";
881 }
882 if (deferred_.contains(def)) continue;
883 if (auto group = fe::lookup(mutual_, def)) {
884 emit_mutual(*group, [&](const Def* m) { emit_nom("anx nom", names[m], m->as<Nom>()); });
885 continue;
886 }
887 if (auto nom = def->isa<Nom>()) {
888 emit_nom("anx nom", name, nom);
889 continue;
890 }
891 auto axm = def->as<Axm>();
892 std::print(os_, "{}axm {}: {}", ctx_.tab, name, Op(&ctx_, axm->type()));
893 auto [curry, trip] = Axm::infer_curry_and_trip(axm->type());
894 if (axm->curry() != curry || axm->trip() != trip) std::print(os_, ", {}", axm->curry());
895 if (axm->trip() != trip) std::print(os_, ", {}", axm->trip());
896 std::println(os_, ";");
897 }
898 if (!curr_mod.empty()) std::println(os_, "{}end", --ctx_.tab);
899 ctx_.mod.clear();
900 }
901
902 /// @name dump
903 ///@{
904 /// @p def and whatever Dumper::mode_ reaches from it.
905 void dump(const Def* def) {
906 if (auto mut = isa_decl(def)) return dump_muts({mut});
907
908 auto muts = fe::Vector<Def*>();
909 for (auto mut : def->local_muts())
910 if (isa_decl(mut)) muts.emplace_back(mut);
911 dump_muts(muts, def);
912 }
913
914 void dump(fe::View<Def*> muts) { dump_muts(muts); }
915 ///@}
916
917private:
918 /// What the current block emitted last; only a declaration is worth setting apart.
919 enum class Prev { None, Let, Decl };
920
921 /// An `import` re-declares what it *exports* - but a plain `let` in that file stays local to it, so the dump has
922 /// to spell that one out or nothing binds the name it refers to.
923 bool is_imported(Def* mut) const { return mut->is_external() && srcs_.contains(mut->loc().src); }
924
925 /// Schedules @p roots and emits what lands at the top level.
926 void emit_top(const auto& roots) {
927 for (auto root : roots)
928 schedule_(root);
929 bucket();
930 emit(nullptr);
931 }
932
933 /// @name schedule
934 ///@{
935 /// The closed mutables @p muts reach, callees first: Mim binds a name before its uses.
936 /// A non-decl @p tail ends the dump; it is spelled out after the `let`s it needs.
937 void dump_muts(fe::View<Def*> muts, const Def* tail = nullptr) {
938 if (muts.empty() && !tail) return;
939 auto todo = fe::Vector<Def*>();
940 for (auto mut : muts)
941 post_order(mut, todo);
942 for (auto [lam, match] : arm_of_)
943 if (match && is_spellable(lam)) ctx_.arms.emplace(lam);
944
945 // Nest::Node::topo_ breaks ties by gid, which a re-read dump need not reproduce.
946 auto roots = fe::Vector<Def*>();
947 for (auto mut : todo) {
948 if (!mut->is_closed() || ctx_.arms.contains(mut)) continue;
949 if (is_imported(mut))
950 done_.emplace(mut);
951 else
952 roots.emplace_back(mut);
953 }
954 if (mode_ == Dump::Scope) scope_.insert(muts.begin(), muts.end());
955
956 const auto nest = Nest(tail ? tail->world() : muts.front()->world(), muts, true);
957 auto _ = fe::Restore(nest_, &nest);
958 emit_top(roots);
959 if (tail) {
960 emit_top(std::array{tail});
961 sep(Prev::Let);
962 emit_tail(tail, "");
963 }
964 }
965
966 static bool is_spellable(const Def* arm) {
967 auto lam = arm->isa_mut<Lam>();
968 return lam && lam->is_set() && lam->filter() == lam->world().lit_tt();
969 }
970
971 /// Like mim::post_order, but visits the mutables in the order the operands reach them.
972 /// Def::local_muts orders by pointer, which a re-read dump need not reproduce.
973 void post_order(Def* mut, fe::Vector<Def*>& todo) {
974 if (!scheduled_.emplace(mut).second) return;
975
976 auto muts = fe::Vector<Def*>();
977 auto seen = DefSet();
978 printed_deps(mut, [&](const Def* op) {
979 note_use(op, nullptr);
980 local_muts(op, seen, muts);
981 });
982 for (auto local_mut : muts)
983 post_order(local_mut, todo);
984
985 todo.emplace_back(mut);
986 }
987
988 void local_muts(const Def* def, DefSet& seen, fe::Vector<Def*>& muts) {
989 if (!def || def->local_muts().empty() || !seen.emplace(def).second) return;
990 if (auto mut = def->isa_mut()) return (void)muts.emplace_back(mut);
991 auto match = def->isa<Match>();
992 printed_deps(def, [&](const Def* op) {
993 // An arm twice in the same `match` cannot be spelled out at both places either.
994 auto arm = match && std::ranges::count(match->arms(), op) == 1;
995 note_use(op, arm ? match : nullptr);
996 local_muts(op, seen, muts);
997 });
998 }
999
1000 /// Records that @p op is an arm of @p match or, if `nullptr`, used otherwise.
1001 void note_use(const Def* op, const Match* match) {
1002 if (!op || !op->isa_mut<Lam>()) return;
1003 if (auto [i, ins] = arm_of_.emplace(op, match); !ins && i->second != match) i->second = nullptr;
1004 }
1005
1006 /// The Def%s the dump prints @p def with: a type nobody prints must not drag in a `let` that nothing refers to.
1007 /// A Lam spells out its Pi, so only the components of that one are referenced.
1008 void printed_deps(const Def* def, auto f) const {
1009 if (auto inst = isa_inst(&ctx_, def)) {
1010 for (const auto& [arg, _] : inst->second)
1011 for (auto op : arg)
1012 f(op);
1013 return;
1014 }
1015 if (auto lam = def->isa_mut<Lam>()) {
1016 f(lam->type()->dom());
1017 f(lam->type()->codom());
1018 for (auto op : lam->ops())
1019 f(op);
1020 return;
1021 }
1022 if (typed_let_ || def->isa<Lit>() || def->isa<Ext>() || def->isa<Inj>() || def->isa<Name>()) f(def->type());
1023 for (auto op : def->ops())
1024 f(op);
1025 }
1026
1027 void note_vars(const Def* def) {
1028 if (auto inst = isa_inst(&ctx_, def)) {
1029 for (const auto& [arg, _] : inst->second)
1030 for (auto op : arg)
1031 if (op->isa<Var>()) ctx_.whole_vars.emplace(op);
1032 return;
1033 }
1034 auto ex = def->isa<Extract>();
1035 for (auto op : def->ops())
1036 if (op && op->isa<Var>() && !(ex && op == ex->tuple() && ex->index()->isa<Lit>()))
1037 ctx_.whole_vars.emplace(op);
1038 }
1039
1040 void schedule_(const Def* def) {
1041 if (!def) return;
1042 if (auto mut = isa_decl(def); mut && !ctx_.arms.contains(mut)) return schedule_mut(mut);
1043 if (!done_.emplace(def).second) return finish(def);
1044 if (ctx_.arms.contains(def)) {
1045 // An arm has no block of its own either.
1046 auto lam = def->as_mut<Lam>();
1047 if (lam->body()->isa<Var>()) ctx_.whole_vars.emplace(lam->body());
1048 schedule_inline(lam->dom(), ctx_.inlined);
1049 schedule_inline(lam->body(), ctx_.inlined);
1050 return;
1051 }
1052 note_vars(def);
1053 schedule_deps(def);
1054 if (!Full(&ctx_, def)) order_.emplace_back(def);
1055 }
1056
1057 void schedule_deps(const Def* def) {
1058 pending_.emplace(def);
1059 printed_deps(def, [&](const Def* op) { schedule_(op); });
1060 pending_.erase(def);
1061 }
1062
1063 /// A mutable reached through @p def%'s deps may need @p def - and hence all of its deps - before they are done.
1064 void finish(const Def* def) {
1065 if (pending_.erase(def)) printed_deps(def, [&](const Def* op) { schedule_(op); });
1066 }
1067
1068 void schedule_mut(Def* mut) {
1069 if (!enter(mut) || done_.contains(mut)) return;
1070
1071 auto group = sibling_group(mut);
1072 for (auto m : group)
1073 done_.emplace(m);
1074 for (auto m : group)
1075 schedule_body(m);
1076 order_.emplace_back(group.front());
1077 if (group.size() > 1) mutual_.emplace(group.front(), DefVec(group.begin(), group.end()));
1078 }
1079
1080 /// A block only sees what precedes it, so the mutually recursive siblings of @p mut print as one `mutual` group.
1081 fe::Vector<Def*> sibling_group(Def* mut) const {
1082 auto node = (*nest_)[mut];
1083 if (!node || !node->is_mutually_recursive()) return {mut};
1084 if (auto key = owner(node->inest()); key && inlines(key)) return {mut};
1085
1086 auto group = fe::Vector<Def*>();
1087 for (auto member : node->scc())
1088 if (auto m = member->mut(); m == mut || !done_.contains(m)) group.emplace_back(m);
1089 if (group.size() < 2 || !std::ranges::all_of(group, [this](Def* m) { return groupable(m); })) return {mut};
1090 return group;
1091 }
1092
1093 bool groupable(Def* mut) const {
1094 if (!isa_decl(mut) || ctx_.arms.contains(mut) || mut->isa<Rule>() || !mut->is_set()) return false;
1095 if (auto lam = mut->isa_mut<Lam>()) return curry_chain(lam).back()->is_set();
1096 return true;
1097 }
1098
1099 void schedule_body(Def* mut) {
1100 auto chain = mut->isa_mut<Lam>() ? curry_chain(mut->as_mut<Lam>()) : fe::Vector<Lam*>();
1101
1102 if (auto rule = mut->isa_mut<Rule>()) {
1103 // The surface syntax has no block for a rule's parts, so whatever depends on its meta variable is inlined.
1104 printed_deps(rule->type(), [&](const Def* op) { schedule_inline(op, ctx_.inlined); });
1105 printed_deps(rule, [&](const Def* op) { schedule_inline(op, ctx_.inlined); });
1106 } else if (chain.empty()) {
1107 for (auto op : mut->deps())
1108 schedule_(op);
1109 } else {
1110 for (auto* lam : chain)
1111 if (lam != mut) done_.emplace(lam), absorbed_.emplace(lam, mut);
1112 // The chain spells out its Pi%s, so only their components are referenced.
1113 for (auto* lam : chain) {
1114 schedule_inline(lam->type()->dom(), ctx_.header);
1115 if (auto var = lam->has_var()) schedule_proj_types(var);
1116 schedule_inline(lam->filter(), ctx_.header);
1117 if (lam == chain.back()) {
1118 schedule_inline(lam->type()->codom(), ctx_.header);
1119 schedule_tail(lam->body());
1120 }
1121 }
1122 }
1123 }
1124
1125 /// A destructured parameter prints the types of its projections, where the Lam's own Var is already substituted.
1126 void schedule_proj_types(const Def* def) {
1127 auto n = def->num_tprojs();
1128 if (n <= 1) return;
1129 for (size_t i = 0; i != n; ++i)
1130 if (auto proj = def->proj(n, i)) {
1131 schedule_inline(proj->type(), ctx_.header);
1132 schedule_proj_types(proj);
1133 }
1134 }
1135
1136 /// Schedules what @p def needs; @p def itself, noted in @p inline_set, prints inline.
1137 void schedule_inline(const Def* def, DefSet& inline_set) {
1138 if (!def || def->is_closed() || has_decl(&ctx_, def)) return schedule_(def);
1139 if (!inline_set.emplace(def).second) return;
1140 note_vars(def);
1141 printed_deps(def, [&](const Def* op) { schedule_inline(op, inline_set); });
1142 }
1143
1144 /// A Lam's body is the tail of its block, so it is emitted there instead of as a `let` of its own.
1145 void schedule_tail(const Def* def) {
1146 if (def && def->isa<Var>()) ctx_.whole_vars.emplace(def);
1147 if (!def || has_decl(&ctx_, def)) return schedule_(def);
1148 if (!done_.emplace(def).second) return finish(def);
1149 note_vars(def);
1150 schedule_deps(def);
1151 }
1152
1153 /// Is @p mut ours to emit - or does it only get referenced by name?
1154 /// Dump::Scope only enters what nests in one of Dumper::scope_.
1155 bool enter(Def* mut) const {
1156 auto node = (*nest_)[mut];
1157 if (!node || mode_ != Dump::Scope) return node != nullptr;
1158 while (!node->inest()->is_root())
1159 node = node->inest();
1160 return scope_.contains(node->mut());
1161 }
1162
1163 /// Turns the schedule into one list per block.
1164 void bucket() {
1165 for (auto def : order_) {
1166 auto mut = isa_decl(def);
1167 auto key = owner(nest_->inest(def));
1168 // A Nom is the one immutable with a declaration of its own, so it never inlines.
1169 if (def->isa<Nom>()) {
1170 while (key && inlines(key))
1171 key = owner((*nest_)[key]->inest());
1172 // TODO A Nom over a Var in its own Lam's signature has no valid place: `lam k (n: Nat, x: V n)`.
1173 }
1174 // A binder that prints inline has no block of its own, so what belongs into it is inlined as well.
1175 if (key && inlines(key) && !(mut && is_recursive(mut))) {
1176 ctx_.inlined.emplace(def);
1177 continue;
1178 }
1179 (key ? bucket_[key] : top_).emplace_back(def);
1180 }
1181 order_.clear();
1182 }
1183
1184 /// Does @p mut print inline - be it one itself, or because it sits inside one that does?
1185 bool inlines(Def* mut) const {
1186 for (auto node = (*nest_)[mut]; node; node = node->inest())
1187 if (auto m = owner(node); m && (!isa_decl(m) || ctx_.arms.contains(m))) return true;
1188 return false;
1189 }
1190
1191 bool is_recursive(Def* mut) const { return (*nest_)[mut]->is_recursive(); }
1192
1193 /// The mutable whose block @p node stands for; `nullptr` for the top level.
1194 Def* owner(const Nest::Node* node) const {
1195 if (!node) return nullptr;
1196 auto mut = node->mut();
1197 if (!mut) return nullptr; // a *virtual* root is the top level
1198 if (auto outer = fe::lookup(absorbed_, mut)) return outer; // a curried chain shares a block
1199 return mut;
1200 }
1201 ///@}
1202
1203 /// @name emit
1204 ///@{
1205 void emit(Def* key) {
1206 auto defs = key ? std::move(bucket_[key]) : std::move(top_);
1207 group_noms(*nest_, defs);
1208 for (auto def : defs) {
1209 if (deferred_.contains(def)) continue;
1210 auto group = fe::lookup(mutual_, def);
1211 sep(group || isa_decl(def) ? Prev::Decl : Prev::Let);
1212 if (group)
1213 emit_mutual(*group, [this](const Def* m) { emit_item(m); });
1214 else
1215 emit_item(def);
1216 }
1217 }
1218
1219 void emit_item(const Def* def) {
1220 if (auto mut = isa_decl(def)) return emit_decl(mut);
1221 emit_let(def);
1222 }
1223
1224 // Only a bare variant may refer back to the `nom`.
1225 void emit_nom(std::string_view kw, std::string_view name, const Nom* nom) {
1226 if (auto variant = nom->op()->isa<Variant>())
1227 return std::println(os_, "{}{} {} = {};", ctx_.tab, kw, name, ctors(&ctx_, variant));
1228 std::println(os_, "{}{} {} = {};", ctx_.tab, kw, name, Op(&ctx_, nom->op()));
1229 }
1230
1231 /// Groups the Nom%s among @p defs that lie on a cycle, in the order of @p defs.
1232 /// No immutable refers back to itself, so such a cycle runs through a recursive SCC.
1233 /// Everything a cycle needs precedes its last member, so that is where the group goes.
1234 void group_noms(const Nest& nest, const DefVec& defs) {
1235 auto grouped = DefSet();
1236 for (auto def : defs) {
1237 auto nom = def->isa<Nom>();
1238 if (!nom || grouped.contains(nom)) continue;
1239 for (auto mut : nom->local_muts()) {
1240 auto node = nest[mut];
1241 if (!node || !node->is_recursive()) continue;
1242 auto on_cycle = scc_noms(nest, node->scc());
1243 auto cycle = DefVec();
1244 for (auto d : defs)
1245 if (on_cycle.contains(d)) cycle.emplace_back(d);
1246 if (cycle.size() > 1) {
1247 grouped.insert(cycle.begin(), cycle.end());
1248 deferred_.insert(cycle.begin(), cycle.end() - 1);
1249 mutual_.emplace(cycle.back(), std::move(cycle));
1250 }
1251 break;
1252 }
1253 }
1254 }
1255
1256 /// The Nom%s @p scc reaches that reach it back.
1257 static DefSet scc_noms(const Nest& nest, const Nest::Node::SCC& scc) {
1258 auto reaches_scc = [&](const Def* nom) {
1259 return std::ranges::any_of(nom->local_muts(), [&](Def* mut) { return scc.contains(nest[mut]); });
1260 };
1261 auto res = DefSet();
1262 auto todo = DefVec();
1263 auto seen = DefSet();
1264 for (auto node : scc)
1265 for (auto dep : node->mut()->deps())
1266 if (dep && seen.emplace(dep).second) todo.emplace_back(dep);
1267 while (!todo.empty()) {
1268 auto def = todo.back();
1269 todo.pop_back();
1270 if (def->isa_mut()) continue;
1271 if (def->isa<Nom>() && reaches_scc(def)) res.emplace(def);
1272 for (auto dep : def->deps())
1273 if (dep && seen.emplace(dep).second) todo.emplace_back(dep);
1274 }
1275 return res;
1276 }
1277
1278 void emit_mutual(const DefVec& group, auto emit_member) {
1279 std::println(os_, "{}mutual", ctx_.tab);
1280 ++ctx_.tab;
1281 {
1282 auto _ = fe::Restore(prev_, Prev::None);
1283 for (auto def : group) {
1284 sep(isa_decl(def) ? Prev::Decl : Prev::Let);
1285 emit_member(def);
1286 }
1287 }
1288 std::println(os_, "{}end", --ctx_.tab);
1289 }
1290
1291 /// A declaration spans several lines, so a blank line sets it apart from its neighbors within the same block.
1292 void sep(Prev curr) {
1293 if (prev_ == Prev::Decl || (prev_ == Prev::Let && curr == Prev::Decl)) os_ << '\n';
1294 prev_ = curr;
1295 }
1296
1297 void emit_let(const Def* def) {
1298 if (auto nom = def->isa<Nom>()) return emit_nom("nom", name(&ctx_, nom), nom);
1299 auto type = typed_let_ ? std::format(": {}", Op(&ctx_, def->type())) : std::string();
1300 std::println(os_, "{}let {}{} = {};", ctx_.tab, name(&ctx_, def), type, Full(&ctx_, def));
1301 }
1302
1303 void emit_decl(Def* mut) {
1304 if (auto lam = mut->isa_mut<Lam>()) return emit_lam(lam);
1305 if (auto rule = mut->isa_mut<Rule>(); rule && rule->is_set()) return emit_rule(rule);
1306 if (!mut->is_set()) return emit_unset(mut);
1307 // `rec` binds the name for the body - which only a self-referential mutable needs; `extern` is out either way.
1308 auto kw = is_recursive(mut) ? "rec " : "let ";
1309 // A `rec` only takes a bare variant.
1310 if (auto variant = mut->isa<Variant>())
1311 return std::println(os_, "{}{}{} = {};", ctx_.tab, kw, id(&ctx_, mut), ctors(&ctx_, variant));
1312 std::println(os_, "{}{}{} = {};", ctx_.tab, kw, id(&ctx_, mut), Full(&ctx_, mut));
1313 }
1314
1315 void emit_rule(Rule* rule) {
1316 auto dom = rule->dom();
1317 auto num = num_binders(dom);
1318 std::print(os_, "{}rule {} ", ctx_.tab, id(&ctx_, rule));
1319 auto var = rule->has_var();
1320 curry(os_, &ctx_, var, dom, false, num, num, num != 0 && split(&ctx_, var, dom, num), false);
1321 std::print(os_, ": {}", Full(&ctx_, rule->lhs()));
1322 if (rule->guard() != rule->world().lit_tt()) std::print(os_, " when {}", Full(&ctx_, rule->guard()));
1323 std::println(os_, " => {};", Full(&ctx_, rule->rhs()));
1324 }
1325
1326 /// Nothing declares a mutable that was never set, so leave a trace instead of an unreadable dump.
1327 void emit_unset(Def* mut) {
1328 std::println(os_, "{}// `{}: {}` is unset", ctx_.tab, id(&ctx_, mut), Op(&ctx_, mut->type()));
1329 }
1330
1331 void emit_lam(Lam* lam) {
1332 auto chain = curry_chain(lam);
1333 auto last = chain.back();
1334 auto fun = isa_fun(&ctx_, last) && !shadows_ret(last);
1335 auto con = Lam::isa_cn(last) && !fun;
1336
1337 // A `fun` binds its `ret` continuation as `return`, so that is the name its Var has to print with.
1338 if (fun) {
1339 if (auto var = last->has_var()) {
1340 if (auto num = num_binders(last->type()->dom()); num == 1)
1341 ctx_.names.emplace(var, "return");
1342 else
1343 ctx_.ret_projs.emplace(var, num - 1);
1344 }
1345 }
1346 // A Lam without a body forward-declares what a native translation unit provides - and only an `extern`
1347 // may go without one.
1348 if (!last->is_set()) return emit_bodyless(lam, chain, fun, con);
1349
1350 // An inner Lam of the chain has no name; its outer Lam%s applied to their own Var%s reduce to it.
1351 auto ref = id(&ctx_, lam);
1352 for (size_t i = 1; i != chain.size(); ++i) {
1353 auto var = chain[i - 1]->has_var();
1354 if (!var) break;
1355 ref += std::format(" {}{}", chain[i - 1]->type()->is_implicit() ? "@" : "", name(&ctx_, var));
1356 ctx_.names.emplace(chain[i], std::format("({})", ref));
1357 }
1358
1359 std::print(os_, "{}{}{} {}", ctx_.tab, external(lam), fun ? "fun" : con ? "con" : "lam", id(&ctx_, lam));
1360 {
1361 auto _ = fe::Restore(ctx_.in_header, true);
1362 for (auto* c : chain) {
1363 os_ << ' ';
1364 lam_ptrn(os_, &ctx_, c, fun, con, c == last, !fun || c != last);
1365 }
1366 lam_codom(os_, &ctx_, last, fun, con);
1367 }
1368 os_ << " =\n";
1369
1370 ++ctx_.tab;
1371 rets_.emplace_back(fun ? last->has_var() : nullptr);
1372 auto _ = fe::Restore(prev_, Prev::None);
1373 emit(lam);
1374 sep(Prev::Let);
1375 emit_tail(last->body(), ";");
1376 rets_.pop_back();
1377 --ctx_.tab;
1378 }
1379
1380 /// A Lam without a body only ever declares what some backend provides: `extern con f [mem.M 0, I32];`.
1381 void emit_bodyless(Lam* lam, const fe::Vector<Lam*>& chain, bool fun, bool con) {
1382 auto last = chain.back();
1383 auto dom = last->type()->dom();
1384 std::print(os_, "{}extern {} {}", ctx_.tab, fun ? "fun" : con ? "con" : "lam", id(&ctx_, lam));
1385 auto num = num_binders(dom);
1386 if (fun) {
1387 std::println(os_, "[{}]: {};", Op(&ctx_, dom->proj(num, 0)), Op(&ctx_, last->ret_dom()));
1388 } else if (auto [_, var] = dom->isa_binder<Sigma>(); var) {
1389 // Flattened, its components would refer to binders nothing prints.
1390 std::println(os_, "{}{};", Op(&ctx_, dom),
1391 con ? std::string() : std::format(": {}", Op(&ctx_, last->type()->codom())));
1392 } else {
1393 std::println(os_, "[{}]{};", Op::map(&ctx_, dom->projs(num)),
1394 con ? std::string() : std::format(": {}", Op(&ctx_, last->type()->codom())));
1395 }
1396 }
1397
1398 void emit_tail(const Def* def, std::string_view end) {
1399 std::println(os_, "{}{}{}", ctx_.tab, tail(&ctx_, def), end);
1400 }
1401
1402 /// Would a `fun` here hide the `return` of an enclosing one @p lam might still refer to?
1403 /// The `return` is a projection of its Lam's Var, so capturing that Var at all is as precise as this gets.
1404 bool shadows_ret(Lam* lam) const {
1405 for (auto* var : rets_)
1406 if (var && lam->has_free_var(var)) return true;
1407 return false;
1408 }
1409 ///@}
1410
1411 std::ostream& os_;
1412 Dump mode_;
1413 bool typed_let_;
1414 const Nest* nest_ = nullptr; ///< Nests the mutables of the scope being emitted.
1415 Prev prev_ = Prev::None;
1416 fe::Vector<const Var*> rets_;
1417 DefVec order_; ///< What to emit, in dependency order.
1418 DefVec top_;
1419 MutMap<DefVec> bucket_;
1420 MutMap<Def*> absorbed_; ///< Inner Lam of a curried chain -> the chain's outermost one.
1421 DefSet done_;
1422 DefSet scope_; ///< The closed mutables Dump::Scope dumps.
1423 DefSet pending_; ///< Def%s whose deps are being scheduled.
1424 DefMap<DefVec> mutual_; ///< The member a `mutual` group prints at -> the whole group.
1425 DefSet deferred_; ///< The other members of a `mutual` group, which print with it.
1426 Ctx ctx_;
1427 Srcs srcs_;
1428 MutSet scheduled_;
1429 /// The one `match` a Lam is an arm of, or `nullptr` if it is used otherwise and hence needs a declaration.
1430 DefMap<const Match*> arm_of_;
1431};
1432
1433} // namespace
1434
1435/*
1436 * Def
1437 */
1438
1439/// Def::Dump::Expr: one Def, one line, no analysis - and the backend of `std::formatter` for every Def pointer.
1440std::ostream& operator<<(std::ostream& os, const Def* def) {
1441 if (def == nullptr) return os << "<nullptr>";
1442 auto _ = def->world().freeze();
1443 return os << Op(nullptr, def);
1444}
1445
1446std::ostream& Def::stream(std::ostream& os, Dump mode) const {
1447 auto _ = world().freeze();
1448 if (mode == Dump::Expr) return os << this << std::endl;
1449 Dumper(os, mode, world().flags().mim_typed_let).dump(this);
1450 return os;
1451}
1452
1453void Def::dump() const { std::cout << this << std::endl; }
1454void Def::dump(Dump mode) const { stream(std::cout, mode); }
1455
1456void Def::write(Dump mode, const char* file) const {
1457 auto ofs = std::ofstream(file);
1458 stream(ofs, mode);
1459}
1460
1461void Def::write(Dump mode) const {
1462 auto file = id(nullptr, this) + ".mim"s;
1463 write(mode, file.c_str());
1464}
1465
1466/*
1467 * World
1468 */
1469
1470/// The name of the file a World is dumped to - and hence the module its own annexes live in.
1471static std::string file_stem(World& world) { return (world.name() ? world.name() : world.sym("_default")).str(); }
1472
1473void World::dump(std::ostream& os) {
1474 auto _ = freeze();
1475 auto old_gid = curr_gid();
1476 auto srcs = Srcs();
1477 auto reserved = fe::Vector<std::string>{"return"};
1478 for (const auto& import : driver().imports()) {
1479 srcs.emplace(import.src);
1480 auto kw = import.tag == ast::Tok::Tag::K_plugin ? "plugin" : "import";
1481 auto module = import.src->path().stem().string();
1482 // A stem that is no identifier names no module, so the file has to be spliced instead.
1483 auto is_mod = ast::Lexer::is_id(module);
1484 if (is_mod) reserved.emplace_back(std::move(module));
1485 // The spelling was relative to the importing file; only the resolved path re-parses from here.
1486 // Generic format: a native Windows `\` would lex as an escape sequence inside the string literal.
1487 if (import.path)
1488 std::print(os, "{} \"{}\"{};\n", kw, ast::Lexer::escape(import.src->path().generic_string()),
1489 is_mod ? "" : " as *");
1490 else
1491 std::print(os, "{} {};\n", kw, import.sym);
1492 }
1493
1494 if (!driver().imports().entries().empty() && externals().size() != 0) os << '\n';
1495
1496 for (auto mut : externals().muts())
1497 reserved.emplace_back(mut->sym().str());
1498
1499 // An `import` declares its own annexes; those of the dumped file itself only exist if the dump declares them.
1500 auto self = file_stem(*this) + ".";
1501 // An instance of a generic `anx nom` shares the key of the Nom its annex Lam returns.
1502 auto generics = ankerl::unordered_dense::map<flags_t, std::pair<Sym, fe::Vector<Lam*>>>();
1503 auto lib = driver().library();
1504 for (auto world : {this, lib == this ? nullptr : lib})
1505 if (world)
1506 for (auto [sym, def] : world->annexes().entries())
1507 if (auto lam = def->isa_mut<Lam>(); lam && !sym.view().starts_with(self)) {
1508 auto chain = curry_chain(lam);
1509 if (auto body = chain.back()->body())
1510 if (auto nom = body->isa<Nom>()) generics.emplace(nom->key(), std::pair{sym, std::move(chain)});
1511 }
1512 auto insts = DefMap<std::pair<Sym, Args>>();
1513
1514 // Only a loaded plugin registers its annexes, so find them by what the externals reach.
1515 auto annexes = fe::Vector<DumpAnnex>();
1516 auto todo = DefVec(externals().muts().begin(), externals().muts().end());
1517 auto seen = DefSet(todo.begin(), todo.end());
1518 while (!todo.empty()) {
1519 auto def = todo.back();
1520 todo.pop_back();
1521 if ((def->isa<Axm>() || is_anx_nom(def)) && def->sym().view().starts_with(self)) {
1522 auto sym = def->sym(); // a short Sym is stored inline, so its view must not outlive it
1523 auto name = sym.view().substr(self.size());
1524 auto dot = name.find('.');
1525 auto mod = dot == std::string_view::npos ? std::string_view() : name.substr(0, dot);
1526 reserved.emplace_back(mod.empty() ? name : mod);
1527 annexes.emplace_back(std::string(mod), std::string(mod.empty() ? name : name.substr(dot + 1)), def);
1528 }
1529 if (auto nom = def->isa<Nom>())
1530 if (auto generic = fe::lookup(generics, nom->key()))
1531 if (auto args = Instantiation(generic->second).args(nom))
1532 insts.emplace(nom, std::pair{generic->first, std::move(*args)});
1533 for (auto dep : def->deps())
1534 if (dep && seen.emplace(dep).second) todo.emplace_back(dep);
1535 }
1536 std::ranges::sort(annexes, {}, [](const auto& a) { return a.def->flags(); });
1537
1538 auto dumper = Dumper(os, Def::Dump::All, flags().mim_typed_let, std::move(srcs));
1539 dumper.plain(reserved);
1540 dumper.live(seen);
1541 dumper.insts(std::move(insts));
1542 if (!annexes.empty()) dumper.dump_annexes(std::move(self), annexes);
1543 dumper.dump(externals().mutate());
1544
1545 assertf(old_gid == curr_gid(), "new nodes created during dump. old_gid: {}; curr_gid: {}", old_gid, curr_gid());
1546}
1547
1548void World::dump() { dump(std::cout); }
1549
1551 if (log().level() >= fe::Log::Level::Debug) dump(log().ostream());
1552}
1553
1554void World::write(const char* file) {
1555 auto ofs = std::ofstream(file);
1556 dump(ofs);
1557}
1558
1560 auto file = file_stem(*this) + ".mim"s;
1561 write(file.c_str());
1562}
1563
1564} // namespace mim
A (possibly paramterized) Array.
Definition tuple.h:305
Definition axm.h:9
static std::pair< u8, u8 > infer_curry_and_trip(const Def *type)
Definition axm.cpp:14
Base class for all Defs.
Definition def.h:289
bool is_set() const
Definition def.h:396
const Def * proj(nat_t a, nat_t i) const
Similar to World::extract while assuming an arity of a, but also works on Sigmas and Arrays.
Definition def.cpp:647
void dump() const
Dump::Expr - one line, no analysis.
Definition dump.cpp:1453
World & world() const noexcept
Definition def.h:1105
constexpr flags_t flags() const noexcept
Definition def.h:311
friend std::ostream & operator<<(std::ostream &, const Def *)
Def::Dump::Expr: one Def, one line, no analysis - and the backend of std::formatter for every Def poi...
Definition dump.cpp:1440
bool has_free_var(const Var *) const
Same as free_vars().contains(var).
Definition def.cpp:277
std::ostream & stream(std::ostream &, Dump) const
Definition dump.cpp:1446
void write(Dump) const
Definition dump.cpp:1461
bool is_external() const noexcept
Definition def.h:569
Sym sym() const
Definition def.h:633
@ Expr
Only this Def; whatever does not inline is referenced by its name.
Definition def.h:732
@ All
Plus everything else that is reachable.
Definition def.h:734
const Var * has_var()
Only returns not nullptr, if Var of this mutable has ever been created.
Definition def.h:496
Common base for TExtremum.
Definition union.h:10
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:360
const Def * tuple() const
Definition tuple.h:370
This node is a hole in the IR that is inferred by its context later on.
Definition check.h:16
A built-in constant of type Nat -> *.
Definition def.h:1013
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:670
Constructs a Join or Variant value.
Definition union.h:60
Creates a new Tuple / Pack by inserting Insert::value at position Insert::index into Insert::tuple.
Definition tuple.h:385
A union type whose ops are sorted and deduplicated: T ∪ T is T.
Definition union.h:44
A function.
Definition lam.h:91
static const Lam * isa_cn(const Def *d)
Definition lam.h:125
static const Lam * isa_returning(const Def *d)
Definition lam.h:126
static std::optional< T > isa(const Def *def)
Definition def.h:976
Scrutinize Match::scrutinee() and dispatch to Match::arms.
Definition union.h:123
Nominal newtype term introduction.
Definition nom.h:34
GIDSet< const Node * > SCC
Strongly Connected Component.
Definition nest.h:125
Builds a nesting tree for all mutables/binders.
Definition nest.h:25
Nest(Def *root)
Definition nest.cpp:9
Nominal newtype formation.
Definition nom.h:11
A (possibly paramterized) Tuple.
Definition tuple.h:332
A dependent function type.
Definition lam.h:14
static const Pi * isa_cn(const Def *d)
Definition lam.h:50
static const Pi * isa_basicblock(const Def *d)
Definition lam.h:52
const Def * codom() const
Definition lam.h:39
static const Pi * isa_implicit(const Def *d)
Is d an Pi::is_implicit Pi?
Definition lam.h:28
static bool claim(const Driver &, Sym sym, uint32_t gid)
Registers that gid renders as sym and reports whether the plain sym may be used.
Definition driver.cpp:256
Used as intermediate value during optimizatinos such as Analysis.
Definition def.h:1065
Type formation of a rewrite Rule.
Definition rule.h:9
A rewrite rule.
Definition rule.h:40
Base class for Arr and Pack.
Definition tuple.h:267
Shape shape() const
Definition tuple.h:279
bool is_fused() const
Does this span several axes? Also true for a dynamic rank.
Definition tuple.h:211
A dependent tuple type.
Definition tuple.h:39
Singleton type formation.
Definition single.h:10
Nominal newtype term elimination.
Definition nom.h:56
Data constructor for a Sigma.
Definition tuple.h:84
A variable introduced by a binder (mutable).
Definition def.h:857
A sum type whose cases are positional: unlike a Join, they are neither sorted nor deduplicated.
Definition union.h:86
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:43
auto & muts()
Definition world.h:715
const Def * level(Defs types)
The lmax of the levels of all types, each of which must be a Type level.
Definition world.cpp:349
Driver & driver()
Definition world.h:107
u32 curr_gid() const
Manage global identifier - a unique number for each Def.
Definition world.h:118
Sym name() const
Definition world.h:113
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
Definition world.cpp:224
void dot(std::ostream &os, DotConfig cfg={}) const
Dumps DOT to os, configured via cfg (see DotConfig).
Definition dot.cpp:267
const Lam * lam(const Pi *pi, Lam::Filter f, const Def *body)
Definition world.h:414
Flags & flags()
Retrieve compile Flags.
Definition world.cpp:225
Annexes & annexes()
Definition world.h:289
void debug_dump()
Dump in Debug build if World::log::level is fe::Log::Level::Debug.
Definition dump.cpp:1550
Sym sym(std::string_view)
Definition world.cpp:228
void write(const char *file)
Write to a file named file.
Definition dump.cpp:1554
const Nom * nom(flags_t key, const Def *type)
Make type Nominal under key.
Definition world.cpp:1134
const Externals & externals() const
Definition world.h:286
auto freeze() const
Freezes the World until the end of the scope and restores the previous frozen state afterwards:
Definition world.h:160
void dump(std::ostream &os)
Dump to os.
Definition dump.cpp:1473
Singleton term introduction.
Definition single.h:32
static bool is_id(std::string_view str)
Does str match the id production - the same rule Lexer::lex_id applies to the input?
Definition lexer.cpp:261
static std::string escape(std::string_view str)
Inverse of Lexer::lex_char: renders str as the body of a Mim string literal.
Definition lexer.cpp:239
Assoc
Associativity of an infix expression.
Definition tok.h:48
constexpr Assoc prec_assoc(Prec p)
Associativity of precedence level p.
Definition tok.h:58
Prec
Expression precedences used by the parser and the dumper; ordered low to high.
Definition tok.h:51
@ bot
Alias for Mode::fast.
Definition math.h:38
Definition ast.h:16
u64 nat_t
Definition types.h:37
void post_order(Def *mut, MutSet &done, fe::Vector< Def * > &res, MutPred auto descend, MutPred auto collect)
Appends the mutables reachable from mut via Def::deps / Def::local_muts to res in post-order: callees...
Definition def.h:1155
GIDMap< const Def *, To > DefMap
Definition def.h:93
GIDSet< Def * > MutSet
Definition def.h:106
static std::string file_stem(World &world)
The name of the file a World is dumped to - and hence the module its own annexes live in.
Definition dump.cpp:1471
fe::Vector< const Def * > DefVec
Definition def.h:98
GIDSet< const Def * > DefSet
Definition def.h:94
std::ostream & operator<<(std::ostream &os, const Def *def)
Def::Dump::Expr: one Def, one line, no analysis - and the backend of std::formatter for every Def poi...
Definition dump.cpp:1440
TExt< false > Bot
Definition union.h:39
constexpr Assoc prec_assoc(Prec p)
Associativity of precedence level p.
Definition tok.h:58
@ Lam
Definition def.h:125
@ Match
Definition def.h:125
@ Axm
Definition def.h:125
@ Extract
Definition def.h:125
@ Nom
Definition def.h:125
Prec
Expression precedences used by the parser and the dumper; ordered low to high.
Definition tok.h:51
GIDMap< Def *, To > MutMap
Definition def.h:105