diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-07-26 19:47:59 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-07-26 19:47:59 +0200 |
| commit | 5cdaceb77af6a1d98145f8afb34ce6884e21a3d6 (patch) | |
| tree | 1f75fa1aeee67a72017dbaa2fb006e93c943700d /include | |
| parent | dea1c177cd3592cc24fd15ad446a676da2e4da28 (diff) | |
| download | elna-5cdaceb77af6a1d98145f8afb34ce6884e21a3d6.tar.gz | |
Type check traits properly
Diffstat (limited to 'include')
| -rw-r--r-- | include/elna/boot/ast.h | 21 | ||||
| -rw-r--r-- | include/elna/boot/dependency.h | 2 | ||||
| -rw-r--r-- | include/elna/boot/evaluator.h | 137 | ||||
| -rw-r--r-- | include/elna/boot/result.h | 103 | ||||
| -rw-r--r-- | include/elna/boot/symbol.h | 8 | ||||
| -rw-r--r-- | include/elna/boot/type_check.h | 31 | ||||
| -rw-r--r-- | include/elna/gcc/elna-generic.h | 5 |
7 files changed, 192 insertions, 115 deletions
diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index d6804df..8ee0e8b 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -407,6 +407,7 @@ namespace elna::boot const std::string& name() const; const identifier& id() const; expression& value() const; + void value(expression& value); private: identifier m_name; @@ -417,7 +418,7 @@ namespace elna::boot { public: const identifier type_name; - const std::vector<field_initializer> field_initializers; + std::vector<field_initializer> field_initializers; record_constructor_expression(const source_position position, identifier&& type_name, @@ -431,7 +432,7 @@ namespace elna::boot public: const std::uint32_t size; type_expression *const m_element_type; - const std::vector<expression *> elements; + std::vector<expression *> elements; array_constructor_expression(const source_position position, std::uint32_t size, type_expression *element_type, @@ -478,7 +479,7 @@ namespace elna::boot const std::vector<identifier_definition> identifiers; type_expression& variable_type(); - expression *const initializer{ nullptr }; + expression *initializer{ nullptr }; const bool is_extern{ false }; }; @@ -579,7 +580,8 @@ namespace elna::boot cast_expression *is_cast() override; type_expression& target(); - expression& value(); + expression& value() const; + void value(expression& value); ~cast_expression() override; }; @@ -744,7 +746,7 @@ namespace elna::boot designator_expression *m_callable; public: - const std::vector<expression *> arguments; + std::vector<expression *> arguments; procedure_call(const source_position position, designator_expression *callable, std::vector<expression *>&& arguments); @@ -941,8 +943,10 @@ namespace elna::boot void accept(parser_visitor *visitor) override; binary_expression *is_binary() override; - expression& lhs(); - expression& rhs(); + expression& lhs() const; + void lhs(expression& lhs); + expression& rhs() const; + void rhs(expression& rhs); binary_operator operation() const; void operation(binary_operator operation); @@ -961,7 +965,8 @@ namespace elna::boot void accept(parser_visitor *visitor) override; unary_expression *is_unary() override; - expression& operand(); + expression& operand() const; + void operand(expression& operand); unary_operator operation() const; void operation(unary_operator operation); diff --git a/include/elna/boot/dependency.h b/include/elna/boot/dependency.h index e6cfb36..6a3a412 100644 --- a/include/elna/boot/dependency.h +++ b/include/elna/boot/dependency.h @@ -39,7 +39,7 @@ namespace elna::boot dependency read_source(std::istream& entry_point); std::filesystem::path build_path(const std::vector<std::string>& segments); - error_list analyze_semantics(std::unique_ptr<unit>& tree, const symbol_bag& bag, + error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag& bag, const target_info& target); template<typename T> diff --git a/include/elna/boot/evaluator.h b/include/elna/boot/evaluator.h index 2d832bf..5812ee8 100644 --- a/include/elna/boot/evaluator.h +++ b/include/elna/boot/evaluator.h @@ -18,7 +18,6 @@ along with GCC; see the file COPYING3. If not see #pragma once #include <cstdint> -#include <map> #include <memory> #include <optional> #include <string> @@ -30,93 +29,14 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { /** - * Size and alignment of a single type on the target machine. + * A module-level variable initializer must be a constant expression. */ - struct type_properties + class non_constant_initializer_error : public error { - std::size_t size{0}; - std::size_t alignment{0}; - }; - - /** - * Size, alignment and offset of each field of a record. - */ - struct record_properties - { - ordered_map<std::size_t> offset_map; - std::size_t size; - std::size_t alignment; - }; - - /** - * Target machine information, populated by the compiler backend glue layer. - */ - struct target_info - { - type_properties int_properties; - type_properties word_properties; - type_properties pointer_properties; - type_properties char_properties; - type_properties float_properties; - type_properties bool_properties; - }; - - template<template<typename, typename> typename C, template<typename> typename Alloc = std::allocator> - class constant_aggregate; - - /** - * Typed constant value produced by the constant expression evaluator. - * - * Record constructors are stored as ordered_map keyed by field name, - * array constructors as a vector of elements. - */ - using constant_value = std::variant< - std::int32_t, - std::uint32_t, - double, - bool, - unsigned char, - std::nullptr_t, - constant_aggregate<ordered_map>, - constant_aggregate<std::vector> - >; - - template<template<typename, typename> typename C, template<typename> typename Alloc> - class constant_aggregate - { - using Container = C<constant_value, Alloc<constant_value>>; - std::shared_ptr<Container> container; - public: - explicit constant_aggregate(const Container& value) - : container(std::make_shared<Container>(value)) - { - } - - explicit constant_aggregate(const Container&& value) - : container(std::make_shared<Container>(std::move(value))) - { - } - - Container& operator*() - { - return *this->container; - } - - const Container& operator*() const - { - return *this->container; - } - - Container *operator->() - { - return this->container.get(); - } - - const Container *operator->() const - { - return this->container.get(); - } + explicit non_constant_initializer_error(const source_position position); + + std::string what() const override; }; std::optional<type_properties> get_type_properties(const type& subject, const target_info& target); @@ -142,20 +62,22 @@ namespace elna::boot { symbol_bag& bag; const target_info& target; - const std::map<std::string, expression*>& evaluated_initializers; static std::optional<constant_value> evaluate_literal(literal_expression& subject); 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<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); std::optional<constant_value> evaluate_cast(cast_expression& subject); std::optional<std::size_t> evaluate_traits_size(const type& subject); std::optional<std::size_t> evaluate_traits_alignment(const type& subject); + void fold_aggregate_field(field_initializer& field_init); public: std::optional<constant_value> evaluate_traits(traits_expression& subject); - explicit evaluator(symbol_bag& bag, const target_info& target, - const std::map<std::string, expression*>& evaluated_initializers); + explicit evaluator(symbol_bag& bag, const target_info& target); /** * Evaluates an expression at compile time. @@ -165,5 +87,44 @@ namespace elna::boot * expression is not constant. */ std::optional<constant_value> evaluate(expression& subject); + + /** + * Folds an expression into literals. + * + * For scalars, the original expression is deleted and a new + * literal node is returned. For constructors, fields or + * elements are folded recursively and the original node is + * returned. + * + * \param original Expression to fold. + * \return The folded expression (either a new literal or the + * original aggregate with folded children). + */ + expression *fold(expression& original); + }; + + /** + * Folding pass that validates constant variable initializers. + * + * Runs after name analysis and type checking. Walks all variable + * declarations, evaluates constant initializers, and records them + * so later declarations can chain through earlier ones. + */ + class constant_folder final : public walking_visitor, public error_container + { + symbol_bag& bag; + const target_info& target; + evaluator constant_evaluator; + + expression& fold_trait(expression& expr); + + public: + constant_folder(symbol_bag& bag, const target_info& target); + + void visit(variable_declaration* declaration) override; + void visit(cast_expression *expr) override; + void visit(binary_expression *expr) override; + void visit(unary_expression *expr) override; + void visit(procedure_call *call) override; }; } diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h index 0719151..89753c1 100644 --- a/include/elna/boot/result.h +++ b/include/elna/boot/result.h @@ -18,6 +18,7 @@ along with GCC; see the file COPYING3. If not see #pragma once #include <cstddef> +#include <cstdint> #include <string> #include <deque> #include <memory> @@ -304,6 +305,108 @@ namespace elna::boot return this->payload.cend(); } }; + + /** + * Size and alignment of a single type on the target machine. + */ + struct type_properties + { + std::size_t size{0}; + std::size_t alignment{0}; + }; + + /** + * Size, alignment and offset of each field of a record. + */ + struct record_properties + { + ordered_map<std::size_t> offset_map; + std::size_t size; + std::size_t alignment; + }; + + /** + * Target machine information, populated by the compiler backend glue layer. + */ + struct target_info + { + type_properties int_properties; + type_properties word_properties; + type_properties pointer_properties; + type_properties char_properties; + type_properties float_properties; + type_properties bool_properties; + }; + + /** + * Address of a module-level variable, tracked by name so that + * different variables have distinct addresses at compile time. + */ + struct global_address + { + std::string name; + + auto operator<=>(const global_address&) const = default; + }; + + template<template<typename, typename> typename C, template<typename> typename Alloc = std::allocator> + class constant_aggregate; + + /** + * Typed constant value produced by the constant expression evaluator. + * + * Record constructors are stored as ordered_map keyed by field name, + * array constructors as a vector of elements. + */ + using constant_value = std::variant< + std::int32_t, + std::uint32_t, + double, + bool, + unsigned char, + std::nullptr_t, + global_address, + constant_aggregate<ordered_map>, + constant_aggregate<std::vector> + >; + + template<template<typename, typename> typename C, template<typename> typename Alloc> + class constant_aggregate + { + using Container = C<constant_value, Alloc<constant_value>>; + std::shared_ptr<Container> container; + + public: + explicit constant_aggregate(const Container& value) + : container(std::make_shared<Container>(value)) + { + } + + explicit constant_aggregate(const Container&& value) + : container(std::make_shared<Container>(std::move(value))) + { + } + + Container& operator*() + { + return *this->container; + } + + const Container& operator*() const + { + return *this->container; + } + + Container *operator->() + { + return this->container.get(); + } + + const Container *operator->() const + { + return this->container.get(); + } + }; } template<> diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 5b56904..46d9c2c 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -20,13 +20,14 @@ along with GCC; see the file COPYING3. If not see #include <cstdint> #include <forward_list> #include <memory> +#include <optional> #include <string> #include <unordered_map> #include <utility> -#include <variant> -#include <vector> #include "elna/boot/result.h" +#include <variant> +#include <vector> namespace elna::boot { @@ -359,6 +360,9 @@ namespace elna::boot /// Whether this is an extern symbol. const bool is_extern; + /// Evaluated constant value, set by the constant folder. + std::optional<constant_value> value; + /** * Constructs a variable symbol information. * diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h index 3a2b61d..bd32c08 100644 --- a/include/elna/boot/type_check.h +++ b/include/elna/boot/type_check.h @@ -22,7 +22,6 @@ along with GCC; see the file COPYING3. If not see #include <vector> #include "elna/boot/ast.h" -#include "elna/boot/evaluator.h" #include "elna/boot/result.h" #include "elna/boot/symbol.h" @@ -125,18 +124,30 @@ namespace elna::boot }; /** - * Type passed to a trait like #min or #max does not support - * the trait. + * A trait invocation has invalid arguments. */ - /** - * A module-level variable initializer must be a constant expression. - */ - class non_constant_initializer_error : public error + class trait_error : public error { public: - explicit non_constant_initializer_error(const source_position position); + /// Wrong number of arguments passed to a trait. + struct argument_count + { + std::size_t expected; + std::size_t actual; + }; + /// \c \#offset second argument is not a field name. + struct offset_not_field_name {}; + + using payload_type = std::variant<argument_count, offset_not_field_name>; + + trait_error(const source_position position, const std::string& trait_name, + payload_type payload); std::string what() const override; + + private: + std::string trait_name; + payload_type m_payload; }; /** @@ -217,9 +228,6 @@ namespace elna::boot std::shared_ptr<procedure_info> current_procedure; const target_info& target; - // Map from const variable name to its initializer, for chaining. - std::map<std::string, expression*> evaluated_initializers; - /* * Whether an expression of type assignment can be assigned to a variable * of type assignee. @@ -248,6 +256,7 @@ namespace elna::boot void visit(if_statement *statement) override; void visit(record_constructor_expression *expression) override; void visit(array_constructor_expression *expression) override; + void visit(traits_expression *trait) override; void visit(slicing_expression *expression) override; void visit(unary_expression *expression) override; void visit(binary_expression *expression) override; diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h index 27fdb43..c2f154f 100644 --- a/include/elna/gcc/elna-generic.h +++ b/include/elna/gcc/elna-generic.h @@ -17,11 +17,9 @@ along with GCC; see the file COPYING3. If not see #pragma once -#include <map> #include <memory> #include <string> #include "elna/boot/ast.h" -#include "elna/boot/evaluator.h" #include "elna/boot/symbol.h" #include "elna/gcc/elna-tree.h" @@ -39,8 +37,6 @@ namespace elna::gcc elna::boot::symbol_bag bag; std::shared_ptr<symbol_table> symbols; const elna::boot::target_info& target; - std::map<std::string, boot::expression *> evaluated_initializers; - std::unique_ptr<boot::evaluator> const_evaluator; static tree build_equality(boot::binary_expression *expression, tree left, tree right); static tree build_equality_comparison(location_t loc, tree left, tree right, @@ -76,7 +72,6 @@ namespace elna::gcc void visit(boot::procedure_declaration *declaration) override; void visit(boot::procedure_call *call) override; void visit(boot::cast_expression *expression) override; - void visit(boot::traits_expression *trait) override; void visit(boot::literal<std::int32_t> *literal) override; void visit(boot::literal<std::uint32_t> *literal) override; void visit(boot::literal<double> *literal) override; |
