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