aboutsummaryrefslogtreecommitdiff
path: root/boot/validation.cc
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-07-28 19:19:32 +0200
committerEugen Wissner <belka@caraus.de>2026-07-28 19:19:32 +0200
commit36a274c9a8bca944234589220def025d3920b3ef (patch)
treef198ad272b74ffa3abb13e5063b55514d521e1f6 /boot/validation.cc
parent5cdaceb77af6a1d98145f8afb34ce6884e21a3d6 (diff)
downloadelna-36a274c9a8bca944234589220def025d3920b3ef.tar.gz
Enforce case label uniqueness and constness
Diffstat (limited to 'boot/validation.cc')
-rw-r--r--boot/validation.cc155
1 files changed, 155 insertions, 0 deletions
diff --git a/boot/validation.cc b/boot/validation.cc
new file mode 100644
index 0000000..ccc8865
--- /dev/null
+++ b/boot/validation.cc
@@ -0,0 +1,155 @@
+/* Final validation after constant folding.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "elna/boot/validation.h"
+
+#include <algorithm>
+#include <numeric>
+#include <unordered_map>
+
+namespace elna::boot
+{
+ validation_error::validation_error(const source_position position, payload_type payload)
+ : error(position), payload(std::move(payload))
+ {
+ }
+
+ std::string validation_error::what() const
+ {
+ return std::visit([](const auto& payload) -> std::string {
+ using T = std::decay_t<decltype(payload)>;
+
+ if constexpr (std::is_same_v<T, non_constant_initializer>)
+ {
+ return "Variable initializers must be constant expressions";
+ }
+ else if constexpr (std::is_same_v<T, duplicate_case>)
+ {
+ return "Duplicate case label";
+ }
+ else if constexpr (std::is_same_v<T, non_constant_case_label>)
+ {
+ return "Case label must be a constant expression";
+ }
+ }, this->payload);
+ }
+
+ std::optional<std::pair<std::string, source_position>> validation_error::note() const
+ {
+ return std::visit([](const auto& payload) -> std::optional<std::pair<std::string, source_position>> {
+ using T = std::decay_t<decltype(payload)>;
+
+ if constexpr (std::is_same_v<T, non_constant_initializer>)
+ {
+ std::string identifier_list = std::accumulate(
+ std::next(payload.identifiers.begin()), payload.identifiers.end(),
+ payload.identifiers.front().name(),
+ [](const std::string& accumulator, const identifier& next) -> std::string {
+ return accumulator + ", " + next.name();
+ });
+ auto position_span = source_position(payload.identifiers.front().position().start(),
+ payload.identifiers.back().position().end());
+ return std::make_pair(std::move(identifier_list), position_span);
+ }
+ else if constexpr (std::is_same_v<T, duplicate_case>)
+ {
+ return std::make_pair("Previous label here", payload.first);
+ }
+ else if constexpr (std::is_same_v<T, non_constant_case_label>)
+ {
+ return std::nullopt;
+ }
+ }, this->payload);
+ }
+
+ validation_error validation_error::non_constant_initializer_error(const source_position position,
+ const std::vector<identifier_definition>& identifiers)
+ {
+ non_constant_initializer payload;
+ payload.identifiers.reserve(identifiers.size());
+
+ std::ranges::transform(identifiers, std::back_inserter(payload.identifiers),
+ [](const auto& identifier) { return identifier.id(); });
+
+ return validation_error(position, std::move(payload));
+ }
+
+ validation_visitor::validation_visitor(symbol_bag& bag, const target_info& target)
+ : bag(bag), target(target), constant_evaluator(this->bag, this->target)
+ {
+ }
+
+ void validation_visitor::visit(variable_declaration* declaration)
+ {
+ if (declaration->initializer == nullptr || has_errors())
+ {
+ return;
+ }
+ auto computed = this->constant_evaluator.evaluate(*declaration->initializer);
+ if (!computed)
+ {
+ auto non_constant_initializer_error = validation_error::non_constant_initializer_error(
+ declaration->initializer->position(), declaration->identifiers);
+ add_error<validation_error>(non_constant_initializer_error);
+ return;
+ }
+ for (const auto& identifier : declaration->identifiers)
+ {
+ auto variable_symbol = this->bag.lookup(identifier.name())->is_variable();
+ variable_symbol->value = computed;
+ }
+ }
+
+ void validation_visitor::visit(procedure_declaration *declaration)
+ {
+ if (declaration->body.has_value())
+ {
+ auto procedure = this->bag.lookup(declaration->identifier.name())->is_procedure();
+ this->bag.enter(procedure->scope);
+ }
+ walking_visitor::visit(declaration);
+ if (declaration->body.has_value())
+ {
+ this->bag.leave();
+ }
+ }
+
+ void validation_visitor::visit(case_statement *statement)
+ {
+ walking_visitor::visit(statement);
+ std::unordered_map<constant_value, source_position, constant_value_hash, constant_value_hash> seen;
+ for (const auto& case_block : statement->cases)
+ {
+ for (auto *label : case_block.labels)
+ {
+ auto value = this->constant_evaluator.evaluate(*label);
+ if (!value.has_value())
+ {
+ add_error<validation_error>(label->position(),
+ validation_error::non_constant_case_label{});
+ continue;
+ }
+ auto [case_position, inserted] = seen.try_emplace(value.value(), label->position());
+ if (!inserted)
+ {
+ add_error<validation_error>(label->position(),
+ validation_error::duplicate_case{ case_position->second });
+ }
+ }
+ }
+ }
+}