diff options
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. |
