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