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