MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
dfa2matcher.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include <automaton/dfa.h>
7
9#include <mim/plug/mem/mem.h>
10
11template<>
12struct std::formatter<automaton::DFA> : fe::ostream_formatter {};
13
14using namespace mim;
15using namespace automaton;
16
19
20// nomenclature:
21// c is the character from the string we want to match
22
23// see lit/regex/match_manual.mim for a hand written state machine impl that this is based on
24
25// the main idea is:
26// for every state in the DFA, we create a lam that checks if the char c at pos is the end of the string \0
27// if not, jump to a checker lam, that consists of a few bit-wise or-ed in-range checks to verify if we can transition
28// to a certain other state with c if any of the checks is true, we jump to the corresponding state lam with the updated
29// position if the check fails, we jump to the next checker lam or the exit lam if we checked all possible transitions
30// Note, since the DFA stores the transitions as chars, not ranges, we have to merge the transitions to ranges first
31// (cf. transitions_to_ranges)
32
33namespace {
34
35namespace core = plug::core;
36namespace mem = plug::mem;
37
38// Name states by their stable id - never by pointer value, which would change from run to run.
39std::string state_to_name(const DFANode* state) { return "state_" + std::to_string(state->id()); }
40
41DFAMap<Ranges> transitions_to_ranges(World& w, const DFANode* state) {
42 DFAMap<Ranges> state2ranges;
43 state->for_transitions([&](std::uint16_t transition, const DFANode* next_state) {
44 if (!state2ranges.contains(next_state))
45 state2ranges.try_emplace(next_state, Ranges{
46 {transition, transition}
47 });
48 else
49 state2ranges[next_state].emplace_back(transition, transition);
50 });
51 Range any_range{0, 255};
52 for (auto& [state, ranges] : state2ranges) {
53 if (std::ranges::contains(ranges, any_range)) {
54 ranges = {any_range};
55 continue;
56 }
57
58 std::sort(ranges.begin(), ranges.end(), RangeCompare{});
59 ranges = merge_ranges(ranges, [&w](std::string_view msg) { w.DLOG("{}", msg); });
60 }
61 return state2ranges;
62}
63
64const Def* match_range(const Def* c, nat_t lo, nat_t hi) {
65 World& w = c->world();
66 if (lo == 0 && hi == 255) return w.lit_tt();
67
68 // let in_range = %core.bit2.and_ 0 (%core.icmp.uge (char, lower), %core.icmp.ule (char, upper));
69 auto below_hi = w.call(core::icmp::ule, w.tuple({c, w.lit_i8(hi)}));
70 auto above_lo = w.call(core::icmp::uge, w.tuple({c, w.lit_i8(lo)}));
71 return w.call(core::bit2::and_, w.lit_nat(2), w.tuple({below_hi, above_lo}));
72}
73
74DFAMap<const Def*> create_check_match_transitions_from(const Def* c, const DFANode* state) {
75 World& w = c->world();
76 DFAMap<const Def*> state2check;
77
78 auto state2ranges = transitions_to_ranges(w, state);
79
80 for (auto& [state, ranges] : state2ranges) {
81 for (auto& [lo, hi] : ranges)
82 if (!state2check.contains(state))
83 state2check.try_emplace(state, match_range(c, lo, hi));
84 else
85 state2check[state]
86 = w.call(core::bit2::or_, w.lit_nat(2), w.tuple({state2check[state], match_range(c, lo, hi)}));
87 }
88 return state2check;
89}
90
91} // namespace
92
93extern "C" const Def* dfa2matcher(World& w, const DFA& dfa, const Def* n) {
94 w.DLOG("dfa to match: {}", dfa);
95
96 auto states = dfa.get_reachable_states();
97 DFAMap<Lam*> state2matcher;
98
99 // ((mem: %mem.M 0, string: Str n, pos: Idx n), Cn [%mem.M 0, Bool, Idx n])
100 auto matcher = w.mut_fun({w.call<mem::M>(0), w.call<mem::Ptr0>(w.arr(n, w.type_i8())), w.type_idx(n)},
101 {w.call<mem::M>(0), w.type_bool(), w.type_idx(n)});
102 matcher->debug_prefix(std::string("match_regex"));
103 auto [args, exit] = matcher->vars<2>();
104 exit->debug_prefix(std::string("exit"));
105 auto [mem, string, pos] = args->projs<3>();
106 mem->debug_prefix(std::string("mem"));
107 string->debug_prefix(std::string("string"));
108 pos->debug_prefix(std::string("pos"));
109
110 auto error = mem::mut_con(w.type_idx(n));
111 error->debug_prefix("error");
112 {
113 auto [mem, pos] = error->vars<2>();
114 mem->debug_prefix(std::string("mem"));
115 pos->debug_prefix(std::string("pos"));
116 error->app(false, exit, {mem, w.lit_ff(), pos});
117 }
118
119 auto accept = mem::mut_con(w.type_idx(n));
120 accept->debug_prefix("accept");
121 {
122 auto [mem, pos] = accept->vars<2>();
123 mem->debug_prefix(std::string("mem"));
124 pos->debug_prefix(std::string("pos"));
125 accept->app(false, exit, {mem, w.lit_tt(), pos});
126 }
127
128 auto exiting = [error, accept](const DFANode* state) { return state->is_accepting() ? accept : error; };
129
130 for (auto state : states) {
131 auto lam = mem::mut_con(w.type_idx(n));
132 lam->debug_prefix(state_to_name(state));
133 state2matcher.emplace(state, lam);
134 }
135
136 for (auto [state, lam] : state2matcher) {
137 auto [mem, i] = lam->vars<2>();
138
139 if (state->is_erroring()) {
140 lam->app(true, error, {mem, i});
141 continue;
142 }
143
144 auto lea = w.call<mem::lea>(Defs{string, i});
145 auto [mem2, c] = w.call<mem::load>(Defs{mem, lea})->projs<2>();
146
147 auto is_end = w.call(core::icmp::e, Defs({c, w.lit_i8(0)}));
148 auto not_end = mem::mut_con(w.type_idx(n));
149 not_end->debug_prefix("not_end_" + state_to_name(state));
150
151 auto new_i = w.call(core::wrap::add, core::Mode::nsuw, w.tuple({i, w.call(core::conv::u, n, w.lit_i64(1))}));
152 lam->app(false, w.select(is_end, exiting(state), not_end), {mem2, i});
153
154 auto transitions = create_check_match_transitions_from(c, state);
155 auto next_check = exiting(state); // if we want to check full string only, use error instead of exiting(state)c
156 for (auto [next_state, check] : transitions) {
157 auto next_lam = state2matcher[next_state];
158 auto checker = mem::mut_con(w.type_idx(n));
159 checker->debug_prefix("check_" + state_to_name(state) + "_to_" + state_to_name(next_state));
160 auto [mem3, pos] = checker->vars<2>();
161 checker->app(false, w.select(check, next_lam, next_check), {mem3, w.select(check, new_i, pos)});
162 next_check = checker;
163 }
164 {
165 auto [mem, pos] = not_end->vars<2>();
166 not_end->app(true, next_check, {mem, pos});
167 }
168 }
169
170 matcher->app(false, state2matcher[dfa.get_start()], {mem, pos});
171 return matcher;
172}
std::set< const NodeType *, typename NodeType::Lt > get_reachable_states() const
Ordered by NodeType::Lt (i.e. by id) so that iteration is deterministic.
Definition automaton.h:34
const NodeType * get_start() const
Definition automaton.h:31
constexpr int id() const noexcept
Definition dfa.h:22
void for_transitions(F &&f, std::uint16_t c) const
Definition dfa.h:28
Base class for all Defs.
Definition def.h:261
This is a thin wrapper for absl::InlinedVector<T, N, A> which is a drop-in replacement for std::vecto...
Definition vector.h:18
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:36
Vector< Range > Ranges
const Def * dfa2matcher(World &w, const DFA &dfa, const Def *n)
You can dl::get this function.
std::pair< std::uint64_t, std::uint64_t > Range
std::map< const DFANode *, To, DFANode::Lt > DFAMap
Definition dfa.h:75
std::optional< Range > merge_ranges(Range a, Range b) noexcept
The core Plugin
Definition core.h:8
The mem Plugin
Definition mem.h:11
Definition ast.h:14
View< const Def * > Defs
Definition def.h:78
u64 nat_t
Definition types.h:37
void error(Loc loc, std::format_string< Args... > f, Args &&... args)
Definition dbg.h:114