diff options
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | docs/std/README.md | 1 | ||||
| -rw-r--r-- | docs/std/io.md | 44 | ||||
| -rw-r--r-- | src/ast/ast.h | 29 | ||||
| -rw-r--r-- | src/codegen/codegen.c | 28 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 86 | ||||
| -rw-r--r-- | src/parser/parser_utils.c | 7 | ||||
| -rw-r--r-- | std/io.zc | 96 | ||||
| -rw-r--r-- | tests/features/test_varargs.zc | 22 |
9 files changed, 280 insertions, 48 deletions
@@ -49,6 +49,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [4. Functions & Lambdas](#4-functions--lambdas) - [Functions](#functions) - [Lambdas (Closures)](#lambdas-closures) + - [Variadic Functions](#variadic-functions) - [5. Control Flow](#5-control-flow) - [Conditionals](#conditionals) - [Pattern Matching](#pattern-matching) @@ -243,6 +244,17 @@ var double = x -> x * factor; // Arrow syntax var full = fn(x: int) -> int { return x * factor; }; // Block syntax ``` +#### Variadic Functions +Functions can accept a variable number of arguments using `...` and the `va_list` type. +```zc +fn log(lvl: int, fmt: char*, ...) { + var ap: va_list; + va_start(ap, fmt); + vprintf(fmt, ap); // Use C stdio + va_end(ap); +} +``` + ### 5. Control Flow #### Conditionals @@ -834,9 +846,10 @@ Zen C includes a standard library (`std`) covering essential functionality. | :--- | :--- | :--- | | **`std/vec.zc`** | Growable dynamic array `Vec<T>`. | [Docs](docs/std/vec.md) | | **`std/string.zc`** | Heap-allocated `String` type with UTF-8 support. | [Docs](docs/std/string.md) | -| **`std/queue.zc`** | FIFO queue. | [Docs](docs/std/queue.md) | +| **`std/queue.zc`** | FIFO queue (Ring Buffer). | [Docs](docs/std/queue.md) | | **`std/map.zc`** | Generic Hash Map `Map<V>`. | [Docs](docs/std/map.md) | | **`std/fs.zc`** | File system operations. | [Docs](docs/std/fs.md) | +| **`std/io.zc`** | Standard Input/Output (`print`/`println`). | [Docs](docs/std/io.md) | | **`std/option.zc`** | Optional values (`Some`/`None`). | [Docs](docs/std/option.md) | | **`std/result.zc`** | Error handling (`Ok`/`Err`). | [Docs](docs/std/result.md) | | **`std/path.zc`** | Cross-platform path manipulation. | [Docs](docs/std/path.md) | diff --git a/docs/std/README.md b/docs/std/README.md index f15b67a..6125a4e 100644 --- a/docs/std/README.md +++ b/docs/std/README.md @@ -2,6 +2,7 @@ - [Env (Environment)](./env.md) - Process environment variables. - [File System (FS)](./fs.md) - File I/O and directory operations. +- [IO](./io.md) - Standard Input/Output. - [Map](./map.md) - Hash map implementation. - [Option](./option.md) - Optional values (Some/None). - [Path](./path.md) - File path manipulation. diff --git a/docs/std/io.md b/docs/std/io.md new file mode 100644 index 0000000..825728d --- /dev/null +++ b/docs/std/io.md @@ -0,0 +1,44 @@ +# Standard Library: IO (`std/io.zc`) + +The `std/io` module provides standard input/output functionality, including printing to stdout and reading from stdin. + +## Usage + +```zc +import "std/io.zc" + +fn main() { + // Printing + io.println("Hello %s", "World"); + + // Formatting strings + autofree var s = io.format_new("Value: %d", 42); + + // Reading input + io.print("Enter name: "); + autofree var name = io.readln(); +} +``` + +## Functions + +### Output + +| Function | Signature | Description | +| :--- | :--- | :--- | +| **print** | `print(fmt: char*, ...) -> int` | Prints formatted output to stdout. | +| **println** | `println(fmt: char*, ...) -> int` | Prints formatted output to stdout with a newline. | + +### Formatting + +| Function | Signature | Description | +| :--- | :--- | :--- | +| **format** | `format(fmt: char*, ...) -> char*` | Formats string into a static buffer (returns pointer). **Not thread-safe**. | +| **format_into** | `format_into(buf: char*, size: usize, fmt: char*, ...) -> int` | Formats string into a user-provided buffer. | +| **format_new** | `format_new(fmt: char*, ...) -> char*` | Formats string into a newly allocated buffer (caller must free). | + +### Input + +| Function | Signature | Description | +| :--- | :--- | :--- | +| **readln** | `readln() -> char*` | Reads a line from stdin. Returns heap-allocated string (caller must free) or `NULL` on EOF/error. | diff --git a/src/ast/ast.h b/src/ast/ast.h index 508a247..523c1fb 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -127,7 +127,11 @@ typedef enum NODE_REFLECTION, NODE_AWAIT, NODE_REPL_PRINT, - NODE_CUDA_LAUNCH + NODE_CUDA_LAUNCH, + NODE_VA_START, + NODE_VA_END, + NODE_VA_COPY, + NODE_VA_ARG } NodeType; // ** AST Node Structure ** @@ -560,6 +564,29 @@ struct ASTNode ASTNode *shared_mem; // Optional shared memory size (NULL = default) ASTNode *stream; // Optional CUDA stream (NULL = default) } cuda_launch; + + struct + { + ASTNode *ap; + ASTNode *last_arg; + } va_start; + + struct + { + ASTNode *ap; + } va_end; + + struct + { + ASTNode *dest; + ASTNode *src; + } va_copy; + + struct + { + ASTNode *ap; + Type *type_info; + } va_arg; }; }; diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 3a93f91..22cbc15 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -896,6 +896,34 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, ")"); } break; + case NODE_VA_START: + fprintf(out, "va_start("); + codegen_expression(ctx, node->va_start.ap, out); + fprintf(out, ", "); + codegen_expression(ctx, node->va_start.last_arg, out); + fprintf(out, ")"); + break; + case NODE_VA_END: + fprintf(out, "va_end("); + codegen_expression(ctx, node->va_end.ap, out); + fprintf(out, ")"); + break; + case NODE_VA_COPY: + fprintf(out, "va_copy("); + codegen_expression(ctx, node->va_copy.dest, out); + fprintf(out, ", "); + codegen_expression(ctx, node->va_copy.src, out); + fprintf(out, ")"); + break; + case NODE_VA_ARG: + { + char *type_str = codegen_type_to_string(node->va_arg.type_info); + fprintf(out, "va_arg("); + codegen_expression(ctx, node->va_arg.ap, out); + fprintf(out, ", %s)", type_str); + free(type_str); + break; + } case NODE_EXPR_CAST: fprintf(out, "(%s)(", node->cast.target_type); codegen_expression(ctx, node->cast.expr, out); diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 4a4f906..be97707 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -3324,6 +3324,92 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) after_unary:; // Label to skip standard creation if overloaded } + else if (is_token(t, "va_start")) + { + lexer_next(l); + if (lexer_peek(l).type != TOK_LPAREN) + { + zpanic_at(t, "Expected '(' after va_start"); + } + lexer_next(l); + ASTNode *ap = parse_expression(ctx, l); + if (lexer_next(l).type != TOK_COMMA) + { + zpanic_at(t, "Expected ',' in va_start"); + } + ASTNode *last = parse_expression(ctx, l); + if (lexer_next(l).type != TOK_RPAREN) + { + zpanic_at(t, "Expected ')' after va_start args"); + } + lhs = ast_create(NODE_VA_START); + lhs->va_start.ap = ap; + lhs->va_start.last_arg = last; + } + else if (is_token(t, "va_end")) + { + lexer_next(l); + if (lexer_peek(l).type != TOK_LPAREN) + { + zpanic_at(t, "Expected '(' after va_end"); + } + lexer_next(l); + ASTNode *ap = parse_expression(ctx, l); + if (lexer_next(l).type != TOK_RPAREN) + { + zpanic_at(t, "Expected ')' after va_end arg"); + } + lhs = ast_create(NODE_VA_END); + lhs->va_end.ap = ap; + } + else if (is_token(t, "va_copy")) + { + lexer_next(l); + if (lexer_peek(l).type != TOK_LPAREN) + { + zpanic_at(t, "Expected '(' after va_copy"); + } + lexer_next(l); + ASTNode *dest = parse_expression(ctx, l); + if (lexer_next(l).type != TOK_COMMA) + { + zpanic_at(t, "Expected ',' in va_copy"); + } + ASTNode *src = parse_expression(ctx, l); + if (lexer_next(l).type != TOK_RPAREN) + { + zpanic_at(t, "Expected ')' after va_copy args"); + } + lhs = ast_create(NODE_VA_COPY); + lhs->va_copy.dest = dest; + lhs->va_copy.src = src; + } + else if (is_token(t, "va_arg")) + { + lexer_next(l); + if (lexer_peek(l).type != TOK_LPAREN) + { + zpanic_at(t, "Expected '(' after va_arg"); + } + lexer_next(l); + ASTNode *ap = parse_expression(ctx, l); + if (lexer_next(l).type != TOK_COMMA) + { + zpanic_at(t, "Expected ',' in va_arg"); + } + + Type *tinfo = parse_type_formal(ctx, l); + + if (lexer_next(l).type != TOK_RPAREN) + { + zpanic_at(t, "Expected ')' after va_arg args"); + } + + lhs = ast_create(NODE_VA_ARG); + lhs->va_arg.ap = ap; + lhs->va_arg.type_info = tinfo; + lhs->type_info = tinfo; // The expression evaluates to this type + } else if (is_token(t, "sizeof")) { lexer_next(l); diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index d3d9027..918e8e1 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -496,7 +496,14 @@ void register_builtins(ParserContext *ctx) add_symbol(ctx, "sprintf", "int", type_new(TYPE_INT)); add_symbol(ctx, "feof", "int", type_new(TYPE_INT)); add_symbol(ctx, "ferror", "int", type_new(TYPE_INT)); + add_symbol(ctx, "feof", "int", type_new(TYPE_INT)); + add_symbol(ctx, "ferror", "int", type_new(TYPE_INT)); add_symbol(ctx, "usleep", "int", type_new(TYPE_INT)); + + // Register va_list as opaque struct + ASTNode *va_def = ast_create(NODE_STRUCT); + va_def->strct.name = xstrdup("va_list"); + register_struct_def(ctx, "va_list", va_def); } void add_instantiated_func(ParserContext *ctx, ASTNode *fn) @@ -3,62 +3,66 @@ import "./core.zc" import "./string.zc" raw { - char* format(const char* fmt, ...) { - static char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - return buffer; + int _z_vprintf(char* fmt, va_list ap) { + return vprintf((const char*)fmt, ap); } - char* format_new(const char* fmt, ...) { - char* buffer = malloc(1024); - va_list args; - va_start(args, fmt); - vsnprintf(buffer, 1024, fmt, args); - va_end(args); - return buffer; + int _z_vsnprintf(char* str, size_t size, char* fmt, va_list ap) { + return vsnprintf(str, size, (const char*)fmt, ap); } - void* _z_get_stdin() - { - return stdin; - } + void* _z_get_stdin() { return stdin; } + int _z_get_eof() { return EOF; } + int _z_fgetc(void* stream) { return fgetc((FILE*)stream); } +} - int _z_get_eof() - { - return EOF; - } +extern fn _z_vprintf(fmt: char*, ap: va_list) -> int; +extern fn _z_vsnprintf(str: char*, size: usize, fmt: char*, ap: va_list) -> int; - int _z_fgetc(void* stream) - { - return fgetc((FILE*)stream); - } - - char* _z_format_alloc(const char* fmt, va_list args) { - int len = vsnprintf(NULL, 0, fmt, args); - if (len < 0) return NULL; - char* buffer = malloc(len + 1); - if (!buffer) return NULL; - vsnprintf(buffer, len + 1, fmt, args); - return buffer; - } - - char* format_s_raw(char* fmt, ...) { - va_list args; - va_start(args, fmt); - char* ret = _z_format_alloc(fmt, args); - va_end(args); - return ret; - } +fn format(fmt: char*, ...) -> char* { + static var buffer: char[1024]; + var ap: va_list; + va_start(ap, fmt); + _z_vsnprintf(buffer, 1024, fmt, ap); + va_end(ap); + return (char*)buffer; } -extern fn format_s_raw(fmt: char*, ...) -> char*; +fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> int { + var ap: va_list; + va_start(ap, fmt); + var ret = _z_vsnprintf(buffer, size, fmt, ap); + va_end(ap); + return ret; +} -// TODO: add format function. We will need to do a few updates... -// like adding support for varargs in Zen C functions. +fn format_new(fmt: char*, ...) -> char* { + var buffer: char* = malloc(1024); + if buffer == NULL return NULL; + + var ap: va_list; + va_start(ap, fmt); + _z_vsnprintf(buffer, 1024, fmt, ap); + va_end(ap); + return buffer; +} +fn print(fmt: char*, ...) -> int { + var ap: va_list; + va_start(ap, fmt); + var ret = _z_vprintf(fmt, ap); + va_end(ap); + return ret; +} + +fn println(fmt: char*, ...) -> int { + var ap: va_list; + va_start(ap, fmt); + var ret = _z_vprintf(fmt, ap); + va_end(ap); + puts(""); + return ret + 1; +} fn readln() -> char* { var cap: usize = 64; diff --git a/tests/features/test_varargs.zc b/tests/features/test_varargs.zc new file mode 100644 index 0000000..b06f66e --- /dev/null +++ b/tests/features/test_varargs.zc @@ -0,0 +1,22 @@ + +test "Variadic Sum" { + // We can't define full variadic functions inside tests easily due to closure wrapping in some contexts? + // Actually, global functions are better. But tests usually run in main. + // Let's rely on declared test function below. + assert(sum_all(3, 10, 20, 30) == 60); + assert(sum_all(5, 1, 1, 1, 1, 1) == 5); + puts("OK"); +} + +fn sum_all(count: int, ...) -> int { + var total = 0; + var ap: va_list; + va_start(ap, count); + + for (var i = 0; i < count; i = i + 1) { + total = total + va_arg(ap, int); + } + + va_end(ap); + return total; +} |
