MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
normalizers.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <iterator>
3#include <numeric>
4#include <ranges>
5
7#include <fe/assert.h>
8
9#include <mim/axm.h>
10#include <mim/def.h>
11#include <mim/driver.h>
12#include <mim/tuple.h>
13#include <mim/world.h>
14
16
18using Ranges = fe::Vector<Range>;
19
20namespace mim::plug::regex {
21
22template<quant id>
23const Def* normalize_quant(const Def* type, const Def* callee, const Def* arg) {
24 auto& world = type->world();
25
26 // quantifiers are idempotent
27 if (Axm::isa(id, arg)) return arg;
28
29 if constexpr (id == quant::plus) {
30 // (\d?)+ and (\d*)+ == \d*
31 if (auto optional_app = Axm::isa(quant::optional, arg))
32 return world.call(quant::star, optional_app->arg());
33 else if (auto star_app = Axm::isa(quant::star, arg))
34 return arg;
35 } else if constexpr (id == quant::star) {
36 // (\d?)* and (\d+)* == \d*
37 if (auto quant_app = Axm::isa<quant>(arg)) return world.app(callee, quant_app->arg());
38 } else if constexpr (id == quant::optional) {
39 // (\d*)? and (\d+)? == \d*
40 if (auto star_app = Axm::isa(quant::star, arg))
41 return arg;
42 else if (auto plus_app = Axm::isa(quant::plus, arg))
43 return world.call(quant::star, plus_app->arg());
44 }
45
46 return {};
47}
48
49template<class ConjOrDisj>
51 assert(!args.empty());
52 auto& world = args.front()->world();
53 return std::accumulate(args.begin() + 1, args.end(), args.front(), [&world](const Def* lhs, const Def* rhs) {
54 return world.call<ConjOrDisj, false>(Defs{lhs, rhs});
55 });
56}
57
58const Def* normalize_conj(const Def* type, const Def* callee, const Def* arg) {
59 auto& world = type->world();
60 world.log().d("conj {}: {} ({})", callee, type, arg);
61
62 if (auto a = Lit::isa(arg->arity())) {
63 switch (*a) {
64 case 0: return world.annex<empty>();
65 case 1: return arg;
66 default:
67 if (auto args = detail::flatten_in_arg<conj>(arg); !args.empty())
68 return make_binary_tree<conj>(args);
69 else
70 return world.annex<empty>();
71 }
72 }
73
74 return {};
75}
76
77const App* lit_range(const Def* d) {
78 if (auto r = Axm::isa<range>(d); r && r->arg(2, 0)->isa<Lit>() && r->arg(2, 1)->isa<Lit>()) return r;
79 return nullptr;
80}
81
82bool compare_re(const Def* lhs, const Def* rhs) {
83 auto lhs_range = lit_range(lhs);
84 auto rhs_range = lit_range(rhs);
85 // sort ranges by increasing lower bound
86 if (lhs_range && rhs_range) return Lit::as(lhs_range->arg(2, 0)) < Lit::as(rhs_range->arg(2, 0));
87 // ranges to the end
88 if (lhs_range) return false;
89 if (rhs_range) return true;
90
91 return lhs->gid() < rhs->gid(); // make irreflexive
92}
93
95 std::stable_sort(args.begin(), args.end(), &compare_re);
96 {
97 auto new_end = std::unique(args.begin(), args.end());
98 args.erase(new_end, args.end());
99 }
100}
101
102auto get_range(const Def* rng) -> Range {
103 auto rng_match = Axm::isa<range, false>(rng);
104 return {Lit::as<std::uint8_t>(rng_match->arg(2, 0)), Lit::as<std::uint8_t>(rng_match->arg(2, 1))};
105}
106
107struct app_range {
109 const Def* operator()(Range rng) { return w.call<range>(Defs{w.lit_i8(rng.first), w.lit_i8(rng.second)}); }
110};
111
112void merge_ranges(DefVec& args) {
113 auto ranges_begin = args.begin();
114 while (ranges_begin != args.end() && !lit_range(*ranges_begin))
115 ranges_begin++;
116 if (ranges_begin == args.end()) return;
117
118 Ranges old_ranges;
119 auto& world = (*ranges_begin)->world();
120
121 std::transform(ranges_begin, args.end(), std::back_inserter(old_ranges), get_range);
122
123 auto new_ranges = automaton::merge_ranges(old_ranges, [&world](std::string_view msg) { world.log().d("{}", msg); });
124
125 // invalidates ranges_begin
126 args.erase(ranges_begin, args.end());
127 std::transform(new_ranges.begin(), new_ranges.end(), std::back_inserter(args), app_range{world});
128
129 make_vector_unique(args);
130}
131
132bool equals_any(const Def* lhs, const Def* rhs) {
133 auto check_arg_equiv = [](const Def* lhs, const Def* rhs) {
134 if (auto rng_lhs = Axm::isa<range>(lhs))
135 if (auto not_rhs = Axm::isa<not_>(rhs)) {
136 if (auto rng_rhs = Axm::isa<range>(not_rhs->arg())) return rng_lhs == rng_rhs;
137 }
138 return false;
139 };
140
141 return check_arg_equiv(lhs, rhs) || check_arg_equiv(rhs, lhs);
142}
143
144bool equals_any(Defs lhs, Defs negated_rhs) {
145 auto to_range = std::views::filter(lit_range) | std::views::transform(get_range);
146 auto rhs_view = negated_rhs | to_range;
147
148 if (std::ranges::distance(rhs_view) != std::ranges::distance(negated_rhs)) return false;
149
150 return std::ranges::includes(lhs | to_range, rhs_view);
151}
152
153const Def* normalize_disj(const Def* type, const Def*, const Def* arg) {
154 auto& world = type->world();
155 if (auto a = Lit::isa(arg->arity())) {
156 switch (*a) {
157 case 0: return {};
158 case 1: return arg;
159 default:
160 auto new_args = detail::flatten_in_arg<disj>(arg);
161
162 // any only subsumes the single-character alternatives.
163 auto make_any = [&world](DefVec args) {
164 std::erase_if(
165 args, [](const Def* d) { return Axm::isa<range>(d) || Axm::isa<not_>(d) || Axm::isa<any>(d); });
166 args.emplace_back(world.annex<any>());
167 if (args.size() == 1) return args.front();
168 make_vector_unique(args);
169 return make_binary_tree<disj>(args);
170 };
171
172 if (std::ranges::any_of(new_args, [](const Def* d) { return Axm::isa<any>(d); }))
173 return make_any(new_args);
174 make_vector_unique(new_args);
175 merge_ranges(new_args);
176
177 for (const auto* cls0 : new_args) {
178 for (const auto* cls1 : new_args)
179 if (equals_any(cls0, cls1)) return make_any(new_args);
180
181 if (auto not_rhs = Axm::isa<not_>(cls0)) {
182 if (auto disj_rhs = Axm::isa<disj>(not_rhs->arg())) {
183 auto rngs = detail::flatten_in_arg<disj>(disj_rhs->arg());
184 make_vector_unique(rngs);
185 if (equals_any(new_args, rngs)) return make_any(new_args);
186 }
187 }
188 }
189
190 world.log().d("final ranges: {}", fe::Join(new_args));
191
192 if (new_args.size() > 1) return make_binary_tree<disj>(new_args);
193 return new_args.back();
194 }
195 }
196 return {};
197}
198
199const Def* normalize_range(const Def* type, const Def* callee, const Def* arg) {
200 auto& world = type->world();
201 auto [lhs, rhs] = arg->projs<2>();
202
203 if (auto l = Lit::isa(lhs), r = Lit::isa(rhs); l && r && *l > *r) return world.raw_app(type, callee, {rhs, lhs});
204
205 return {};
206}
207
208const Def* any_unwanted_for_not(const Def* arg) {
209 if (Axm::isa<regex::range>(arg) || Axm::isa<regex::not_>(arg) || Axm::isa<regex::any>(arg)) return nullptr;
210 if (auto disj = Axm::isa<regex::disj>(arg)) {
211 for (const auto* disj_arg : disj->args())
212 if (auto ret = any_unwanted_for_not(disj_arg)) return ret;
213 return nullptr;
214 }
215 return arg;
216}
217
218const Def* normalize_not(const Def*, const Def* callee, const Def* arg) {
219 if (auto unwanted = any_unwanted_for_not(arg)) {
220 arg->blame("regex.not must only be used with regex.disj, regex.range, regex.any and regex.not: {} {}", callee,
221 arg)
222 .n("found unwanted: {}", unwanted)
223 .bail();
224 }
225 return {};
226}
227
229
230} // namespace mim::plug::regex
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:289
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:457
fe::Error & blame(fe::cite_string< Args... > s, Args &&... args) const
Reports an error that blames this; chain Error::n for Notes and Error::bail to throw.
Definition def.h:333
constexpr u32 gid() const noexcept
Global id - unique number for this Def.
Definition def.h:312
const Def * arity() const
Number of elements available to Extract / Insert (may be dynamic).
Definition def.cpp:612
static std::optional< T > isa(const Def *def)
Definition def.h:976
static T as(const Def *def)
Definition def.h:982
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:43
fe::Vector< Range > Ranges
automaton::Range Range
std::pair< std::uint64_t, std::uint64_t > Range
std::optional< Range > merge_ranges(Range a, Range b) noexcept
The regex Plugin
Definition lower_regex.h:5
void merge_ranges(DefVec &args)
void make_vector_unique(DefVec &args)
auto get_range(const Def *rng) -> Range
const App * lit_range(const Def *d)
const Def * normalize_conj(const Def *type, const Def *callee, const Def *arg)
const Def * normalize_range(const Def *type, const Def *callee, const Def *arg)
bool equals_any(const Def *lhs, const Def *rhs)
const Def * normalize_disj(const Def *type, const Def *, const Def *arg)
const Def * normalize_quant(const Def *type, const Def *callee, const Def *arg)
const Def * normalize_not(const Def *, const Def *callee, const Def *arg)
bool compare_re(const Def *lhs, const Def *rhs)
const Def * make_binary_tree(Defs args)
const Def * any_unwanted_for_not(const Def *arg)
fe::View< const Def * > Defs
Definition def.h:96
fe::Vector< const Def * > DefVec
Definition def.h:98
#define MIM_regex_NORMALIZER_IMPL
Definition autogen.h:135
const Def * operator()(Range rng)