diff options
| -rw-r--r-- | src/ast/ast.h | 3 | ||||
| -rw-r--r-- | src/parser/parser.h | 2 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 57 | ||||
| -rw-r--r-- | src/parser/parser_type.c | 105 | ||||
| -rw-r--r-- | src/parser/parser_utils.c | 96 | ||||
| -rw-r--r-- | tests/collections/test_string_suite.zc | 63 | ||||
| -rw-r--r-- | tests/generics/test_multi_generics.zc | 13 |
7 files changed, 251 insertions, 88 deletions
diff --git a/src/ast/ast.h b/src/ast/ast.h index e38453c..1b83a91 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -376,7 +376,8 @@ struct ASTNode char *name; ASTNode *fields; int is_template; - char *generic_param; + char **generic_params; // Array of generic parameter names (for example, ["K", "V"]) + int generic_param_count; // Number of generic parameters char *parent; int is_union; int is_packed; // @packed attribute. 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; diff --git a/tests/collections/test_string_suite.zc b/tests/collections/test_string_suite.zc index 9f79b69..4d2c080 100644 --- a/tests/collections/test_string_suite.zc +++ b/tests/collections/test_string_suite.zc @@ -8,47 +8,45 @@ test "test_string_methods" { // Substring var s1: String = String::from("Hello World"); - var sub: String = String::substring(&s1, 0, 5); + var sub: String = s1.substring(0, 5); var expected1: String = String::from("Hello"); - if (String::eq(&sub, expected1)) { + if (sub.eq(expected1)) { println " -> substring(0, 5): Passed"; } else { assert(false, "substring(0, 5) failed"); } // Substring middle - var sub2: String = String::substring(&s1, 6, 5); + var sub2: String = s1.substring(6, 5); var expected2: String = String::from("World"); - if (String::eq(&sub2, expected2)) { + if (sub2.eq(expected2)) { println " -> substring(6, 5): Passed"; } else { assert(false, "substring(6, 5) failed"); } - // Find - var pos = String::find(&s1, 'W'); - if (pos.is_some) { - var idx = pos.unwrap(); - if (idx == 6) { - println " -> find('W'): Passed"; - } else { - assert(false, "find('W') returned wrong index"); - } + // Find character - found + var pos: Option<usize> = s1.find('W'); + if (pos.is_some()) { + var idx: usize = pos.unwrap(); + assert(idx == (usize)6, "find('W') index mismatch"); + println " -> find('W'): Passed (found at index 6)"; } else { - assert(false, "find('W') failed to find char"); + assert(false, "find('W') failed (not found)"); } - var pos2 = String::find(&s1, 'Z'); + // Find character - not found + var pos2: Option<usize> = s1.find('Z'); if (pos2.is_none()) { - println " -> find('Z'): Passed (Correctly not found)"; + println " -> find('Z'): Passed (not found)"; } else { - assert(false, "find('Z') should have returned none"); + assert(false, "find('Z') failed (found unexpectedly)"); } - + // Length - var len = String::length(&s1); - if (len == 11) { - println " -> length(): Passed"; + var len: usize = s1.length(); + if (len == (usize)11) { + println " -> length(): Passed (11)"; } else { assert(false, "length() failed"); } @@ -66,30 +64,17 @@ test "test_string_methods" { assert(false, "!contains('Z') failed"); } - // Cleanup - s1.free(); - sub.free(); - sub2.free(); - expected1.free(); - expected2.free(); - // Append - println "Testing String Append..."; - var s2 = String::from("Hello"); - var s3 = String::from(" World"); - String::append(&s2, s3); - var expected_full = String::from("Hello World"); - - if (String::eq(&s2, expected_full)) { + var s2: String = String::from("Hello"); + var s3: String = String::from(" World"); + s2.append(s3); + var expected6: String = String::from("Hello World"); + if (s2.eq(expected6)) { println " -> append(): Passed"; } else { assert(false, "append() failed"); } - s2.free(); - s3.free(); // Free the wrapper... - expected_full.free(); - println "All String methods passed!"; } diff --git a/tests/generics/test_multi_generics.zc b/tests/generics/test_multi_generics.zc new file mode 100644 index 0000000..30f4103 --- /dev/null +++ b/tests/generics/test_multi_generics.zc @@ -0,0 +1,13 @@ + +struct Pair<A, B> { + first: A; + second: B; +} + +test "multi-type generics basic" { + var p: Pair<int, float>; + p.first = 42; + p.second = 3.14; + assert(p.first == 42, "First field failed"); + println "Multi-type generics test passed!"; +} |
