Spicy
unit.h
1 // Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details.
2 //
3 // This code adapts a number of operators from HILTI's struct type to Spicy's unit type.
4 
5 #pragma once
6 
7 #include <string>
8 #include <vector>
9 
10 #include <hilti/ast/builder/expression.h>
11 #include <hilti/ast/expressions/member.h>
12 #include <hilti/ast/operator.h>
13 #include <hilti/ast/operators/common.h>
14 #include <hilti/ast/types/any.h>
15 #include <hilti/ast/types/bytes.h>
16 #include <hilti/ast/types/integer.h>
17 #include <hilti/ast/types/reference.h>
18 #include <hilti/ast/types/stream.h>
19 #include <hilti/ast/types/unknown.h>
20 
21 #include <spicy/ast/types/unit.h>
22 
23 using namespace ::hilti::operator_;
24 
25 namespace spicy::operator_ {
26 
27 namespace unit::detail {
28 
29 // Returns an operand as a member expression.
30 static hilti::expression::Member memberExpression(const Expression& op) {
31  if ( auto c = op.tryAs<hilti::expression::Coerced>() )
32  return c->expression().as<hilti::expression::Member>();
33 
34  return op.as<hilti::expression::Member>();
35 }
36 
37 // Checks if an operand refers to a valid field inside a unit.
38 static inline void checkName(const Expression& op0, const Expression& op1, Node& node) {
39  auto id = memberExpression(op1).id().local();
40  auto i = op0.type().as<type::Unit>().itemByName(id);
41 
42  if ( ! i )
43  node.addError(hilti::util::fmt("type does not have field '%s'", id));
44 }
45 
46 // Returns the type of a unit field referenced by an operand.
47 static inline Type itemType(const Expression& op0, const Expression& op1) {
48  if ( auto st = op0.type().tryAs<type::Unit>() ) {
49  if ( auto i = st->itemByName(memberExpression(op1).id().local()) )
50  return i->itemType();
51  }
52 
53  return type::unknown;
54 }
55 
56 } // namespace unit::detail
57 
58 BEGIN_OPERATOR_CUSTOM(unit, Unset)
59  Type result(const hilti::node::Range<Expression>& ops) const { return type::void_; }
60 
61  bool isLhs() const { return true; }
62 
63  std::vector<Operand> operands() const {
64  return {{.type = type::Unit(type::Wildcard()), .doc = "unit"},
65  {.type = type::Member(type::Wildcard()), .doc = "<field>"}};
66  }
67 
68  void validate(const hilti::expression::ResolvedOperator& i, hilti::operator_::position_t p) const {
69  detail::checkName(i.op0(), i.op1(), p.node);
70  }
71 
72  std::string doc() const {
73  return R"(
74 Clears an optional field.
75 )";
76  }
77 END_OPERATOR_CUSTOM_x
78 
79 BEGIN_OPERATOR_CUSTOM_x(unit, MemberNonConst, Member)
80  Type result(const hilti::node::Range<Expression>& ops) const {
81  if ( ops.empty() )
82  return type::DocOnly("<field type>");
83 
84  return detail::itemType(ops[0], ops[1]);
85  }
86 
87  bool isLhs() const { return true; }
88 
89  std::vector<Operand> operands() const {
90  return {{.type = type::Unit(type::Wildcard()), .doc = "unit"},
91  {.type = type::Member(type::Wildcard()), .doc = "<field>"}};
92  }
93 
94  void validate(const hilti::expression::ResolvedOperator& i, hilti::operator_::position_t p) const {
95  detail::checkName(i.op0(), i.op1(), p.node);
96 
97  if ( i.op0().isConstant() )
98  p.node.addError("cannot assign to field of constant unit instance");
99  }
100 
101  std::string doc() const {
102  return R"(
103 Retrieves the value of a unit's field. If the field does not have a value assigned,
104 it returns its ``&default`` expression if that has been defined; otherwise it
105 triggers an exception.
106 )";
107  }
108 END_OPERATOR_CUSTOM_x
109 
110 BEGIN_OPERATOR_CUSTOM_x(unit, MemberConst, Member)
111  Type result(const hilti::node::Range<Expression>& ops) const {
112  if ( ops.empty() )
113  return type::DocOnly("<field type>");
114 
115  return detail::itemType(ops[0], ops[1]);
116  }
117 
118  bool isLhs() const { return false; }
119 
120  std::vector<Operand> operands() const {
121  return {{.type = type::constant(type::Unit(type::Wildcard())), .doc = "unit"},
122  {.type = type::Member(type::Wildcard()), .doc = "<field>"}};
123  }
124 
125  void validate(const hilti::expression::ResolvedOperator& i, position_t p) const {
126  detail::checkName(i.op0(), i.op1(), p.node);
127  }
128 
129  std::string doc() const {
130  return R"(
131 Retrieves the value of a unit's field. If the field does not have a value assigned,
132 it returns its ``&default`` expression if that has been defined; otherwise it
133 triggers an exception.
134 )";
135  }
136 END_OPERATOR_CUSTOM_x
137 
138 BEGIN_OPERATOR_CUSTOM(unit, TryMember)
139  Type result(const hilti::node::Range<Expression>& ops) const {
140  if ( ops.empty() )
141  return type::DocOnly("<field type>");
142 
143  return detail::itemType(ops[0], ops[1]);
144  }
145 
146  bool isLhs() const { return false; }
147 
148  std::vector<Operand> operands() const {
149  return {{.type = type::Unit(type::Wildcard()), .doc = "unit"},
150  {.type = type::Member(type::Wildcard()), .doc = "<field>"}};
151  }
152 
153  void validate(const hilti::expression::ResolvedOperator& i, position_t p) const {
154  detail::checkName(i.op0(), i.op1(), p.node);
155  }
156 
157  std::string doc() const {
158  return R"(
159 Retrieves the value of a unit's field. If the field does not have a value
160 assigned, it returns its ``&default`` expression if that has been defined;
161 otherwise it signals a special non-error exception to the host application
162 (which will normally still lead to aborting execution, similar to the standard
163 dereference operator, unless the host application specifically handles this
164 exception differently).
165 )";
166  }
167 END_OPERATOR_CUSTOM
168 
169 BEGIN_OPERATOR_CUSTOM(unit, HasMember)
170  Type result(const hilti::node::Range<Expression>& /* ops */) const { return type::Bool(); }
171 
172  bool isLhs() const { return false; }
173 
174  std::vector<Operand> operands() const {
175  return {{.type = type::constant(type::Unit(type::Wildcard())), .doc = "unit"},
176  {.type = type::Member(type::Wildcard()), .doc = "<field>"}};
177  }
178 
179  void validate(const hilti::expression::ResolvedOperator& i, position_t p) const {
180  detail::checkName(i.op0(), i.op1(), p.node);
181  }
182 
183  std::string doc() const {
184  return "Returns true if the unit's field has a value assigned (not counting any ``&default``).";
185  }
186 END_OPERATOR_CUSTOM
187 
188 OPERATOR_DECLARE_ONLY(unit, MemberCall)
189 
190 namespace unit {
191 
192 class MemberCall : public hilti::expression::ResolvedOperatorBase {
193 public:
194  using hilti::expression::ResolvedOperatorBase::ResolvedOperatorBase;
195 
196  struct Operator : public hilti::trait::isOperator {
197  Operator(const type::Unit& stype, const type::unit::item::Field& f) : _field(f) {
198  auto ftype = f.itemType().as<type::Function>();
199  auto op0 = Operand{.type = stype};
200  auto op1 = Operand{.type = type::Member(f.id())};
201  auto op2 = Operand{.type = type::OperandList::fromParameters(ftype.parameters())};
202  _operands = {op0, op1, op2};
203  _result = ftype.result().type();
204  };
205 
206  static Kind kind() { return Kind::MemberCall; }
207  std::vector<Operand> operands() const { return _operands; }
208  Type result(const hilti::node::Range<Expression>& /* ops */) const { return _result; }
209  bool isLhs() const { return false; }
210  void validate(const hilti::expression::ResolvedOperator& /* i */, position_t p) const {}
211  std::string doc() const { return "<dynamic - no doc>"; }
212  std::string docNamespace() const { return "<dynamic - no ns>"; }
213 
214  Expression instantiate(const std::vector<Expression>& operands, const Meta& meta) const {
215  auto ops = std::vector<Expression>{operands[0],
216  hilti::expression::Member(_field.id(), _field.itemType(), _field.meta()),
217  operands[2]};
218 
219  auto ro = hilti::expression::ResolvedOperator(MemberCall(*this, ops, meta));
220  ro.setMeta(meta);
221  return ro;
222  }
223 
224  private:
225  type::unit::item::Field _field;
226  std::vector<Operand> _operands;
227  Type _result;
228  };
229 };
230 
231 } // namespace unit
232 
233 BEGIN_METHOD(unit, Offset)
234  auto signature() const {
235  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
236  .result = hilti::type::UnsignedInteger(64),
237  .id = "offset",
238  .args = {},
239  .doc = R"(
240 Returns the offset of the current location in the input stream relative to the
241 unit's start. If executed from inside a field hook, the offset will represent
242 the first byte that the field has been parsed from. If this method is called
243 before the unit's parsing has begun, it will throw a runtime exception. Once
244 parsing has started, the offset will remain available for the unit's entire
245 life time.
246 
247 Usage of this method requires the unit to be declared with the ``%random-access``
248 property.
249 )"};
250  }
251 END_METHOD
252 
253 BEGIN_METHOD(unit, Position)
254  auto signature() const {
255  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
256  .result = hilti::type::stream::Iterator(),
257  .id = "position",
258  .args = {},
259  .doc = R"(
260 Returns an iterator to the current position in the unit's input stream. If
261 executed from inside a field hook, the position will represent the first byte
262 that the field has been parsed from. If this method is called before the unit's
263 parsing has begun, it will throw a runtime exception.
264 
265 Usage of this method requires the unit to be declared with the ``%random-access``
266 property.
267 )"};
268  }
269 END_METHOD
270 
271 
272 BEGIN_METHOD(unit, Input)
273  auto signature() const {
274  return hilti::operator_::Signature{.self = type::constant(spicy::type::Unit(type::Wildcard())),
275  .result = hilti::type::stream::Iterator(),
276  .id = "input",
277  .args = {},
278  .doc = R"(
279 Returns an iterator referring to the input location where the current unit has
280 begun parsing. If this method is called before the units parsing has begun, it
281 will throw a runtime exception. Once available, the input position will remain
282 accessible for the unit's entire life time.
283 
284 Usage of this method requires the unit to be declared with the ``%random-access``
285 property.
286 )"};
287  }
288 END_METHOD
289 
290 BEGIN_METHOD(unit, SetInput)
291  auto signature() const {
292  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
293  .result = hilti::type::void_,
294  .id = "set_input",
295  .args = {{.id = "i",
296  .type = type::constant(hilti::type::stream::Iterator())}},
297  .doc = R"(
298 Moves the current parsing position to *i*. The iterator *i* must be into the
299 input of the current unit, or the method will throw a runtime execption.
300 
301 Usage of this method requires the unit to be declared with the ``%random-access``
302 property.
303 )"};
304  }
305 END_METHOD
306 
307 BEGIN_METHOD(unit, Find)
308  auto signature() const {
309  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
311  .id = "find",
312  .args =
313  {
314  {.id = "needle", .type = type::constant(hilti::type::Bytes())},
315  {.id = "dir",
316  .type = type::constant(hilti::type::Enum(type::Wildcard())),
317  .optional = true},
318  {.id = "start",
319  .type = type::constant(hilti::type::stream::Iterator()),
320  .optional = true},
321 
322  },
323  .doc = R"(
324 Searches a *needle* pattern inside the input region defined by where the unit
325 began parsing and its current parsing position. If executed from inside a field
326 hook, the current parasing position will represent the *first* byte that the
327 field has been parsed from. By default, the search will start at the beginning
328 of that region and scan forward. If the direction is
329 ``spicy::Direcction::Backward``, the search will start at the end of the region
330 and scan backward. In either case, a starting position can also be explicitly
331 given, but must lie inside the same region.
332 
333 Usage of this method requires the unit to be declared with the ``%random-access``
334 property.
335 )"};
336  }
337 END_METHOD
338 
339 BEGIN_METHOD(unit, ConnectFilter)
340  auto signature() const {
341  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
342  .result = hilti::type::void_,
343  .id = "connect_filter",
344  .args = {{.id = "filter",
346  spicy::type::Unit(type::Wildcard()))}},
347  .doc = R"(
348 Connects a separate filter unit to transform the unit's input transparently
349 before parsing. The filter unit will see the original input, and this unit will
350 receive everything the filter passes on through ``forward()``.
351 
352 Filters can be connected only before a unit's parsing begins. The latest
353 possible point is from inside the target unit's ``%init`` hook.
354 )"};
355  }
356 END_METHOD
357 
358 BEGIN_METHOD(unit, Forward)
359  auto signature() const {
360  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
361  .result = hilti::type::void_,
362  .id = "forward",
363  .args = {{.id = "data", .type = hilti::type::Bytes()}},
364  .doc = R"(
365 If the unit is connected as a filter to another one, this method forwards
366 transformed input over to that other one to parse. If the unit is not connected,
367 this method will silently discard the data.
368 )"};
369  }
370 END_METHOD
371 
372 BEGIN_METHOD(unit, ForwardEod)
373  auto signature() const {
374  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
375  .result = hilti::type::void_,
376  .id = "forward_eod",
377  .args = {},
378  .doc = R"(
379 If the unit is connected as a filter to another one, this method signals that
380 other one that end of its input has been reached. If the unit is not connected,
381 this method will not do anything.
382 )"};
383  }
384 END_METHOD
385 
386 BEGIN_METHOD(unit, Backtrack)
387  auto signature() const {
388  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
389  .result = hilti::type::void_,
390  .id = "backtrack",
391  .args = {},
392  .doc = R"(
393 Aborts parsing at the current position and returns back to the most recent
394 ``&try`` attribute. Turns into a parse error if there's no ``&try`` in scope.
395 )"};
396  }
397 END_METHOD
398 
399 static inline auto contextResult(bool is_const) {
400  return [=](const hilti::node::Range<Expression>& /* orig_ops */,
401  const hilti::node::Range<Expression>& resolved_ops) -> std::optional<Type> {
402  if ( resolved_ops.empty() )
403  return type::DocOnly("<context>&");
404 
405  const auto& ctype = resolved_ops[0].type().as<type::Unit>().contextType();
406  assert(ctype);
407  return Type(type::StrongReference(*ctype));
408  };
409 }
410 
411 BEGIN_METHOD(unit, ContextConst)
412  auto signature() const {
413  return hilti::operator_::Signature{.self = hilti::type::constant(spicy::type::Unit(type::Wildcard())),
414  .result = contextResult(true),
415  .id = "context",
416  .args = {},
417  .doc = R"(
418 Returns a reference to the ``%context`` instance associated with the unit.
419 )"};
420  }
421 END_METHOD
422 
423 BEGIN_METHOD(unit, ContextNonConst)
424  auto signature() const {
425  return hilti::operator_::Signature{.self = spicy::type::Unit(type::Wildcard()),
426  .result = contextResult(false),
427  .id = "context",
428  .args = {},
429  .doc = R"(
430 Returns a reference to the ``%context`` instance associated with the unit.
431 )"};
432  }
433 END_METHOD
434 
435 } // namespace spicy::operator_
Definition: member.h:19
Definition: coerced.h:13
Definition: node.h:39
Definition: unit.h:57
Definition: visitor-types.h:28
Definition: operator-registry.h:16
Definition: operator.h:38
E node
Definition: visitor-types.h:33
Definition: optional.h:14
Definition: reference.h:16
Type self
Definition: operator.h:241
Definition: integer.h:54
Definition: bytes.h:40
Definition: stream.h:16
std::string fmt(const char *fmt, const Args &... args)
Definition: util.h:80
Definition: resolved-operator.h:40
Definition: bitfield.h:18
Definition: enum.h:52
Definition: operator.h:240