aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-09-12 00:18:11 +0200
committerEugen Wissner <belka@caraus.de>2026-09-12 00:18:11 +0200
commiteb1d068beeaaa420b558595751295eec35ad0c3c (patch)
treea10aadd88ba358726573f7324ff6f9c489ebad77 /boot
parentbd2b80d6ac582b9f0bcd96c86d4b79bfdb64eca1 (diff)
downloadelna-eb1d068beeaaa420b558595751295eec35ad0c3c.tar.gz
Support variadic extern procedures
Diffstat (limited to 'boot')
-rw-r--r--boot/lexer.ll3
-rw-r--r--boot/name_analysis.cc112
-rw-r--r--boot/parser.yy48
-rw-r--r--boot/symbol.cc8
-rw-r--r--boot/type_check.cc44
5 files changed, 131 insertions, 84 deletions
diff --git a/boot/lexer.ll b/boot/lexer.ll
index 17fe0ca..e8dde7c 100644
--- a/boot/lexer.ll
+++ b/boot/lexer.ll
@@ -184,6 +184,9 @@ program {
#\[ {
return yy::parser::make_GENERIC_OPEN(this->location);
}
+#\{ {
+ return yy::parser::make_ATTRIBUTE_OPEN(this->location);
+}
(0|{NATURAL})u|{HEXADECIMAL} {
if (auto result = parse_integer<std::uint64_t>(yytext, 0))
{
diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc
index caff18b..4a1a0ca 100644
--- a/boot/name_analysis.cc
+++ b/boot/name_analysis.cc
@@ -69,17 +69,21 @@ namespace elna::boot
{
using enum kind;
case unknown:
- return "Attribute '#" + this->name + "' not declared";
+ return "Attribute '" + this->name + "' not declared";
case duplicate:
- return "Attribute '#" + this->name + "' specified more than once";
+ return "Attribute '" + this->name + "' specified more than once";
case unsupported:
- return "Attribute '#" + this->name + "' is not supported in this position";
+ return "Attribute '" + this->name + "' is not supported in this position";
case argument_count:
- return "Attribute '#" + this->name + "' takes exactly one argument";
+ return "Attribute '" + this->name + "' takes exactly one argument";
case not_constant:
- return "Attribute '#" + this->name + "' requires a constant expression";
+ return "Attribute '" + this->name + "' requires a constant expression";
case invalid_alignment:
return "Alignment must be a power of two";
+ case unexpected_arguments:
+ return "Attribute '" + this->name + "' takes no arguments";
+ case missing_parameter:
+ return "Attribute '" + this->name + "' requires at least one parameter";
default:
__builtin_unreachable();
}
@@ -320,7 +324,7 @@ namespace elna::boot
}
std::pair<procedure_type, std::vector<std::string>> resolving_visitor::build_procedure(
- procedure_type_expression& expression)
+ procedure_type_expression& expression, const bool variadic_supported)
{
const procedure_type::return_t result_return = build_return_type(expression.return_type);
std::pair<procedure_type, std::vector<std::string>> result_type{
@@ -332,65 +336,70 @@ namespace elna::boot
for (const auto& parameter_name : parameter_names)
{
- evaluate_alignment(parameter_name.attributes, false);
+ evaluate_attributes<>(parameter_name.attributes);
result_type.first.parameters.push_back(parameter_type);
result_type.second.push_back(parameter_name.name());
}
}
+ result_type.first.variadic = variadic_supported
+ ? evaluate_attributes<attribute_kind::varargs>(expression.attributes).variadic
+ : evaluate_attributes<>(expression.attributes).variadic;
+
+ if (result_type.first.variadic && result_type.first.parameters.empty())
+ {
+ add_error<attribute_error>(expression.position(), "varargs",
+ attribute_error::kind::missing_parameter);
+ }
return result_type;
}
- std::optional<std::size_t> resolving_visitor::evaluate_alignment(
- const std::vector<attribute>& attributes, const bool supported)
+ std::optional<attribute_kind> resolving_visitor::parse_attribute(const std::string& name)
{
- std::optional<std::size_t> result;
+ if (name == "aligned")
+ {
+ return attribute_kind::aligned;
+ }
+ if (name == "varargs")
+ {
+ return attribute_kind::varargs;
+ }
+ return std::nullopt;
+ }
- for (const attribute& written : attributes)
+ void resolving_visitor::evaluate_alignment(const attribute& written, std::optional<std::size_t>& alignment)
+ {
+ if (written.arguments().size() != 1)
{
- if (written.name().name() != "aligned")
- {
- add_error<attribute_error>(written.name().position(), written.name().name(),
- attribute_error::kind::unknown);
- }
- else if (!supported)
- {
- add_error<attribute_error>(written.name().position(), written.name().name(),
- attribute_error::kind::unsupported);
- }
- else if (written.arguments().size() != 1)
- {
- add_error<attribute_error>(written.name().position(), written.name().name(),
- attribute_error::kind::argument_count);
- }
- else if (result.has_value())
- {
- add_error<attribute_error>(written.name().position(), written.name().name(),
- attribute_error::kind::duplicate);
- }
- else if (auto value = this->constant_evaluator.evaluate_index(*written.arguments().front()))
+ add_error<attribute_error>(written.name().position(), written.name().name(),
+ attribute_error::kind::argument_count);
+ }
+ else if (alignment.has_value())
+ {
+ add_error<attribute_error>(written.name().position(), written.name().name(),
+ attribute_error::kind::duplicate);
+ }
+ else if (auto value = this->constant_evaluator.evaluate_index(*written.arguments().front()))
+ {
+ if (value.value() == 0 || (value.value() & (value.value() - 1)) != 0)
{
- if (value.value() == 0 || (value.value() & (value.value() - 1)) != 0)
- {
- add_error<attribute_error>(written.arguments().front()->position(),
- written.name().name(), attribute_error::kind::invalid_alignment);
- }
- else
- {
- result = value;
- }
+ add_error<attribute_error>(written.arguments().front()->position(),
+ written.name().name(), attribute_error::kind::invalid_alignment);
}
else
{
- add_error<attribute_error>(written.arguments().front()->position(),
- written.name().name(), attribute_error::kind::not_constant);
+ alignment = value;
}
}
- return result;
+ else
+ {
+ add_error<attribute_error>(written.arguments().front()->position(),
+ written.name().name(), attribute_error::kind::not_constant);
+ }
}
ordered_map<field_info> resolving_visitor::build_composite_type(
const std::vector<field_declaration>& fields,
- ordered_map<field_origin>& field_names, const type& aggregate, const bool supported)
+ ordered_map<field_origin>& field_names, const type& aggregate)
{
ordered_map<field_info> result;
@@ -401,7 +410,7 @@ namespace elna::boot
for (const identifier_definition& field_name : field.first)
{
const std::optional<std::size_t> alignment =
- evaluate_alignment(field_name.attributes, supported);
+ evaluate_attributes<attribute_kind::aligned>(field_name.attributes).alignment;
auto [existing, inserted] = field_names.insert(field_name.name(),
field_origin{ .position = field.second->position(), .base_type = type() });
@@ -522,7 +531,8 @@ namespace elna::boot
auto variable_symbol = register_variable(variable_identifier.name(),
variable_type, declaration->position(), declaration->is_extern);
variable_symbol->exported = variable_identifier.exported();
- variable_symbol->alignment = evaluate_alignment(variable_identifier.attributes, true);
+ variable_symbol->alignment =
+ evaluate_attributes<attribute_kind::aligned>(variable_identifier.attributes).alignment;
variable_symbol->value = computed;
}
}
@@ -690,7 +700,8 @@ namespace elna::boot
ordered_map<field_origin> field_names;
collect_field_names(result_type->base, field_names);
result_type->fields = build_composite_type(expression->fields, field_names, type(result_type));
- result_type->alignment = evaluate_alignment(expression->attributes, true);
+ result_type->alignment =
+ evaluate_attributes<attribute_kind::aligned>(expression->attributes).alignment;
this->current_type = type(result_type);
}
@@ -698,7 +709,7 @@ namespace elna::boot
void resolving_visitor::visit(procedure_type_expression *expression)
{
std::shared_ptr<procedure_type> const result_type =
- std::make_shared<procedure_type>(std::move(build_procedure(*expression).first));
+ std::make_shared<procedure_type>(std::move(build_procedure(*expression, true).first));
this->current_type = type(result_type);
}
@@ -1418,7 +1429,8 @@ namespace elna::boot
// is why the body sees them too.
const std::shared_ptr<symbol_table> scope = this->bag.enter();
const std::vector<type> type_parameters = enter_parameters(declaration->parameters);
- auto [heading, parameter_names] = build_procedure(declaration->heading());
+ auto [heading, parameter_names] =
+ build_procedure(declaration->heading(), !declaration->body.has_value());
std::shared_ptr<procedure_info> info;
if (declaration->body.has_value())
diff --git a/boot/parser.yy b/boot/parser.yy
index 21ff827..cc0a46c 100644
--- a/boot/parser.yy
+++ b/boot/parser.yy
@@ -88,7 +88,7 @@ along with GCC; see the file COPYING3. If not see
%token <std::string> STRING
%token <bool> BOOLEAN
%token LEFT_PAREN "(" RIGHT_PAREN ")" LEFT_SQUARE "[" RIGHT_SQUARE "]"
- LEFT_BRACE "{" RIGHT_BRACE "}" GENERIC_OPEN "#["
+ LEFT_BRACE "{" RIGHT_BRACE "}" GENERIC_OPEN "#[" ATTRIBUTE_OPEN "#{"
%token ASSIGNMENT ":="
EXCLAMATION "!" ARROW "->"
AT "@" HAT "^"
@@ -190,9 +190,9 @@ identifier_definition:
IDENTIFIER "*" { $$ = std::make_unique<boot::identifier_definition>($1, boot::make_position(@1), true); }
| IDENTIFIER { $$ = std::make_unique<boot::identifier_definition>($1, boot::make_position(@1), false); }
attribute:
- TRAIT "(" required_expressions ")"
+ "#{" IDENTIFIER optional_expressions "}"
{
- $$ = std::make_unique<boot::attribute>(boot::identifier($1, boot::make_position(@1)), $3);
+ $$ = std::make_unique<boot::attribute>(boot::identifier($2, boot::make_position(@2)), $3);
}
attributes:
%empty {}
@@ -202,17 +202,10 @@ attributes:
$$.emplace($$.cbegin(), std::move(*$1));
}
attributed_identifier:
- attributes identifier_definition
+ identifier_definition attributes
{
- $$ = $2;
- $$->attributes = $1;
-
- // An empty rule is located where the previous token ended, which
- // would push the declaration's position back over it.
- if ($$->attributes.empty())
- {
- @$ = @2;
- }
+ $$ = $1;
+ $$->attributes = $2;
}
identifier_definitions:
attributed_identifier "," identifier_definitions
@@ -225,8 +218,11 @@ return_declaration:
%empty {}
| "->" "!" { $$ = boot::procedure_type_expression::return_t(std::monostate{}); }
| "->" type_expression { $$ = boot::procedure_type_expression::return_t($2); }
-procedure_heading: "(" field_declarations ")" return_declaration
- { $$ = std::make_unique<boot::procedure_type_expression>(boot::make_position(@$), $2, $4); }
+procedure_heading: "(" field_declarations ")" attributes return_declaration
+ {
+ $$ = std::make_unique<boot::procedure_type_expression>(boot::make_position(@$), $2, $5);
+ $$->attributes = $4;
+ }
procedure_declaration:
"proc" identifier_definition generic_parameters procedure_heading procedure_body
{
@@ -616,33 +612,21 @@ type_expression:
{
$$ = new boot::pointer_type_expression(boot::make_position(@$), $2);
}
- | attributes "record" field_declarations "end"
+ | "record" attributes field_declarations "end"
{
- std::vector<boot::attribute> written = $1;
-
- if (written.empty())
- {
- @$ = @2 + @4;
- }
auto *record = new boot::record_type_expression(boot::make_position(@$), $3);
- record->attributes = std::move(written);
+ record->attributes = $2;
$$ = record;
}
- | attributes "record" "(" name_reference ")" field_declarations "end"
+ | "record" "(" name_reference ")" attributes field_declarations "end"
{
- std::vector<boot::attribute> written = $1;
-
- if (written.empty())
- {
- @$ = @2 + @7;
- }
- boot::named_expression *reference = $4;
+ boot::named_expression *reference = $3;
auto *record = new boot::record_type_expression(boot::make_position(@$), $6,
boot::identifier(reference->name, reference->position()),
std::move(reference->arguments));
- record->attributes = std::move(written);
+ record->attributes = $5;
delete reference;
$$ = record;
}
diff --git a/boot/symbol.cc b/boot/symbol.cc
index 0814217..466715b 100644
--- a/boot/symbol.cc
+++ b/boot/symbol.cc
@@ -117,7 +117,8 @@ namespace elna::boot
auto right_procedure = resolved_that.get<procedure_type>();
return right_procedure != nullptr && left_procedure->return_type == right_procedure->return_type
- && left_procedure->parameters == right_procedure->parameters;
+ && left_procedure->parameters == right_procedure->parameters
+ && left_procedure->variadic == right_procedure->variadic;
}
if (auto left_parameter = resolved_this.get<parameter_type>())
{
@@ -190,6 +191,10 @@ namespace elna::boot
{
std::string result = "proc(" + join(payload->parameters) + ")";
+ if (payload->variadic)
+ {
+ result += " #{varargs}";
+ }
if (payload->return_type.no_return)
{
result += " -> !";
@@ -578,6 +583,7 @@ namespace elna::boot
}
auto result = std::make_shared<procedure_type>(std::move(result_return));
+ result->variadic = procedure->variadic;
result->parameters.reserve(procedure->parameters.size());
for (const type& parameter_type : procedure->parameters)
{
diff --git a/boot/type_check.cc b/boot/type_check.cc
index 8847693..5cdd2a2 100644
--- a/boot/type_check.cc
+++ b/boot/type_check.cc
@@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see
#include "elna/boot/evaluator.h"
#include "elna/boot/name_analysis.h"
+#include <climits>
#include <ranges>
#include <utility>
@@ -286,6 +287,9 @@ namespace elna::boot
case not_addressable:
return "Expression of type '" + this->actual.to_string()
+ "' is not addressable";
+ case variadic_argument:
+ return "Type '" + this->actual.to_string()
+ + "' cannot be passed as a variadic argument";
default:
__builtin_unreachable();
}
@@ -1337,6 +1341,34 @@ namespace elna::boot
procedure.parameters, bindings);
}
+ type type_analysis_visitor::promote_variadic_argument(const expression& argument)
+ {
+ const type resolved = resolve_underlying_type(argument.type_decoration);
+
+ if (resolved.get<array_type>() != nullptr || resolved.get<slice_type>() != nullptr)
+ {
+ add_error<type_requirement_error>(argument.position(), argument.type_decoration,
+ type_requirement_error::kind::variadic_argument);
+ return type();
+ }
+ if (is_primitive_type(resolved, "Single"))
+ {
+ return this->bag.lookup("Double")->is_type()->symbol;
+ }
+ if (is_discrete_type(resolved))
+ {
+ const std::optional<type_properties> properties = get_type_properties(resolved, this->target);
+
+ if (properties.has_value() && properties->size < this->target.c_int_properties.size)
+ {
+ const std::size_t bit_size = this->target.c_int_properties.size * CHAR_BIT;
+
+ return this->bag.lookup("Int" + std::to_string(bit_size))->is_type()->symbol;
+ }
+ }
+ return argument.type_decoration;
+ }
+
void type_analysis_visitor::visit(procedure_call *call)
{
const named_expression *reference = call->callable().is_named();
@@ -1388,7 +1420,17 @@ namespace elna::boot
++argument_iterator;
++type_iterator;
}
- if (call->arguments.size() != procedure->parameters.size())
+ while (procedure->variadic && argument_iterator != std::cend(call->arguments))
+ {
+ if (inferred == nullptr)
+ {
+ (*argument_iterator)->accept(this);
+ }
+ call->variadic_types.push_back(promote_variadic_argument(**argument_iterator));
+ ++argument_iterator;
+ }
+ if (call->arguments.size() < procedure->parameters.size()
+ || (!procedure->variadic && call->arguments.size() > procedure->parameters.size()))
{
if (auto *procedure_name = call->callable().is_named())
{