summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-23 14:54:12 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-23 14:54:12 +0000
commit8d0ea93a7220730ccce754429549fd63e4eeaa7c (patch)
tree8af868229559a54829991a20b7641d6c608a108d
parentf73df8d5de30a7f3f320fccf5f57c13094940a6a (diff)
Default arguments
-rw-r--r--README.md27
-rw-r--r--src/main.c6
-rw-r--r--src/parser/parser_expr.c6
-rw-r--r--src/parser/parser_stmt.c102
-rw-r--r--src/parser/parser_utils.c115
-rw-r--r--tests/features/test_default_args.zc39
6 files changed, 211 insertions, 84 deletions
diff --git a/README.md b/README.md
index f5e47d9..2995ad4 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/src/main.c b/src/main.c
index 6fa8809..da1bec6 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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");
+}