diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-09-08 10:46:13 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-09-08 10:46:13 +0200 |
| commit | 72f0f9b9629013e83fc4346a35113e9eecae45b5 (patch) | |
| tree | 47779eda823f936e1c8d3e99dfc153901497c34f /boot/evaluator.cc | |
| parent | 72f5e82196d95008ed12c12de6bdccf24361e106 (diff) | |
| download | elna-72f0f9b9629013e83fc4346a35113e9eecae45b5.tar.gz | |
Allow compile-time casts
Diffstat (limited to 'boot/evaluator.cc')
| -rw-r--r-- | boot/evaluator.cc | 279 |
1 files changed, 239 insertions, 40 deletions
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 <cmath> #include <cstddef> #include <limits> #include <ranges> @@ -680,28 +681,130 @@ namespace elna::boot }, lhs.value()); } - std::optional<constant_value> evaluator::cast_to_int(const constant_value& source_expression) + static std::optional<constant_value> integer_constant(const bool target_signed, + const std::size_t target_size, const double value) { - return std::visit([](auto&& source_value) -> std::optional<constant_value> { + std::optional<integer_literal> result = + integer_literal::from(target_size, static_cast<std::size_t>(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<double> 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<int>(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<double> widen_to_double(const integer_literal& source_value) + { + if (source_value.is_signed()) + { + std::optional<std::ptrdiff_t> signed_value = source_value.try_to<std::ptrdiff_t>(); + + return signed_value.has_value() + ? std::optional(static_cast<double>(signed_value.value())) + : std::nullopt; + } + std::optional<std::size_t> unsigned_value = source_value.try_to<std::size_t>(); + + return unsigned_value.has_value() + ? std::optional(static_cast<double>(unsigned_value.value())) + : std::nullopt; + } + + std::optional<constant_value> 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<constant_value> { using T = std::decay_t<decltype(source_value)>; if constexpr (std::is_same_v<T, integer_literal>) { - std::optional<std::ptrdiff_t> signed_value = source_value.template try_to<std::ptrdiff_t>(); + // 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<T, float_literal>) + { + std::optional<double> 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<T, bool> || std::is_same_v<T, std::uint32_t>) + { + return integer_constant(target_signed, target_size, static_cast<double>(source_value)); + } + else if constexpr (std::is_same_v<T, std::nullptr_t>) + { + return integer_constant(target_signed, target_size, 0.0); + } + else + { + return std::nullopt; + } + }, source_expression); + } + + std::optional<constant_value> 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<constant_value> { + using T = std::decay_t<decltype(source_value)>; + + if constexpr (std::is_same_v<T, float_literal>) + { + return constant_value{ source_value.cast_to(target_format) }; + } + else if constexpr (std::is_same_v<T, integer_literal>) + { + std::optional<double> 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<T, double> - || std::is_same_v<T, bool> - || std::is_same_v<T, std::uint32_t>) + else if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, std::uint32_t>) { - return integer_literal::from(static_cast<std::ptrdiff_t>(source_value)); + const float_literal widened = float_literal::from(static_cast<double>(source_value)); + + return constant_value{ widened.cast_to(target_format) }; } else if constexpr (std::is_same_v<T, std::nullptr_t>) { - return integer_literal::from<std::ptrdiff_t>(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<constant_value> evaluator::cast_to_word(const constant_value& source_expression) + std::optional<constant_value> evaluator::cast_to_bool(const constant_value& source_expression) { return std::visit([](auto&& source_value) -> std::optional<constant_value> { using T = std::decay_t<decltype(source_value)>; - if constexpr (std::is_same_v<T, integer_literal>) + if constexpr (std::is_same_v<T, bool>) + { + return constant_value{ source_value }; + } + else if constexpr (std::is_same_v<T, integer_literal>) + { + return constant_value{ !(source_value == std::ptrdiff_t{ 0 }) }; + } + else if constexpr (std::is_same_v<T, float_literal>) + { + return constant_value{ !(source_value == 0.0) }; + } + else if constexpr (std::is_same_v<T, std::uint32_t>) + { + return constant_value{ source_value != 0U }; + } + else if constexpr (std::is_same_v<T, std::nullptr_t>) + { + return constant_value{ false }; + } + else + { + return std::nullopt; + } + }, source_expression); + } + + std::optional<constant_value> evaluator::cast_to_char(const constant_value& source_expression) + { + return std::visit([](auto&& source_value) -> std::optional<constant_value> { + using T = std::decay_t<decltype(source_value)>; + + if constexpr (std::is_same_v<T, std::uint32_t>) + { + return constant_value{ source_value }; + } + else if constexpr (std::is_same_v<T, integer_literal>) + { + std::optional<std::uint32_t> code_point = source_value.template try_to<std::uint32_t>(); + + return code_point.has_value() + ? std::optional(constant_value{ code_point.value() }) + : std::nullopt; + } + else if constexpr (std::is_same_v<T, float_literal>) { - std::optional<std::size_t> unsigned_value = source_value.template try_to<std::size_t>(); + std::optional<double> 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<std::uint32_t>(truncated.value()) }) : std::nullopt; } - else if constexpr (std::is_same_v<T, double> - || std::is_same_v<T, bool> - || std::is_same_v<T, std::uint32_t>) + else if constexpr (std::is_same_v<T, bool>) { - return integer_literal::from(static_cast<std::size_t>(source_value)); + return constant_value{ static_cast<std::uint32_t>(source_value) }; } else if constexpr (std::is_same_v<T, std::nullptr_t>) { - return integer_literal::from<std::ptrdiff_t>(0U); + return constant_value{ std::uint32_t{ 0 } }; } else { @@ -740,39 +885,93 @@ namespace elna::boot }, source_expression); } + std::optional<constant_value> evaluator::cast_to_enumeration(const constant_value& source_expression, + const std::size_t member_count, const std::size_t target_size) + { + std::optional<constant_value> position = cast_to_integer(source_expression, false, target_size); + + if (!position.has_value()) + { + return std::nullopt; + } + const auto *converted = std::get_if<integer_literal>(&position.value()); + + if (converted == nullptr || *converted < std::size_t{ 1 } || *converted > member_count) + { + return std::nullopt; + } + return position; + } + + std::optional<constant_value> evaluator::cast_to_pointer(const constant_value& source_expression) + { + if (std::holds_alternative<std::nullptr_t>(source_expression)) + { + return source_expression; + } + if (const auto *address = std::get_if<integer_literal>(&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<constant_value> evaluator::evaluate_cast(cast_expression& subject) { - auto value = evaluate(subject.value()); + std::optional<constant_value> 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_type> enumeration = target_type.get<enumeration_type>()) { - auto symbol = this->bag.lookup(target->name); + std::optional<type_properties> 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<type_properties> 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<std::size_t> evaluator::evaluate_traits_size(const type& subject) |
