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
12
13#ifndef DOXYGEN
14template<>
15struct std::formatter<automaton::DFA> : fe::ostream_formatter {};
16#endif
17
18using namespace mim;
19using namespace automaton;
20
22using Ranges = fe::Vector<Range>;
23
24// nomenclature:
25// c is the character from the string we want to match
26
27// see lit/regex/match_manual.mim for a hand written state machine impl that this is based on
28
29// the main idea is:
30// For every state in the DFA, we create a lam that checks whether pos is the end of the string, i.e. n.
31// If not, it loads c at pos and jumps to a chain of checker lams, each a few bit-wise or-ed in-range checks.
32// A successful check jumps to the target state's lam with the next position, a failed one to the next checker or exit.
33// Since the DFA stores the transitions as chars, we have to merge them to ranges first (cf. transitions_to_ranges).
34
35namespace {
36
37namespace core = plug::core;
38namespace mem = plug::mem;
39namespace regex = plug::regex;
40
41// Name states by their stable id - never by pointer value, which would change from run to run.
42std::string state_to_name(const DFANode* state) { return "state_" + std::to_string(state->id()); }
43
44DFAMap<Ranges> transitions_to_ranges(World& w, const DFANode* state) {
45 DFAMap<Ranges> state2ranges;
46 state->for_transitions([&](std::uint16_t transition, const DFANode* next_state) {
47 state2ranges[next_state].emplace_back(transition, transition);
48 });
49 for (auto& [state, ranges] : state2ranges) {
50 std::sort(ranges.begin(), ranges.end(), RangeCompare{});
51 ranges = merge_ranges(ranges, [&w](std::string_view msg) { w.log().d("{}", msg); });
52 }
53 return state2ranges;
54}
55
56const Def* match_range(const Def* c, nat_t lo, nat_t hi) {
57 World& w = c->world();
58 if (lo == 0 && hi == 255) return w.lit_tt();
59
60 // let in_range = core.bit2.and_ 0 (core.icmp.uge (char, lower), core.icmp.ule (char, upper));
61 auto below_hi = w.call(core::icmp::ule, Defs{c, w.lit_i8(hi)});
62 auto above_lo = w.call(core::icmp::uge, Defs{c, w.lit_i8(lo)});
63 return w.call(core::bit2::and_, w.lit_nat(2), w.tuple({below_hi, above_lo}));
64}
65
66DFAMap<const Def*> create_check_match_transitions_from(const Def* c, const DFANode* state) {
67 World& w = c->world();
68 DFAMap<const Def*> state2check;
69
70 auto state2ranges = transitions_to_ranges(w, state);
71
72 for (auto& [state, ranges] : state2ranges) {
73 for (auto& [lo, hi] : ranges) {
74 auto match = match_range(c, lo, hi);
75 if (auto [i, ins] = state2check.try_emplace(state, match); !ins)
76 i->second = w.call(core::bit2::or_, w.lit_nat(2), w.tuple({i->second, match}));
77 }
78 }
79 return state2check;
80}
81
82} // namespace
83
84extern "C" const Def* dfa2matcher(World& w, const DFA& dfa, const Def* n) {
85 w.log().d("DFA to match: {}", dfa);
86
87 auto states = dfa.get_reachable_states();
88 DFAMap<Lam*> state2matcher;
89
90 auto pos_t = w.call<regex::Pos>(n);
91 auto end = w.call<regex::pos>(n, n);
92 auto one = w.call<regex::pos>(n, w.lit_nat_1());
93 auto matcher
94 = w.mut_fun({w.call<mem::M>(0), w.call<mem::Str>(n), pos_t}, {w.call<mem::M>(0), w.type_bool(), pos_t});
95 matcher->debug_prefix(std::string("match_regex"));
96 auto [args, exit] = matcher->vars<2>();
97 exit->debug_prefix(std::string("exit"));
98 auto [mem, string, pos] = args->projs<3>();
99 mem->debug_prefix(std::string("mem"));
100 string->debug_prefix(std::string("string"));
101 pos->debug_prefix(std::string("pos"));
102
103 auto error = mem::mut_con(pos_t);
104 error->debug_prefix("error");
105 {
106 auto [mem, pos] = error->vars<2>();
107 mem->debug_prefix(std::string("mem"));
108 pos->debug_prefix(std::string("pos"));
109 error->app(false, exit, {mem, w.lit_ff(), pos});
110 }
111
112 auto accept = mem::mut_con(pos_t);
113 accept->debug_prefix("accept");
114 {
115 auto [mem, pos] = accept->vars<2>();
116 mem->debug_prefix(std::string("mem"));
117 pos->debug_prefix(std::string("pos"));
118 accept->app(false, exit, {mem, w.lit_tt(), pos});
119 }
120
121 auto exiting = [error, accept](const DFANode* state) { return state->is_accepting() ? accept : error; };
122
123 for (auto state : states) {
124 auto lam = mem::mut_con(pos_t);
125 lam->debug_prefix(state_to_name(state));
126 state2matcher.emplace(state, lam);
127 }
128
129 for (auto [state, lam] : state2matcher) {
130 auto [mem, i] = lam->vars<2>();
131
132 if (state->is_erroring()) {
133 lam->app(true, error, {mem, i});
134 continue;
135 }
136
137 auto is_end = w.call(core::icmp::e, Defs{i, end});
138 auto not_end = mem::mut_con(pos_t);
139 not_end->debug_prefix("not_end_" + state_to_name(state));
140 lam->app(false, w.select(is_end, exiting(state), not_end), {mem, i});
141
142 auto [mem2, j] = not_end->vars<2>();
143 auto lea = w.call<mem::lea>(Defs{string, w.call(core::conv::u, n, j)});
144 auto [mem3, c] = w.call<mem::load>(Defs{mem2, lea})->projs<2>();
145 auto new_i = w.call(core::wrap::add, core::Mode::nsuw, Defs{j, one});
146
147 auto transitions = create_check_match_transitions_from(c, state);
148 auto next_check = exiting(state); // if we want to check full string only, use error instead of exiting(state)
149 for (auto [next_state, check] : transitions) {
150 auto next_lam = state2matcher[next_state];
151 auto checker = mem::mut_con(pos_t);
152 checker->debug_prefix("check_" + state_to_name(state) + "_to_" + state_to_name(next_state));
153 auto [mem, pos] = checker->vars<2>();
154 checker->app(false, w.select(check, next_lam, next_check), {mem, w.select(check, new_i, pos)});
155 next_check = checker;
156 }
157 not_end->app(true, next_check, {mem3, j});
158 }
159
160 matcher->app(false, state2matcher[dfa.get_start()], {mem, pos});
161 return matcher;
162}
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:38
const NodeType * get_start() const
Definition automaton.h:34
void for_transitions(std::invocable< const DFANode * > auto f, std::uint16_t c) const
Definition dfa.h:27
constexpr int id() const noexcept
Definition dfa.h:23
Base class for all Defs.
Definition def.h:289
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:43
fe::Vector< Range > Ranges
const mim::Def * dfa2matcher(mim::World &, const automaton::DFA &, const mim::Def *)
You can fe::dl::get this function.
std::pair< std::uint64_t, std::uint64_t > Range
std::map< const DFANode *, To, DFANode::Lt > DFAMap
Definition dfa.h:70
std::optional< Range > merge_ranges(Range a, Range b) noexcept
The core Plugin
Definition core.h:8
The mem Plugin
The regex Plugin
Definition lower_regex.h:5
Definition ast.h:16
u64 nat_t
Definition types.h:37
fe::View< const Def * > Defs
Definition def.h:96