diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-11-01 22:10:48 +0100 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-11-01 22:10:48 +0100 |
| commit | 48a51628b841a9b69db846326dcaa67239a8559f (patch) | |
| tree | 76707565b96d42ee652179e4037e42a2c5559888 | |
| parent | 5dd5d460f154c146d26f115706a21b7808658edf (diff) | |
| download | elna-cpp.tar.gz | |
| -rw-r--r-- | boot/name_analysis.cc | 31 | ||||
| -rw-r--r-- | boot/symbol.cc | 97 | ||||
| -rw-r--r-- | boot/type_check.cc | 116 | ||||
| -rw-r--r-- | include/elna/boot/symbol.h | 16 | ||||
| -rw-r--r-- | source/elna/lexer.elna | 38 | ||||
| -rw-r--r-- | source/sys/posix/stdlib.elna | 3 | ||||
| -rw-r--r-- | source/sys/posix/string.elna | 3 | ||||
| -rw-r--r-- | testsuite/compilable/generic_inference.elna | 7 |
8 files changed, 192 insertions, 119 deletions
diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 9305cd2..959aefc 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -968,13 +968,42 @@ namespace elna::boot void resolving_visitor::visit(procedure_call *call) { call->callable().accept(this); + visit_all(this, call->arguments); + + if (const auto *reference = call->callable().is_named(); + reference != nullptr && reference->arguments.empty()) + { + const std::shared_ptr<info> symbol = this->bag.lookup(reference->name); + const std::shared_ptr<procedure_info> procedure = + symbol == nullptr ? nullptr : symbol->is_procedure(); + + if (procedure != nullptr && !procedure->parameters.empty()) + { + std::vector<type> bindings(procedure->parameters.size()); + auto argument_iterator = call->arguments.begin(); + auto declared_iterator = procedure->symbol.parameters.begin(); + + while (argument_iterator != call->arguments.end() + && declared_iterator != procedure->symbol.parameters.end()) + { + const type actual = unqualify((*argument_iterator)->type_decoration); + bind_arguments(procedure->parameters, bindings, *declared_iterator, actual); + ++argument_iterator; + ++declared_iterator; + } + if (std::ranges::none_of(bindings, &type::empty)) + { + call->callable().type_decoration = substitute( + call->callable().type_decoration, procedure->parameters, bindings); + } + } + } const type callable_type = resolve_underlying_type(call->callable().type_decoration); if (const auto procedure = callable_type.get<procedure_type>()) { call->type_decoration = procedure->return_type; } - visit_all(this, call->arguments); this->current_type = type(); } diff --git a/boot/symbol.cc b/boot/symbol.cc index 310ae8a..dcbd460 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -700,6 +700,103 @@ namespace elna::boot return subject; } + bool bind_arguments(const std::vector<type>& parameters, std::vector<type>& bindings, + const type& declared, const type& actual) + { + const type left = resolve_aliases(declared); + const type right = resolve_aliases(actual); + + if (const auto parameter = left.get<parameter_type>()) + { + for (std::size_t i = 0; i < parameters.size(); ++i) + { + if (parameters[i].get<parameter_type>() == parameter && bindings[i].empty()) + { + // The actual type is recorded as it stands, which inside a + // generic body may itself be an abstract parameter. + bindings[i] = actual; + } + } + return true; + } + else if (const auto left_pointer = left.get<pointer_type>()) + { + const std::shared_ptr<pointer_type> right_pointer = right.get<pointer_type>(); + + return right_pointer != nullptr + && bind_arguments(parameters, bindings, left_pointer->base, right_pointer->base); + } + else if (const auto left_slice = left.get<slice_type>()) + { + const std::shared_ptr<slice_type> right_slice = right.get<slice_type>(); + + return right_slice != nullptr + && bind_arguments(parameters, bindings, left_slice->base, right_slice->base); + } + else if (const auto left_array = left.get<array_type>()) + { + const std::shared_ptr<array_type> right_array = right.get<array_type>(); + + return right_array != nullptr + && bind_arguments(parameters, bindings, left_array->base, right_array->base); + } + else if (const auto left_constant = left.get<constant_type>()) + { + const std::shared_ptr<constant_type> right_constant = right.get<constant_type>(); + const type right_unqualified = right_constant != nullptr + ? right_constant->unqualified + : right; + + return bind_arguments(parameters, bindings, left_constant->unqualified, right_unqualified); + } + else if (const auto left_instance = left.get<instantiated_type>()) + { + for (type candidate = right; !candidate.empty(); candidate = base_of(candidate)) + { + const std::shared_ptr<instantiated_type> right_instance = + resolve_aliases(candidate).get<instantiated_type>(); + + if (right_instance == nullptr + || !(left_instance->generic == right_instance->generic) + || left_instance->arguments.size() != right_instance->arguments.size()) + { + continue; + } + for (std::size_t i = 0; i < left_instance->arguments.size(); ++i) + { + if (!bind_arguments(parameters, bindings, + left_instance->arguments.at(i), right_instance->arguments.at(i))) + { + return false; + } + } + return true; + } + return false; + } + else if (const auto left_procedure = left.get<procedure_type>()) + { + const std::shared_ptr<procedure_type> right_procedure = right.get<procedure_type>(); + + if (right_procedure == nullptr + || left_procedure->parameters.size() != right_procedure->parameters.size()) + { + return false; + } + for (std::size_t i = 0; i < left_procedure->parameters.size(); ++i) + { + if (!bind_arguments(parameters, bindings, + left_procedure->parameters.at(i), right_procedure->parameters.at(i))) + { + return false; + } + } + return left_procedure->return_type.empty() + || bind_arguments(parameters, bindings, left_procedure->return_type, right_procedure->return_type); + } + return true; + } + type instantiate(const std::shared_ptr<instantiated_type>& instance) { std::shared_ptr<generic_type> const generic = unwrap_generic(instance->generic); diff --git a/boot/type_check.cc b/boot/type_check.cc index 7adaaed..a135bcb 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -1079,112 +1079,6 @@ namespace elna::boot } /* - * Walks a declared parameter type and an actual argument type in parallel, - * binding a parameter the first time it is met and leaving every later - * occurrence to the ordinary assignability check. Both sides are resolved - * at every step, so a transparent instantiation is seen through and only a - * record-referent one is compared as an instantiation. - * - * Returns false when the walk fails on shape rather than on type. - */ - static bool bind_arguments(const std::vector<type>& parameters, std::vector<type>& bindings, - const type& declared, const type& actual) - { - const type left = resolve_aliases(declared); - const type right = resolve_aliases(actual); - - if (const auto parameter = left.get<parameter_type>()) - { - for (std::size_t i = 0; i < parameters.size(); ++i) - { - if (parameters[i].get<parameter_type>() == parameter && bindings[i].empty()) - { - // The actual type is recorded as it stands, which inside a - // generic body may itself be an abstract parameter. - bindings[i] = actual; - } - } - return true; - } - else if (const auto left_pointer = left.get<pointer_type>()) - { - const std::shared_ptr<pointer_type> right_pointer = right.get<pointer_type>(); - - return right_pointer != nullptr - && bind_arguments(parameters, bindings, left_pointer->base, right_pointer->base); - } - else if (const auto left_slice = left.get<slice_type>()) - { - const std::shared_ptr<slice_type> right_slice = right.get<slice_type>(); - - return right_slice != nullptr - && bind_arguments(parameters, bindings, left_slice->base, right_slice->base); - } - else if (const auto left_array = left.get<array_type>()) - { - const std::shared_ptr<array_type> right_array = right.get<array_type>(); - - return right_array != nullptr - && bind_arguments(parameters, bindings, left_array->base, right_array->base); - } - else if (const auto left_constant = left.get<constant_type>()) - { - const std::shared_ptr<constant_type> right_constant = right.get<constant_type>(); - const type right_unqualified = right_constant != nullptr - ? right_constant->unqualified - : right; - - return bind_arguments(parameters, bindings, left_constant->unqualified, right_unqualified); - } - else if (const auto left_instance = left.get<instantiated_type>()) - { - for (type candidate = right; !candidate.empty(); candidate = base_of(candidate)) - { - const std::shared_ptr<instantiated_type> right_instance = - resolve_aliases(candidate).get<instantiated_type>(); - - if (right_instance == nullptr - || !(left_instance->generic == right_instance->generic) - || left_instance->arguments.size() != right_instance->arguments.size()) - { - continue; - } - for (std::size_t i = 0; i < left_instance->arguments.size(); ++i) - { - if (!bind_arguments(parameters, bindings, - left_instance->arguments.at(i), right_instance->arguments.at(i))) - { - return false; - } - } - return true; - } - return false; - } - else if (const auto left_procedure = left.get<procedure_type>()) - { - const std::shared_ptr<procedure_type> right_procedure = right.get<procedure_type>(); - - if (right_procedure == nullptr - || left_procedure->parameters.size() != right_procedure->parameters.size()) - { - return false; - } - for (std::size_t i = 0; i < left_procedure->parameters.size(); ++i) - { - if (!bind_arguments(parameters, bindings, - left_procedure->parameters.at(i), right_procedure->parameters.at(i))) - { - return false; - } - } - return left_procedure->return_type.empty() - || bind_arguments(parameters, bindings, left_procedure->return_type, right_procedure->return_type); - } - return true; - } - - /* * The volatile builtins take the pointee as their type argument, so the * rule applies to what is loaded or stored rather than to a pointer. */ @@ -1382,19 +1276,9 @@ namespace elna::boot if (procedure != nullptr && !procedure->parameters.empty()) { - // Inference reads the argument types, so they are decorated - // before the parameter types they will be checked against exist. visit_all(this, call->arguments); inferred = procedure; callable_type = infer_call(*call, *reference, *procedure); - - // Name analysis decorated the call with the declared return - // type, which still mentions the parameters inference has - // just bound. - if (const auto substituted = callable_type.get<procedure_type>()) - { - call->type_decoration = substituted->return_type; - } } } if (const auto procedure = callable_type.get<procedure_type>()) diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index c9ba88a..f9905c1 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -762,6 +762,22 @@ namespace elna::boot const std::vector<type>& arguments); /** + * Walks a declared parameter type and an actual argument type in parallel, + * binding a parameter the first time it is met and leaving every later + * occurrence to the ordinary assignability check. Both sides are resolved + * at every step, so a transparent instantiation is seen through and only a + * record-referent one is compared as an instantiation. + * + * \param parameters Type parameters of the generic procedure. + * \param bindings Bound type arguments, indexed like \p parameters. + * \param declared Declared parameter type. + * \param actual Actual argument type. + * \return Whether the declared and actual types match in shape. + */ + bool bind_arguments(const std::vector<type>& parameters, std::vector<type>& bindings, + const type& declared, const type& actual); + + /** * Resolves a type to the view layout and codegen see, in which an * instantiation is its generic's referent with the arguments dropped. * diff --git a/source/elna/lexer.elna b/source/elna/lexer.elna index a543c67..0a72d0d 100644 --- a/source/elna/lexer.elna +++ b/source/elna/lexer.elna @@ -277,6 +277,9 @@ type ElnaLexerCharacterToken = record(ElnaLexerToken) codepoint: Word32 end + ElnaLexerStringToken = record(ElnaLexerToken) + string: StdString + end const ELNA_LEXER_UTF8_LEAD_TAG_2BYTE = 0xC0u8 @@ -975,10 +978,12 @@ begin if escape <> ELNA_LEXER_ESCAPE_INVALID_CHAR then current_position := current_position + 2; output^^ := escape; + output^ := output^ + 1; consumed := 2 end else output^^ := current_position^; + output^ := output^ + 1; consumed := 1 end return consumed @@ -1051,6 +1056,35 @@ begin end return result +-- TODO: Support dumping and test. +proc elna_lexer_parse_string_literal(start, cursor: ^const Word8) -> StdOptional#[ElnaLexerToken] +var + raw_length := cast(cursor - start: Word) + storage: ^Word8 := malloc#[Word8](raw_length) + output := storage + result := { StdOptional#[ElnaLexerToken] } + failed := false +begin + while ~failed & start < cursor do + var + consumed := elna_lexer_decode_one_character(start, @output) + begin + if consumed = 0 then + start := start + consumed + else + failed := true + end + end; + if ~failed then + var + string_token := malloc#[ElnaLexerStringToken](#size(ElnaLexerStringToken)) + final_length := cast(output - storage: Word) + begin + string_token^.kind := ElnaLexerTokenKind.string; + string_token^.string := realloc(storage, final_length)[1 to final_length] + end +return result + (* Skips blanks and advances the cursor to span the next significant token. *) @@ -1193,13 +1227,15 @@ var begin if start^ = '(' then var - comment_token := elna_lexer_stop_comment(ElnaLexerTokenKind.docblock, start, cursor - 2u) + comment_token := elna_lexer_stop_comment(ElnaLexerTokenKind.docblock, start, cursor - 2) begin result := std_optional_make#[ElnaLexerToken](comment_token) elsif start^ = '\'' then result := elna_lexer_parse_byte_literal(start) elsif start^ = '`' then result := elna_lexer_parse_character_literal(start) + elsif start^ = '"' then + result := elna_lexer_parse_string_literal(start + 1, cursor) end return result diff --git a/source/sys/posix/stdlib.elna b/source/sys/posix/stdlib.elna index 8746970..d4b381e 100644 --- a/source/sys/posix/stdlib.elna +++ b/source/sys/posix/stdlib.elna @@ -9,6 +9,9 @@ extern proc free#[T]*(ptr: ^T) extern +proc realloc#[T]*(ptr: ^T; size: Word) -> ^T +extern + proc strtol*(str: ^const Word8; endptr: ^^Word8; base: Int) -> Int extern diff --git a/source/sys/posix/string.elna b/source/sys/posix/string.elna index b699606..ebe347c 100644 --- a/source/sys/posix/string.elna +++ b/source/sys/posix/string.elna @@ -9,4 +9,7 @@ extern proc memcpy#[T]*(dst: ^T; src: ^const T; n: Word) -> ^T extern +proc memchr#[T]*(s: ^T; c: Int8; n: Word) -> ^T +extern + end. diff --git a/testsuite/compilable/generic_inference.elna b/testsuite/compilable/generic_inference.elna index e5e6e3b..c03b902 100644 --- a/testsuite/compilable/generic_inference.elna +++ b/testsuite/compilable/generic_inference.elna @@ -13,6 +13,10 @@ var bytes: Cell#[Word8] f: File b: Word8 + s: []const Word8 + +proc identity#[T](value: ^T) -> ^T +return value proc store#[T](target: ^Cell#[T]; value: ^T) target^.held := value @@ -25,5 +29,6 @@ return begin store(@files, @f); relay(@bytes, @b); - store(@files, cast(0u: ^File)) + store(@files, cast(0u: ^File)); + s := identity(@b)[1 to 1] end. |
