aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-08-05 01:03:55 +0200
committerEugen Wissner <belka@caraus.de>2026-08-05 21:27:36 +0200
commitb2dee14873402ee3d13d59ae5579593796ea862f (patch)
tree1f245eac1a23a73f45124498fc8274874e35750e
parent1c2fb173ea3b674207badc721556ad72962b266e (diff)
downloadelna-b2dee14873402ee3d13d59ae5579593796ea862f.tar.gz
Check that constants are initialized
-rw-r--r--boot/ast.cc33
-rw-r--r--boot/dependency.cc2
-rw-r--r--boot/evaluator.cc101
-rw-r--r--boot/name_analysis.cc62
-rw-r--r--boot/parser.yy31
-rw-r--r--boot/result.cc40
-rw-r--r--boot/symbol.cc23
-rw-r--r--boot/validation.cc69
-rw-r--r--include/elna/boot/ast.h21
-rw-r--r--include/elna/boot/driver.h2
-rw-r--r--include/elna/boot/evaluator.h28
-rw-r--r--include/elna/boot/name_analysis.h22
-rw-r--r--include/elna/boot/result.h46
-rw-r--r--include/elna/boot/symbol.h12
-rw-r--r--include/elna/boot/type_check.h8
-rw-r--r--include/elna/boot/validation.h20
-rw-r--r--testsuite/compilable/compile_time_array_length.elna2
-rw-r--r--testsuite/compilable/const_alias.elna4
-rw-r--r--testsuite/compilable/take_const_address.elna2
-rw-r--r--testsuite/fail_compilation/assign_const_primitive.elna2
-rw-r--r--testsuite/fail_compilation/assign_const_to_pointer.elna2
-rw-r--r--testsuite/fail_compilation/assign_element_of_const.elna2
-rw-r--r--testsuite/fail_compilation/assign_from_const_pointer.elna2
-rw-r--r--testsuite/fail_compilation/const_uninitialized.elna4
24 files changed, 343 insertions, 197 deletions
diff --git a/boot/ast.cc b/boot/ast.cc
index 7157315..fb1e276 100644
--- a/boot/ast.cc
+++ b/boot/ast.cc
@@ -394,6 +394,7 @@ namespace elna::boot
void walking_visitor::visit(array_type_expression *expression)
{
expression->base().accept(this);
+ expression->dimensions().accept(this);
}
void walking_visitor::visit(slice_type_expression *expression)
@@ -451,7 +452,7 @@ namespace elna::boot
void walking_visitor::visit(array_constructor_expression *expression)
{
- expression->m_element_type->accept(this);
+ expression->array_type().accept(this);
for (auto *element : expression->elements)
{
element->accept(this);
@@ -626,14 +627,15 @@ namespace elna::boot
}
array_type_expression::array_type_expression(const source_position position,
- type_expression *base, const std::uint32_t size)
- : node(position), m_base(base), size(size)
+ type_expression *base, expression *dimensions)
+ : node(position), m_base(base), m_dimensions(dimensions)
{
}
array_type_expression::~array_type_expression()
{
- delete m_base;
+ delete this->m_base;
+ delete this->m_dimensions;
}
void array_type_expression::accept(parser_visitor *visitor)
@@ -646,9 +648,14 @@ namespace elna::boot
return this;
}
- type_expression& array_type_expression::base()
+ type_expression& array_type_expression::base() const
{
- return *m_base;
+ return *this->m_base;
+ }
+
+ expression& array_type_expression::dimensions() const
+ {
+ return *this->m_dimensions;
}
slice_type_expression::slice_type_expression(const source_position position,
@@ -659,7 +666,7 @@ namespace elna::boot
slice_type_expression::~slice_type_expression()
{
- delete m_base;
+ delete this->m_base;
}
void slice_type_expression::accept(parser_visitor *visitor)
@@ -818,9 +825,8 @@ namespace elna::boot
}
array_constructor_expression::array_constructor_expression(const source_position position,
- std::uint32_t size, type_expression *element_type,
- std::vector<expression *>&& elements)
- : node(position), size(size), m_element_type(element_type), elements(std::move(elements))
+ array_type_expression *element_type, std::vector<expression *>&& elements)
+ : node(position), m_element_type(element_type), elements(std::move(elements))
{
}
@@ -834,9 +840,14 @@ namespace elna::boot
return this;
}
+ array_type_expression& array_constructor_expression::array_type() const
+ {
+ return *this->m_element_type;
+ }
+
array_constructor_expression::~array_constructor_expression()
{
- delete m_element_type;
+ delete this->m_element_type;
for (const expression *element : elements)
{
delete element;
diff --git a/boot/dependency.cc b/boot/dependency.cc
index a44b1f5..9ab5141 100644
--- a/boot/dependency.cc
+++ b/boot/dependency.cc
@@ -56,7 +56,7 @@ namespace elna::boot
error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag& bag,
const target_info& target)
{
- name_analysis_visitor name_analyser(bag);
+ name_analysis_visitor name_analyser(bag, target);
tree->accept(&name_analyser);
if (name_analyser.has_errors())
diff --git a/boot/evaluator.cc b/boot/evaluator.cc
index 8f414fa..407370a 100644
--- a/boot/evaluator.cc
+++ b/boot/evaluator.cc
@@ -19,13 +19,56 @@ along with GCC; see the file COPYING3. If not see
#include "elna/boot/ast.h"
-#include <algorithm>
#include <cstddef>
#include <limits>
#include <ranges>
namespace elna::boot
{
+ non_constant_expression_error::non_constant_expression_error(const source_position position, payload_type payload)
+ : error(position), payload(std::move(payload))
+ {
+ }
+
+ std::string non_constant_expression_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, initializer>)
+ {
+ return "Variable initializers must be constant expressions";
+ }
+ else if constexpr (std::is_same_v<T, case_label>)
+ {
+ return "Case label must be a constant expression";
+ }
+ else if constexpr (std::is_same_v<T, array_dimensions>)
+ {
+ return "Array dimensions for type '" + payload.array_type.to_string()
+ + "' should be constant";
+ }
+ }, this->payload);
+ }
+
+ std::optional<std::pair<std::string, source_position>> non_constant_expression_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, initializer>)
+ {
+ auto position_span = source_position(payload.identifiers.front().position().start(),
+ payload.identifiers.back().position().end());
+ return std::make_pair(join(payload.identifiers), position_span);
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ }, this->payload);
+ }
+
std::optional<type_properties> get_type_properties(const type& subject, const target_info& target)
{
auto resolved = resolve_underlying_type(subject);
@@ -209,6 +252,8 @@ namespace elna::boot
std::optional<constant_value> evaluator::evaluate_literal(literal_expression& subject)
{
+ // The type decoration here is trustable since the name analysis derives
+ // the decoration from literal's own value.
type const decoration = subject.type_decoration;
if (is_primitive_type(decoration, "Int") || is_primitive_type(decoration, "Word"))
@@ -283,54 +328,49 @@ namespace elna::boot
auto resolved_base = resolve_underlying_type(type_to_check);
if (auto enumeration = resolved_base.get<enumeration_type>())
{
- auto enumeration_position = std::distance(enumeration->members.begin(),
+ auto enumeration_distance = std::distance(enumeration->members.begin(),
std::ranges::find(enumeration->members, subject.field().name()));
+ const std::size_t enumeration_position = static_cast<std::size_t>(enumeration_distance);
- return constant_value{
- integer_literal::from(static_cast<std::size_t>(enumeration_position + 1))
- };
+ if (enumeration_position >= enumeration->members.size())
+ {
+ return std::nullopt;
+ }
+ return constant_value{ integer_literal::from(enumeration_position + 1U) };
}
else if (auto base = evaluate(subject.base()))
{
if (auto *record = std::get_if<constant_aggregate<ordered_map>>(&base.value()))
{
- return (**record)[subject.field().name()];
+ auto field_iterator = (*record)->find(subject.field().name());
+
+ return field_iterator != std::cend(**record)
+ ? std::make_optional(field_iterator->second)
+ : std::nullopt;
+ }
+ else if (auto *vector = std::get_if<constant_aggregate<std::vector>>(&base.value());
+ subject.field() == "length")
+ {
+ return constant_value{ integer_literal::from((*vector)->size()) };
}
else if (auto *string_value = std::get_if<std::string>(&base.value()); subject.field() == "length")
{
return constant_value{ integer_literal::from(string_value->size()) };
}
}
- else if (auto array = resolved_base.get<array_type>(); subject.field() == "length")
- {
- return constant_value{ integer_literal::from(array->size) };
- }
return std::nullopt;
}
std::optional<std::size_t> evaluator::evaluate_index(expression& subject)
{
auto evaluated_index = evaluate(subject);
- if (!evaluated_index.has_value())
+ if (!evaluated_index.has_value() || !std::holds_alternative<integer_literal>(evaluated_index.value()))
{
return std::nullopt;
}
- auto index_literal = std::get<integer_literal>(evaluated_index.value());
-
- if (index_literal.is_signed())
- {
- auto signed_index = index_literal.to_signed();
+ auto& index_literal = std::get<integer_literal>(evaluated_index.value());
- if (!signed_index.has_value() || signed_index.value() <= 0)
- {
- return std::nullopt;
- }
- return static_cast<std::size_t>(signed_index.value());
- }
- else
- {
- return index_literal.to_unsigned();
- }
+ return index_literal.is_negative() ? std::nullopt : index_literal.to_unsigned();
}
std::optional<constant_value> evaluator::evaluate_slicing(slicing_expression& subject)
@@ -656,14 +696,13 @@ namespace elna::boot
std::optional<constant_value> evaluator::evaluate_cast(cast_expression& subject)
{
- if (auto value = evaluate(subject.value()))
- {
- return constant_value{ value.value() };
- }
- else
+ auto value = evaluate(subject.value());
+
+ if (!value.has_value() || subject.type_decoration.empty())
{
return std::nullopt;
}
+ return constant_value{ value.value() };
}
std::optional<std::size_t> evaluator::evaluate_traits_size(const type& subject)
diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc
index 3667dff..56ea399 100644
--- a/boot/name_analysis.cc
+++ b/boot/name_analysis.cc
@@ -126,7 +126,8 @@ namespace elna::boot
case field_on_type:
return "Cannot access field '" + this->name + "' on type '"
+ this->composite.to_string() + "'";
- break;
+ default:
+ __builtin_unreachable();
}
}
else if constexpr (std::is_same_v<T, duplicate>)
@@ -163,6 +164,23 @@ namespace elna::boot
return std::nullopt;
}
+ not_initialized_error::not_initialized_error(const source_position position, std::vector<identifier> identifiers)
+ : error(position), identifiers(std::move(identifiers))
+ {
+ }
+
+ std::string not_initialized_error::what() const
+ {
+ return "All constants should be initialized";
+ }
+
+ std::optional<std::pair<std::string, source_position>> not_initialized_error::note() const
+ {
+ auto position_span = source_position(this->identifiers.front().position().start(),
+ this->identifiers.back().position().end());
+ return std::make_pair(join(this->identifiers), position_span);
+ }
+
// Members of a constant aggregate are constant themselves.
static type qualify_member_type(const type& element, const type& aggregate)
{
@@ -177,8 +195,8 @@ namespace elna::boot
}
}
- name_analysis_visitor::name_analysis_visitor(symbol_bag bag)
- : bag(std::move(bag))
+ name_analysis_visitor::name_analysis_visitor(symbol_bag bag, const target_info& target)
+ : bag(std::move(bag)), constant_evaluator(this->bag, target)
{
}
@@ -294,14 +312,23 @@ namespace elna::boot
void name_analysis_visitor::visit(array_type_expression *expression)
{
- walking_visitor::visit(expression);
+ expression->base().accept(this);
+ auto array_base = this->current_type;
- if (this->current_type.get<constant_type>() != nullptr)
+ if (array_base.get<constant_type>() != nullptr)
{
add_error<const_qualifier_error>(expression->position(),
const_qualifier_error::kind::array_position);
}
- this->current_type = type(std::make_shared<array_type>(this->current_type, expression->size));
+ expression->dimensions().accept(this);
+ const auto size_constant = this->constant_evaluator.evaluate_index(expression->dimensions());
+
+ if (!size_constant.has_value())
+ {
+ add_error<non_constant_expression_error>(expression->position(),
+ non_constant_expression_error::array_dimensions{ array_base });
+ }
+ this->current_type = type(std::make_shared<array_type>(array_base, size_constant.value()));
}
void name_analysis_visitor::visit(slice_type_expression *expression)
@@ -437,13 +464,12 @@ namespace elna::boot
void name_analysis_visitor::visit(array_constructor_expression *expression)
{
- expression->m_element_type->accept(this);
- auto element_type = this->current_type;
+ expression->array_type().accept(this);
+ expression->type_decoration = this->current_type;
for (auto *element : expression->elements)
{
element->accept(this);
}
- expression->type_decoration = type(std::make_shared<array_type>(element_type, expression->size));
this->current_type = type();
}
@@ -523,20 +549,36 @@ namespace elna::boot
{
declaration->variable_type().accept(this);
auto variable_type = this->current_type;
+ std::optional<constant_value> computed;
+
if (declaration->initializer != nullptr)
{
declaration->initializer->accept(this);
this->current_type = variable_type;
+ computed = this->constant_evaluator.evaluate(*declaration->initializer);
+
+ if (this->bag.is_global() && !computed.has_value())
+ {
+ add_error<non_constant_expression_error>(declaration->initializer->position(),
+ non_constant_expression_error::initializer{ extract_identifiers(declaration->identifiers) });
+ }
+ }
+ else if (resolve_aliases(variable_type).get<constant_type>() != nullptr)
+ {
+ auto position_span = source_position(declaration->identifiers.front().id().position().start(),
+ declaration->identifiers.back().id().position().end());
+ add_error<not_initialized_error>(position_span,
+ extract_identifiers(declaration->identifiers));
}
for (const identifier_definition& variable_identifier : declaration->identifiers)
{
auto variable_symbol = register_variable(variable_identifier.name(), declaration->is_extern,
declaration->position());
variable_symbol->exported = variable_identifier.exported();
+ variable_symbol->value = computed;
}
}
-
void name_analysis_visitor::visit(procedure_declaration *declaration)
{
std::shared_ptr<procedure_info> info;
diff --git a/boot/parser.yy b/boot/parser.yy
index bc0cb53..a28b6cb 100644
--- a/boot/parser.yy
+++ b/boot/parser.yy
@@ -135,11 +135,11 @@ along with GCC; see the file COPYING3. If not see
%type <elna::boot::binary_expression *> binary_expression;
%type <std::vector<elna::boot::expression *>> expressions actual_parameter_list;
%type <elna::boot::designator_expression *> designator_expression;
-%type <elna::boot::procedure_call*> call_expression;
+%type <std::unique_ptr<elna::boot::procedure_call>> call_expression;
%type <elna::boot::statement *> statement;
%type <std::vector<elna::boot::statement *>> statements statement_part;
%type <elna::boot::procedure_declaration *> procedure_declaration;
-%type <elna::boot::procedure_type_expression *> procedure_heading;
+%type <std::unique_ptr<elna::boot::procedure_type_expression>> procedure_heading;
%type <elna::boot::procedure_type_expression::return_t> return_declaration;
%type <std::vector<elna::boot::procedure_declaration *>> procedure_part;
%type <elna::boot::type_declaration *> type_declaration;
@@ -157,6 +157,7 @@ along with GCC; see the file COPYING3. If not see
%type <std::vector<std::string>> import_declaration;
%type <std::vector<elna::boot::identifier>> identifiers;
%type <std::vector<elna::boot::import_declaration *>> import_declarations import_part;
+%type <std::unique_ptr<elna::boot::array_type_expression>> array_type_expression;
%%
program:
import_part type_part variable_part procedure_part statement_part "end" "."
@@ -188,15 +189,16 @@ return_declaration:
| ":" "!" { $$ = boot::procedure_type_expression::return_t(std::monostate{}); }
| ":" type_expression { $$ = boot::procedure_type_expression::return_t($2); }
procedure_heading: "(" optional_fields ")" return_declaration
- { $$ = new boot::procedure_type_expression(boot::make_position(@$), $2, $4); }
+ { $$ = std::make_unique<boot::procedure_type_expression>(boot::make_position(@$), $2, $4); }
procedure_declaration:
"proc" identifier_definition procedure_heading procedure_body
{
- $$ = new boot::procedure_declaration(boot::make_position(@$), std::move(*$2), $3, std::move(*$4));
+ $$ = new boot::procedure_declaration(boot::make_position(@$),
+ std::move(*$2), $3.release(), std::move(*$4));
}
| "proc" identifier_definition procedure_heading "extern"
{
- $$ = new boot::procedure_declaration(boot::make_position(@$), std::move(*$2), $3);
+ $$ = new boot::procedure_declaration(boot::make_position(@$), std::move(*$2), $3.release());
}
procedure_part:
%empty {}
@@ -207,7 +209,7 @@ procedure_part:
}
call_expression: designator_expression actual_parameter_list
{
- $$ = new boot::procedure_call(boot::make_position(@$), $1, $2);
+ $$ = std::make_unique<boot::procedure_call>(boot::make_position(@$), $1, $2);
}
with_counter:
"with" identifier { $$ = $2; }
@@ -254,15 +256,15 @@ simple_expression:
{
$$ = new boot::cast_expression(boot::make_position(@$), $5, $3);
}
- | call_expression { $$ = $1; }
+ | call_expression { $$ = $1.release(); }
| "(" expression ")" { $$ = $2; }
| identifier "{" field_initializers "}"
{
$$ = new boot::record_constructor_expression(boot::make_position(@$), std::move(*$1), $3);
}
- | "[" INTEGER "]" type_expression "{" expressions "}"
+ | array_type_expression "{" expressions "}"
{
- $$ = new boot::array_constructor_expression(boot::make_position(@$), $2, $4, $6);
+ $$ = new boot::array_constructor_expression(boot::make_position(@$), $1.release(), $3);
}
operand:
unary_expression { $$ = $1; }
@@ -399,7 +401,7 @@ statement:
boot::conditional_statements *then = new boot::conditional_statements($2, $4);
$$ = new boot::if_statement(boot::make_position(@$), then, $5, $6);
}
- | call_expression { $$ = $1; }
+ | call_expression { $$ = $1.release(); }
| "defer" statements "end"
{ $$ = new boot::defer_statement(boot::make_position(@$), $2); }
| "case" expression "of" switch_cases else_statements "end"
@@ -449,11 +451,10 @@ field_initializers:
$$.emplace($$.cbegin(), std::move(*$1));
}
| field_initializer { $$.push_back(std::move(*$1)); }
+array_type_expression: "[" expression "]" type_expression
+ { $$ = std::make_unique<boot::array_type_expression>(boot::make_position(@$), $4, $2); }
type_expression:
- "[" INTEGER "]" type_expression
- {
- $$ = new boot::array_type_expression(boot::make_position(@$), $4, $2);
- }
+ array_type_expression { $$ = $1.release(); }
| "[" "]" type_expression
{
$$ = new boot::slice_type_expression(boot::make_position(@$), $3);
@@ -476,7 +477,7 @@ type_expression:
}
| "proc" procedure_heading
{
- $$ = $2;
+ $$ = $2.release();
}
| "(" identifiers ")"
{
diff --git a/boot/result.cc b/boot/result.cc
index c47be95..e9090de 100644
--- a/boot/result.cc
+++ b/boot/result.cc
@@ -17,6 +17,7 @@ along with GCC; see the file COPYING3. If not see
#include "elna/boot/result.h"
+#include <algorithm>
#include <cstring>
#include <numeric>
@@ -87,6 +88,11 @@ namespace elna::boot
return this->m_name;
}
+ std::string identifier::to_string() const
+ {
+ return name();
+ }
+
const source_position& identifier::position() const
{
return this->m_position;
@@ -123,6 +129,17 @@ namespace elna::boot
return this->m_exported;
}
+ std::vector<identifier> extract_identifiers(const std::vector<identifier_definition>& identifiers)
+ {
+ std::vector<identifier> result;
+ result.reserve(identifiers.size());
+
+ std::ranges::transform(identifiers, std::back_inserter(result),
+ [](const auto& identifier) { return identifier.id(); });
+
+ return result;
+ }
+
integer_literal::integer_literal(bool is_signed, std::size_t size)
: m_signed(is_signed), m_size(size)
{
@@ -313,10 +330,6 @@ namespace elna::boot
std::optional<std::ptrdiff_t> integer_literal::to_signed() const
{
- if (!is_signed())
- {
- return std::nullopt;
- }
if (is_negative_minimum(sizeof(std::ptrdiff_t) * CHAR_BIT))
{
return std::numeric_limits<ptrdiff_t>::min();
@@ -327,15 +340,11 @@ namespace elna::boot
{
return std::nullopt;
}
- return mpz_sgn(this->raw) < 0 ? -rop : rop;
+ return is_negative() ? -rop : rop;
}
std::optional<std::size_t> integer_literal::to_unsigned() const
{
- if (is_signed())
- {
- return std::nullopt;
- }
auto [written, rop] = export_to_words<std::size_t>();
return written > 1 ? std::nullopt : std::make_optional(rop);
@@ -384,11 +393,11 @@ namespace elna::boot
{
std::size_t required_bits = mpz_sizeinbase(this->raw, 2);
- if (!is_negative_minimum(bits))
+ if (!is_negative() || !is_negative_minimum(bits))
{
++required_bits; // Add one bit for the sign.
}
- return required_bits <= bits && (mpz_sgn(this->raw) >= 0 || is_signed());
+ return required_bits <= bits && (!is_negative() || is_signed());
}
std::optional<integer_literal> integer_literal::check() &&
@@ -398,12 +407,12 @@ namespace elna::boot
bool integer_literal::is_negative_minimum(const std::size_t bits) const
{
- return mpz_sgn(this->raw) < 0 && mpz_scan1(this->raw, 0) == bits - 1;
+ return mpz_scan1(this->raw, 0) == bits - 1;
}
bool integer_literal::is_negative_minimum() const
{
- return is_negative_minimum(bits());
+ return is_negative() && is_negative_minimum(bits());
}
std::size_t integer_literal::bits() const
@@ -411,6 +420,11 @@ namespace elna::boot
return size() * CHAR_BIT;
}
+ bool integer_literal::is_negative() const
+ {
+ return mpz_sgn(this->raw) < 0;
+ }
+
std::size_t constant_value_hash::operator()(const elna::boot::constant_value& value) const noexcept
{
return std::visit([](auto&& alternative) -> std::size_t {
diff --git a/boot/symbol.cc b/boot/symbol.cc
index 6172621..1cc652c 100644
--- a/boot/symbol.cc
+++ b/boot/symbol.cc
@@ -165,19 +165,16 @@ namespace elna::boot
}
else if constexpr (std::is_same_v<T, std::shared_ptr<procedure_type>>)
{
- std::string result = "proc(";
- for (std::size_t i = 0; i < payload->parameters.size(); ++i)
+ std::string result = "proc(" + join(payload->parameters) + ")";
+
+ if (payload->return_type.no_return)
{
- if (i > 0) { result += ", ";
-}
- result += payload->parameters[i].to_string();
- }
- result += ")";
- if (payload->return_type.no_return) {
result += ": !";
- } else if (!payload->return_type.proper_type.empty()) {
+ }
+ else if (!payload->return_type.proper_type.empty())
+ {
result += ": " + payload->return_type.proper_type.to_string();
-}
+ }
return result;
}
else if constexpr (std::is_same_v<T, std::shared_ptr<enumeration_type>>)
@@ -368,6 +365,12 @@ namespace elna::boot
this->imports.push_front(bag.exported_symbols());
}
+ bool symbol_bag::is_global() const
+ {
+ return this->symbols->scope() != nullptr
+ && this->symbols->scope()->scope() == nullptr;
+ }
+
std::shared_ptr<symbol_table> symbol_bag::exported_symbols() const
{
if (!m_exported)
diff --git a/boot/validation.cc b/boot/validation.cc
index dc4b5b0..b29381a 100644
--- a/boot/validation.cc
+++ b/boot/validation.cc
@@ -17,14 +17,12 @@ along with GCC; see the file COPYING3. If not see
#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))
+ : error(position), payload(payload)
{
}
@@ -33,18 +31,10 @@ namespace elna::boot
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>)
+ 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);
}
@@ -53,64 +43,27 @@ namespace elna::boot
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>)
+ 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)
+ : bag(bag), constant_evaluator(this->bag, target)
{
}
- void validation_visitor::visit(variable_declaration* declaration)
+ void validation_visitor::visit(unit *unit)
{
- if (declaration->initializer == nullptr || has_errors())
- {
- return;
- }
- auto computed = this->constant_evaluator.evaluate(*declaration->initializer);
- if (!computed)
+ for (procedure_declaration *procedure : unit->procedures)
{
- 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;
+ procedure->accept(this);
}
- for (const auto& identifier : declaration->identifiers)
+ for (auto *entry_statement : unit->entry_point)
{
- auto variable_symbol = this->bag.lookup(identifier.name())->is_variable();
- variable_symbol->value = computed;
+ entry_statement->accept(this);
}
}
@@ -139,8 +92,8 @@ namespace elna::boot
auto value = this->constant_evaluator.evaluate(*label);
if (!value.has_value())
{
- add_error<validation_error>(label->position(),
- validation_error::non_constant_case_label{});
+ add_error<non_constant_expression_error>(label->position(),
+ non_constant_expression_error::case_label{});
continue;
}
auto [case_position, inserted] = seen.try_emplace(value.value(), label->position());
diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h
index 996c58a..1ea17fb 100644
--- a/include/elna/boot/ast.h
+++ b/include/elna/boot/ast.h
@@ -17,7 +17,6 @@ along with GCC; see the file COPYING3. If not see
#pragma once
-#include <cstdint>
#include <memory>
#include <string>
#include <vector>
@@ -316,18 +315,19 @@ namespace elna::boot
class array_type_expression : public type_expression
{
type_expression *m_base;
+ expression *m_dimensions;
public:
- const std::uint32_t size;
array_type_expression(const source_position position,
- type_expression *base, const std::uint32_t size);
+ type_expression *base, expression *dimensions);
~array_type_expression() override;
void accept(parser_visitor *visitor) override;
array_type_expression *is_array() override;
- type_expression& base();
+ type_expression& base() const;
+ expression& dimensions() const;
};
class slice_type_expression : public type_expression
@@ -415,7 +415,7 @@ namespace elna::boot
{
public:
const identifier type_name;
- std::vector<field_initializer> field_initializers;
+ const std::vector<field_initializer> field_initializers;
record_constructor_expression(const source_position position,
identifier&& type_name,
@@ -426,17 +426,18 @@ namespace elna::boot
class array_constructor_expression : public expression
{
+ array_type_expression *m_element_type;
+
public:
- const std::uint32_t size;
- type_expression *const m_element_type;
- std::vector<expression *> elements;
+ const std::vector<expression *> elements;
array_constructor_expression(const source_position position,
- std::uint32_t size, type_expression *element_type,
- std::vector<expression *>&& elements);
+ array_type_expression *element_type, std::vector<expression *>&& elements);
void accept(parser_visitor *visitor) override;
array_constructor_expression *is_array_constructor() override;
+ array_type_expression& array_type() const;
+
~array_constructor_expression() override;
};
diff --git a/include/elna/boot/driver.h b/include/elna/boot/driver.h
index 34a8c99..60d40fb 100644
--- a/include/elna/boot/driver.h
+++ b/include/elna/boot/driver.h
@@ -35,7 +35,7 @@ namespace elna::boot
std::string what() const override;
};
- class driver : public error_container
+ class driver final : public error_container
{
public:
std::unique_ptr<unit> tree;
diff --git a/include/elna/boot/evaluator.h b/include/elna/boot/evaluator.h
index 84bd5ab..0d1a7c8 100644
--- a/include/elna/boot/evaluator.h
+++ b/include/elna/boot/evaluator.h
@@ -22,9 +22,35 @@ along with GCC; see the file COPYING3. If not see
#include <memory>
#include <optional>
+#include <variant>
namespace elna::boot
{
+ class non_constant_expression_error final : public error
+ {
+ public:
+ struct initializer
+ {
+ std::vector<identifier> identifiers;
+ };
+ struct case_label
+ {
+ };
+ struct array_dimensions
+ {
+ type array_type;
+ };
+ using payload_type = std::variant<initializer, case_label, array_dimensions>;
+
+ non_constant_expression_error(const source_position position, payload_type payload);
+
+ std::string what() const override;
+ std::optional<std::pair<std::string, source_position>> note() const override;
+
+ private:
+ payload_type payload;
+ };
+
std::optional<type_properties> get_type_properties(const type& subject, const target_info& target);
/**
@@ -53,7 +79,6 @@ namespace elna::boot
std::optional<constant_value> evaluate_named(named_expression& subject);
std::optional<constant_value> evaluate_array_access(array_access_expression& subject);
std::optional<constant_value> evaluate_field_access(field_access_expression& subject);
- std::optional<std::size_t> evaluate_index(expression& subject);
std::optional<constant_value> evaluate_slicing(slicing_expression& subject);
std::optional<constant_value> evaluate_unary(unary_expression& subject);
std::optional<constant_value> evaluate_binary(binary_expression& subject);
@@ -62,6 +87,7 @@ namespace elna::boot
std::optional<std::size_t> evaluate_traits_alignment(const type& subject);
public:
+ std::optional<std::size_t> evaluate_index(expression& subject);
std::optional<constant_value> evaluate_traits(traits_expression& subject);
explicit evaluator(symbol_bag& bag, const target_info& target);
diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h
index 996c357..f6663ea 100644
--- a/include/elna/boot/name_analysis.h
+++ b/include/elna/boot/name_analysis.h
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3. If not see
#include "elna/boot/ast.h"
#include "elna/boot/result.h"
#include "elna/boot/symbol.h"
+#include "elna/boot/evaluator.h"
#include <string>
#include <memory>
@@ -32,7 +33,7 @@ namespace elna::boot
* Error declaring or using a symbol (undeclared, redefinition,
* local export).
*/
- class declaration_error : public error
+ class declaration_error final : public error
{
public:
enum class kind { undeclared_type, undeclared_trait, undeclared_symbol, local_export };
@@ -56,7 +57,7 @@ namespace elna::boot
* \c const qualifier used incorrectly — wrong position or
* duplicate.
*/
- class const_qualifier_error : public error
+ class const_qualifier_error final : public error
{
public:
enum class kind { array_position, duplicate };
@@ -72,7 +73,7 @@ namespace elna::boot
/**
* Error accessing or defining a member of a record or enumeration.
*/
- class member_error : public error
+ class member_error final : public error
{
public:
enum class kind { not_found, field_on_type };
@@ -94,6 +95,17 @@ namespace elna::boot
payload_type payload;
};
+ class not_initialized_error final : public error
+ {
+ std::vector<identifier> identifiers;
+
+ public:
+ not_initialized_error(const source_position position, std::vector<identifier> identifiers);
+
+ std::string what() const override;
+ std::optional<std::pair<std::string, source_position>> note() const override;
+ };
+
/**
* Origin of a field in a composite type.
*/
@@ -109,8 +121,8 @@ namespace elna::boot
class name_analysis_visitor final : public walking_visitor, public error_container
{
type current_type;
-
symbol_bag bag;
+ evaluator constant_evaluator;
std::pair<procedure_type, std::vector<std::string>> build_procedure(
procedure_type_expression& expression);
@@ -125,7 +137,7 @@ namespace elna::boot
const type& element_type);
public:
- name_analysis_visitor(symbol_bag bag);
+ name_analysis_visitor(symbol_bag bag, const target_info& target);
void visit(array_type_expression *expression) override;
void visit(slice_type_expression *expression) override;
diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h
index 36be8ee..3d01eb2 100644
--- a/include/elna/boot/result.h
+++ b/include/elna/boot/result.h
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3. If not see
#include <string>
#include <deque>
#include <memory>
+#include <numeric>
#include <optional>
#include <utility>
#include <variant>
@@ -158,6 +159,8 @@ namespace elna::boot
const std::string& name() const;
const source_position& position() const;
+ std::string to_string() const;
+
bool operator==(const identifier& that) const;
bool operator==(std::string_view that) const;
@@ -181,6 +184,44 @@ namespace elna::boot
};
/**
+ * Checks whether the givn object can be converted to a string using
+ * the .to_string() method.
+ */
+ template<typename T>
+ concept has_to_string = requires(const T& stringable) {
+ { stringable.to_string() } -> std::convertible_to<std::string>;
+ };
+
+ /**
+ * Extracts identifiers (name and position) from identifier definitions and
+ * returns them in an allocated vector.
+ *
+ * \param identifiers Identifier definitions.
+ * \return Extracted identifiers.
+ */
+ std::vector<identifier> extract_identifiers(const std::vector<identifier_definition>& identifiers);
+
+ /**
+ * Joins an array of string-convertable objects (with a .t_string() method)
+ * into a delimiter separated list.
+ *
+ * \tparam T Array element type.
+ * \param identifiers Identifier array.
+ * \param delimiter List delimiter.
+ * \return Comma separted list.
+ */
+ template<has_to_string T>
+ std::string join(const std::vector<T>& identifiers, std::string_view delimiter = ", ")
+ {
+ return std::accumulate(std::next(identifiers.begin()), identifiers.end(),
+ identifiers.front().to_string(),
+ [delimiter](const std::string& accumulator, const T& next) -> std::string {
+ return accumulator + std::string(delimiter) + next.to_string();
+ }
+ );
+ }
+
+ /**
* Checks whether \p T is a signed, std::int*_t type.
*
* \tparam T The examined type.
@@ -271,6 +312,11 @@ namespace elna::boot
std::string to_string(const std::uint8_t base = 10U) const;
/**
+ * \return Whether the stored value is a negative integer.
+ */
+ bool is_negative() const;
+
+ /**
* Exports the stored value as a host \c std::ptrdiff_t.
*
* \return The converted value, or \c std::nullopt if out of range.
diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h
index 5000ab8..bd07a7e 100644
--- a/include/elna/boot/symbol.h
+++ b/include/elna/boot/symbol.h
@@ -494,11 +494,17 @@ namespace elna::boot
*/
void add_import(const symbol_bag& bag);
- private:
/**
- * Returns a reduced symbol table with exported symbols, computed lazily
- * and cached.
+ * Tells whether the current scope is the module global scope.
+ *
+ * The module scope is not the top-level scope, it's parent is the
+ * scope containing builtins.
+ *
+ * \return Whether the current scope is the global scope.
*/
+ bool is_global() const;
+
+ private:
std::shared_ptr<symbol_table> exported_symbols() const;
mutable std::shared_ptr<symbol_table> m_exported;
diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h
index 82f0092..93e891a 100644
--- a/include/elna/boot/type_check.h
+++ b/include/elna/boot/type_check.h
@@ -30,7 +30,7 @@ namespace elna::boot
/**
* Expected type does not match the actual type of an expression.
*/
- class type_mismatch_error : public error
+ class type_mismatch_error final : public error
{
public:
struct expected_type
@@ -90,7 +90,7 @@ namespace elna::boot
/**
* Cyclic type declaration.
*/
- class cyclic_declaration_error : public error
+ class cyclic_declaration_error final : public error
{
std::vector<std::string> cycle;
@@ -104,7 +104,7 @@ namespace elna::boot
* Argument count in a procedure call or array constructor doesn't match
* the expected number of parameters or elements.
*/
- class argument_count_error : public error
+ class argument_count_error final : public error
{
std::size_t expected;
std::size_t actual;
@@ -119,7 +119,7 @@ namespace elna::boot
/**
* A trait invocation is invalid.
*/
- class trait_error : public error
+ class trait_error final : public error
{
public:
struct argument_count
diff --git a/include/elna/boot/validation.h b/include/elna/boot/validation.h
index c11a652..4a256fa 100644
--- a/include/elna/boot/validation.h
+++ b/include/elna/boot/validation.h
@@ -27,30 +27,20 @@ namespace elna::boot
/**
* Validation error.
*/
- class validation_error : public error
+ class validation_error final : public error
{
public:
- struct non_constant_initializer
- {
- std::vector<identifier> identifiers;
- };
struct duplicate_case
{
source_position first;
};
- struct non_constant_case_label
- {
- };
- using payload_type = std::variant<non_constant_initializer, duplicate_case, non_constant_case_label>;
+ using payload_type = std::variant<duplicate_case>;
validation_error(const source_position position, payload_type payload);
std::string what() const override;
std::optional<std::pair<std::string, source_position>> note() const override;
- static validation_error non_constant_initializer_error(const source_position position,
- const std::vector<identifier_definition>& identifiers);
-
private:
payload_type payload;
};
@@ -58,19 +48,17 @@ namespace elna::boot
/**
* Validates:
* - case label uniqueness
- * - Initializer constness.
*/
class validation_visitor final : public walking_visitor, public error_container
{
symbol_bag& bag;
- const target_info& target;
evaluator constant_evaluator;
public:
validation_visitor(symbol_bag& bag, const target_info& target);
- void visit(variable_declaration* declaration) override;
- void visit(procedure_declaration* declaration) override;
+ void visit(unit *unit) override;
+ void visit(procedure_declaration *declaration) override;
void visit(case_statement *statement) override;
};
}
diff --git a/testsuite/compilable/compile_time_array_length.elna b/testsuite/compilable/compile_time_array_length.elna
index 1ce8956..12e706f 100644
--- a/testsuite/compilable/compile_time_array_length.elna
+++ b/testsuite/compilable/compile_time_array_length.elna
@@ -1,5 +1,5 @@
var
- source: [3]Int := [3]Int{ 1, 2, 3}
+ source: const [3]Int := [3]Int{ 1, 2, 3 }
target: Word := source.length
end.
diff --git a/testsuite/compilable/const_alias.elna b/testsuite/compilable/const_alias.elna
index 9cb9ffb..e17c55f 100644
--- a/testsuite/compilable/const_alias.elna
+++ b/testsuite/compilable/const_alias.elna
@@ -3,8 +3,8 @@ type
CCI = const CI
var
- x: CCI
- y: const CI
+ x: CCI := 8
+ y: const CI := 9
begin
end.
diff --git a/testsuite/compilable/take_const_address.elna b/testsuite/compilable/take_const_address.elna
index 54a2992..f8c9e5c 100644
--- a/testsuite/compilable/take_const_address.elna
+++ b/testsuite/compilable/take_const_address.elna
@@ -1,6 +1,6 @@
var
x: ^const Int
- y: const Int
+ y: const Int := 1
begin
x := @y
diff --git a/testsuite/fail_compilation/assign_const_primitive.elna b/testsuite/fail_compilation/assign_const_primitive.elna
index 34b095b..22fef25 100644
--- a/testsuite/fail_compilation/assign_const_primitive.elna
+++ b/testsuite/fail_compilation/assign_const_primitive.elna
@@ -1,6 +1,6 @@
proc f()
var
- x: const Int
+ x: const Int := 4
begin
x := 5 (* @Error Cannot assign to a value of type 'const Int', because it is constant or contains constant members *)
return
diff --git a/testsuite/fail_compilation/assign_const_to_pointer.elna b/testsuite/fail_compilation/assign_const_to_pointer.elna
index c3d9230..87047e5 100644
--- a/testsuite/fail_compilation/assign_const_to_pointer.elna
+++ b/testsuite/fail_compilation/assign_const_to_pointer.elna
@@ -1,5 +1,5 @@
var
- c: const Int
+ c: const Int := 2
p: Pointer
begin
diff --git a/testsuite/fail_compilation/assign_element_of_const.elna b/testsuite/fail_compilation/assign_element_of_const.elna
index 9afd520..a5b5cda 100644
--- a/testsuite/fail_compilation/assign_element_of_const.elna
+++ b/testsuite/fail_compilation/assign_element_of_const.elna
@@ -1,6 +1,6 @@
proc f()
var
- a: const [2]Int
+ a: const [2]Int := [2]Int{ 1, 3 }
begin
a[1] := 6 (* @Error Cannot assign to a value of type 'const Int', because it is constant or contains constant members *)
return
diff --git a/testsuite/fail_compilation/assign_from_const_pointer.elna b/testsuite/fail_compilation/assign_from_const_pointer.elna
index 164bb42..c39d5cd 100644
--- a/testsuite/fail_compilation/assign_from_const_pointer.elna
+++ b/testsuite/fail_compilation/assign_from_const_pointer.elna
@@ -1,5 +1,5 @@
var
- cv: const Pointer
+ cv: const Pointer := nil
p: Pointer
begin
diff --git a/testsuite/fail_compilation/const_uninitialized.elna b/testsuite/fail_compilation/const_uninitialized.elna
new file mode 100644
index 0000000..06db3d7
--- /dev/null
+++ b/testsuite/fail_compilation/const_uninitialized.elna
@@ -0,0 +1,4 @@
+var
+ x: const Int (* @Error All constants should be initialized *)
+
+end.