Coverage Report

Created: 2026-10-17 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hobbes/include/hobbes/lang/pat/pattern.H
Line
Count
Source
1
/*
2
 * pattern : defines a high-level pattern matching expression
3
 */
4
5
#ifndef HOBBES_LANG_PAT_PATTERN_HPP_INCLUDED
6
#define HOBBES_LANG_PAT_PATTERN_HPP_INCLUDED
7
8
#include <hobbes/lang/expr.H>
9
#include <hobbes/lang/pat/regex.H>
10
#include <hobbes/util/str.H>
11
#include <hobbes/util/ptr.H>
12
#include <hobbes/util/hash.H>
13
#include <hobbes/util/lannotation.H>
14
15
namespace hobbes {
16
17
using Idxs = std::vector<size_t>;
18
19
// the main signature for this module -- convert a pattern match expression to a primitive compilable expression
20
class Pattern;
21
using PatternPtr = std::shared_ptr<Pattern>;
22
using Patterns = std::vector<PatternPtr>;
23
24
struct PatternRow {
25
10
  inline PatternRow() = default;
26
749
  inline PatternRow(const Patterns& ps, const ExprPtr& r) : patterns(ps), result(r) { }
27
1.26k
  inline PatternRow(const Patterns& ps, const ExprPtr& g, const ExprPtr& r) : patterns(ps), guard(g), result(r) { }
28
29
  Patterns patterns; // match conditions for this row
30
  ExprPtr  guard;    // a required condition for a match (may be null if there is no guard)
31
  ExprPtr  result;   // the expression to be evaluated when the given pattern conditions are met
32
};
33
using PatternRows = std::vector<PatternRow>;
34
using UnreachableMatchRowsPtr = std::shared_ptr<std::vector<std::pair<size_t, PatternRow>>>;
35
36
// support quick hashing of pattern tables
37
bool operator==(const PatternRow&, const PatternRow&);
38
size_t hash(const PatternRow&);
39
40
// sneak the whole compiler through a back door to see how bindings are set up and maybe to do low-level compilation for special cases of pattern matches
41
class cc;
42
43
// evaluate the expression for the first pattern row matching the input
44
ExprPtr compileMatch(cc*, const Exprs&, const PatternRows&, const LexicalAnnotation&);
45
46
// produce an expression to test whether an expression matches a single pattern
47
ExprPtr compileMatchTest(cc*, const ExprPtr&, const PatternPtr&, const LexicalAnnotation&);
48
49
// produce an expression to test whether (and/or how) a regex matches a string
50
ExprPtr compileRegexFn(cc*, const std::string& regex, const LexicalAnnotation&);
51
52
// a general representation of patterns to match
53
class Pattern : public LexicallyAnnotated {
54
public:
55
1.05k
  virtual ~Pattern() = default;
56
  virtual void show(std::ostream&) const = 0;
57
  virtual bool operator==(const Pattern&) const = 0;
58
59
  // allow explicit naming of stages of a pattern (for internal use only)
60
  const std::string& name() const;
61
  void name(const std::string&);
62
private:
63
  std::string pname;
64
protected:
65
  virtual void assignSubNames(const std::string&) = 0;
66
67
  // improves performance of case-analysis over instances (to avoid 'dynamic_cast')
68
public:
69
  int case_id() const;
70
protected:
71
  Pattern(int cid, const LexicalAnnotation&);
72
private:
73
  int cid;
74
};
75
76
template <typename Case>
77
  class PatternCase : public Pattern {
78
  public:
79
    using Base = PatternCase<Case>;
80
    PatternCase(const LexicalAnnotation&);
81
  };
82
83
std::string show(const PatternPtr& p);
84
std::string show(const Patterns& ps);
85
std::string show(const PatternRow& pr);
86
87
// literal patterns
88
//   contain a "proxy value" for internal equality/ordering checks: ((), false, 0xde, 'c', 1, 3L, 2.3, ...)
89
//   contain a "residual expression value" for generating code
90
//   (this distinction allows us to use aliased constants like exchanges, timespans, etc in match expressions)
91
class MatchLiteral : public PatternCase<MatchLiteral> {
92
public:
93
  MatchLiteral(const PrimitivePtr& proxy, const LexicalAnnotation&);
94
  MatchLiteral(const PrimitivePtr& proxy, const ExprPtr& value, const LexicalAnnotation&);
95
96
  const ExprPtr&      expression()    const;
97
  const PrimitivePtr& equivConstant() const;
98
99
  void show(std::ostream&) const override;
100
  bool operator==(const Pattern&) const override;
101
102
  static const int type_case_id = 0;
103
private:
104
  PrimitivePtr p;
105
  ExprPtr      e;
106
107
  void assignSubNames(const std::string&) override;
108
};
109
110
// match any value, maybe bind it to a variable name
111
class MatchAny : public PatternCase<MatchAny> {
112
public:
113
  MatchAny(const std::string& vn, const LexicalAnnotation&);
114
115
  const std::string& value() const;
116
117
  void show(std::ostream&) const override;
118
  bool operator==(const Pattern&) const override;
119
120
  static const int type_case_id = 1;
121
private:
122
  std::string vn;
123
124
  void assignSubNames(const std::string&) override;
125
};
126
127
// match an array of patterns
128
class MatchArray : public PatternCase<MatchArray> {
129
public:
130
  MatchArray(const Patterns& ps, const LexicalAnnotation&); // default indexes = [0,ps.size())
131
132
  const PatternPtr& pattern(size_t i) const;
133
  const Idxs&       indexes() const;
134
  size_t            size() const; // alias for indexes().size()
135
136
  // allow external permutation of array match sequence
137
  void indexes(const Idxs&);
138
139
  void show(std::ostream&) const override;
140
  bool operator==(const Pattern&) const override;
141
142
  static const int type_case_id = 2;
143
private:
144
  Patterns ps;
145
  Idxs     idxs;
146
147
  void assignSubNames(const std::string&) override;
148
};
149
150
// match a regular expression
151
class MatchRegex : public PatternCase<MatchRegex> {
152
public:
153
  MatchRegex(const RegexPtr&, const LexicalAnnotation&);
154
  MatchRegex(const std::string&, const LexicalAnnotation&);
155
156
  std::string text() const;
157
  const RegexPtr& value() const;
158
159
  static PatternPtr toRegex(const MatchArray&);
160
161
  void show(std::ostream&) const override;
162
  bool operator==(const Pattern&) const override;
163
  static const int type_case_id = 3;
164
private:
165
  RegexPtr regex;
166
167
  void assignSubNames(const std::string&) override;
168
};
169
170
// match a record of patterns
171
class MatchRecord : public PatternCase<MatchRecord> {
172
public:
173
  using Field = std::pair<std::string, PatternPtr>;
174
  using Fields = std::vector<Field>;
175
176
  MatchRecord(const Fields& fs, const LexicalAnnotation&); // default indexes = [0,fs.size())
177
178
  const Field& pattern(size_t i) const;
179
  const Idxs&  indexes() const;
180
  size_t       size() const; // alias for indexes().size()
181
182
  const Fields& fields() const;
183
  void fields(const Fields&);
184
185
  void show(std::ostream&) const override;
186
  bool operator==(const Pattern&) const override;
187
188
  static const int type_case_id = 4;
189
private:
190
  Fields fs;
191
  Idxs   is;
192
  
193
  void assignSubNames(const std::string&) override;
194
195
  static void show(std::ostream&, const Field& f);
196
};
197
198
// match a variant constructor
199
class MatchVariant : public PatternCase<MatchVariant> {
200
public:
201
  MatchVariant(const std::string& lbl, const PatternPtr& p, const LexicalAnnotation&);
202
203
  const std::string& label() const;
204
  const PatternPtr&  value() const;
205
206
  void show(std::ostream&) const override;
207
  bool operator==(const Pattern&) const override;
208
209
  static const int type_case_id = 5;
210
private:
211
  std::string lbl;
212
  PatternPtr  p;
213
  
214
  void assignSubNames(const std::string&) override;
215
};
216
217
template <typename Case>
218
1.05k
  PatternCase<Case>::PatternCase(const LexicalAnnotation& la) : Pattern(Case::type_case_id, la) {
219
1.05k
  }
hobbes::PatternCase<hobbes::MatchLiteral>::PatternCase(hobbes::LexicalAnnotation const&)
Line
Count
Source
218
29
  PatternCase<Case>::PatternCase(const LexicalAnnotation& la) : Pattern(Case::type_case_id, la) {
219
29
  }
hobbes::PatternCase<hobbes::MatchAny>::PatternCase(hobbes::LexicalAnnotation const&)
Line
Count
Source
218
869
  PatternCase<Case>::PatternCase(const LexicalAnnotation& la) : Pattern(Case::type_case_id, la) {
219
869
  }
hobbes::PatternCase<hobbes::MatchArray>::PatternCase(hobbes::LexicalAnnotation const&)
Line
Count
Source
218
12
  PatternCase<Case>::PatternCase(const LexicalAnnotation& la) : Pattern(Case::type_case_id, la) {
219
12
  }
hobbes::PatternCase<hobbes::MatchRegex>::PatternCase(hobbes::LexicalAnnotation const&)
Line
Count
Source
218
8
  PatternCase<Case>::PatternCase(const LexicalAnnotation& la) : Pattern(Case::type_case_id, la) {
219
8
  }
hobbes::PatternCase<hobbes::MatchRecord>::PatternCase(hobbes::LexicalAnnotation const&)
Line
Count
Source
218
44
  PatternCase<Case>::PatternCase(const LexicalAnnotation& la) : Pattern(Case::type_case_id, la) {
219
44
  }
hobbes::PatternCase<hobbes::MatchVariant>::PatternCase(hobbes::LexicalAnnotation const&)
Line
Count
Source
218
89
  PatternCase<Case>::PatternCase(const LexicalAnnotation& la) : Pattern(Case::type_case_id, la) {
219
89
  }
220
221
// destruction-side for high-level pattern representations
222
template <typename T>
223
  struct switchPattern {
224
    virtual T with(const MatchLiteral*) const = 0;
225
    virtual T with(const MatchAny*)     const = 0;
226
    virtual T with(const MatchArray*)   const = 0;
227
    virtual T with(const MatchRegex*)   const = 0;
228
    virtual T with(const MatchRecord*)  const = 0;
229
    virtual T with(const MatchVariant*) const = 0;
230
  };
231
232
template <typename T>
233
3.95k
  T switchOf(const PatternPtr& p, const switchPattern<T>& f) {
234
3.95k
    switch (p->case_id()) {
235
133
    case MatchLiteral::type_case_id:
236
133
      return f.with(rcast<const MatchLiteral*>(p.get()));
237
3.05k
    case MatchAny::type_case_id:
238
3.05k
      return f.with(rcast<const MatchAny*>(p.get()));
239
48
    case MatchArray::type_case_id:
240
48
      return f.with(rcast<const MatchArray*>(p.get()));
241
9
    case MatchRegex::type_case_id:
242
9
      return f.with(rcast<const MatchRegex*>(p.get()));
243
231
    case MatchRecord::type_case_id:
244
231
      return f.with(rcast<const MatchRecord*>(p.get()));
245
471
    case MatchVariant::type_case_id:
246
471
      return f.with(rcast<const MatchVariant*>(p.get()));
247
0
    default:
248
0
      throw annotated_error(*p, "Internal error, cannot switch on unknown high-level pattern: " + show(p));
249
3.95k
    }
250
3.95k
  }
std::shared_ptr<hobbes::MonoType> hobbes::switchOf<std::shared_ptr<hobbes::MonoType> >(std::shared_ptr<hobbes::Pattern> const&, hobbes::switchPattern<std::shared_ptr<hobbes::MonoType> > const&)
Line
Count
Source
233
900
  T switchOf(const PatternPtr& p, const switchPattern<T>& f) {
234
900
    switch (p->case_id()) {
235
25
    case MatchLiteral::type_case_id:
236
25
      return f.with(rcast<const MatchLiteral*>(p.get()));
237
754
    case MatchAny::type_case_id:
238
754
      return f.with(rcast<const MatchAny*>(p.get()));
239
12
    case MatchArray::type_case_id:
240
12
      return f.with(rcast<const MatchArray*>(p.get()));
241
2
    case MatchRegex::type_case_id:
242
2
      return f.with(rcast<const MatchRegex*>(p.get()));
243
19
    case MatchRecord::type_case_id:
244
19
      return f.with(rcast<const MatchRecord*>(p.get()));
245
88
    case MatchVariant::type_case_id:
246
88
      return f.with(rcast<const MatchVariant*>(p.get()));
247
0
    default:
248
0
      throw annotated_error(*p, "Internal error, cannot switch on unknown high-level pattern: " + show(p));
249
900
    }
250
900
  }
unsigned char hobbes::switchOf<unsigned char>(std::shared_ptr<hobbes::Pattern> const&, hobbes::switchPattern<unsigned char> const&)
Line
Count
Source
233
2.08k
  T switchOf(const PatternPtr& p, const switchPattern<T>& f) {
234
2.08k
    switch (p->case_id()) {
235
50
    case MatchLiteral::type_case_id:
236
50
      return f.with(rcast<const MatchLiteral*>(p.get()));
237
1.73k
    case MatchAny::type_case_id:
238
1.73k
      return f.with(rcast<const MatchAny*>(p.get()));
239
24
    case MatchArray::type_case_id:
240
24
      return f.with(rcast<const MatchArray*>(p.get()));
241
4
    case MatchRegex::type_case_id:
242
4
      return f.with(rcast<const MatchRegex*>(p.get()));
243
88
    case MatchRecord::type_case_id:
244
88
      return f.with(rcast<const MatchRecord*>(p.get()));
245
178
    case MatchVariant::type_case_id:
246
178
      return f.with(rcast<const MatchVariant*>(p.get()));
247
0
    default:
248
0
      throw annotated_error(*p, "Internal error, cannot switch on unknown high-level pattern: " + show(p));
249
2.08k
    }
250
2.08k
  }
bool hobbes::switchOf<bool>(std::shared_ptr<hobbes::Pattern> const&, hobbes::switchPattern<bool> const&)
Line
Count
Source
233
187
  T switchOf(const PatternPtr& p, const switchPattern<T>& f) {
234
187
    switch (p->case_id()) {
235
6
    case MatchLiteral::type_case_id:
236
6
      return f.with(rcast<const MatchLiteral*>(p.get()));
237
173
    case MatchAny::type_case_id:
238
173
      return f.with(rcast<const MatchAny*>(p.get()));
239
0
    case MatchArray::type_case_id:
240
0
      return f.with(rcast<const MatchArray*>(p.get()));
241
0
    case MatchRegex::type_case_id:
242
0
      return f.with(rcast<const MatchRegex*>(p.get()));
243
7
    case MatchRecord::type_case_id:
244
7
      return f.with(rcast<const MatchRecord*>(p.get()));
245
1
    case MatchVariant::type_case_id:
246
1
      return f.with(rcast<const MatchVariant*>(p.get()));
247
0
    default:
248
0
      throw annotated_error(*p, "Internal error, cannot switch on unknown high-level pattern: " + show(p));
249
187
    }
250
187
  }
std::shared_ptr<hobbes::MState> hobbes::switchOf<std::shared_ptr<hobbes::MState> >(std::shared_ptr<hobbes::Pattern> const&, hobbes::switchPattern<std::shared_ptr<hobbes::MState> > const&)
Line
Count
Source
233
131
  T switchOf(const PatternPtr& p, const switchPattern<T>& f) {
234
131
    switch (p->case_id()) {
235
14
    case MatchLiteral::type_case_id:
236
14
      return f.with(rcast<const MatchLiteral*>(p.get()));
237
0
    case MatchAny::type_case_id:
238
0
      return f.with(rcast<const MatchAny*>(p.get()));
239
4
    case MatchArray::type_case_id:
240
4
      return f.with(rcast<const MatchArray*>(p.get()));
241
1
    case MatchRegex::type_case_id:
242
1
      return f.with(rcast<const MatchRegex*>(p.get()));
243
36
    case MatchRecord::type_case_id:
244
36
      return f.with(rcast<const MatchRecord*>(p.get()));
245
76
    case MatchVariant::type_case_id:
246
76
      return f.with(rcast<const MatchVariant*>(p.get()));
247
0
    default:
248
0
      throw annotated_error(*p, "Internal error, cannot switch on unknown high-level pattern: " + show(p));
249
131
    }
250
131
  }
unsigned long hobbes::switchOf<unsigned long>(std::shared_ptr<hobbes::Pattern> const&, hobbes::switchPattern<unsigned long> const&)
Line
Count
Source
233
652
  T switchOf(const PatternPtr& p, const switchPattern<T>& f) {
234
652
    switch (p->case_id()) {
235
38
    case MatchLiteral::type_case_id:
236
38
      return f.with(rcast<const MatchLiteral*>(p.get()));
237
395
    case MatchAny::type_case_id:
238
395
      return f.with(rcast<const MatchAny*>(p.get()));
239
8
    case MatchArray::type_case_id:
240
8
      return f.with(rcast<const MatchArray*>(p.get()));
241
2
    case MatchRegex::type_case_id:
242
2
      return f.with(rcast<const MatchRegex*>(p.get()));
243
81
    case MatchRecord::type_case_id:
244
81
      return f.with(rcast<const MatchRecord*>(p.get()));
245
128
    case MatchVariant::type_case_id:
246
128
      return f.with(rcast<const MatchVariant*>(p.get()));
247
0
    default:
248
0
      throw annotated_error(*p, "Internal error, cannot switch on unknown high-level pattern: " + show(p));
249
652
    }
250
652
  }
251
252
template <typename T>
253
  std::vector<T> switchOf(const Patterns& ps, const switchPattern<T>& f) {
254
    std::vector<T> r;
255
    for (auto p : ps) {
256
      r.push_back(switchOf(p, f));
257
    }
258
    return r;
259
  }
260
261
// construction utilities
262
0
inline MatchArray* mkpatarray(const std::vector<unsigned char>& bs, const LexicalAnnotation& la) {
263
0
  Patterns ps;
264
0
  for (auto b : bs) {
265
0
    ps.push_back(PatternPtr(new MatchLiteral(PrimitivePtr(new Byte(b, la)), la)));
266
0
  }
267
0
  return new MatchArray(ps, la);
268
0
}
269
270
4
inline MatchArray* mkpatarray(const std::string& cs, const LexicalAnnotation& la) {
271
4
  Patterns ps;
272
11
  for (auto c : cs) {
273
11
    ps.push_back(PatternPtr(new MatchLiteral(PrimitivePtr(new Char(c, la)), la)));
274
11
  }
275
4
  return new MatchArray(ps, la);
276
4
}
277
278
// is it possible for a pattern to be refuted?
279
bool refutable(const PatternPtr&);
280
281
// is a pattern a match on unit?
282
bool isUnitPat(const PatternPtr&);
283
284
// what variable names are introduced by a pattern (excluding _)?
285
str::set accessibleBindingNames(const PatternPtr&);
286
287
// simplify introducing list comprehensions
288
struct CSelection {
289
  PatternPtr pat;
290
  ExprPtr    seq;
291
  Exprs      conds;
292
};
293
using CSelectionPtr = std::shared_ptr<CSelection>;
294
using CSelections = std::vector<CSelectionPtr>;
295
296
Expr* desugarComprehension(cc* c, const ExprPtr& ex, const CSelections& cs, const LexicalAnnotation& la);
297
298
}
299
300
// make sure that pattern rows can be used as keys in hash maps
301
namespace std {
302
template <>
303
  struct hash< hobbes::PatternRow > {
304
2.35k
    inline size_t operator()(const hobbes::PatternRow& x) const {
305
2.35k
      return hobbes::hash(x);
306
2.35k
    }
307
  };
308
}
309
310
#endif