diff --git a/hilti/toolchain/CMakeLists.txt b/hilti/toolchain/CMakeLists.txt index ff2282b41..7cf077769 100644 --- a/hilti/toolchain/CMakeLists.txt +++ b/hilti/toolchain/CMakeLists.txt @@ -82,6 +82,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 diff --git a/hilti/toolchain/include/compiler/detail/visitors.h b/hilti/toolchain/include/compiler/detail/visitors.h index 2a5d1716b..ca38f2ff0 100644 --- a/hilti/toolchain/include/compiler/detail/visitors.h +++ b/hilti/toolchain/include/compiler/detail/visitors.h @@ -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 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 +Result foldConstant(const Expression& expr) { + auto ctor = foldConstant(expr); + if ( ! ctor ) + return ctor.error(); + + if ( auto ctor_ = ctor->tryAs() ) + 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& ctx, Node* root, Unit* unit); diff --git a/hilti/toolchain/src/compiler/parser/parser.yy b/hilti/toolchain/src/compiler/parser/parser.yy index 986a955ef..3656b691b 100644 --- a/hilti/toolchain/src/compiler/parser/parser.yy +++ b/hilti/toolchain/src/compiler/parser/parser.yy @@ -269,7 +269,6 @@ static uint64_t check_int64_range(uint64_t x, bool positive, const hilti::Meta& %type , std::vector>> global_scope_items %type const_real -%type const_sint %type const_uint %% @@ -786,6 +785,7 @@ expr_e : BEGIN_ '(' expr ')' { $$ = hilti::expression::Unres expr_f : ctor { $$ = hilti::expression::Ctor(std::move($1), __loc__); } | PORT '(' expr ',' expr ')' { $$ = hilti::builder::port(std::move($3), std::move($5), __loc__); } + | INTERVAL '(' expr ')' { $$ = hilti::expression::UnresolvedOperator(hilti::operator_::Kind::Cast, { std::move($3), hilti::expression::Type_(hilti::type::Interval())}, __loc__); } | '-' expr_g { $$ = hilti::expression::UnresolvedOperator(hilti::operator_::Kind::SignNeg, {std::move($2)}, __loc__); } | '[' expr FOR local_id IN expr ']' { $$ = hilti::expression::ListComprehension(std::move($6), std::move($2), std::move($4), {}, __loc__); } @@ -814,12 +814,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()); } } @@ -852,10 +846,6 @@ 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; } diff --git a/hilti/toolchain/src/compiler/visitors/constant-folder.cc b/hilti/toolchain/src/compiler/visitors/constant-folder.cc new file mode 100644 index 000000000..58f62b516 --- /dev/null +++ b/hilti/toolchain/src/compiler/visitors/constant-folder.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 { + 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(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(n.op1()) ) + return ctor::Real(- op->value(), op->meta()); + else + return {}; + } +}; + +} // anonymous namespace + +Result 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"); +} diff --git a/hilti/toolchain/src/compiler/visitors/normalizer.cc b/hilti/toolchain/src/compiler/visitors/normalizer.cc index ad5c140b1..313c9081e 100644 --- a/hilti/toolchain/src/compiler/visitors/normalizer.cc +++ b/hilti/toolchain/src/compiler/visitors/normalizer.cc @@ -1,5 +1,7 @@ // Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. +#include +#include #include #include #include @@ -20,12 +22,18 @@ namespace { struct VisitorNormalizer : public visitor::PreOrder { bool modified = false; - // Log debug message recording resolving a epxxression. + // Log debug message recording resolving a expression. void logChange(const Node& old, const Expression& nexpr) { HILTI_DEBUG(logging::debug::Normalizer, util::fmt("[%s] %s -> expression %s (%s)", old.typename_(), old, nexpr, old.location())); } + // Log debug message recording resolving a ctor. + void logChange(const Node& old, const Ctor& nexpr) { + HILTI_DEBUG(logging::debug::Normalizer, + util::fmt("[%s] %s -> ctor %s (%s)", old.typename_(), old, nexpr, old.location())); + } + // Log debug message recording resolving a statement. void logChange(const Node& old, const Statement& nstmt) { HILTI_DEBUG(logging::debug::Normalizer, @@ -119,6 +127,45 @@ struct VisitorNormalizer : public visitor::PreOrder { } } + void operator()(const operator_::signed_integer::CastToInterval& cast, position_t p) { + if ( auto ctor = detail::foldConstant(cast.op0()) ) { + try { + auto i = ctor::Interval(ctor::Interval::Value(ctor->value(), hilti::rt::Interval::SecondTag())); + logChange(p.node, i); + p.node = expression::Ctor(std::move(i)); + modified = true; + } catch ( hilti::rt::OutOfRange& e ) { + p.node.addError(e.what()); + } + } + } + + void operator()(const operator_::unsigned_integer::CastToInterval& cast, position_t p) { + if ( auto ctor = detail::foldConstant(cast.op0()) ) { + try { + auto i = ctor::Interval(ctor::Interval::Value(ctor->value(), hilti::rt::Interval::SecondTag())); + logChange(p.node, i); + p.node = expression::Ctor(std::move(i)); + modified = true; + } catch ( hilti::rt::OutOfRange& e ) { + p.node.addError(e.what()); + } + } + } + + void operator()(const operator_::real::CastToInterval& cast, position_t p) { + if ( auto ctor = detail::foldConstant(cast.op0()) ) { + try { + auto i = ctor::Interval(hilti::ctor::Interval::Value(ctor->value(), hilti::rt::Interval::SecondTag())); + logChange(p.node, i); + p.node = expression::Ctor(std::move(i)); + modified = true; + } catch ( hilti::rt::OutOfRange& e ) { + p.node.addError(e.what()); + } + } + } + void operator()(const statement::If& n, position_t p) { if ( n.init() && ! n.condition() ) { auto cond = expression::UnresolvedID(n.init()->id()); diff --git a/spicy/toolchain/src/compiler/parser/parser.yy b/spicy/toolchain/src/compiler/parser/parser.yy index 7f808d0df..2c6951fbb 100644 --- a/spicy/toolchain/src/compiler/parser/parser.yy +++ b/spicy/toolchain/src/compiler/parser/parser.yy @@ -34,7 +34,7 @@ namespace spicy { namespace detail { class Parser; } } %verbose %glr-parser -%expect 131 +%expect 130 %expect-rr 160 %{ @@ -912,6 +912,7 @@ expr_e : CAST type_param_begin type type_param_end '(' expr ')' { $$ = expr_f : ctor { $$ = hilti::expression::Ctor(std::move($1), __loc__); } | PORT '(' expr ',' expr ')' { $$ = hilti::builder::port(std::move($3), std::move($5), __loc__); } + | INTERVAL '(' expr ')' { $$ = hilti::expression::UnresolvedOperator(hilti::operator_::Kind::Cast, { std::move($3), hilti::expression::Type_(hilti::type::Interval())}, __loc__); } | '-' expr_g { $$ = hilti::expression::UnresolvedOperator(hilti::operator_::Kind::SignNeg, {std::move($2)}, __loc__); } | '[' expr FOR local_id IN expr ']' { $$ = hilti::expression::ListComprehension(std::move($6), std::move($2), std::move($4), {}, __loc__); } @@ -945,12 +946,6 @@ ctor : CADDRESS { $$ = hilti::ctor::Address(hil | '+' CUINTEGER { $$ = hilti::ctor::SignedInteger(check_int64_range($2, true, __loc__), 64, __loc__); } | '-' CUINTEGER { $$ = hilti::ctor::SignedInteger(-check_int64_range($2, false, __loc__), 64, __loc__); } | OPTIONAL '(' expr ')' { $$ = hilti::ctor::Optional(std::move($3), __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()); } - } | INTERVAL_NS '(' const_sint ')' { $$ = hilti::ctor::Interval(hilti::ctor::Interval::Value($3, hilti::rt::Interval::NanosecondTag()), __loc__); } | 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()); } diff --git a/tests/Baseline/spicy.types.interval.ctor-out-of-range/output b/tests/Baseline/spicy.types.interval.ctor-out-of-range/output index 028b73376..78c4be0c9 100644 --- a/tests/Baseline/spicy.types.interval.ctor-out-of-range/output +++ b/tests/Baseline/spicy.types.interval.ctor-out-of-range/output @@ -1,4 +1,4 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. [error] <...>/ctor-out-of-range.spicy:7:12: value cannot be represented as an interval [error] <...>/ctor-out-of-range.spicy:8:12: value cannot be represented as an interval -[error] spicyc: parse error +[error] spicyc: aborting after errors diff --git a/tests/hilti/types/interval/ops.hlt b/tests/hilti/types/interval/ops.hlt index 8d73a56cd..ca3470f71 100644 --- a/tests/hilti/types/interval/ops.hlt +++ b/tests/hilti/types/interval/ops.hlt @@ -7,6 +7,8 @@ import hilti; global i1 = interval(2.5); global i2 = interval(1.5); +global i3 = interval(10 + 15); +global i4 = interval(10.0 + 15.0); hilti::print(i1); hilti::print(i2); @@ -30,4 +32,7 @@ assert i2 <= i2; assert cast(5) == interval(5.0); assert cast(5.0) == interval(5.0); +assert i3 == interval(25.0); +assert i3 == i4; + }