Skip to content

Commit

Permalink
Support arbitrary expression as argument to interval(...) and `inte…
Browse files Browse the repository at this point in the history
…rval_ns(...)`.

So far we used constructor expressions like `interval(...)` to create
*constants* of the given type, and for that we required that the
argument was a single atomic value. The result was that these
constructor expressions were really more like literals for the
corresponding types than "normal" expressions. While that's useful
internally, it's confusing for the user. This commits is beginning to
change that, using `interval`/`interval_NS` as the first instances
that now supports arbitrary expressions as its argument. In follow-up
commits, we'll port other constructor expressions over to that new
syntax. (This also unifies HILTI & Spicy to each provide both of those
interval constructors.)

To make this change without loosing a way to create actual constants
(which we need at some places), we implement this in two stages: the
parser initially turns such `interval(...)` expressions into call
operators[1] that the types now define for each desired constructor. We
then extend the normalizer to look at all the relevant call operators:
if they are just receiving a constant value as their argument, then it
substitutes them with a plain ``Ctor`` node (``ctor::Interval`` in our
cases here).

That check for "if they just receive a constant value" isn't quite
trivial in general. In our case, where we just want to go back to what
we had, it's not that difficult, but more generally it would be nice
if we had a constant folder that could compute results even for more
complex, but constant, expressions. As that's a piece that our AST
infrastructure is still missing, this commit starts going that route
by adding a new "constant folder" visitor. Right now, it's covers only
the simple cases we need here, but it's something we can extend going
forward.

Addresses #1123.

[1] These call operators are a bit special because they actually don't
have anything to call: in `interval(42)` the `interval` isn't any kind
of actual value; we just need it for overload resolution. To make that
work, we use a `expression::Member` as the first operand, which models
a predefined ID. Normally `Member` is just for field lookups in, e.g.,
structs, but it works just as well elsewhere.
  • Loading branch information
rsmmr committed Dec 7, 2022
1 parent 6b21ae0 commit ab4f12b
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 28 deletions.
22 changes: 22 additions & 0 deletions doc/autogen/types/interval.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@

.. rubric:: Operators

.. spicy:operator:: interval::Call interval interval(int)
Creates an interval interpreting the argument as number of seconds.

.. spicy:operator:: interval::Call interval interval(real)
Creates an interval interpreting the argument as number of seconds.

.. spicy:operator:: interval::Call interval interval(uint)
Creates an interval interpreting the argument as number of seconds.

.. spicy:operator:: interval::Call interval interval_ns(int)
Creates an interval interpreting the argument as number of
nanoseconds.

.. spicy:operator:: interval::Call interval interval_ns(uint)
Creates an interval interpreting the argument as number of
nanoseconds.

.. spicy:operator:: interval::Difference interval t:interval <sp> op:- <sp> t:interval
Returns the difference of the intervals.
Expand Down
1 change: 1 addition & 0 deletions hilti/toolchain/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ set(SOURCES
src/compiler/plugin.cc
src/compiler/unit.cc
src/compiler/visitors/coercer.cc
src/compiler/visitors/constant-folder.cc
src/compiler/visitors/normalizer.cc
src/compiler/visitors/printer.cc
src/compiler/visitors/renderer.cc
Expand Down
5 changes: 5 additions & 0 deletions hilti/toolchain/include/ast/builder/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,5 +341,10 @@ inline auto port(Expression port, Expression protocol, const Meta& m = Meta()) {
std::vector<Expression>{std::move(port), std::move(protocol)}, m);
}

inline Expression namedCtor(const std::string& name, const std::vector<Expression>& args, Meta m = Meta()) {
return expression::UnresolvedOperator(operator_::Kind::Call,
{expression::Member(ID(name)), expression::Ctor(hilti::ctor::Tuple(args))},
std::move(m));
}

} // namespace hilti::builder
33 changes: 33 additions & 0 deletions hilti/toolchain/include/ast/operators/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,39 @@ private:
\
__END_OPERATOR_CUSTOM

/** Defines a constructor-style call operator introduced by a keyword. */
#define KEYWORD_CTOR(ns, cls, kw, result_, ty_op, doc_) \
__BEGIN_OPERATOR_CUSTOM(ns, Call, cls) \
\
const auto& signature() const { \
static hilti::operator_::Signature _signature = {.result = result_, \
.args = \
{ \
{"op", ty_op}, \
}, \
.doc = doc_}; \
return _signature; \
} \
\
const std::vector<hilti::operator_::Operand>& operands() const { \
static std::vector<hilti::operator_::Operand> _operands = {{{}, hilti::type::Member(kw)}, \
{{}, hilti::type::OperandList(signature().args)}}; \
return _operands; \
} \
\
std::string doc() const { return signature().doc; } \
\
hilti::Type result(const hilti::node::Range<hilti::Expression>& ops) const { \
return *hilti::operator_::type(signature().result, ops, ops); \
} \
\
bool isLhs() const { return signature().lhs; } \
hilti::operator_::Priority priority() const { return signature().priority; } \
\
void validate(const hilti::expression::ResolvedOperator& /* i */, hilti::operator_::position_t /* p */) const {} \
\
__END_OPERATOR_CUSTOM

/**
* No-op to have the auto-generated code pick up on an operator that's
* fully defined separately.
Expand Down
11 changes: 11 additions & 0 deletions hilti/toolchain/include/ast/operators/interval.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ STANDARD_OPERATOR_2x(interval, MultipleUnsignedInteger, Multiple, type::Interval
STANDARD_OPERATOR_2x(interval, MultipleReal, Multiple, type::Interval(), type::Interval(), type::Real(),
"Multiplies the interval with the given factor.");

KEYWORD_CTOR(interval, CtorSignedIntegerNs, "interval_ns", type::Interval(), type::SignedInteger(type::Wildcard()),
"Creates an interval interpreting the argument as number of nanoseconds.");
KEYWORD_CTOR(interval, CtorSignedIntegerSecs, "interval", type::Interval(), type::SignedInteger(type::Wildcard()),
"Creates an interval interpreting the argument as number of seconds.");
KEYWORD_CTOR(interval, CtorUnsignedIntegerNs, "interval_ns", type::Interval(), type::UnsignedInteger(type::Wildcard()),
"Creates an interval interpreting the argument as number of nanoseconds.");
KEYWORD_CTOR(interval, CtorUnsignedIntegerSecs, "interval", type::Interval(), type::UnsignedInteger(type::Wildcard()),
"Creates an interval interpreting the argument as number of seconds.");
KEYWORD_CTOR(interval, CtorRealSecs, "interval", type::Interval(), type::Real(),
"Creates an interval interpreting the argument as number of seconds.");

BEGIN_METHOD(interval, Seconds)
const auto& signature() const {
static auto _signature =
Expand Down
27 changes: 27 additions & 0 deletions hilti/toolchain/include/compiler/detail/visitors.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ std::string renderOperatorInstance(const expression::ResolvedOperator& o);
void renderNode(const Node& n, std::ostream& out, bool include_scopes = false);
void renderNode(const Node& n, logging::DebugStream stream, bool include_scopes = false);

/**
* Folds an expression into a constant value if that's possible. Note that the
* current implementation is very, very basic, and covers just a few cases. If
* the function returns an error, that does not necessarily mean that the
* expression is not represeneting a constant value, but only that we aren't
* able to compute it.
*/
Result<Ctor> foldConstant(const Expression& expr);

/**
* Folds an expression intoa constant value of a specific type, if that's
* possible. This behaves like the non-templated version of `foldConstant()`
* but adds a check that the resulting ``Ctor`` is of the exepcted type. If
* not, it will fail.
*/
template<typename Ctor>
Result<Ctor> foldConstant(const Expression& expr) {
auto ctor = foldConstant(expr);
if ( ! ctor )
return ctor.error();

if ( auto ctor_ = ctor->tryAs<Ctor>() )
return *ctor_;
else
return result::Error("unexpected type");
}

namespace ast {
/** Implements the corresponding functionality for the default HILTI compiler plugin. */
void buildScopes(const std::shared_ptr<hilti::Context>& ctx, Node* root, Unit* unit);
Expand Down
25 changes: 25 additions & 0 deletions hilti/toolchain/src/compiler/codegen/operators.cc
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,31 @@ struct Visitor : hilti::visitor::PreOrder<cxx::Expression, Visitor> {
result_t operator()(const operator_::interval::Sum& n) { return binary(n, "+"); }
result_t operator()(const operator_::interval::Unequal& n) { return binary(n, "!="); }

result_t operator()(const operator_::interval::CtorSignedIntegerSecs& n) {
auto args = tupleArguments(n, n.op1());
return fmt("::hilti::rt::Interval(%s, hilti::rt::Interval::SecondTag())", args[0]);
}

result_t operator()(const operator_::interval::CtorSignedIntegerNs& n) {
auto args = tupleArguments(n, n.op1());
return fmt("::hilti::rt::Interval(%s, hilti::rt::Interval::NanoSecondTag())", args[0]);
}

result_t operator()(const operator_::interval::CtorUnsignedIntegerSecs& n) {
auto args = tupleArguments(n, n.op1());
return fmt("::hilti::rt::Interval(%s, hilti::rt::Interval::SecondTag())", args[0]);
}

result_t operator()(const operator_::interval::CtorUnsignedIntegerNs& n) {
auto args = tupleArguments(n, n.op1());
return fmt("::hilti::rt::Interval(%s, hilti::rt::Interval::NanoSecondTag())", args[0]);
}

result_t operator()(const operator_::interval::CtorRealSecs& n) {
auto args = tupleArguments(n, n.op1());
return fmt("::hilti::rt::Interval(%f, hilti::rt::Interval::SecondTag())", args[0]);
}

// List
result_t operator()(const operator_::list::iterator::IncrPostfix& n) { return fmt("%s++", op0(n)); }
result_t operator()(const operator_::list::iterator::IncrPrefix& n) { return fmt("++%s", op0(n)); }
Expand Down
20 changes: 7 additions & 13 deletions hilti/toolchain/src/compiler/parser/parser.yy
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace hilti { namespace detail { class Parser; } }

%glr-parser
%expect 115
%expect-rr 187
%expect-rr 189

%{

Expand Down Expand Up @@ -165,6 +165,7 @@ static uint64_t check_int64_range(uint64_t x, bool positive, const hilti::Meta&
%token INOUT "inout"
%token INT "int"
%token INTERVAL "interval"
%token INTERVAL_NS "interval_ns"
%token IOSRC "iosrc"
%token ITERATOR "iterator"
%token CONST_ITERATOR "const_iterator"
Expand Down Expand Up @@ -234,7 +235,7 @@ static uint64_t check_int64_range(uint64_t x, bool positive, const hilti::Meta&
%type <std::vector<hilti::Declaration>> struct_fields union_fields opt_union_fields
%type <hilti::Type> base_type_no_attrs base_type type function_type tuple_type struct_type enum_type union_type
%type <hilti::Ctor> ctor tuple struct_ list regexp map set
%type <hilti::Expression> expr tuple_elem tuple_expr member_expr expr_0 expr_1 expr_2 expr_3 expr_4 expr_5 expr_6 expr_7 expr_8 expr_9 expr_a expr_b expr_c expr_d expr_e expr_f expr_g
%type <hilti::Expression> expr tuple_elem tuple_expr member_expr ctor_expr expr_0 expr_1 expr_2 expr_3 expr_4 expr_5 expr_6 expr_7 expr_8 expr_9 expr_a expr_b expr_c expr_d expr_e expr_f expr_g
%type <std::vector<hilti::Expression>> opt_tuple_elems1 opt_tuple_elems2 exprs opt_exprs opt_type_arguments
%type <std::optional<hilti::Expression>> opt_func_default_expr
%type <hilti::Function> function_with_body method_with_body hook_with_body function_without_body
Expand Down Expand Up @@ -269,7 +270,6 @@ static uint64_t check_int64_range(uint64_t x, bool positive, const hilti::Meta&
%type <std::pair<std::vector<hilti::Declaration>, std::vector<hilti::Statement>>> global_scope_items

%type <double> const_real
%type <int64_t> const_sint
%type <uint64_t> const_uint

%%
Expand Down Expand Up @@ -785,6 +785,7 @@ expr_e : BEGIN_ '(' expr ')' { $$ = hilti::expression::Unres
| expr_f { $$ = std::move($1); }

expr_f : ctor { $$ = hilti::expression::Ctor(std::move($1), __loc__); }
| ctor_expr { $$ = std::move($1); }
| PORT '(' expr ',' expr ')' { $$ = hilti::builder::port(std::move($3), std::move($5), __loc__); }
| '-' expr_g { $$ = hilti::expression::UnresolvedOperator(hilti::operator_::Kind::SignNeg, {std::move($2)}, __loc__); }
| '[' expr FOR local_id IN expr ']'
Expand Down Expand Up @@ -814,12 +815,6 @@ ctor : CBOOL { $$ = hilti::ctor::Bool($1, __
| CADDRESS { $$ = hilti::ctor::Address(hilti::ctor::Address::Value($1), __loc__); }
| CADDRESS '/' CUINTEGER { $$ = hilti::ctor::Network(hilti::ctor::Network::Value($1, $3), __loc__); }
| CPORT { $$ = hilti::ctor::Port(hilti::ctor::Port::Value($1), __loc__); }
| INTERVAL '(' const_real ')' { try { $$ = hilti::ctor::Interval(hilti::ctor::Interval::Value($3, hilti::rt::Interval::SecondTag()), __loc__); }
catch ( hilti::rt::OutOfRange& e ) { $$ = hilti::ctor::Interval(hilti::ctor::Interval::Value()); error(@$, e.what()); }
}
| INTERVAL '(' const_sint ')' { try { $$ = hilti::ctor::Interval(hilti::ctor::Interval::Value($3, hilti::rt::Interval::SecondTag()), __loc__); }
catch ( hilti::rt::OutOfRange& e ) { $$ = hilti::ctor::Interval(hilti::ctor::Interval::Value()); error(@$, e.what()); }
}
| TIME '(' const_real ')' { try { $$ = hilti::ctor::Time(hilti::ctor::Time::Value($3, hilti::rt::Time::SecondTag()), __loc__); }
catch ( hilti::rt::OutOfRange& e ) { $$ = hilti::ctor::Time(hilti::ctor::Time::Value()); error(@$, e.what()); }
}
Expand Down Expand Up @@ -848,14 +843,13 @@ ctor : CBOOL { $$ = hilti::ctor::Bool($1, __
| tuple { $$ = std::move($1); }
;

ctor_expr : INTERVAL '(' expr ')' { $$ = builder::namedCtor("interval", { std::move($3) }, __loc__); }
| INTERVAL_NS '(' expr ')' { $$ = builder::namedCtor("interval_ns", { std::move($3) }, __loc__); }

const_real : CUREAL { $$ = $1; }
| '+' CUREAL { $$ = $2; }
| '-' CUREAL { $$ = -$2; }

const_sint : CUINTEGER { $$ = check_int64_range($1, true, __loc__); }
| '+' CUINTEGER { $$ = check_int64_range($2, true, __loc__); }
| '-' CUINTEGER { $$ = -check_int64_range($2, false, __loc__); }

const_uint : CUINTEGER { $$ = $1; }
| '+' CUINTEGER { $$ = $2; }

Expand Down
1 change: 1 addition & 0 deletions hilti/toolchain/src/compiler/parser/scanner.ll
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ init return token::INIT;
inout return token::INOUT;
int return token::INT;
interval return token::INTERVAL;
interval_ns return token::INTERVAL_NS;
iterator return token::ITERATOR;
list return token::LIST;
local return token::LOCAL;
Expand Down
48 changes: 48 additions & 0 deletions hilti/toolchain/src/compiler/visitors/constant-folder.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details.

#include <hilti/ast/ctors/integer.h>
#include <hilti/ast/detail/visitor.h>
#include <hilti/ast/expression.h>
#include <hilti/ast/operators/signed-integer.h>
#include <hilti/ast/operators/real.h>
#include <hilti/ast/types/integer.h>
#include <hilti/base/logger.h>
#include <hilti/base/util.h>
#include <hilti/ast/builder/expression.h>
#include <hilti/compiler/detail/visitors.h>

using namespace hilti;

namespace {

// For now, this is only a very basic constant folder that only covers cases we
// need to turn type constructor expressions coming with a single argument into
// ctor expressions.
struct VisitorConstantFolder : public visitor::PreOrder<Ctor, VisitorConstantFolder> {
result_t operator()(const expression::Ctor& n, position_t p) { return n.ctor(); }

result_t operator()(const operator_::signed_integer::SignNeg& n, position_t p) {
if ( auto op = detail::foldConstant<ctor::SignedInteger>(n.op1()) )
return ctor::SignedInteger(- op->value(), op->width(), op->meta());
else
return {};
}

result_t operator()(const operator_::real::SignNeg& n, position_t p) {
if ( auto op = detail::foldConstant<ctor::Real>(n.op1()) )
return ctor::Real(- op->value(), op->meta());
else
return {};
}
};

} // anonymous namespace

Result<Ctor> detail::foldConstant(const Expression& expr) {
auto v = VisitorConstantFolder();

if ( auto ctor = v.dispatch(expr) )
return *ctor;
else
return result::Error("not a foldable constant expression");
}
Loading

0 comments on commit ab4f12b

Please sign in to comment.