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