From 977add96ffb28354e487c6587ae02e2cc82f03ac Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sun, 18 Jan 2026 11:32:57 +0000 Subject: Related to #68 --- src/codegen/codegen.c | 123 +++++++++++++++++++++++++++++++-------- src/parser/parser_stmt.c | 38 ++++++++++-- tests/control_flow/test_match.zc | 52 +++++++++++++++++ 3 files changed, 184 insertions(+), 29 deletions(-) diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 9539338..644e9f5 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -10,6 +10,104 @@ #include "ast.h" #include "zprep_plugin.h" +// Helper: emit a single pattern condition (either a value, or a range) +static void emit_single_pattern_cond(const char *pat, int id, FILE *out) +{ + // Check for range pattern: "start..end" or "start..=end" + char *range_incl = strstr(pat, "..="); + char *range_excl = strstr(pat, ".."); + + if (range_incl) + { + // Inclusive range: start..=end -> _m_id >= start && _m_id <= end + int start_len = (int)(range_incl - pat); + char *start = xmalloc(start_len + 1); + strncpy(start, pat, start_len); + start[start_len] = 0; + char *end = xstrdup(range_incl + 3); + fprintf(out, "(_m_%d >= %s && _m_%d <= %s)", id, start, id, end); + free(start); + free(end); + } + else if (range_excl) + { + // Exclusive range: start..end -> _m_id >= start && _m_id < end + int start_len = (int)(range_excl - pat); + char *start = xmalloc(start_len + 1); + strncpy(start, pat, start_len); + start[start_len] = 0; + char *end = xstrdup(range_excl + 2); + fprintf(out, "(_m_%d >= %s && _m_%d < %s)", id, start, id, end); + free(start); + free(end); + } + else if (pat[0] == '"') + { + // String pattern + fprintf(out, "strcmp(_m_%d, %s) == 0", id, pat); + } + else if (pat[0] == '\'') + { + // Char literal pattern + fprintf(out, "_m_%d == %s", id, pat); + } + else + { + // Numeric or simple pattern + fprintf(out, "_m_%d == %s", id, pat); + } +} + +// Helper: emit condition for a pattern (may contain OR patterns with '|') +static void emit_pattern_condition(ParserContext *ctx, const char *pattern, int id, FILE *out) +{ + // Check if pattern contains '|' for OR patterns + if (strchr(pattern, '|')) + { + // Split by '|' and emit OR conditions + char *pattern_copy = xstrdup(pattern); + char *saveptr; + char *part = strtok_r(pattern_copy, "|", &saveptr); + int first = 1; + fprintf(out, "("); + while (part) + { + if (!first) + { + fprintf(out, " || "); + } + + // Check if part is an enum variant + EnumVariantReg *reg = find_enum_variant(ctx, part); + if (reg) + { + fprintf(out, "_m_%d.tag == %d", id, reg->tag_id); + } + else + { + emit_single_pattern_cond(part, id, out); + } + first = 0; + part = strtok_r(NULL, "|", &saveptr); + } + fprintf(out, ")"); + free(pattern_copy); + } + else + { + // Single pattern (may be a range) + EnumVariantReg *reg = find_enum_variant(ctx, pattern); + if (reg) + { + fprintf(out, "_m_%d.tag == %d", id, reg->tag_id); + } + else + { + emit_single_pattern_cond(pattern, id, out); + } + } +} + // static function for internal use. static char *g_current_func_ret_type = NULL; static void codegen_match_internal(ParserContext *ctx, ASTNode *node, FILE *out, int use_result) @@ -138,29 +236,8 @@ static void codegen_match_internal(ParserContext *ctx, ASTNode *node, FILE *out, } else { - EnumVariantReg *reg = find_enum_variant(ctx, c->match_case.pattern); - if (reg) - { - fprintf(out, "_m_%d.tag == %d", id, reg->tag_id); - } - else if (c->match_case.pattern[0] == '"') - { - fprintf(out, "strcmp(_m_%d, %s) == 0", id, c->match_case.pattern); - } - else if (isdigit(c->match_case.pattern[0]) || c->match_case.pattern[0] == '-') - { - // Numeric pattern - fprintf(out, "_m_%d == %s", id, c->match_case.pattern); - } - else if (c->match_case.pattern[0] == '\'') - { - // Char literal pattern - fprintf(out, "_m_%d == %s", id, c->match_case.pattern); - } - else - { - fprintf(out, "1"); - } + // Use helper for OR patterns, range patterns, and simple patterns + emit_pattern_condition(ctx, c->match_case.pattern, id, out); } fprintf(out, ") { "); if (c->match_case.binding_name) diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index bfb45d8..d78a9e8 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -217,7 +217,11 @@ ASTNode *parse_match(ParserContext *ctx, Lexer *l) break; } - // --- 1. Parse Comma-Separated Patterns --- + // --- 1. Parse Patterns (with OR and range support) --- + // Patterns can be: + // - Single value: 1 + // - OR patterns: 1 || 2 or 1 or 2 + // - Range patterns: 1..5 or 1..=5 char patterns_buf[1024]; patterns_buf[0] = 0; int pattern_count = 0; @@ -238,20 +242,42 @@ ASTNode *parse_match(ParserContext *ctx, Lexer *l) p_str = tmp; } + // Check for range pattern: value..end or value..=end + if (lexer_peek(l).type == TOK_DOTDOT || lexer_peek(l).type == TOK_DOTDOT_EQ) + { + int is_inclusive = (lexer_peek(l).type == TOK_DOTDOT_EQ); + lexer_next(l); // eat .. or ..= + Token end_tok = lexer_next(l); + char *end_str = token_strdup(end_tok); + + // Build range pattern: "start..end" or "start..=end" + char *range_str = xmalloc(strlen(p_str) + strlen(end_str) + 4); + sprintf(range_str, "%s%s%s", p_str, is_inclusive ? "..=" : "..", end_str); + free(p_str); + free(end_str); + p_str = range_str; + } + if (pattern_count > 0) { - strcat(patterns_buf, ","); + strcat(patterns_buf, "|"); } strcat(patterns_buf, p_str); free(p_str); pattern_count++; - Lexer lookahead = *l; - skip_comments(&lookahead); - if (lexer_peek(&lookahead).type == TOK_COMMA) + // Check for OR continuation: ||, 'or', or comma (legacy) + Token next = lexer_peek(l); + skip_comments(l); + int is_or = (next.type == TOK_OR) || + (next.type == TOK_OP && next.len == 2 && next.start[0] == '|' && + next.start[1] == '|') || + (next.type == TOK_COMMA); // Legacy comma support + if (is_or) { - lexer_next(l); // eat comma + lexer_next(l); // eat ||, 'or', or comma skip_comments(l); + continue; } else { diff --git a/tests/control_flow/test_match.zc b/tests/control_flow/test_match.zc index 7646256..bf757b1 100644 --- a/tests/control_flow/test_match.zc +++ b/tests/control_flow/test_match.zc @@ -20,6 +20,16 @@ fn get_sign(n: int) -> string { } } +fn classify_extended(n: int) -> int { + match n { + 1 || 2 => { return 100; }, // OR pattern with || + 3 or 4 => { return 200; }, // OR pattern with 'or' + 5..8 => { return 300; }, // Range exclusive (5, 6, 7) + 10..=15 => { return 400; }, // Range inclusive (10-15) + _ => { return -1; } + } +} + test "test_match" { println "Testing match expressions..."; @@ -86,6 +96,48 @@ test "test_match" { exit(1); } + // OR pattern with || + var or1 = classify_extended(1); + var or2 = classify_extended(2); + if (or1 == 100 && or2 == 100) { + println " -> match OR (||): Passed"; + } else { + println " -> match OR (||): Failed"; + exit(1); + } + + // OR pattern with 'or' + var or3 = classify_extended(3); + var or4 = classify_extended(4); + if (or3 == 200 && or4 == 200) { + println " -> match OR (or): Passed"; + } else { + println " -> match OR (or): Failed"; + exit(1); + } + + // Range exclusive (5..8 matches 5, 6, 7) + var r5 = classify_extended(5); + var r7 = classify_extended(7); + var r8 = classify_extended(8); // Should NOT match + if (r5 == 300 && r7 == 300 && r8 == -1) { + println " -> match range exclusive: Passed"; + } else { + println " -> match range exclusive: Failed"; + exit(1); + } + + // Range inclusive (10..=15 matches 10-15) + var r10 = classify_extended(10); + var r15 = classify_extended(15); + var r16 = classify_extended(16); // Should NOT match + if (r10 == 400 && r15 == 400 && r16 == -1) { + println " -> match range inclusive: Passed"; + } else { + println " -> match range inclusive: Failed"; + exit(1); + } + println "All match tests passed!"; } -- cgit v1.2.3