From 72f0f9b9629013e83fc4346a35113e9eecae45b5 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Tue, 8 Sep 2026 10:46:13 +0200 Subject: Allow compile-time casts --- boot/evaluator.cc | 279 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 239 insertions(+), 40 deletions(-) (limited to 'boot/evaluator.cc') diff --git a/boot/evaluator.cc b/boot/evaluator.cc index 6a9fb3b..b1648af 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/ast.h" +#include #include #include #include @@ -680,28 +681,130 @@ namespace elna::boot }, lhs.value()); } - std::optional evaluator::cast_to_int(const constant_value& source_expression) + static std::optional integer_constant(const bool target_signed, + const std::size_t target_size, const double value) { - return std::visit([](auto&& source_value) -> std::optional { + std::optional result = + integer_literal::from(target_size, static_cast(std::abs(value))); + + if (result.has_value() && value < 0.0) + { + result = result.value().negate(); + } + if (!result.has_value() || !result.value().fit_into(target_signed, target_size)) + { + return std::nullopt; + } + return constant_value{ std::move(result).value() }; + } + + /// The caller converts the result, so the range is checked here rather than + /// left to an out of range floating point to integer conversion. + static std::optional truncate_into(const float_literal& source_value, + const bool target_signed, const std::size_t target_size) + { + if (!source_value.is_finite()) + { + return std::nullopt; + } + const double truncated = std::trunc(source_value.value()); + const int value_bits = static_cast(target_size * CHAR_BIT) - (target_signed ? 1 : 0); + const double bound = std::ldexp(1.0, value_bits); + + if (truncated >= bound || truncated < (target_signed ? -bound : 0.0)) + { + return std::nullopt; + } + return truncated; + } + + static std::optional widen_to_double(const integer_literal& source_value) + { + if (source_value.is_signed()) + { + std::optional signed_value = source_value.try_to(); + + return signed_value.has_value() + ? std::optional(static_cast(signed_value.value())) + : std::nullopt; + } + std::optional unsigned_value = source_value.try_to(); + + return unsigned_value.has_value() + ? std::optional(static_cast(unsigned_value.value())) + : std::nullopt; + } + + std::optional evaluator::cast_to_integer(const constant_value& source_expression, + const bool target_signed, const std::size_t target_size) + { + return std::visit([target_signed, target_size](auto&& source_value) -> std::optional { using T = std::decay_t; if constexpr (std::is_same_v) { - std::optional signed_value = source_value.template try_to(); + // Changing the signedness without changing the width + // reinterprets the bits and loses nothing. + if (source_value.size() == target_size) + { + return constant_value{ source_value.cast_to(target_signed, target_size) }; + } + integer_literal narrowed = source_value; + + return narrowed.fit_into(target_signed, target_size) + ? std::optional(constant_value{ std::move(narrowed) }) + : std::nullopt; + } + else if constexpr (std::is_same_v) + { + std::optional truncated = truncate_into(source_value, target_signed, target_size); + + return truncated.has_value() + ? integer_constant(target_signed, target_size, truncated.value()) + : std::nullopt; + } + else if constexpr (std::is_same_v || std::is_same_v) + { + return integer_constant(target_signed, target_size, static_cast(source_value)); + } + else if constexpr (std::is_same_v) + { + return integer_constant(target_signed, target_size, 0.0); + } + else + { + return std::nullopt; + } + }, source_expression); + } + + std::optional evaluator::cast_to_float(const constant_value& source_expression, + const float_literal::format_kind target_format) + { + return std::visit([target_format](auto&& source_value) -> std::optional { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + return constant_value{ source_value.cast_to(target_format) }; + } + else if constexpr (std::is_same_v) + { + std::optional widened = widen_to_double(source_value); - return signed_value.has_value() - ? std::optional(integer_literal::from(signed_value.value())) + return widened.has_value() + ? std::optional(constant_value{ float_literal::from(widened.value()).cast_to(target_format) }) : std::nullopt; } - else if constexpr (std::is_same_v - || std::is_same_v - || std::is_same_v) + else if constexpr (std::is_same_v || std::is_same_v) { - return integer_literal::from(static_cast(source_value)); + const float_literal widened = float_literal::from(static_cast(source_value)); + + return constant_value{ widened.cast_to(target_format) }; } else if constexpr (std::is_same_v) { - return integer_literal::from(0); + return constant_value{ float_literal::from(0.0).cast_to(target_format) }; } else { @@ -710,28 +813,70 @@ namespace elna::boot }, source_expression); } - std::optional evaluator::cast_to_word(const constant_value& source_expression) + std::optional evaluator::cast_to_bool(const constant_value& source_expression) { return std::visit([](auto&& source_value) -> std::optional { using T = std::decay_t; - if constexpr (std::is_same_v) + if constexpr (std::is_same_v) + { + return constant_value{ source_value }; + } + else if constexpr (std::is_same_v) + { + return constant_value{ !(source_value == std::ptrdiff_t{ 0 }) }; + } + else if constexpr (std::is_same_v) + { + return constant_value{ !(source_value == 0.0) }; + } + else if constexpr (std::is_same_v) + { + return constant_value{ source_value != 0U }; + } + else if constexpr (std::is_same_v) + { + return constant_value{ false }; + } + else + { + return std::nullopt; + } + }, source_expression); + } + + std::optional evaluator::cast_to_char(const constant_value& source_expression) + { + return std::visit([](auto&& source_value) -> std::optional { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + return constant_value{ source_value }; + } + else if constexpr (std::is_same_v) + { + std::optional code_point = source_value.template try_to(); + + return code_point.has_value() + ? std::optional(constant_value{ code_point.value() }) + : std::nullopt; + } + else if constexpr (std::is_same_v) { - std::optional unsigned_value = source_value.template try_to(); + std::optional truncated = truncate_into(source_value, false, sizeof(std::uint32_t)); - return unsigned_value.has_value() - ? std::optional(integer_literal::from(unsigned_value.value())) + return truncated.has_value() + ? std::optional(constant_value{ static_cast(truncated.value()) }) : std::nullopt; } - else if constexpr (std::is_same_v - || std::is_same_v - || std::is_same_v) + else if constexpr (std::is_same_v) { - return integer_literal::from(static_cast(source_value)); + return constant_value{ static_cast(source_value) }; } else if constexpr (std::is_same_v) { - return integer_literal::from(0U); + return constant_value{ std::uint32_t{ 0 } }; } else { @@ -740,39 +885,93 @@ namespace elna::boot }, source_expression); } + std::optional evaluator::cast_to_enumeration(const constant_value& source_expression, + const std::size_t member_count, const std::size_t target_size) + { + std::optional position = cast_to_integer(source_expression, false, target_size); + + if (!position.has_value()) + { + return std::nullopt; + } + const auto *converted = std::get_if(&position.value()); + + if (converted == nullptr || *converted < std::size_t{ 1 } || *converted > member_count) + { + return std::nullopt; + } + return position; + } + + std::optional evaluator::cast_to_pointer(const constant_value& source_expression) + { + if (std::holds_alternative(source_expression)) + { + return source_expression; + } + if (const auto *address = std::get_if(&source_expression)) + { + // Only the null pointer has a constant representation. + if (*address == std::ptrdiff_t{ 0 }) + { + return constant_value{ nullptr }; + } + } + return std::nullopt; + } + std::optional evaluator::evaluate_cast(cast_expression& subject) { - auto value = evaluate(subject.value()); + std::optional value = evaluate(subject.value()); if (!value.has_value()) { return std::nullopt; } - // The target type is looked up in the symbol table instead of reading - // the type decoration set by name analysis. - if (auto *target = subject.target().is_named()) + const type source_type = resolve_underlying_type(subject.value().type_decoration); + const type target_type = resolve_underlying_type(subject.type_decoration); + + if (source_type == target_type) + { + return value; + } + if (const std::shared_ptr enumeration = target_type.get()) { - auto symbol = this->bag.lookup(target->name); + std::optional properties = get_type_properties(target_type, this->target); - if (symbol != nullptr) + if (!properties.has_value()) { - if (auto type_symbol = symbol->is_type()) - { - const type resolved = resolve_underlying_type(type_symbol->symbol); - - if (is_primitive_type(resolved, "Int")) - { - return cast_to_int(value.value()); - } - else if (is_primitive_type(resolved, "Word")) - { - return cast_to_word(value.value()); - } - } + return std::nullopt; } + return cast_to_enumeration(value.value(), enumeration->members.size(), properties->size); } + if (is_integral_type(target_type)) + { + std::optional properties = get_type_properties(target_type, this->target); - return constant_value{ value.value() }; + return properties.has_value() + ? cast_to_integer(value.value(), is_integer_type(target_type), properties->size) + : std::nullopt; + } + if (is_float_type(target_type)) + { + return cast_to_float(value.value(), is_primitive_type(target_type, "Single") + ? float_literal::format_kind::binary32 + : float_literal::format_kind::binary64); + } + if (is_primitive_type(target_type, "Bool")) + { + return cast_to_bool(value.value()); + } + if (is_primitive_type(target_type, "Char")) + { + return cast_to_char(value.value()); + } + if (is_any_pointer_type(target_type)) + { + return cast_to_pointer(value.value()); + } + return std::nullopt; } std::optional evaluator::evaluate_traits_size(const type& subject) -- cgit v1.2.3