diff options
| author | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-23 14:54:12 +0000 |
|---|---|---|
| committer | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-23 14:54:12 +0000 |
| commit | 8d0ea93a7220730ccce754429549fd63e4eeaa7c (patch) | |
| tree | 8af868229559a54829991a20b7641d6c608a108d | |
| parent | f73df8d5de30a7f3f320fccf5f57c13094940a6a (diff) | |
Default arguments
| -rw-r--r-- | README.md | 27 | ||||
| -rw-r--r-- | src/main.c | 6 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 6 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 102 | ||||
| -rw-r--r-- | src/parser/parser_utils.c | 115 | ||||
| -rw-r--r-- | tests/features/test_default_args.zc | 39 |
6 files changed, 211 insertions, 84 deletions
@@ -48,6 +48,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [Type Aliases](#type-aliases) - [4. Functions & Lambdas](#4-functions--lambdas) - [Functions](#functions) + - [Default Arguments](#default-arguments) - [Lambdas (Closures)](#lambdas-closures) - [Variadic Functions](#variadic-functions) - [5. Control Flow](#5-control-flow) @@ -236,6 +237,32 @@ fn add(a: int, b: int) -> int { add(a: 10, b: 20); ``` +#### Default Arguments +Functions can define default values for trailing arguments. These can be literals, expressions, or valid Zen C code (like struct constructors). +```zc +// Simple default value +fn increment(val: int, amount: int = 1) -> int { + return val + amount; +} + +// Expression default value (evaluated at call site) +fn offset(val: int, pad: int = 10 * 2) -> int { + return val + pad; +} + +// Struct default value +struct Config { debug: bool; } +fn init(cfg: Config = Config { debug: true }) { + if cfg.debug { println "Debug Mode"; } +} + +fn main() { + increment(10); // 11 + offset(5); // 25 + init(); // Prints "Debug Mode" +} +``` + #### Lambdas (Closures) Anonymous functions that can capture their environment. ```zc @@ -37,6 +37,7 @@ void print_usage() printf(" transpile Transpile to C code only (no compilation)\n"); printf(" lsp Start Language Server\n"); printf("Options:\n"); + printf(" --help Print this help message\n"); printf(" --version Print version information\n"); printf(" -o <file> Output executable name\n"); printf(" --emit-c Keep generated C file (out.c)\n"); @@ -92,6 +93,11 @@ int main(int argc, char **argv) { // default mode } + else if (strcmp(command, "--help") == 0 || strcmp(command, "-h") == 0) + { + print_usage(); + return 0; + } else if (command[0] == '-') { // implicit build or run? assume build if starts with flag, but usually diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index be97707..bfbdb28 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -2093,8 +2093,10 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) { if (sig->defaults[i]) { - ASTNode *def = ast_create(NODE_RAW_STMT); - def->raw_stmt.content = xstrdup(sig->defaults[i]); + Lexer def_l; + lexer_init(&def_l, sig->defaults[i]); + ASTNode *def = parse_expression(ctx, &def_l); + if (!head) { head = def; diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index 5eefb81..dfc99db 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -1376,19 +1376,20 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, } expr = final_expr; - char *allocated_expr = NULL; clean_expr = final_expr; - int skip_rewrite = 0; + // Parse expression fully + Lexer lex; + lexer_init(&lex, clean_expr); + ASTNode *expr_node = parse_expression(ctx, &lex); - // Check if struct and has to_string (Robust Logic) - { - Lexer lex; - lexer_init(&lex, clean_expr); - // Parse using temporary lexer to check type - ASTNode *expr_node = parse_expression(ctx, &lex); + char *rw_expr = NULL; + int used_codegen = 0; - if (expr_node && expr_node->type_info) + if (expr_node) + { + // Check for to_string conversion on struct types + if (expr_node->type_info) { Type *t = expr_node->type_info; char *struct_name = NULL; @@ -1410,47 +1411,52 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, sprintf(mangled, "%s__to_string", struct_name); if (find_func(ctx, mangled)) { - char *inner_wrapped = xmalloc(strlen(clean_expr) + 5); - sprintf(inner_wrapped, "#{%s}", clean_expr); - char *inner_c = rewrite_expr_methods(ctx, inner_wrapped); - free(inner_wrapped); - - // Now wrap in to_string call using C99 compound literal for safety - char *new_expr = xmalloc(strlen(inner_c) + strlen(mangled) + 64); - if (is_ptr) - { - sprintf(new_expr, "%s(%s)", mangled, inner_c); - } - else + char *inner_c = NULL; + size_t len = 0; + FILE *ms = open_memstream(&inner_c, &len); + if (ms) { - sprintf(new_expr, "%s(({ %s _z_tmp = (%s); &_z_tmp; }))", mangled, - struct_name, inner_c); + codegen_expression(ctx, expr_node, ms); + fclose(ms); } - if (expr != s) + if (inner_c) { - free(expr); // Free if explicitly allocated + char *new_expr = xmalloc(strlen(inner_c) + strlen(mangled) + 64); + if (is_ptr) + { + sprintf(new_expr, "%s(%s)", mangled, inner_c); + } + else + { + sprintf(new_expr, "%s(({ %s _z_tmp = (%s); &_z_tmp; }))", mangled, + struct_name, inner_c); + } + rw_expr = new_expr; + free(inner_c); } - expr = new_expr; - skip_rewrite = 1; // Don't rewrite again on the C99 syntax } } } - } - // Rewrite the expression to handle pointer access (header_ptr.magic -> - // header_ptr->magic) - char *rw_expr; - if (skip_rewrite) - { - rw_expr = xstrdup(expr); + if (!rw_expr) + { + char *buf = NULL; + size_t len = 0; + FILE *ms = open_memstream(&buf, &len); + if (ms) + { + codegen_expression(ctx, expr_node, ms); + fclose(ms); + rw_expr = buf; + used_codegen = 1; + } + } } - else + + if (!rw_expr) { - char *wrapped_expr = xmalloc(strlen(expr) + 5); - sprintf(wrapped_expr, "#{%s}", expr); - rw_expr = rewrite_expr_methods(ctx, wrapped_expr); - free(wrapped_expr); + rw_expr = xstrdup(expr); // Fallback } if (fmt) @@ -1466,11 +1472,10 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, } else { - // Auto-detect format based on type if possible const char *format_spec = NULL; - char *inferred_type = find_symbol_type(ctx, clean_expr); // Simple variable lookup + Type *t = expr_node ? expr_node->type_info : NULL; + char *inferred_type = t ? type_to_string(t) : find_symbol_type(ctx, clean_expr); - // Basic Type Mappings if (inferred_type) { if (strcmp(inferred_type, "int") == 0 || strcmp(inferred_type, "i32") == 0 || @@ -1507,6 +1512,10 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, { format_spec = "%p"; // Pointer } + if (t) + { + free(inferred_type); + } } // Check for Literals if variable lookup failed @@ -1549,10 +1558,13 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, } } - free(rw_expr); // Don't forget to free! - if (allocated_expr) + if (rw_expr && used_codegen) + { + free(rw_expr); + } + else if (rw_expr && !used_codegen) { - free(allocated_expr); // Don't forget to free the auto-generated call! + free(rw_expr); } cur = p + 1; diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 918e8e1..97e6e78 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -1991,10 +1991,9 @@ char *instantiate_function_template(ParserContext *ctx, const char *name, const char *process_fstring(ParserContext *ctx, const char *content, char ***used_syms, int *count) { - (void)ctx; // suppress unused parameter warning - char *gen = xmalloc(4096); + char *gen = xmalloc(8192); // Increased buffer size - strcpy(gen, "({ static char _b[1024]; _b[0]=0; char _t[128]; "); + strcpy(gen, "({ static char _b[4096]; _b[0]=0; char _t[1024]; "); char *s = xstrdup(content); char *cur = s; @@ -2048,7 +2047,7 @@ char *process_fstring(ParserContext *ctx, const char *content, char ***used_syms } *p = 0; - char *expr = brace + 1; + char *expr_str = brace + 1; char *fmt = NULL; if (colon) { @@ -2056,34 +2055,19 @@ char *process_fstring(ParserContext *ctx, const char *content, char ***used_syms fmt = colon + 1; } - // Analyze usage in expression - { - Lexer lex; - lexer_init(&lex, expr); - Token t; - while ((t = lexer_next(&lex)).type != TOK_EOF) - { - if (t.type == TOK_IDENT) - { - char *name = token_strdup(t); - Symbol *sym = find_symbol_entry(ctx, name); - if (sym) - { - sym->is_used = 1; - } + // Parse expression fully to handle default arguments etc. + Lexer expr_lex; + lexer_init(&expr_lex, expr_str); + ASTNode *expr_node = parse_expression(ctx, &expr_lex); - if (used_syms && count) - { - *used_syms = xrealloc(*used_syms, sizeof(char *) * (*count + 1)); - (*used_syms)[*count] = name; - (*count)++; - } - else - { - free(name); - } - } - } + // Codegen expression to temporary buffer + char *code_buffer = NULL; + size_t code_len = 0; + FILE *mem_stream = open_memstream(&code_buffer, &code_len); + if (mem_stream) + { + codegen_expression(ctx, expr_node, mem_stream); + fclose(mem_stream); } if (fmt) @@ -2091,18 +2075,44 @@ char *process_fstring(ParserContext *ctx, const char *content, char ***used_syms strcat(gen, "sprintf(_t, \"%"); strcat(gen, fmt); strcat(gen, "\", "); - strcat(gen, expr); + if (code_buffer) + { + strcat(gen, code_buffer); + } + else + { + strcat(gen, expr_str); // Fallback + } strcat(gen, "); strcat(_b, _t); "); } else { strcat(gen, "sprintf(_t, _z_str("); - strcat(gen, expr); + if (code_buffer) + { + strcat(gen, code_buffer); + } + else + { + strcat(gen, expr_str); + } strcat(gen, "), "); - strcat(gen, expr); + if (code_buffer) + { + strcat(gen, code_buffer); + } + else + { + strcat(gen, expr_str); + } strcat(gen, "); strcat(_b, _t); "); } + if (code_buffer) + { + free(code_buffer); + } + cur = p + 1; } @@ -3261,9 +3271,40 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "=")) { - lexer_next(l); - Token val = lexer_next(l); - defaults[count - 1] = token_strdup(val); + lexer_next(l); // consume = + + const char *start_ptr = lexer_peek(l).start; + int nesting = 0; + while (1) + { + Token t = lexer_peek(l); + if (t.type == TOK_EOF) + { + zpanic_at(t, "Unexpected EOF in default arg"); + } + + if (nesting == 0 && (t.type == TOK_COMMA || t.type == TOK_RPAREN)) + { + break; + } + + if (t.type == TOK_LPAREN || t.type == TOK_LBRACE || t.type == TOK_LBRACKET) + { + nesting++; + } + if (t.type == TOK_RPAREN || t.type == TOK_RBRACE || t.type == TOK_RBRACKET) + { + nesting--; + } + + lexer_next(l); + } + const char *end_ptr = lexer_peek(l).start; + size_t len = end_ptr - start_ptr; + char *def_val = xmalloc(len + 1); + strncpy(def_val, start_ptr, len); + def_val[len] = 0; + defaults[count - 1] = def_val; } } if (lexer_peek(l).type == TOK_COMMA) diff --git a/tests/features/test_default_args.zc b/tests/features/test_default_args.zc new file mode 100644 index 0000000..cbeba83 --- /dev/null +++ b/tests/features/test_default_args.zc @@ -0,0 +1,39 @@ + +struct Point { + x: int; + y: int; +} + +fn add_points(p1: Point, p2: Point = Point { x: 10, y: 10 }) -> Point { + return Point { + x: p1.x + p2.x, + y: p1.y + p2.y + }; +} + +fn operation(a: int, b: int = 5 * 2) -> int { + return a + b; +} + +fn main() { + var p1 = Point { x: 5, y: 5 }; + var res1 = add_points(p1); + + println "res1.x: {res1.x}, res1.y: {res1.y}"; + assert(res1.x == 15, "result is not 15"); + assert(res1.y == 15, "result is not 15"); + + var p2 = Point { x: 1, y: 1 }; + var p3 = Point { x: 5, y: 5 }; + var res2 = add_points(p3, p2); + + println "res2.x: {res2.x}, res2.y: {res2.y}"; + assert(res2.x == 6, "result is not 6"); + assert(res2.y == 6, "result is not 6"); + + println "operation(10): {operation(10)}"; + assert(operation(10) == 20, "result is not 20"); + + println "operation(10, 5): {operation(10, 5)}"; + assert(operation(10, 5) == 15, "result is not 15"); +} |
