MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
sexpr.cpp
Go to the documentation of this file.
1#include "mim/sexpr.h"
2
3#include <iostream>
4#include <ranges>
5#include <regex>
6#include <sstream>
7
8#include "mim/def.h"
9
10#include "mim/be/emitter.h"
11
12namespace mim::sexpr {
13
14struct BB {
15 BB() = default;
16 BB(const BB&) = delete;
17 BB(BB&& other) noexcept = default;
18 BB& operator=(BB other) noexcept { return swap(*this, other), *this; }
19
20 std::deque<std::ostringstream>& head() { return parts[0]; }
21 std::deque<std::ostringstream>& body() { return parts[1]; }
22 std::deque<std::ostringstream>& tail() { return parts[2]; }
23
24 template<class... Args>
25 void body(std::format_string<Args...> s, Args&&... args) {
26 std::print(body().emplace_back(), s, std::forward<Args>(args)...);
27 }
28
29 template<class... Args>
30 void tail(std::format_string<Args...> s, Args&&... args) {
31 std::print(tail().emplace_back(), s, std::forward<Args>(args)...);
32 }
33
34 template<class... Args>
35 std::string assign(fe::Tab tab, bool slotted, std::string name, std::format_string<Args...> s, Args&&... args) {
36 if (!is_assigned(name)) {
37 assign(name);
38 auto& os = body().emplace_back();
39 if (slotted) {
40 std::print(os, "\n{}(let", tab);
41 ++tab;
42 std::print(os, "\n{}{}", tab, name);
43 std::print(os, "\n{}(scope", tab);
44 ++tab;
45 std::print(os, "\n{}", tab);
46 std::print(os, s, std::forward<Args>(args)...);
47 --tab;
48 --tab;
49 } else {
50 std::print(os, "\n{}(let", tab);
51 ++tab;
52 std::print(os, "\n{}{}", tab, name);
53 std::print(os, "\n{}", tab);
54 std::print(os, s, std::forward<Args>(args)...);
55 --tab;
56 }
57 }
58 return name;
59 }
60
61 template<class Fn>
62 std::string assign(fe::Tab tab, bool slotted, std::string name, Fn&& print_term) {
63 if (!is_assigned(name)) {
64 assign(name);
65 auto& os = body().emplace_back();
66 if (slotted) {
67 std::print(os, "\n{}(let", tab);
68 ++tab;
69 std::print(os, "\n{}{}", tab, name);
70 std::print(os, "\n{}(scope", tab);
71 print_term(tab, os);
72 --tab;
73 } else {
74 std::print(os, "\n{}(let", tab);
75 ++tab;
76 std::print(os, "\n{}{}", tab, name);
77 --tab;
78 print_term(tab, os);
79 }
80 }
81 return name;
82 }
83
84 friend void swap(BB& a, BB& b) noexcept {
85 using std::swap;
86 swap(a.parts, b.parts);
87 swap(a.assigned, b.assigned);
88 }
89
90 bool is_assigned(std::string name) const { return assigned.contains(name); }
91 void assign(std::string name) { assigned.insert(name); }
92
93 std::array<std::deque<std::ostringstream>, 3> parts;
94 absl::flat_hash_set<std::string> assigned;
95};
96
97class Emitter : public mim::Emitter<std::string, std::string, BB, Emitter> {
98public:
100
101 Emitter(World& world, std::ostream& ostream, bool typed = false, bool slotted = false)
102 : Super(world, "sexpr_emitter", ostream, true) {
103 typed_ = typed;
104 slotted_ = slotted;
105 types_enabled_ = true;
106 slots_enabled_ = true;
107 bindings_enabled_ = true;
108 }
109
110 bool direct_style() override { return true; }
111 bool is_valid(std::string_view s) { return !s.empty(); }
112 void start() override;
113 void emit_imported(Lam*);
114 std::string prepare();
115 void emit_epilogue(Lam*);
116 void finalize();
117
118 LamSet next_lams(Lam* lam);
119
120 void emit_decl(BB& bb, const Def* def);
121 void emit_lam(Lam* parent, Lam* curr, LamSet& rec_lams);
122 std::string emit_var(BB& bb, const Def* var, const Def* type, bool meta_var = false);
123 std::string emit_head(BB& bb, Lam* lam, bool nested = false);
124 std::string emit_cons_type(BB& bb, fe::View<const Def*> ops);
125 std::string emit_type(BB& bb, const Def* type, bool in_term = false);
126 std::string emit_cons(std::vector<std::string> op_vals);
127 std::string emit_node(BB& bb, const Def* def, std::string node_name, bool variadic = false, bool with_type = false);
128 std::string emit_bb(BB& bb, const Def* def);
129
130private:
131 // A Def that has a name can be considered to be bound to a variable.
132 // Defs that are unbound will be printed inline (by definition)
133 bool is_bound(const Def* def) const { return !def->sym().empty(); }
134
135 std::string id(const Def*, bool is_var_use = false) const;
136 std::string indent(size_t tabs, std::string term);
137 std::string flatten(std::string term);
138
139 // Determines whether the symbolic expression should
140 // be emitted with type annotations.
141 bool typed() const { return typed_; }
142 bool typed_;
143
144 // Temporarily disable type annotations while emitting.
145 // We do not want to annotate values that are emitted as part of
146 // a dependent type (i.e. during calls to emit_bb from emit_type for an array shape)
147 bool toggle_types() { return types_enabled_ = !types_enabled_; }
148 bool types_enabled() const { return typed() && types_enabled_; }
149 bool types_enabled_;
150
151 // Determines whether the symbolic expression should
152 // be emitted in a style that is compatible with slotted-egraphs.
153 bool slotted() const { return slotted_; }
154 bool slotted_;
155
156 // Temporarily disable slots while emitting.
157 // While slots are disabled, no identifier is prefixed with '$'
158 // and no var uses are wrapped in var nodes.
159 bool toggle_slots() { return slots_enabled_ = !slots_enabled_; }
160 bool slots_enabled() const { return slotted() && slots_enabled_; }
161 bool slots_enabled_;
162
163 // Temporarily disable the creation/use of bindings while emitting.
164 // While bindings are disabled every var use of a binding will be
165 // printed as the bindings' definition instead.
166 // This is useful to print a term via emit_bb() with the assumption
167 // that no variables have been bound. (i.e. for printing a lambda filter)
168 bool toggle_bindings() { return bindings_enabled_ = !bindings_enabled_; }
169 bool bindings_enabled() const { return bindings_enabled_; }
170 bool bindings_enabled_;
171
172 // Ensures that we don't redeclare things, for example axm.foo
173 // should only be declared once.
174 absl::flat_hash_set<std::string> declared_;
175 bool is_declared(std::string name) { return declared_.contains(name); }
176
177 std::ostringstream decls_;
178 std::ostringstream func_decls_;
179 std::ostringstream func_impls_;
180};
181
182std::string Emitter::id(const Def* def, bool is_var_use) const {
183 std::string prefix = slots_enabled() ? "$" : "";
184 std::string id;
185
186 auto var_wrap = [&](std::string id) {
187 auto cond_slotted = slots_enabled() && is_var_use && id.starts_with(prefix);
188 auto cond_regular = !slotted() && is_var_use;
189 return cond_slotted || cond_regular ? std::format("(var {})", id) : id;
190 };
191
192 // Axioms, rules, unset lambdas(imports) and externals need to be emitted without a uid
193 if (def->isa<Axm>())
194 id = def->sym().str();
195 else if (def->isa<Rule>())
196 id = def->sym().str();
197 else if (def->isa<Lam>() && !def->is_set())
198 id = def->sym().str();
199 else if (def->is_external())
200 id = def->sym().str();
201 // Top-level lambdas should never be treated as slots ($-prefixed)
202 else if (def->isa<Lam>() && def->is_closed())
203 id = def->unique_name();
204 else
205 id = prefix + def->unique_name();
206
207 return var_wrap(id);
208}
209
210// Adjusts the base indentation of a term-string like
211//
212// " (app
213// foo
214// bar
215// )"
216//
217// to the number of tabs specified with 'tabs' (i.e. for tabs=1)
218//
219// " (app
220// foo
221// bar
222// )"
223//
224std::string Emitter::indent(size_t tabs, std::string term) {
225 std::string indent(tabs * 4, ' ');
226 std::string result;
227 std::string line;
228
229 while (!term.empty() && (term.front() == '\n' || term.front() == '\r'))
230 term.erase(0, 1);
231
232 std::stringstream term_stream(term);
233 size_t min_indent = term.find_first_not_of(' ');
234 while (std::getline(term_stream, line)) {
235 // Skips empty lines
236 if (line.find_first_not_of(" \t\r\n") == std::string::npos) continue;
237 result += "\n" + indent + line.substr(min_indent);
238 }
239
240 return result;
241}
242
243// Removes all indentation so a term-string like
244//
245// " (app
246// foo
247// bar
248// )"
249//
250// becomes flattened like below
251//
252// "(app foo bar)"
253//
254std::string Emitter::flatten(std::string term) {
255 term = std::regex_replace(term, std::regex("( {4})"), "");
256
257 while (!term.empty() && (term.front() == '\n' || term.front() == '\r'))
258 term.erase(0, 1);
259
260 term = std::regex_replace(term, std::regex("(\\r|\\n)"), " ");
261 return term;
262}
263
265 Super::start();
266
267 ostream() << decls_.str();
268 ostream() << func_decls_.str();
269 ostream() << func_impls_.str();
270}
271
273 auto bb = BB();
274
275 const std::string lam_kind = Lam::isa_returning(lam) ? "fun" : Lam::isa_cn(lam) ? "con" : "lam";
276 const std::string ext = lam->is_external() ? "extern" : "intern";
277
278 if (slotted()) {
279 std::print(func_decls_, "(root {} {}", ext, id(lam));
280 ++tab;
281 if (types_enabled()) std::print(func_decls_, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
282 std::print(func_decls_, "\n{}({}", tab, lam_kind);
283 std::print(func_decls_, "{}", emit_var(bb, lam->var(), lam->type()->dom()));
284 ++tab;
285 // Since alpha-equivalent lambdas are all represented in the same eclass in slotted, we need
286 // to somehow make these imports not alpha-equivalent because our type-analysis stores
287 // types on eclasses and we would otherwise be overwriting the types of other imports if we have
288 // multiple. We solve this issue by putting these filler symbols "<foo-filter>" ... into the bodies.
289 std::print(func_decls_, "\n{}(scope <{}-filter> <{}-body>)", tab, id(lam), id(lam));
290 --tab;
291 if (types_enabled()) std::print(func_decls_, ")");
292 std::print(func_decls_, "))\n\n");
293 --tab;
294
295 } else {
296 std::print(func_decls_, "(root {} {}", ext, id(lam));
297 ++tab;
298 if (types_enabled()) std::print(func_decls_, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
299 std::print(func_decls_, "\n{}({}", tab, lam_kind);
300 std::print(func_decls_, "{}", emit_var(bb, lam->var(), lam->type()->dom()));
301 if (types_enabled()) std::print(func_decls_, ")");
302 std::print(func_decls_, "))\n\n");
303 --tab;
304 }
305}
306
307std::string Emitter::prepare() { return root()->unique_name(); }
308
310 auto& bb = lam2bb_[lam];
311 if (is_bound(lam)) bb.tail("{}", emit(lam->body()));
312}
313
315 // We don't want to emit config lams that define which rules should be emitted.
316 // The rules in the body of such a lambda will be emitted into decls_
317 // via emit_bb() but we don't want to emit the lambda itself.
318 // We can't do this with Axm::isa because 'eqsat' is an out-of-tree plugin
319 // that isn't guaranteed to have been cloned so we can't include its header file.
320 if (root()->codom()->sym().str() == "eqsat.Config") return;
321
322 LamSet rec_lams;
323 auto root_lam = nest().root()->mut()->as_mut<Lam>();
324 if (is_bound(root_lam)) emit_lam(root_lam, root_lam, rec_lams);
325}
326
329 for (auto op : lam->deps()) {
330 for (auto mut : op->local_muts())
331 if (auto next = nest()[mut]) {
332 if (auto next_lam = next->mut()->isa<Lam>()) next_lams.insert(next_lam);
333 }
334 }
335 return next_lams;
336}
337
338void Emitter::emit_decl(BB& bb, const Def* def) {
339 if (auto axm = def->isa<Axm>()) {
340 if (!world().annexes().flags2entry().contains(axm->flags()) && !is_declared(axm->sym().str())) {
341 // Slots may have been disabled if we are coming from a rule declaration below
342 // in which case we want to enable them for the duration of emitting the axioms' type.
343 bool enable_slots = !slots_enabled();
344 if (enable_slots) toggle_slots();
345
346 if (typed()) std::print(decls_, "(@ {}\n", emit_type(bb, axm->type()));
347
348 std::print(decls_, "(axm {}", id(axm));
349
350 if (typed()) std::print(decls_, ")");
351 std::print(decls_, ")\n\n");
352
353 if (enable_slots) toggle_slots();
354
355 declared_.insert(axm->sym().str());
356 }
357 } else if (def->isa_imm<Rule>()) {
358 assert(false && "TODO no vars in immutable Rule");
359 } else if (auto rule = def->isa_mut<Rule>()) {
360 bool suppress_annotations = types_enabled();
361 bool suppress_slots = slots_enabled();
362
363 if (suppress_annotations) toggle_types();
364 auto meta_var_val = emit_var(bb, rule->var(), rule->dom(), true);
365
366 if (suppress_slots) toggle_slots();
367 auto lhs_val = emit_bb(bb, rule->lhs());
368 auto rhs_val = emit_bb(bb, rule->rhs());
369 auto guard_val = emit_bb(bb, rule->guard());
370
371 if (suppress_slots) toggle_slots();
372 if (suppress_annotations) toggle_types();
373
374 std::print(decls_, "(rule {} {} {} {} {})\n\n", indent(1, id(rule)), indent(1, meta_var_val),
375 indent(1, lhs_val), indent(1, rhs_val), indent(1, guard_val));
376
377 declared_.insert(rule->sym().str());
378 }
379}
380
381void Emitter::emit_lam(Lam* parent, Lam* curr, LamSet& rec_lams) {
382 // We do not want to re-emit recursively defined lambdas because it would result in an endless loop
383 auto lam_node = nest()[curr];
384 if (lam_node->is_recursive()) rec_lams.emplace(curr);
385 assert(lam2bb_.contains(curr));
386 auto& bb = lam2bb_[curr];
387 auto& parent_bb = lam2bb_[parent];
388
389 // Lambdas that are not bound to a variable will be printed inline.
390 // I.e. their definition will simply be emitted in place as in (app (lm x.x) 2)
391 // Only the lambdas that are bound to a variable will be emitted here.
392 const bool EMIT = is_bound(curr) && !parent_bb.is_assigned(id(curr));
393 // A lambda nested inside of a top-level lambda will be wrapped with a let-binding
394 // as in (root (lam x (let child (lam y y) (app child x))))
395 const bool NESTED = curr != root();
396
397 if (EMIT) {
398 parent_bb.assign(id(curr));
399 std::print(func_impls_, "{}", emit_head(bb, curr, NESTED));
400 }
401
402 for (auto next_lam : next_lams(curr)) {
403 if (!rec_lams.contains(next_lam)) {
404 // The parent of the next lam will be the parent of the current lam
405 // if the current lam doesn't get emitted (is inline). This way we maintain
406 // a correct child-parent relation between actually emitted lambdas.
407 auto next_parent = EMIT ? curr : parent;
408 emit_lam(next_parent, next_lam, rec_lams);
409 }
410 }
411
412 if (EMIT) {
413 int unclosed_parens = 0;
414
415 for (auto& term : bb.body()) {
416 auto opened = std::ranges::count(term.str(), '(');
417 auto closed = std::ranges::count(term.str(), ')');
418 unclosed_parens += opened - closed;
419 std::print(func_impls_, "{}", indent(tab.indent(), term.str()));
420 }
421
422 for (auto& term : bb.tail())
423 std::print(func_impls_, "{}", indent(tab.indent(), term.str()));
424
425 std::string closing_parens(unclosed_parens, ')');
426 std::print(func_impls_, "{}", closing_parens);
427
428 // Close type annotation '@'
429 if (types_enabled()) std::print(func_impls_, ")");
430
431 --tab;
432 --tab;
433 if (slotted()) {
434 --tab;
435 if (NESTED) {
436 --tab;
437 // Close 'lam' and lam var 'scope'
438 std::print(func_impls_, "))");
439 // Close the 'let' and let 'scope' at the end of the parent lambdas' definition.
440 parent_bb.tail("))");
441 } else {
442 // Close 'root', 'lam' and lam var 'scope'
443 std::print(func_impls_, ")))\n\n");
444 }
445
446 } else if (NESTED) {
447 // Close 'lam'
448 std::print(func_impls_, ")");
449 // Close the 'let' at the end of the parent lambdas' definition.
450 parent_bb.tail(")");
451 } else {
452 // Close 'root' and 'lam'
453 std::print(func_impls_, "))\n\n");
454 }
455 }
456}
457
458std::string Emitter::emit_var(BB& bb, const Def* var, const Def* type, bool meta_var) {
459 std::ostringstream os;
460
461 ++tab;
462 if (slotted()) {
463 // We assume that the depth of projections for rule meta vars is at most one so
464 // (a: Nat, b: Bool) is okay but (a: [b: Nat]) is not.
465 if (meta_var) {
466 toggle_slots();
467 auto projs = var->projs();
468 if (projs.size() == 1 || std::ranges::all_of(projs, [](auto proj) { return proj->sym().empty(); }))
469 std::print(os, "\n{}(cons (metavar {}) nil)", tab, id(var));
470 else {
471 std::vector<std::string> meta_vars;
472 for (auto proj : projs) {
473 ++tab;
474 auto meta_var = std::format("\n{}(metavar {})", tab, id(proj));
475 --tab;
476 meta_vars.push_back(meta_var);
477 }
478 std::print(os, "{}", emit_cons(meta_vars));
479 }
480 toggle_slots();
481 } else {
482 std::print(os, "\n{}{}", tab, id(var));
483 }
484 }
485
486 else if (meta_var) {
487 auto projs = var->projs();
488 if (projs.size() == 1 || std::ranges::all_of(projs, [](auto proj) { return proj->sym().empty(); }))
489 std::print(os, "\n{}(metavar {})", tab, id(var));
490 else {
491 std::print(os, "\n{}(metavar {}", tab, id(var));
492 size_t i = 0;
493 for (auto proj : projs)
494 std::print(os, "{}", emit_var(bb, proj, type->proj(i++), meta_var));
495 std::print(os, ")");
496 }
497 } else {
498 std::print(os, "\n{}{}", tab, id(var));
499 }
500 --tab;
501
502 return os.str();
503}
504
505std::string Emitter::emit_head(BB& bb, Lam* lam, bool nested) {
506 std::ostringstream os;
507
508 const std::string lam_kind = Lam::isa_returning(lam) ? "fun" : Lam::isa_cn(lam) ? "con" : "lam";
509 const std::string ext = lam->is_external() ? "extern" : "intern";
510
511 if (slotted()) {
512 if (nested) {
513 std::print(os, "\n{}(let", tab);
514 ++tab;
515 std::print(os, "\n{}{}", tab, id(lam));
516 std::print(os, "\n{}(scope", tab);
517 ++tab;
518 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
519 std::print(os, "\n{}({}", tab, lam_kind);
520 } else {
521 // We toggle slot-printing to emit the lam id without a slot prefix '$'
522 toggle_slots();
523 std::print(os, "(root {} {}", ext, id(lam));
524 toggle_slots();
525 ++tab;
526 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
527 std::print(os, "\n{}({}", tab, lam_kind);
528 }
529
530 } else if (nested) {
531 std::print(os, "\n{}(let", tab);
532 ++tab;
533 std::print(os, "\n{}{}", tab, id(lam));
534 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
535 std::print(os, "\n{}({}", tab, lam_kind);
536 } else {
537 std::print(os, "(root {} {}", ext, id(lam));
538 ++tab;
539 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
540 std::print(os, "\n{}({}", tab, lam_kind);
541 }
542
543 std::print(os, "{}", emit_var(bb, lam->var(), lam->type()->dom()));
544
545 if (slotted()) {
546 ++tab;
547 std::print(os, "\n{}(scope", tab);
548 // Occasionally a filter will refer to variables that will only
549 // start to get bound in the body of the lambda and we therefore
550 // disable the use of variables for the duration of emitting the filter
551 // in order to print every variable use by its definition instead.
552 toggle_bindings();
553 std::print(os, "{}", emit_bb(bb, lam->filter()));
554 toggle_bindings();
555 } else {
556 std::print(os, "{}", emit_bb(bb, lam->filter()));
557 }
558 ++tab;
559
560 return os.str();
561}
562
563std::string Emitter::emit_cons_type(BB& bb, fe::View<const Def*> ops) {
564 std::ostringstream os;
565
566 if (ops.size() == 0) {
567 std::print(os, "nil");
568 return os.str();
569 }
570
571 size_t op_idx = 0;
572 for (auto op : ops) {
573 std::print(os, "(cons {} ", emit_type(bb, op));
574 if (op_idx == ops.size() - 1) std::print(os, "nil");
575 op_idx++;
576 }
577
578 std::string closing_brackets(ops.size(), ')');
579 std::print(os, "{}", closing_brackets);
580
581 return os.str();
582}
583
584std::string Emitter::emit_type(BB& bb, const Def* type, bool in_term /* = false*/) {
585 std::ostringstream os;
586 auto scope_wrap = [&](std::string val) { return slotted() ? "(scope " + val + ")" : val; };
587
588 if (type->isa<Nat>()) {
589 std::print(os, "Nat");
590 } else if (auto size = Idx::isa(type)) {
591 if (auto lit_size = Idx::size2bitwidth(size)) {
592 switch (*lit_size) {
593 case 1: return types_[type] = "Bool";
594 case 8: return types_[type] = "I8";
595 case 16: return types_[type] = "I16";
596 case 32: return types_[type] = "I32";
597 case 64: return types_[type] = "I64";
598 default: break;
599 }
600 std::print(os, "(idx (lit {} Nat))", size);
601 } else {
602 std::print(os, "(idx {})", emit_type(bb, size, in_term));
603 }
604 } else if (auto lit = type->isa<Lit>()) {
605 if (lit->type()->isa<Nat>())
606 std::print(os, "(lit {} Nat)", lit);
607 else if (auto size = Idx::isa(lit->type()))
608 if (auto lit_size = Idx::size2bitwidth(size); lit_size && *lit_size == 1)
609 std::print(os, "(lit {} Bool)", lit);
610 else
611 std::print(os, "(lit {} {})", lit->get(), emit_type(bb, lit->type(), in_term));
612 else
613 std::print(os, "(lit {} {})", lit->get(), emit_type(bb, lit->type(), in_term));
614 } else if (auto arr = type->isa<Arr>()) {
615 std::string arity_val;
616 if (auto top = arr->arity()->isa<Top>()) {
617 arity_val = "(top " + emit_type(bb, top->type(), in_term) + ")";
618 } else {
619 // We disable type annotations only if they aren't already disabled
620 // because we would otherwise get annotations inside of the array type.
621 // We also disable the use of bindings unless this type is emitted as part of a term.
622 // In that case the array shape may refer to bound variables.
623 bool suppress_annotations = types_enabled();
624 if (suppress_annotations) toggle_types();
625 if (!in_term) toggle_bindings();
626 arity_val = flatten(emit_bb(bb, arr->arity()));
627 if (suppress_annotations) toggle_types();
628 if (!in_term) toggle_bindings();
629 }
630 std::string arr_val = arity_val + " " + emit_type(bb, arr->body(), in_term);
631
632 if (auto var = arr->has_var()) {
633 auto var_val = id(var);
634 std::print(os, "(arr {} {})", var_val, scope_wrap(arr_val));
635 } else {
636 auto dummy_var = slotted() ? "$dummy" : "dummy";
637 std::print(os, "(arr {} {})", dummy_var, scope_wrap(arr_val));
638 }
639
640 } else if (auto pi = type->isa<Pi>()) {
641 std::string pi_kind = Pi::isa_implicit(pi) ? "pi*" : "pi";
642 std::string doms = emit_type(bb, pi->dom(), in_term) + " " + emit_type(bb, pi->codom(), in_term);
643
644 if (auto var = pi->has_var()) {
645 auto var_val = id(var);
646 std::print(os, "({} {} {})", pi_kind, var_val, scope_wrap(doms));
647 } else {
648 auto dummy_var = slotted() ? "$dummy" : "dummy";
649 std::print(os, "({} {} {})", pi_kind, dummy_var, scope_wrap(doms));
650 }
651
652 } else if (auto sigma = type->isa<Sigma>()) {
653 std::ostringstream op_vals;
654 slotted() ? op_vals << emit_cons_type(bb, sigma->ops()) + " nil"
655 : op_vals << fe::Join(
656 sigma->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }), " ");
657
658 if (auto var = sigma->has_var()) {
659 auto var_val = id(var);
660 std::print(os, "(sigma {} {})", var_val, scope_wrap(op_vals.str()));
661 } else {
662 auto dummy_var = slotted() ? "$dummy" : "dummy";
663 std::print(os, "(sigma {} {})", dummy_var, scope_wrap(op_vals.str()));
664 }
665
666 } else if (auto tuple = type->isa<Tuple>()) {
667 if (slotted())
668 std::print(os, "(tuple {})", emit_cons_type(bb, tuple->ops()));
669 else
670 std::print(
671 os, "(tuple {})",
672 fe::Join(tuple->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }),
673 " "));
674 } else if (auto app = type->isa<App>()) {
675 std::print(os, "(app {} {})", emit_type(bb, app->callee(), in_term), emit_type(bb, app->arg(), in_term));
676 } else if (auto axm = type->isa<Axm>()) {
677 std::print(os, "{}", id(axm));
678 emit_decl(bb, axm);
679 } else if (auto var = type->isa<Var>()) {
680 if (var->binder()->isa<Rule>())
681 std::print(os, "\n{}{}", tab, id(var));
682 else
683 std::print(os, "{}", id(var, true));
684 } else if (auto hole = type->isa<Hole>()) {
685 std::print(os, "(hole {})", emit_type(bb, hole->type(), in_term));
686 } else if (auto extract = type->isa<Extract>()) {
687 // Projections of rule variables are meta vars and should just be printed by name
688 if (auto var = extract->tuple()->isa<Var>(); var && var->binder()->isa<Rule>())
689 std::print(os, "{}", id(extract));
690 else if (in_term && bb.is_assigned(id(extract)))
691 std::print(os, "{}", id(extract));
692 else
693 std::print(os, "(extract {} {})", emit_type(bb, extract->tuple(), in_term),
694 emit_type(bb, extract->index(), in_term));
695 } else if (auto mType = type->isa<Type>()) {
696 std::print(os, "(type {})", emit_type(bb, mType->level(), in_term));
697 } else if (type->isa<Univ>()) {
698 std::print(os, "Univ");
699 } else if (auto reform = type->isa<Reform>()) {
700 std::print(os, "(reform {})", emit_type(bb, reform->dom(), in_term));
701 } else if (auto join = type->isa<Join>()) {
702 if (slotted())
703 std::print(os, "(join {})", emit_cons_type(bb, join->ops()));
704 else
705 std::print(
706 os, "(join {})",
707 fe::Join(join->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }),
708 " "));
709 } else if (auto meet = type->isa<Meet>()) {
710 if (slotted())
711 std::print(os, "(meet {})", emit_cons_type(bb, meet->ops()));
712 else
713 std::print(
714 os, "(meet {})",
715 fe::Join(meet->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }),
716 " "));
717 } else if (auto bot = type->isa<Bot>()) {
718 std::print(os, "(bot {})", emit_type(bb, bot->type(), in_term));
719 } else if (auto top = type->isa<Top>()) {
720 std::print(os, "(top {})", emit_type(bb, top->type(), in_term));
721 } else {
722 fe::throwf("unsupported type `{}`", type);
723 fe::unreachable();
724 }
725
726 return os.str();
727}
728
729// This is primarily needed because slotted-egraphs don't support
730// variadic enodes (yet?) so we have to represent those as nested cons lists
731// i.e. for Tuple: (tuple (cons a (cons b nil)))
732std::string Emitter::emit_cons(std::vector<std::string> op_vals) {
733 std::ostringstream os;
734
735 if (op_vals.size() == 0) {
736 ++tab;
737 std::print(os, "\n{}nil", tab);
738 --tab;
739 return os.str();
740 }
741
742 size_t op_idx = 0;
743 for (auto op_val : op_vals) {
744 ++tab;
745 std::print(os, "\n{}(cons", tab);
746 ++tab;
747 std::print(os, "{}", indent(tab.indent(), op_val));
748 --tab;
749 if (op_idx == op_vals.size() - 1) std::print(os, "\n{}nil", tab);
750 --tab;
751
752 op_idx++;
753 }
754
755 std::string closing_brackets(op_vals.size(), ')');
756 std::print(os, "{}", closing_brackets);
757
758 return os.str();
759}
760
761std::string Emitter::emit_node(BB& bb, const Def* def, std::string node_name, bool variadic, bool with_type) {
762 std::ostringstream os;
763
764 std::vector<std::string> op_vals;
765
766 auto type_val = emit_type(bb, def->type());
767 if (with_type) {
768 if (!type_val.empty()) op_vals.push_back(type_val);
769 }
770
771 if (auto pack = def->isa<Pack>()) {
772 if (auto var = pack->has_var()) {
773 std::string var_val = " " + (slotted() ? id(var) + " (scope" : id(var));
774 op_vals.push_back(var_val);
775 } else {
776 std::string var_val = slotted() ? " $dummy (scope" : " dummy";
777 op_vals.push_back(var_val);
778 }
779 if (auto arity_val = emit_bb(bb, pack->arity()); !arity_val.empty()) op_vals.push_back(arity_val);
780 }
781
782 if (auto proxy = def->isa<Proxy>()) {
783 std::ostringstream tag;
784 std::print(tag, "\n{}", proxy->tag());
785 op_vals.push_back(tag.str());
786 }
787
788 for (auto op : def->ops())
789 if (auto op_val = emit_bb(bb, op); !op_val.empty()) op_vals.push_back(op_val);
790
791 if (is_bound(def) && bindings_enabled()) {
792 bb.assign(tab, slotted(), id(def), [&](fe::Tab tab, auto& os) {
793 ++tab;
794 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, type_val);
795 std::print(os, "\n{}({}", tab, node_name);
796
797 if (slotted() && variadic)
798 std::print(os, "{}", emit_cons(op_vals));
799 else {
800 ++tab;
801 for (auto op_val : op_vals)
802 std::print(os, "{}", indent(tab.indent(), op_val));
803 --tab;
804 }
805
806 // Close the packs' var 'scope'
807 if (slotted() && def->isa<Pack>()) std::print(os, ")");
808
809 std::print(os, ")");
810 if (types_enabled()) std::print(os, ")");
811 --tab;
812 });
813 std::print(os, "\n{}{}", tab, id(def, true));
814
815 } else {
816 std::print(os, "\n{}({}", tab, node_name);
817
818 if (slotted() && variadic)
819 std::print(os, "{}", emit_cons(op_vals));
820 else
821 for (auto op_val : op_vals)
822 std::print(os, "{}", op_val);
823
824 // Close the packs' var 'scope'
825 if (slotted() && def->isa<Pack>()) std::print(os, ")");
826
827 std::print(os, ")");
828 }
829
830 return os.str();
831}
832
833std::string Emitter::emit_bb(BB& bb, const Def* def) {
834 std::ostringstream os;
835
836 ++tab;
837 if (def->type()->isa<Type>() || def->type()->isa<Univ>()) {
838 std::print(os, "\n{}{}", tab, emit_type(bb, def, true));
839 // Short circuit because we probably don't want to type
840 // annotate a type (or do we?)
841 --tab;
842 return os.str();
843 }
844
845 // We don't annotate axioms since this makes the sexpr's extremely cluttered and the axioms
846 // will already be emitted separately with an annotation.
847 if (types_enabled() && !def->isa<Axm>()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, def->type()));
848
849 if (def->isa_imm<Lam>()) {
850 assert(false && "TODO immutable lam inline");
851 } else if (auto lam = def->isa_mut<Lam>()) {
852 if (is_bound(lam))
853 std::print(os, "\n{}{}", tab, id(lam, true));
854 else {
855 auto lam_kind = Lam::isa_returning(lam) ? "fun" : Lam::isa_cn(lam) ? "con" : "lam";
856 if (slotted()) {
857 std::print(os, "\n{}({}", tab, lam_kind);
858 std::print(os, "{}", emit_var(bb, lam->var(), lam->var()->type()));
859 ++tab;
860 std::print(os, "\n{}(scope", tab);
861 std::print(os, "{}", emit_bb(bb, lam->filter()));
862 std::print(os, "{}", emit_bb(bb, lam->body()));
863 --tab;
864 std::print(os, "))");
865 } else {
866 std::print(os, "\n{}({}", tab, lam_kind);
867 std::print(os, "\n{}{}", tab, emit_var(bb, lam->var(), lam->var()->type()));
868 std::print(os, "{}", emit_bb(bb, lam->filter()));
869 std::print(os, "{}", emit_bb(bb, lam->body()));
870 std::print(os, ")");
871 }
872 }
873 } else if (auto lit = def->isa<Lit>()) {
874 if (lit->type()->isa<Nat>())
875 std::print(os, "\n{}(lit {} Nat)", tab, lit);
876 else if (auto size = Idx::isa(lit->type()))
877 if (auto lit_size = Idx::size2bitwidth(size); lit_size && *lit_size == 1)
878 std::print(os, "\n{}(lit {} Bool)", tab, lit);
879 else
880 std::print(os, "\n{}(lit {} {})", tab, lit->get(), emit_type(bb, lit->type()));
881 else
882 std::print(os, "\n{}(lit {} {})", tab, lit->get(), emit_type(bb, lit->type()));
883 } else if (auto tuple = def->isa<Tuple>()) {
884 std::print(os, "{}", emit_node(bb, tuple, "tuple", true));
885 } else if (auto pack = def->isa<Pack>()) {
886 std::print(os, "{}", emit_node(bb, pack, "pack"));
887 } else if (auto extract = def->isa<Extract>()) {
888 // Projections of rule variables are meta vars and should just be printed by name
889 if (auto var = extract->tuple()->isa<Var>(); var && var->binder()->isa<Rule>())
890 std::print(os, "\n{}{}", tab, id(extract));
891 else
892 std::print(os, "{}", emit_node(bb, extract, "extract"));
893 } else if (auto insert = def->isa<Insert>()) {
894 std::print(os, "{}", emit_node(bb, insert, "insert"));
895 } else if (auto var = def->isa<Var>()) {
896 if (var->binder()->isa<Rule>())
897 std::print(os, "\n{}{}", tab, id(var));
898 else
899 std::print(os, "\n{}{}", tab, id(var, true));
900 } else if (auto app = def->isa<App>()) {
901 std::print(os, "{}", emit_node(bb, app, "app"));
902 } else if (auto axm = def->isa<Axm>()) {
903 std::print(os, "\n{}{}", tab, id(axm));
904 emit_decl(bb, axm);
905 } else if (auto bot = def->isa<Bot>()) {
906 if (is_bound(bot)) {
907 bb.assign(tab, slotted(), id(bot), "(bot {})", emit_type(bb, bot->type()));
908 std::print(os, "\n{}{}", tab, id(bot, true));
909 } else {
910 std::print(os, "\n{}(bot {})", tab, emit_type(bb, bot->type()));
911 }
912 } else if (auto top = def->isa<Top>()) {
913 if (is_bound(top)) {
914 bb.assign(tab, slotted(), id(top), "(top {})", emit_type(bb, top->type()));
915 std::print(os, "\n{}{}", tab, id(top, true));
916 } else {
917 std::print(os, "\n{}(top {})", tab, emit_type(bb, top->type()));
918 }
919 } else if (auto rule = def->isa<Rule>()) {
920 std::print(os, "\n{}{}", tab, id(rule, true));
921 emit_decl(bb, rule);
922 } else if (auto inj = def->isa<Inj>()) {
923 std::print(os, "{}", emit_node(bb, inj, "inj", false, true));
924 } else if (auto merge = def->isa<Merge>()) {
925 std::print(os, "{}", emit_node(bb, merge, "merge", true, true));
926 } else if (auto match = def->isa<Match>()) {
927 std::print(os, "{}", emit_node(bb, match, "match", true));
928 } else if (auto proxy = def->isa<Proxy>()) {
929 std::print(os, "{}", emit_node(bb, proxy, "proxy", true, true));
930 } else if (auto hole = def->isa<Hole>()) {
931 std::print(os, "\n{}(hole {})", tab, emit_type(bb, hole->type()));
932 } else {
933 fe::throwf("SExpr backend: unhandled def `{}` of type `{}`", def, def->type());
934 fe::unreachable();
935 }
936
937 if (types_enabled() && !def->isa<Axm>()) std::print(os, ")");
938 --tab;
939
940 return os.str();
941}
942
943void emit(World& world, std::ostream& ostream) {
944 Emitter emitter(world, ostream);
945 emitter.run();
946}
947
948void emit_typed(World& world, std::ostream& ostream) {
949 Emitter emitter(world, ostream, true);
950 emitter.run();
951}
952
953void emit_slotted(World& world, std::ostream& ostream) {
954 Emitter emitter(world, ostream, false, true);
955 emitter.run();
956}
957
958void emit_slotted_typed(World& world, std::ostream& ostream) {
959 Emitter emitter(world, ostream, true, true);
960 emitter.run();
961}
962
963} // namespace mim::sexpr
A (possibly paramterized) Array.
Definition tuple.h:110
Definition axm.h:9
Lam * root() const
Definition phase.h:624
Base class for all Defs.
Definition def.h:273
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Definition def.h:589
Defs deps() const noexcept
Definition def.cpp:468
constexpr auto ops() const noexcept
Definition def.h:348
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:580
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:479
auto projs(F f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:440
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1111
bool is_external() const noexcept
Definition def.h:553
Sym sym() const
Definition def.h:612
std::string unique_name() const
name + "_" + Def::gid
Definition def.cpp:616
const T * isa_imm() const
Definition def.h:574
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:161
This node is a hole in the IR that is inferred by its context later on.
Definition check.h:16
static constexpr nat_t size2bitwidth(nat_t n)
Definition def.h:1006
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:645
Constructs a Join value.
Definition lattice.h:67
Creates a new Tuple / Pack by inserting Insert::value at position Insert::index into Insert::tuple.
Definition tuple.h:186
A function.
Definition lam.h:113
const Def * filter() const
Definition lam.h:125
static const Lam * isa_cn(const Def *d)
Definition lam.h:144
static const Lam * isa_returning(const Def *d)
Definition lam.h:146
const Pi * type() const
Definition lam.h:133
const Def * body() const
Definition lam.h:126
Scrutinize Match::scrutinee() and dispatch to Match::arms.
Definition lattice.h:109
Constructs a Meet value.
Definition lattice.h:52
const Nest & nest() const
Definition phase.h:642
Def * mut() const
The mutable capsulated in this Node or nullptr, if it's a virtual root comprising several Nodes.
Definition nest.h:46
const Node * root() const
Definition nest.h:224
A (possibly paramterized) Tuple.
Definition tuple.h:137
virtual void run()
Entry point and generates some debug output; invokes Phase::start.
Definition phase.cpp:32
std::string_view name() const
Definition phase.h:80
virtual void start()=0
Actual entry.
World & world()
Definition phase.h:77
A dependent function type.
Definition lam.h:14
const Def * dom() const
Definition lam.h:35
static Pi * isa_implicit(const Def *d)
Is d an Pi::is_implicit (mutable) Pi?
Definition lam.h:62
Used as intermediate value during optimizatinos such as Analysis.
Definition def.h:1032
Type formation of a rewrite Rule.
Definition rule.h:9
A rewrite rule.
Definition rule.h:40
A dependent tuple type.
Definition tuple.h:23
Data constructor for a Sigma.
Definition tuple.h:61
A variable introduced by a binder (mutable).
Definition def.h:825
Def * binder() const
The binder of this Var.
Definition def.h:835
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:40
std::string prepare()
Definition sexpr.cpp:307
void start() override
Actual entry.
Definition sexpr.cpp:264
std::string emit_var(BB &bb, const Def *var, const Def *type, bool meta_var=false)
Definition sexpr.cpp:458
void emit_lam(Lam *parent, Lam *curr, LamSet &rec_lams)
Definition sexpr.cpp:381
std::string emit_node(BB &bb, const Def *def, std::string node_name, bool variadic=false, bool with_type=false)
Definition sexpr.cpp:761
bool direct_style() override
Definition sexpr.cpp:110
std::string emit_type(BB &bb, const Def *type, bool in_term=false)
Definition sexpr.cpp:584
void emit_imported(Lam *)
Definition sexpr.cpp:272
std::string emit_cons(std::vector< std::string > op_vals)
Definition sexpr.cpp:732
Emitter(World &world, std::ostream &ostream, bool typed=false, bool slotted=false)
Definition sexpr.cpp:101
void emit_decl(BB &bb, const Def *def)
Definition sexpr.cpp:338
bool is_valid(std::string_view s)
Definition sexpr.cpp:111
mim::Emitter< std::string, std::string, BB, Emitter > Super
Definition sexpr.cpp:99
std::string emit_cons_type(BB &bb, fe::View< const Def * > ops)
Definition sexpr.cpp:563
LamSet next_lams(Lam *lam)
Definition sexpr.cpp:327
std::string emit_bb(BB &bb, const Def *def)
Definition sexpr.cpp:833
void emit_epilogue(Lam *)
Definition sexpr.cpp:309
std::string emit_head(BB &bb, Lam *lam, bool nested=false)
Definition sexpr.cpp:505
void emit_slotted(World &, std::ostream &)
Definition sexpr.cpp:953
void emit_typed(World &, std::ostream &)
Definition sexpr.cpp:948
void emit(World &, std::ostream &)
Definition sexpr.cpp:943
void emit_slotted_typed(World &, std::ostream &)
Definition sexpr.cpp:958
GIDSet< Lam * > LamSet
Definition lam.h:220
TBound< true > Join
AKA union.
Definition lattice.h:167
TExt< true > Top
Definition lattice.h:165
TExt< false > Bot
Definition lattice.h:164
TBound< false > Meet
AKA intersection.
Definition lattice.h:166
std::array< std::deque< std::ostringstream >, 3 > parts
Definition sexpr.cpp:93
BB & operator=(BB other) noexcept
Definition sexpr.cpp:18
std::deque< std::ostringstream > & tail()
Definition sexpr.cpp:22
BB(BB &&other) noexcept=default
BB()=default
std::string assign(fe::Tab tab, bool slotted, std::string name, std::format_string< Args... > s, Args &&... args)
Definition sexpr.cpp:35
bool is_assigned(std::string name) const
Definition sexpr.cpp:90
std::deque< std::ostringstream > & head()
Definition sexpr.cpp:20
friend void swap(BB &a, BB &b) noexcept
Definition sexpr.cpp:84
std::deque< std::ostringstream > & body()
Definition sexpr.cpp:21
absl::flat_hash_set< std::string > assigned
Definition sexpr.cpp:94
void body(std::format_string< Args... > s, Args &&... args)
Definition sexpr.cpp:25
BB(const BB &)=delete
void assign(std::string name)
Definition sexpr.cpp:91
void tail(std::format_string< Args... > s, Args &&... args)
Definition sexpr.cpp:30
std::string assign(fe::Tab tab, bool slotted, std::string name, Fn &&print_term)
Definition sexpr.cpp:62