summaryrefslogtreecommitdiff
path: root/src/parser
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-16 21:22:20 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-16 21:22:20 +0000
commita5d5a97818fb4fbd26c4fb25a5c410b1a60a1b18 (patch)
tree41b4fa9a4952db1496127031f22de988b7d45418 /src/parser
parent73d0a63df903445ecd32f5b95bb3ff34e3dc2976 (diff)
Added multi-type generics support.
Diffstat (limited to 'src/parser')
-rw-r--r--src/parser/parser.h2
-rw-r--r--src/parser/parser_stmt.c57
-rw-r--r--src/parser/parser_type.c105
-rw-r--r--src/parser/parser_utils.c96
4 files changed, 212 insertions, 48 deletions
diff --git a/src/parser/parser.h b/src/parser/parser.h
index 1e047b4..ca8c447 100644
--- a/src/parser/parser.h
+++ b/src/parser/parser.h
@@ -321,6 +321,8 @@ void add_to_global_list(ParserContext *ctx, ASTNode *node);
void register_builtins(ParserContext *ctx);
void add_instantiated_func(ParserContext *ctx, ASTNode *fn);
void instantiate_generic(ParserContext *ctx, const char *name, const char *concrete_type, Token t);
+void instantiate_generic_multi(ParserContext *ctx, const char *name, char **args, int arg_count,
+ Token t);
char *sanitize_mangled_name(const char *name);
void register_type_alias(ParserContext *ctx, const char *alias, const char *original);
const char *find_type_alias(ParserContext *ctx, const char *alias);
diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c
index f391f97..ac2d622 100644
--- a/src/parser/parser_stmt.c
+++ b/src/parser/parser_stmt.c
@@ -3238,14 +3238,33 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union)
Token n = lexer_next(l);
char *name = token_strdup(n);
- // Generic Param <T>
- char *gp = NULL;
+ // Generic Params <T> or <K, V>
+ char **gps = NULL;
+ int gp_count = 0;
if (lexer_peek(l).type == TOK_LANGLE)
{
- lexer_next(l);
- Token g = lexer_next(l);
- gp = token_strdup(g);
- lexer_next(l);
+ lexer_next(l); // eat <
+ while (1)
+ {
+ Token g = lexer_next(l);
+ gps = realloc(gps, sizeof(char *) * (gp_count + 1));
+ gps[gp_count++] = token_strdup(g);
+
+ Token next = lexer_peek(l);
+ if (next.type == TOK_COMMA)
+ {
+ lexer_next(l); // eat ,
+ }
+ else if (next.type == TOK_RANGLE)
+ {
+ lexer_next(l); // eat >
+ break;
+ }
+ else
+ {
+ zpanic_at(next, "Expected ',' or '>' in generic parameter list");
+ }
+ }
register_generic(ctx, name);
}
@@ -3255,16 +3274,13 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union)
lexer_next(l);
ASTNode *n = ast_create(NODE_STRUCT);
n->strct.name = name;
- n->strct.is_template = (gp != NULL);
- n->strct.generic_param = gp;
+ n->strct.is_template = (gp_count > 0);
+ n->strct.generic_params = gps;
+ n->strct.generic_param_count = gp_count;
n->strct.is_union = is_union;
n->strct.fields = NULL;
n->strct.is_incomplete = 1;
- if (!gp)
- {
- add_to_struct_list(ctx, n);
- }
return n;
}
@@ -3390,7 +3406,7 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union)
add_to_struct_list(ctx, node);
// Auto-prefix struct name if in module context
- if (ctx->current_module_prefix && !gp)
+ if (ctx->current_module_prefix && gp_count == 0)
{ // Don't prefix generic templates
char *prefixed_name = xmalloc(strlen(ctx->current_module_prefix) + strlen(name) + 2);
sprintf(prefixed_name, "%s_%s", ctx->current_module_prefix, name);
@@ -3403,24 +3419,25 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union)
// Initialize Type Info so we can track traits (like Drop)
node->type_info = type_new(TYPE_STRUCT);
node->type_info->name = xstrdup(name);
- if (gp)
+ if (gp_count > 0)
{
node->type_info->kind = TYPE_GENERIC;
// TODO: track generic params
}
node->strct.fields = h;
- node->strct.generic_param = gp;
+ node->strct.generic_params = gps;
+ node->strct.generic_param_count = gp_count;
node->strct.is_union = is_union;
- if (gp)
+ if (gp_count > 0)
{
node->strct.is_template = 1;
register_template(ctx, name, node);
}
// Register definition for 'use' lookups and LSP
- if (!gp)
+ if (gp_count == 0)
{
register_struct_def(ctx, name, node);
}
@@ -3430,10 +3447,10 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union)
Type *parse_type_obj(ParserContext *ctx, Lexer *l)
{
- // 1. Parse the base type (int, U32, MyStruct, etc.)
+ // Parse the base type (int, U32, MyStruct, etc.)
Type *t = parse_type_base(ctx, l);
- // 2. Handle Pointers (e.g. int***)
+ // Handle Pointers
while (lexer_peek(l).type == TOK_OP && lexer_peek(l).start[0] == '*')
{
lexer_next(l); // eat *
@@ -3443,8 +3460,6 @@ Type *parse_type_obj(ParserContext *ctx, Lexer *l)
t = ptr;
}
- // (Optional: You can add array parsing here later if needed)
-
return t;
}
diff --git a/src/parser/parser_type.c b/src/parser/parser_type.c
index 3b4abf3..7dded00 100644
--- a/src/parser/parser_type.c
+++ b/src/parser/parser_type.c
@@ -382,44 +382,99 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l)
Type *ty = type_new(TYPE_STRUCT);
ty->name = name;
- // Handle Generics <T>
+ // Handle Generics <T> or <K, V>
if (lexer_peek(l).type == TOK_LANGLE)
{
lexer_next(l); // eat <
- Type *arg = parse_type_formal(ctx, l);
+ Type *first_arg = parse_type_formal(ctx, l);
+ char *first_arg_str = type_to_string(first_arg);
- // Handle nested generics like Vec<Vec<int>> where >> is tokenized as one
- // op
+ // Check for multi-arg: <K, V>
Token next_tok = lexer_peek(l);
- if (next_tok.type == TOK_RANGLE)
- {
- lexer_next(l); // Consume >
- }
- else if (next_tok.type == TOK_OP && next_tok.len == 2 &&
- strncmp(next_tok.start, ">>", 2) == 0)
+ if (next_tok.type == TOK_COMMA)
{
- // Split >> into two > tokens
- // Consume the first > by advancing lexer manually
- l->pos += 1;
- l->col += 1;
+ // Multi-arg case
+ char **args = xmalloc(sizeof(char *) * 8);
+ int arg_count = 0;
+ args[arg_count++] = xstrdup(first_arg_str);
+
+ while (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l); // eat ,
+ Type *arg = parse_type_formal(ctx, l);
+ char *arg_str = type_to_string(arg);
+ args = realloc(args, sizeof(char *) * (arg_count + 1));
+ args[arg_count++] = xstrdup(arg_str);
+ free(arg_str);
+ }
+
+ // Consume >
+ next_tok = lexer_peek(l);
+ if (next_tok.type == TOK_RANGLE)
+ {
+ lexer_next(l);
+ }
+ else if (next_tok.type == TOK_OP && next_tok.len == 2 &&
+ strncmp(next_tok.start, ">>", 2) == 0)
+ {
+ l->pos += 1;
+ l->col += 1;
+ }
+ else
+ {
+ zpanic_at(t, "Expected > after generic");
+ }
+
+ // Call multi-arg instantiation
+ instantiate_generic_multi(ctx, name, args, arg_count, t);
+
+ // Build mangled name
+ char mangled[256];
+ strcpy(mangled, name);
+ for (int i = 0; i < arg_count; i++)
+ {
+ char *clean = sanitize_mangled_name(args[i]);
+ strcat(mangled, "_");
+ strcat(mangled, clean);
+ free(clean);
+ free(args[i]);
+ }
+ free(args);
+
+ free(ty->name);
+ ty->name = xstrdup(mangled);
}
else
{
- zpanic_at(t, "Expected > after generic");
- }
+ // Single-arg case - PRESERVE ORIGINAL FLOW EXACTLY
+ if (next_tok.type == TOK_RANGLE)
+ {
+ lexer_next(l); // Consume >
+ }
+ else if (next_tok.type == TOK_OP && next_tok.len == 2 &&
+ strncmp(next_tok.start, ">>", 2) == 0)
+ {
+ // Split >> into two > tokens
+ l->pos += 1;
+ l->col += 1;
+ }
+ else
+ {
+ zpanic_at(t, "Expected > after generic");
+ }
- char *arg_str = type_to_string(arg);
- instantiate_generic(ctx, name, arg_str, t);
+ instantiate_generic(ctx, name, first_arg_str, t);
- char *clean_arg = sanitize_mangled_name(arg_str);
- char mangled[256];
- sprintf(mangled, "%s_%s", name, clean_arg);
- free(clean_arg);
+ char *clean_arg = sanitize_mangled_name(first_arg_str);
+ char mangled[256];
+ sprintf(mangled, "%s_%s", name, clean_arg);
+ free(clean_arg);
- free(ty->name);
- ty->name = xstrdup(mangled);
- free(arg_str);
+ free(ty->name);
+ ty->name = xstrdup(mangled);
+ }
+ free(first_arg_str);
ty->kind = TYPE_STRUCT;
ty->args = NULL;
ty->arg_count = 0;
diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c
index 6129990..51d89d7 100644
--- a/src/parser/parser_utils.c
+++ b/src/parser/parser_utils.c
@@ -1948,8 +1948,11 @@ void instantiate_generic(ParserContext *ctx, const char *tpl, const char *arg, T
ASTNode *i = ast_create(NODE_STRUCT);
i->strct.name = xstrdup(m);
i->strct.is_template = 0;
- i->strct.fields = copy_fields_replacing(ctx, t->struct_node->strct.fields,
- t->struct_node->strct.generic_param, arg);
+ // Use first generic param for substitution (single-param backward compat)
+ const char *gp = (t->struct_node->strct.generic_param_count > 0)
+ ? t->struct_node->strct.generic_params[0]
+ : "T";
+ i->strct.fields = copy_fields_replacing(ctx, t->struct_node->strct.fields, gp, arg);
struct_node_copy = i;
register_struct_def(ctx, m, i);
}
@@ -2004,6 +2007,95 @@ void instantiate_generic(ParserContext *ctx, const char *tpl, const char *arg, T
}
}
+void instantiate_generic_multi(ParserContext *ctx, const char *tpl, char **args, int arg_count,
+ Token token)
+{
+ // Build mangled name from all args
+ char m[256];
+ strcpy(m, tpl);
+ for (int i = 0; i < arg_count; i++)
+ {
+ char *clean = sanitize_mangled_name(args[i]);
+ strcat(m, "_");
+ strcat(m, clean);
+ free(clean);
+ }
+
+ // Check if already instantiated
+ Instantiation *c = ctx->instantiations;
+ while (c)
+ {
+ if (strcmp(c->name, m) == 0)
+ {
+ return; // Already done
+ }
+ c = c->next;
+ }
+
+ // Find the template
+ GenericTemplate *t = ctx->templates;
+ while (t)
+ {
+ if (strcmp(t->name, tpl) == 0)
+ {
+ break;
+ }
+ t = t->next;
+ }
+ if (!t)
+ {
+ zpanic_at(token, "Unknown generic: %s", tpl);
+ }
+
+ // Register instantiation first (to break cycles)
+ Instantiation *ni = xmalloc(sizeof(Instantiation));
+ ni->name = xstrdup(m);
+ ni->template_name = xstrdup(tpl);
+ ni->concrete_arg = (arg_count > 0) ? xstrdup(args[0]) : xstrdup("T");
+ ni->struct_node = NULL;
+ ni->next = ctx->instantiations;
+ ctx->instantiations = ni;
+
+ if (t->struct_node->type == NODE_STRUCT)
+ {
+ ASTNode *i = ast_create(NODE_STRUCT);
+ i->strct.name = xstrdup(m);
+ i->strct.is_template = 0;
+
+ // Copy fields with sequential substitutions for each param
+ ASTNode *fields = t->struct_node->strct.fields;
+ int param_count = t->struct_node->strct.generic_param_count;
+
+ // Perform substitution for each param (simple approach: copy for first param, then replace
+ // in-place)
+ if (param_count > 0 && arg_count > 0)
+ {
+ // First substitution
+ i->strct.fields = copy_fields_replacing(
+ ctx, fields, t->struct_node->strct.generic_params[0], args[0]);
+
+ // Subsequent substitutions (for params B, C, etc.)
+ for (int j = 1; j < param_count && j < arg_count; j++)
+ {
+ ASTNode *tmp = copy_fields_replacing(
+ ctx, i->strct.fields, t->struct_node->strct.generic_params[j], args[j]);
+ // This leaks prev fields, but that's acceptable for now, still, TODO.
+ i->strct.fields = tmp;
+ }
+ }
+ else
+ {
+ i->strct.fields = copy_fields_replacing(ctx, fields, "T", "int");
+ }
+
+ ni->struct_node = i;
+ register_struct_def(ctx, m, i);
+
+ i->next = ctx->instantiated_structs;
+ ctx->instantiated_structs = i;
+ }
+}
+
int is_file_imported(ParserContext *ctx, const char *p)
{
ImportedFile *c = ctx->imported_files;