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, 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 if (root()->sym().str().starts_with("internal_")) return;
311 auto& bb = lam2bb_[lam];
312 if (is_bound(lam)) bb.tail("{}", emit(lam->body()));
313}
314
316 if (root()->sym().str().starts_with("internal_")) return;
317 // We don't want to emit config lams that define which rules should be emitted.
318 // The rules in the body of such a lambda will be emitted into decls_
319 // via emit_bb() but we don't want to emit the lambda itself.
320 // We can't do this with Axm::isa because 'eqsat' is an out-of-tree plugin
321 // that isn't guaranteed to have been cloned so we can't include its header file.
322 else if (root()->codom()->sym().str() == "%eqsat.Config")
323 return;
324
325 LamSet rec_lams;
326 auto root_lam = nest().root()->mut()->as_mut<Lam>();
327 if (is_bound(root_lam)) emit_lam(root_lam, root_lam, rec_lams);
328}
329
332 for (auto op : lam->deps()) {
333 for (auto mut : op->local_muts())
334 if (auto next = nest()[mut]) {
335 if (auto next_lam = next->mut()->isa<Lam>()) next_lams.insert(next_lam);
336 }
337 }
338 return next_lams;
339}
340
341void Emitter::emit_decl(BB& bb, const Def* def) {
342 if (auto axm = def->isa<Axm>()) {
343 if (!world().annexes().flags2entry().contains(axm->flags()) && !is_declared(axm->sym().str())) {
344 // Slots may have been disabled if we are coming from a rule declaration below
345 // in which case we want to enable them for the duration of emitting the axioms' type.
346 bool enable_slots = !slots_enabled();
347 if (enable_slots) toggle_slots();
348
349 if (typed()) std::print(decls_, "(@ {}\n", emit_type(bb, axm->type()));
350
351 std::print(decls_, "(axm {}", id(axm));
352
353 if (typed()) std::print(decls_, ")");
354 std::print(decls_, ")\n\n");
355
356 if (enable_slots) toggle_slots();
357
358 declared_.insert(axm->sym().str());
359 }
360 } else if (def->isa_imm<Rule>()) {
361 assert(false && "TODO no vars in immutable Rule");
362 } else if (auto rule = def->isa_mut<Rule>()) {
363 bool suppress_annotations = types_enabled();
364 bool suppress_slots = slots_enabled();
365
366 if (suppress_annotations) toggle_types();
367 auto meta_var_val = emit_var(bb, rule->var(), rule->dom(), true);
368
369 if (suppress_slots) toggle_slots();
370 auto lhs_val = emit_bb(bb, rule->lhs());
371 auto rhs_val = emit_bb(bb, rule->rhs());
372 auto guard_val = emit_bb(bb, rule->guard());
373
374 if (suppress_slots) toggle_slots();
375 if (suppress_annotations) toggle_types();
376
377 std::print(decls_, "(rule {} {} {} {} {})\n\n", indent(1, id(rule)), indent(1, meta_var_val),
378 indent(1, lhs_val), indent(1, rhs_val), indent(1, guard_val));
379
380 declared_.insert(rule->sym().str());
381 }
382}
383
384void Emitter::emit_lam(Lam* parent, Lam* curr, LamSet& rec_lams) {
385 // We do not want to re-emit recursively defined lambdas because it would result in an endless loop
386 auto lam_node = nest()[curr];
387 if (lam_node->is_recursive()) rec_lams.emplace(curr);
388 assert(lam2bb_.contains(curr));
389 auto& bb = lam2bb_[curr];
390 auto& parent_bb = lam2bb_[parent];
391
392 // Lambdas that are not bound to a variable will be printed inline.
393 // I.e. their definition will simply be emitted in place as in (app (lm x.x) 2)
394 // Only the lambdas that are bound to a variable will be emitted here.
395 const bool EMIT = is_bound(curr) && !parent_bb.is_assigned(id(curr));
396 // A lambda nested inside of a top-level lambda will be wrapped with a let-binding
397 // as in (root (lam x (let child (lam y y) (app child x))))
398 const bool NESTED = curr != root();
399
400 if (EMIT) {
401 parent_bb.assign(id(curr));
402 std::print(func_impls_, "{}", emit_head(bb, curr, NESTED));
403 }
404
405 for (auto next_lam : next_lams(curr)) {
406 if (!rec_lams.contains(next_lam)) {
407 // The parent of the next lam will be the parent of the current lam
408 // if the current lam doesn't get emitted (is inline). This way we maintain
409 // a correct child-parent relation between actually emitted lambdas.
410 auto next_parent = EMIT ? curr : parent;
411 emit_lam(next_parent, next_lam, rec_lams);
412 }
413 }
414
415 if (EMIT) {
416 int unclosed_parens = 0;
417
418 for (auto& term : bb.body()) {
419 auto opened = std::ranges::count(term.str(), '(');
420 auto closed = std::ranges::count(term.str(), ')');
421 unclosed_parens += opened - closed;
422 std::print(func_impls_, "{}", indent(tab.indent(), term.str()));
423 }
424
425 for (auto& term : bb.tail())
426 std::print(func_impls_, "{}", indent(tab.indent(), term.str()));
427
428 std::string closing_parens(unclosed_parens, ')');
429 std::print(func_impls_, "{}", closing_parens);
430
431 // Close type annotation '@'
432 if (types_enabled()) std::print(func_impls_, ")");
433
434 --tab;
435 --tab;
436 if (slotted()) {
437 --tab;
438 if (NESTED) {
439 --tab;
440 // Close 'lam' and lam var 'scope'
441 std::print(func_impls_, "))");
442 // Close the 'let' and let 'scope' at the end of the parent lambdas' definition.
443 parent_bb.tail("))");
444 } else {
445 // Close 'root', 'lam' and lam var 'scope'
446 std::print(func_impls_, ")))\n\n");
447 }
448
449 } else if (NESTED) {
450 // Close 'lam'
451 std::print(func_impls_, ")");
452 // Close the 'let' at the end of the parent lambdas' definition.
453 parent_bb.tail(")");
454 } else {
455 // Close 'root' and 'lam'
456 std::print(func_impls_, "))\n\n");
457 }
458 }
459}
460
461std::string Emitter::emit_var(BB& bb, const Def* var, const Def* type, bool meta_var) {
462 std::ostringstream os;
463
464 ++tab;
465 if (slotted()) {
466 // We assume that the depth of projections for rule meta vars is at most one so
467 // (a: Nat, b: Bool) is okay but (a: [b: Nat]) is not.
468 if (meta_var) {
469 toggle_slots();
470 auto projs = var->projs();
471 if (projs.size() == 1 || std::ranges::all_of(projs, [](auto proj) { return proj->sym().empty(); }))
472 std::print(os, "\n{}(cons (metavar {}) nil)", tab, id(var));
473 else {
474 std::vector<std::string> meta_vars;
475 for (auto proj : projs) {
476 ++tab;
477 auto meta_var = std::format("\n{}(metavar {})", tab, id(proj));
478 --tab;
479 meta_vars.push_back(meta_var);
480 }
481 std::print(os, "{}", emit_cons(meta_vars));
482 }
483 toggle_slots();
484 } else {
485 std::print(os, "\n{}{}", tab, id(var));
486 }
487 }
488
489 else if (meta_var) {
490 auto projs = var->projs();
491 if (projs.size() == 1 || std::ranges::all_of(projs, [](auto proj) { return proj->sym().empty(); }))
492 std::print(os, "\n{}(metavar {})", tab, id(var));
493 else {
494 std::print(os, "\n{}(metavar {}", tab, id(var));
495 size_t i = 0;
496 for (auto proj : projs)
497 std::print(os, "{}", emit_var(bb, proj, type->proj(i++), meta_var));
498 std::print(os, ")");
499 }
500 } else {
501 std::print(os, "\n{}{}", tab, id(var));
502 }
503 --tab;
504
505 return os.str();
506}
507
508std::string Emitter::emit_head(BB& bb, Lam* lam, bool nested) {
509 std::ostringstream os;
510
511 const std::string lam_kind = Lam::isa_returning(lam) ? "fun" : Lam::isa_cn(lam) ? "con" : "lam";
512 const std::string ext = lam->is_external() ? "extern" : "intern";
513
514 if (slotted()) {
515 if (nested) {
516 std::print(os, "\n{}(let", tab);
517 ++tab;
518 std::print(os, "\n{}{}", tab, id(lam));
519 std::print(os, "\n{}(scope", tab);
520 ++tab;
521 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
522 std::print(os, "\n{}({}", tab, lam_kind);
523 } else {
524 // We toggle slot-printing to emit the lam id without a slot prefix '$'
525 toggle_slots();
526 std::print(os, "(root {} {}", ext, id(lam));
527 toggle_slots();
528 ++tab;
529 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
530 std::print(os, "\n{}({}", tab, lam_kind);
531 }
532
533 } else if (nested) {
534 std::print(os, "\n{}(let", tab);
535 ++tab;
536 std::print(os, "\n{}{}", tab, id(lam));
537 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
538 std::print(os, "\n{}({}", tab, lam_kind);
539 } else {
540 std::print(os, "(root {} {}", ext, id(lam));
541 ++tab;
542 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, lam->type()));
543 std::print(os, "\n{}({}", tab, lam_kind);
544 }
545
546 std::print(os, "{}", emit_var(bb, lam->var(), lam->type()->dom()));
547
548 if (slotted()) {
549 ++tab;
550 std::print(os, "\n{}(scope", tab);
551 // Occasionally a filter will refer to variables that will only
552 // start to get bound in the body of the lambda and we therefore
553 // disable the use of variables for the duration of emitting the filter
554 // in order to print every variable use by its definition instead.
555 toggle_bindings();
556 std::print(os, "{}", emit_bb(bb, lam->filter()));
557 toggle_bindings();
558 } else {
559 std::print(os, "{}", emit_bb(bb, lam->filter()));
560 }
561 ++tab;
562
563 return os.str();
564}
565
567 std::ostringstream os;
568
569 if (ops.size() == 0) {
570 std::print(os, "nil");
571 return os.str();
572 }
573
574 size_t op_idx = 0;
575 for (auto op : ops) {
576 std::print(os, "(cons {} ", emit_type(bb, op));
577 if (op_idx == ops.size() - 1) std::print(os, "nil");
578 op_idx++;
579 }
580
581 std::string closing_brackets(ops.size(), ')');
582 std::print(os, "{}", closing_brackets);
583
584 return os.str();
585}
586
587std::string Emitter::emit_type(BB& bb, const Def* type, bool in_term /* = false*/) {
588 std::ostringstream os;
589 auto scope_wrap = [&](std::string val) { return slotted() ? "(scope " + val + ")" : val; };
590
591 if (type->isa<Nat>()) {
592 std::print(os, "Nat");
593 } else if (auto size = Idx::isa(type)) {
594 if (auto lit_size = Idx::size2bitwidth(size)) {
595 switch (*lit_size) {
596 case 1: return types_[type] = "Bool";
597 case 8: return types_[type] = "I8";
598 case 16: return types_[type] = "I16";
599 case 32: return types_[type] = "I32";
600 case 64: return types_[type] = "I64";
601 default: break;
602 }
603 std::print(os, "(idx (lit {} Nat))", size);
604 } else {
605 std::print(os, "(idx {})", emit_type(bb, size, in_term));
606 }
607 } else if (auto lit = type->isa<Lit>()) {
608 if (lit->type()->isa<Nat>())
609 std::print(os, "(lit {} Nat)", lit);
610 else if (auto size = Idx::isa(lit->type()))
611 if (auto lit_size = Idx::size2bitwidth(size); lit_size && *lit_size == 1)
612 std::print(os, "(lit {} Bool)", lit);
613 else
614 std::print(os, "(lit {} {})", lit->get(), emit_type(bb, lit->type(), in_term));
615 else
616 std::print(os, "(lit {} {})", lit->get(), emit_type(bb, lit->type(), in_term));
617 } else if (auto arr = type->isa<Arr>()) {
618 std::string arity_val;
619 if (auto top = arr->arity()->isa<Top>()) {
620 arity_val = "(top " + emit_type(bb, top->type(), in_term) + ")";
621 } else {
622 // We disable type annotations only if they aren't already disabled
623 // because we would otherwise get annotations inside of the array type.
624 // We also disable the use of bindings unless this type is emitted as part of a term.
625 // In that case the array shape may refer to bound variables.
626 bool suppress_annotations = types_enabled();
627 if (suppress_annotations) toggle_types();
628 if (!in_term) toggle_bindings();
629 arity_val = flatten(emit_bb(bb, arr->arity()));
630 if (suppress_annotations) toggle_types();
631 if (!in_term) toggle_bindings();
632 }
633 std::string arr_val = arity_val + " " + emit_type(bb, arr->body(), in_term);
634
635 if (auto var = arr->has_var()) {
636 auto var_val = id(var);
637 std::print(os, "(arr {} {})", var_val, scope_wrap(arr_val));
638 } else {
639 auto dummy_var = slotted() ? "$dummy" : "dummy";
640 std::print(os, "(arr {} {})", dummy_var, scope_wrap(arr_val));
641 }
642
643 } else if (auto pi = type->isa<Pi>()) {
644 std::string pi_kind = Pi::isa_implicit(pi) ? "pi*" : "pi";
645 std::string doms = emit_type(bb, pi->dom(), in_term) + " " + emit_type(bb, pi->codom(), in_term);
646
647 if (auto var = pi->has_var()) {
648 auto var_val = id(var);
649 std::print(os, "({} {} {})", pi_kind, var_val, scope_wrap(doms));
650 } else {
651 auto dummy_var = slotted() ? "$dummy" : "dummy";
652 std::print(os, "({} {} {})", pi_kind, dummy_var, scope_wrap(doms));
653 }
654
655 } else if (auto sigma = type->isa<Sigma>()) {
656 std::ostringstream op_vals;
657 slotted() ? op_vals << emit_cons_type(bb, sigma->ops()) + " nil"
658 : op_vals << fe::Join(
659 sigma->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }), " ");
660
661 if (auto var = sigma->has_var()) {
662 auto var_val = id(var);
663 std::print(os, "(sigma {} {})", var_val, scope_wrap(op_vals.str()));
664 } else {
665 auto dummy_var = slotted() ? "$dummy" : "dummy";
666 std::print(os, "(sigma {} {})", dummy_var, scope_wrap(op_vals.str()));
667 }
668
669 } else if (auto tuple = type->isa<Tuple>()) {
670 if (slotted())
671 std::print(os, "(tuple {})", emit_cons_type(bb, tuple->ops()));
672 else
673 std::print(
674 os, "(tuple {})",
675 fe::Join(tuple->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }),
676 " "));
677 } else if (auto app = type->isa<App>()) {
678 std::print(os, "(app {} {})", emit_type(bb, app->callee(), in_term), emit_type(bb, app->arg(), in_term));
679 } else if (auto axm = type->isa<Axm>()) {
680 std::print(os, "{}", id(axm));
681 emit_decl(bb, axm);
682 } else if (auto var = type->isa<Var>()) {
683 if (var->binder()->isa<Rule>())
684 std::print(os, "\n{}{}", tab, id(var));
685 else
686 std::print(os, "{}", id(var, true));
687 } else if (auto hole = type->isa<Hole>()) {
688 std::print(os, "(hole {})", emit_type(bb, hole->type(), in_term));
689 } else if (auto extract = type->isa<Extract>()) {
690 // Projections of rule variables are meta vars and should just be printed by name
691 if (auto var = extract->tuple()->isa<Var>(); var && var->binder()->isa<Rule>())
692 std::print(os, "{}", id(extract));
693 else if (in_term && bb.is_assigned(id(extract)))
694 std::print(os, "{}", id(extract));
695 else
696 std::print(os, "(extract {} {})", emit_type(bb, extract->tuple(), in_term),
697 emit_type(bb, extract->index(), in_term));
698 } else if (auto mType = type->isa<Type>()) {
699 std::print(os, "(type {})", emit_type(bb, mType->level(), in_term));
700 } else if (type->isa<Univ>()) {
701 std::print(os, "Univ");
702 } else if (auto reform = type->isa<Reform>()) {
703 std::print(os, "(reform {})", emit_type(bb, reform->dom(), in_term));
704 } else if (auto join = type->isa<Join>()) {
705 if (slotted())
706 std::print(os, "(join {})", emit_cons_type(bb, join->ops()));
707 else
708 std::print(
709 os, "(join {})",
710 fe::Join(join->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }),
711 " "));
712 } else if (auto meet = type->isa<Meet>()) {
713 if (slotted())
714 std::print(os, "(meet {})", emit_cons_type(bb, meet->ops()));
715 else
716 std::print(
717 os, "(meet {})",
718 fe::Join(meet->ops() | std::views::transform([&](auto op) { return emit_type(bb, op, in_term); }),
719 " "));
720 } else if (auto bot = type->isa<Bot>()) {
721 std::print(os, "(bot {})", emit_type(bb, bot->type(), in_term));
722 } else if (auto top = type->isa<Top>()) {
723 std::print(os, "(top {})", emit_type(bb, top->type(), in_term));
724 } else {
725 fe::throwf("unsupported type '{}'", type);
726 fe::unreachable();
727 }
728
729 return os.str();
730}
731
732// This is primarily needed because slotted-egraphs don't support
733// variadic enodes (yet?) so we have to represent those as nested cons lists
734// i.e. for Tuple: (tuple (cons a (cons b nil)))
735std::string Emitter::emit_cons(std::vector<std::string> op_vals) {
736 std::ostringstream os;
737
738 if (op_vals.size() == 0) {
739 ++tab;
740 std::print(os, "\n{}nil", tab);
741 --tab;
742 return os.str();
743 }
744
745 size_t op_idx = 0;
746 for (auto op_val : op_vals) {
747 ++tab;
748 std::print(os, "\n{}(cons", tab);
749 ++tab;
750 std::print(os, "{}", indent(tab.indent(), op_val));
751 --tab;
752 if (op_idx == op_vals.size() - 1) std::print(os, "\n{}nil", tab);
753 --tab;
754
755 op_idx++;
756 }
757
758 std::string closing_brackets(op_vals.size(), ')');
759 std::print(os, "{}", closing_brackets);
760
761 return os.str();
762}
763
764std::string Emitter::emit_node(BB& bb, const Def* def, std::string node_name, bool variadic, bool with_type) {
765 std::ostringstream os;
766
767 std::vector<std::string> op_vals;
768
769 auto type_val = emit_type(bb, def->type());
770 if (with_type) {
771 if (!type_val.empty()) op_vals.push_back(type_val);
772 }
773
774 if (auto pack = def->isa<Pack>()) {
775 if (auto var = pack->has_var()) {
776 std::string var_val = " " + (slotted() ? id(var) + " (scope" : id(var));
777 op_vals.push_back(var_val);
778 } else {
779 std::string var_val = slotted() ? " $dummy (scope" : " dummy";
780 op_vals.push_back(var_val);
781 }
782 if (auto arity_val = emit_bb(bb, pack->arity()); !arity_val.empty()) op_vals.push_back(arity_val);
783 }
784
785 if (auto proxy = def->isa<Proxy>()) {
786 std::ostringstream tag;
787 std::print(tag, "\n{}", proxy->tag());
788 op_vals.push_back(tag.str());
789 }
790
791 for (auto op : def->ops())
792 if (auto op_val = emit_bb(bb, op); !op_val.empty()) op_vals.push_back(op_val);
793
794 if (is_bound(def) && bindings_enabled()) {
795 bb.assign(tab, slotted(), id(def), [&](fe::Tab tab, auto& os) {
796 ++tab;
797 if (types_enabled()) std::print(os, "\n{}(@ {}", tab, type_val);
798 std::print(os, "\n{}({}", tab, node_name);
799
800 if (slotted() && variadic)
801 std::print(os, "{}", emit_cons(op_vals));
802 else {
803 ++tab;
804 for (auto op_val : op_vals)
805 std::print(os, "{}", indent(tab.indent(), op_val));
806 --tab;
807 }
808
809 // Close the packs' var 'scope'
810 if (slotted() && def->isa<Pack>()) std::print(os, ")");
811
812 std::print(os, ")");
813 if (types_enabled()) std::print(os, ")");
814 --tab;
815 });
816 std::print(os, "\n{}{}", tab, id(def, true));
817
818 } else {
819 std::print(os, "\n{}({}", tab, node_name);
820
821 if (slotted() && variadic)
822 std::print(os, "{}", emit_cons(op_vals));
823 else
824 for (auto op_val : op_vals)
825 std::print(os, "{}", op_val);
826
827 // Close the packs' var 'scope'
828 if (slotted() && def->isa<Pack>()) std::print(os, ")");
829
830 std::print(os, ")");
831 }
832
833 return os.str();
834}
835
836std::string Emitter::emit_bb(BB& bb, const Def* def) {
837 std::ostringstream os;
838
839 ++tab;
840 if (def->type()->isa<Type>() || def->type()->isa<Univ>()) {
841 std::print(os, "\n{}{}", tab, emit_type(bb, def, true));
842 // Short circuit because we probably don't want to type
843 // annotate a type (or do we?)
844 --tab;
845 return os.str();
846 }
847
848 // We don't annotate axioms since this makes the sexpr's extremely cluttered and the axioms
849 // will already be emitted separately with an annotation.
850 if (types_enabled() && !def->isa<Axm>()) std::print(os, "\n{}(@ {}", tab, emit_type(bb, def->type()));
851
852 if (def->isa_imm<Lam>()) {
853 assert(false && "TODO immutable lam inline");
854 } else if (auto lam = def->isa_mut<Lam>()) {
855 if (is_bound(lam))
856 std::print(os, "\n{}{}", tab, id(lam, true));
857 else {
858 auto lam_kind = Lam::isa_returning(lam) ? "fun" : Lam::isa_cn(lam) ? "con" : "lam";
859 if (slotted()) {
860 std::print(os, "\n{}({}", tab, lam_kind);
861 std::print(os, "{}", emit_var(bb, lam->var(), lam->var()->type()));
862 ++tab;
863 std::print(os, "\n{}(scope", tab);
864 std::print(os, "{}", emit_bb(bb, lam->filter()));
865 std::print(os, "{}", emit_bb(bb, lam->body()));
866 --tab;
867 std::print(os, "))");
868 } else {
869 std::print(os, "\n{}({}", tab, lam_kind);
870 std::print(os, "\n{}{}", tab, emit_var(bb, lam->var(), lam->var()->type()));
871 std::print(os, "{}", emit_bb(bb, lam->filter()));
872 std::print(os, "{}", emit_bb(bb, lam->body()));
873 std::print(os, ")");
874 }
875 }
876 } else if (auto lit = def->isa<Lit>()) {
877 if (lit->type()->isa<Nat>())
878 std::print(os, "\n{}(lit {} Nat)", tab, lit);
879 else if (auto size = Idx::isa(lit->type()))
880 if (auto lit_size = Idx::size2bitwidth(size); lit_size && *lit_size == 1)
881 std::print(os, "\n{}(lit {} Bool)", tab, lit);
882 else
883 std::print(os, "\n{}(lit {} {})", tab, lit->get(), emit_type(bb, lit->type()));
884 else
885 std::print(os, "\n{}(lit {} {})", tab, lit->get(), emit_type(bb, lit->type()));
886 } else if (auto tuple = def->isa<Tuple>()) {
887 std::print(os, "{}", emit_node(bb, tuple, "tuple", true));
888 } else if (auto pack = def->isa<Pack>()) {
889 std::print(os, "{}", emit_node(bb, pack, "pack"));
890 } else if (auto extract = def->isa<Extract>()) {
891 // Projections of rule variables are meta vars and should just be printed by name
892 if (auto var = extract->tuple()->isa<Var>(); var && var->binder()->isa<Rule>())
893 std::print(os, "\n{}{}", tab, id(extract));
894 else
895 std::print(os, "{}", emit_node(bb, extract, "extract"));
896 } else if (auto insert = def->isa<Insert>()) {
897 std::print(os, "{}", emit_node(bb, insert, "insert"));
898 } else if (auto var = def->isa<Var>()) {
899 if (var->binder()->isa<Rule>())
900 std::print(os, "\n{}{}", tab, id(var));
901 else
902 std::print(os, "\n{}{}", tab, id(var, true));
903 } else if (auto app = def->isa<App>()) {
904 std::print(os, "{}", emit_node(bb, app, "app"));
905 } else if (auto axm = def->isa<Axm>()) {
906 std::print(os, "\n{}{}", tab, id(axm));
907 emit_decl(bb, axm);
908 } else if (auto bot = def->isa<Bot>()) {
909 if (is_bound(bot)) {
910 bb.assign(tab, slotted(), id(bot), "(bot {})", emit_type(bb, bot->type()));
911 std::print(os, "\n{}{}", tab, id(bot, true));
912 } else {
913 std::print(os, "\n{}(bot {})", tab, emit_type(bb, bot->type()));
914 }
915 } else if (auto top = def->isa<Top>()) {
916 if (is_bound(top)) {
917 bb.assign(tab, slotted(), id(top), "(top {})", emit_type(bb, top->type()));
918 std::print(os, "\n{}{}", tab, id(top, true));
919 } else {
920 std::print(os, "\n{}(top {})", tab, emit_type(bb, top->type()));
921 }
922 } else if (auto rule = def->isa<Rule>()) {
923 std::print(os, "\n{}{}", tab, id(rule, true));
924 emit_decl(bb, rule);
925 } else if (auto inj = def->isa<Inj>()) {
926 std::print(os, "{}", emit_node(bb, inj, "inj", false, true));
927 } else if (auto merge = def->isa<Merge>()) {
928 std::print(os, "{}", emit_node(bb, merge, "merge", true, true));
929 } else if (auto match = def->isa<Match>()) {
930 std::print(os, "{}", emit_node(bb, match, "match", true));
931 } else if (auto proxy = def->isa<Proxy>()) {
932 std::print(os, "{}", emit_node(bb, proxy, "proxy", true, true));
933 } else if (auto hole = def->isa<Hole>()) {
934 std::print(os, "\n{}(hole {})", tab, emit_type(bb, hole->type()));
935 } else {
936 fe::throwf("Unhandled Def in SExpr backend: {} : {}", def, def->type());
937 fe::unreachable();
938 }
939
940 if (types_enabled() && !def->isa<Axm>()) std::print(os, ")");
941 --tab;
942
943 return os.str();
944}
945
946void emit(World& world, std::ostream& ostream) {
947 Emitter emitter(world, ostream);
948 emitter.run();
949}
950
951void emit_typed(World& world, std::ostream& ostream) {
952 Emitter emitter(world, ostream, true);
953 emitter.run();
954}
955
956void emit_slotted(World& world, std::ostream& ostream) {
957 Emitter emitter(world, ostream, false, true);
958 emitter.run();
959}
960
961void emit_slotted_typed(World& world, std::ostream& ostream) {
962 Emitter emitter(world, ostream, true, true);
963 emitter.run();
964}
965
966} // namespace mim::sexpr
A (possibly paramterized) Array.
Definition tuple.h:121
Definition axm.h:9
Lam * root() const
Definition phase.h:483
Base class for all Defs.
Definition def.h:261
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Definition def.h:536
Defs deps() const noexcept
Definition def.cpp:514
constexpr auto ops() const noexcept
Definition def.h:317
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:527
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:441
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:402
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
bool is_external() const noexcept
Definition def.h:500
Sym sym() const
Definition def.h:558
std::string unique_name() const
name + "_" + Def::gid
Definition def.cpp:626
const T * isa_imm() const
Definition def.h:521
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:210
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:951
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:658
Constructs a Join value.
Definition lattice.h:72
Creates a new Tuple / Pack by inserting Insert::value at position Insert::index into Insert::tuple.
Definition tuple.h:237
A function.
Definition lam.h:110
const Def * filter() const
Definition lam.h:122
static const Lam * isa_cn(const Def *d)
Definition lam.h:141
static const Lam * isa_returning(const Def *d)
Definition lam.h:143
const Pi * type() const
Definition lam.h:130
const Def * body() const
Definition lam.h:123
Scrutinize Match::scrutinee() and dispatch to Match::arms.
Definition lattice.h:118
Constructs a Meet value.
Definition lattice.h:55
const Nest & nest() const
Definition phase.h:501
Def * mut() const
The mutable capsulated in this Node or nullptr, if it's a virtual root comprising several Nodes.
Definition nest.h:45
const Node * root() const
Definition nest.h:223
A (possibly paramterized) Tuple.
Definition tuple.h:170
virtual void run()
Entry point and generates some debug output; invokes Phase::start.
Definition phase.cpp:34
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:54
Type formation of a rewrite Rule.
Definition rule.h:9
A rewrite rule.
Definition rule.h:43
A dependent tuple type.
Definition tuple.h:22
Data constructor for a Sigma.
Definition tuple.h:70
A variable introduced by a binder (mutable).
Definition def.h:756
Def * binder() const
The binder of this Var.
Definition def.h:766
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:36
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:461
void emit_lam(Lam *parent, Lam *curr, LamSet &rec_lams)
Definition sexpr.cpp:384
std::string emit_node(BB &bb, const Def *def, std::string node_name, bool variadic=false, bool with_type=false)
Definition sexpr.cpp:764
bool direct_style() override
Definition sexpr.cpp:110
std::string emit_cons_type(BB &bb, View< const Def * > ops)
Definition sexpr.cpp:566
std::string emit_type(BB &bb, const Def *type, bool in_term=false)
Definition sexpr.cpp:587
void emit_imported(Lam *)
Definition sexpr.cpp:272
std::string emit_cons(std::vector< std::string > op_vals)
Definition sexpr.cpp:735
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:341
bool is_valid(std::string_view s)
Definition sexpr.cpp:111
mim::Emitter< std::string, std::string, BB, Emitter > Super
Definition sexpr.cpp:99
LamSet next_lams(Lam *lam)
Definition sexpr.cpp:330
std::string emit_bb(BB &bb, const Def *def)
Definition sexpr.cpp:836
void emit_epilogue(Lam *)
Definition sexpr.cpp:309
std::string emit_head(BB &bb, Lam *lam, bool nested=false)
Definition sexpr.cpp:508
void emit_slotted(World &, std::ostream &)
Definition sexpr.cpp:956
void emit_typed(World &, std::ostream &)
Definition sexpr.cpp:951
void emit(World &, std::ostream &)
Definition sexpr.cpp:946
void emit_slotted_typed(World &, std::ostream &)
Definition sexpr.cpp:961
GIDSet< Lam * > LamSet
Definition lam.h:220
Span< const T, N > View
Definition span.h:102
TBound< true > Join
AKA union.
Definition lattice.h:179
TExt< true > Top
Definition lattice.h:177
TExt< false > Bot
Definition lattice.h:176
TBound< false > Meet
AKA intersection.
Definition lattice.h:178
@ Lam
Definition def.h:109
@ Axm
Definition def.h:109
@ Rule
Definition def.h:109
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