aboutsummaryrefslogtreecommitdiff
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
parentbd2b80d6ac582b9f0bcd96c86d4b79bfdb64eca1 (diff)
downloadelna-eb1d068beeaaa420b558595751295eec35ad0c3c.tar.gz
Support variadic extern procedures
-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
-rw-r--r--gcc/gcc/elna-builtins.cc8
-rw-r--r--gcc/gcc/elna-generic.cc25
-rw-r--r--include/elna/boot/ast.h9
-rw-r--r--include/elna/boot/name_analysis.h77
-rw-r--r--include/elna/boot/result.h2
-rw-r--r--include/elna/boot/symbol.h3
-rw-r--r--include/elna/boot/type_check.h8
-rw-r--r--include/elna/gcc/elna-generic.h3
-rw-r--r--testsuite/fail_compilation/aligned_attribute.elna8
-rw-r--r--testsuite/fail_compilation/varargs_argument.elna12
-rw-r--r--testsuite/fail_compilation/varargs_attribute.elna8
-rw-r--r--testsuite/runnable/aligned_attribute.elna8
-rw-r--r--testsuite/runnable/varargs.elna16
18 files changed, 292 insertions, 110 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())
{
diff --git a/gcc/gcc/elna-builtins.cc b/gcc/gcc/elna-builtins.cc
index 2dc1147..a8e54c8 100644
--- a/gcc/gcc/elna-builtins.cc
+++ b/gcc/gcc/elna-builtins.cc
@@ -59,7 +59,8 @@ namespace elna::gcc
.size = static_cast<std::size_t>(
(TYPE_PRECISION(elna_bool_type_node) + BITS_PER_UNIT - 1) / BITS_PER_UNIT),
.alignment = TYPE_ALIGN_UNIT(elna_bool_type_node)
- }
+ },
+ .c_int_properties = get_host_numeric_properties(integer_type_node)
};
return info;
}
@@ -176,6 +177,11 @@ namespace elna::gcc
{
return_type = get_inner_alias(procedure.return_type.proper_type, symbols);
}
+ if (procedure.variadic)
+ {
+ return build_varargs_function_type_array(return_type,
+ static_cast<int>(procedure.parameters.size()), parameter_types.data());
+ }
return build_function_type_array(return_type,
static_cast<int>(procedure.parameters.size()), parameter_types.data());
}
diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc
index 8ab6762..8fd2ec8 100644
--- a/gcc/gcc/elna-generic.cc
+++ b/gcc/gcc/elna-generic.cc
@@ -43,16 +43,29 @@ namespace elna::gcc
}
void generic_visitor::build_procedure_call(location_t call_location,
- tree procedure_address, const std::vector<boot::expression *>& arguments)
+ tree procedure_address, const std::vector<boot::expression *>& arguments,
+ const std::vector<boot::type>& variadic_types)
{
vec<tree, va_gc> *argument_trees = nullptr;
tree symbol_type = TREE_TYPE(TREE_TYPE(procedure_address));
+ const std::size_t fixed_count = arguments.size() - variadic_types.size();
vec_alloc(argument_trees, arguments.size());
- for (boot::expression *const argument : arguments)
+ for (std::size_t i = 0; i < arguments.size(); ++i)
{
- argument->accept(this);
+ arguments.at(i)->accept(this);
this->current_expression = prepare_rvalue(this->current_expression);
+
+ if (i >= fixed_count)
+ {
+ tree passed_type = get_inner_alias(variadic_types.at(i - fixed_count), this->symbols);
+
+ if (passed_type != TREE_TYPE(this->current_expression)
+ && (INTEGRAL_TYPE_P(passed_type) || SCALAR_FLOAT_TYPE_P(passed_type)))
+ {
+ this->current_expression = fold_convert(passed_type, this->current_expression);
+ }
+ }
argument_trees->quick_push(this->current_expression);
}
this->current_expression = fold_build_call_array_loc(call_location, TREE_TYPE(symbol_type),
@@ -114,11 +127,13 @@ namespace elna::gcc
{
this->current_expression = build1(ADDR_EXPR,
build_pointer_type(expression_type), this->current_expression);
- build_procedure_call(call_location, this->current_expression, call->arguments);
+ build_procedure_call(call_location, this->current_expression,
+ call->arguments, call->variadic_types);
}
else if (POINTER_TYPE_P(expression_type) && TREE_CODE(TREE_TYPE(expression_type)) == FUNCTION_TYPE)
{
- build_procedure_call(call_location, this->current_expression, call->arguments);
+ build_procedure_call(call_location, this->current_expression,
+ call->arguments, call->variadic_types);
}
else
{
diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h
index 76fb94e..8f5d863 100644
--- a/include/elna/boot/ast.h
+++ b/include/elna/boot/ast.h
@@ -374,7 +374,7 @@ namespace elna::boot
const identifier& id() const;
bool exported() const;
/**
- * \return Attributes written in front of the identifier.
+ * \return Attributes attached to the identifier.
*/
std::vector<attribute> attributes;
@@ -501,7 +501,7 @@ namespace elna::boot
* absent.
*/
std::vector<type_expression *> base_arguments;
- /// Attributes written in front of the \c record keyword.
+ /// Attributes attached to the record itself.
std::vector<attribute> attributes;
record_type_expression(const source_position position,
@@ -642,6 +642,8 @@ namespace elna::boot
const return_t return_type;
const std::vector<field_declaration> parameters;
+ /// Attributes attached to the procedure type.
+ std::vector<attribute> attributes;
procedure_type_expression(const source_position position,
std::vector<field_declaration>&& parameters, return_t return_type = return_t());
@@ -911,6 +913,9 @@ namespace elna::boot
public:
std::vector<expression *> arguments;
+ /// Types the arguments after the parameters of a variadic procedure
+ /// are passed as, set by the type checker.
+ std::vector<type> variadic_types;
procedure_call(const source_position position, designator_expression *callable,
std::vector<expression *>&& arguments);
diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h
index f2f2813..73f0a66 100644
--- a/include/elna/boot/name_analysis.h
+++ b/include/elna/boot/name_analysis.h
@@ -67,6 +67,15 @@ namespace elna::boot
};
/**
+ * Attributes the compiler knows.
+ */
+ enum class attribute_kind
+ {
+ aligned,
+ varargs
+ };
+
+ /**
* Error applying an attribute.
*/
class attribute_error final : public diagnostic
@@ -79,7 +88,9 @@ namespace elna::boot
unsupported,
argument_count,
not_constant,
- invalid_alignment
+ invalid_alignment,
+ unexpected_arguments,
+ missing_parameter
};
attribute_error(const source_position position, const std::string& name, kind attribute_kind);
@@ -202,16 +213,66 @@ namespace elna::boot
procedure_type::return_t build_return_type(
const procedure_type_expression::return_t& return_type);
std::pair<procedure_type, std::vector<std::string>> build_procedure(
- procedure_type_expression& expression);
+ procedure_type_expression& expression, bool variadic_supported);
ordered_map<field_info> build_composite_type(const std::vector<field_declaration>& fields,
- ordered_map<field_origin>& field_names, const type& aggregate,
- bool supported = true);
+ ordered_map<field_origin>& field_names, const type& aggregate);
/*
- * Reads the alignment out of the attributes written in front of a
- * declaration, reporting the ones that do not belong there.
+ * What the attributes attached to a declaration decide.
*/
- std::optional<std::size_t> evaluate_alignment(const std::vector<attribute>& attributes,
- bool supported);
+ struct attribute_values
+ {
+ std::optional<std::size_t> alignment;
+ bool variadic{ false };
+ };
+ static std::optional<attribute_kind> parse_attribute(const std::string& name);
+ /*
+ * Reads the attributes attached to a declaration, reporting the ones
+ * that do not belong there. The declaration accepts the attributes
+ * passed as template arguments.
+ */
+ template<attribute_kind... supported>
+ attribute_values evaluate_attributes(const std::vector<attribute>& attributes)
+ {
+ constexpr auto is_supported = [](const attribute_kind checked)
+ {
+ return ((checked == supported) || ...);
+ };
+ attribute_values result;
+
+ for (const attribute& written : attributes)
+ {
+ const std::string& name = written.name().name();
+ const source_position position = written.name().position();
+ const std::optional<attribute_kind> written_kind = parse_attribute(name);
+
+ if (!written_kind.has_value())
+ {
+ add_error<attribute_error>(position, name, attribute_error::kind::unknown);
+ }
+ else if (!is_supported(written_kind.value()))
+ {
+ add_error<attribute_error>(position, name, attribute_error::kind::unsupported);
+ }
+ else if (written_kind.value() == attribute_kind::aligned)
+ {
+ evaluate_alignment(written, result.alignment);
+ }
+ else if (!written.arguments().empty())
+ {
+ add_error<attribute_error>(position, name, attribute_error::kind::unexpected_arguments);
+ }
+ else if (result.variadic)
+ {
+ add_error<attribute_error>(position, name, attribute_error::kind::duplicate);
+ }
+ else
+ {
+ result.variadic = true;
+ }
+ }
+ return result;
+ }
+ void evaluate_alignment(const attribute& written, std::optional<std::size_t>& alignment);
/*
* Resolves a type expression, reporting names that denote something
* other than a type.
diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h
index 6defcb8..a8ad73b 100644
--- a/include/elna/boot/result.h
+++ b/include/elna/boot/result.h
@@ -862,6 +862,8 @@ namespace elna::boot
type_properties single_properties;
type_properties double_properties;
type_properties bool_properties;
+ /// C's \c int, the type narrower variadic arguments are promoted to.
+ type_properties c_int_properties;
};
template<template<typename, typename> typename C, template<typename> typename Alloc = std::allocator>
diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h
index e9c9a0c..1ca5deb 100644
--- a/include/elna/boot/symbol.h
+++ b/include/elna/boot/symbol.h
@@ -201,6 +201,9 @@ namespace elna::boot
std::vector<type> parameters;
const return_t return_type;
+ /// Whether further arguments may follow the parameters, as with a C
+ /// variadic function.
+ bool variadic{ false };
explicit procedure_type(return_t return_type = return_t());
};
diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h
index e948ada..5faec43 100644
--- a/include/elna/boot/type_check.h
+++ b/include/elna/boot/type_check.h
@@ -97,7 +97,8 @@ namespace elna::boot
dereference_of_parameter,
parameter_arithmetic,
zero_sized,
- not_addressable
+ not_addressable,
+ variadic_argument
};
type_requirement_error(const source_position position,
@@ -271,6 +272,11 @@ namespace elna::boot
const procedure_info& procedure);
void check_return(const procedure_body& body, const source_position position,
const std::string& name);
+ /*
+ * Checks an argument passed after the parameters of a variadic procedure
+ * and returns the type it is passed as.
+ */
+ type promote_variadic_argument(const expression& argument);
protected:
void visit_entry_point(unit *unit) override;
diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h
index c0c23b1..eec6cb4 100644
--- a/include/elna/gcc/elna-generic.h
+++ b/include/elna/gcc/elna-generic.h
@@ -57,7 +57,8 @@ namespace elna::gcc
void build_case_integral(boot::case_statement *statement, tree condition_expression);
void build_case_general(boot::case_statement *statement, tree condition_expression);
void build_procedure_call(location_t call_location,
- tree procedure_address, const std::vector<boot::expression *>& arguments);
+ tree procedure_address, const std::vector<boot::expression *>& arguments,
+ const std::vector<boot::type>& variadic_types);
bool build_builtin_procedures(boot::procedure_call *call);
void build_assert_builtin(location_t call_location, const std::vector<boot::expression *>& arguments);
diff --git a/testsuite/fail_compilation/aligned_attribute.elna b/testsuite/fail_compilation/aligned_attribute.elna
index 3b5a56e..aab5513 100644
--- a/testsuite/fail_compilation/aligned_attribute.elna
+++ b/testsuite/fail_compilation/aligned_attribute.elna
@@ -1,15 +1,15 @@
type
Uneven = record
- #aligned(3) x: Word8 (* @Error Alignment must be a power of two *)
+ x #{aligned 3}: Word8 (* @Error Alignment must be a power of two *)
end
Twice = record
- #aligned(4) #aligned(8) y: Word8 (* @Error Attribute '#aligned' specified more than once *)
+ y #{aligned 4} #{aligned 8}: Word8 (* @Error Attribute 'aligned' specified more than once *)
end
Unknown = record
- #packed(1) z: Word8 (* @Error Attribute '#packed' not declared *)
+ z #{packed 1}: Word8 (* @Error Attribute 'packed' not declared *)
end
-proc takes(#aligned(8) p: Int) (* @Error Attribute '#aligned' is not supported in this position *)
+proc takes(p #{aligned 8}: Int) (* @Error Attribute 'aligned' is not supported in this position *)
begin
return
diff --git a/testsuite/fail_compilation/varargs_argument.elna b/testsuite/fail_compilation/varargs_argument.elna
new file mode 100644
index 0000000..1325c29
--- /dev/null
+++ b/testsuite/fail_compilation/varargs_argument.elna
@@ -0,0 +1,12 @@
+var
+ written: Int32
+
+proc printf(format: ^const Word8) #{varargs} -> Int32
+extern
+
+program()
+begin
+ written := printf("%s".ptr, "text") (* @Error Type '\[\]const Word8' cannot be passed as a variadic argument *)
+return 0u8
+
+end.
diff --git a/testsuite/fail_compilation/varargs_attribute.elna b/testsuite/fail_compilation/varargs_attribute.elna
new file mode 100644
index 0000000..1c6d3e0
--- /dev/null
+++ b/testsuite/fail_compilation/varargs_attribute.elna
@@ -0,0 +1,8 @@
+proc with_body(x: Int) #{varargs} (* @Error Attribute 'varargs' is not supported in this position *)
+begin
+return
+
+proc no_parameters() #{varargs} -> Int32 (* @Error Attribute 'varargs' requires at least one parameter *)
+extern
+
+end.
diff --git a/testsuite/runnable/aligned_attribute.elna b/testsuite/runnable/aligned_attribute.elna
index 330847e..45c0c37 100644
--- a/testsuite/runnable/aligned_attribute.elna
+++ b/testsuite/runnable/aligned_attribute.elna
@@ -5,20 +5,20 @@ type
end
Spread = record
- a, #aligned(8) b: Word8
+ a, b #{aligned 8}: Word8
end
Packed = record
a: Word8;
- #aligned(1) wide: Word
+ wide #{aligned 1}: Word
end
- Over = #aligned(16) record
+ Over = record #{aligned 16}
a: Word8
end
var
- #aligned(32) tuned: Word8
+ tuned #{aligned 32}: Word8
spread: Spread
first: ^Word8
second: ^Word8
diff --git a/testsuite/runnable/varargs.elna b/testsuite/runnable/varargs.elna
new file mode 100644
index 0000000..feeb94b
--- /dev/null
+++ b/testsuite/runnable/varargs.elna
@@ -0,0 +1,16 @@
+var
+ buffer: [8]Word8
+ written: Int32
+
+proc snprintf(buffer: ^Word8; size: Word; format: ^const Word8) #{varargs} -> Int32
+extern
+
+program()
+begin
+ written := snprintf(@buffer[1], 8u, "%d %.1f".ptr, 200u8, 1.5f);
+ assert(written = 7i32);
+ assert(buffer[1] = '2');
+ assert(buffer[7] = '5')
+return 0u8
+
+end.