summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md15
-rw-r--r--docs/std/README.md1
-rw-r--r--docs/std/io.md44
-rw-r--r--src/ast/ast.h29
-rw-r--r--src/codegen/codegen.c28
-rw-r--r--src/parser/parser_expr.c86
-rw-r--r--src/parser/parser_utils.c7
-rw-r--r--std/io.zc96
-rw-r--r--tests/features/test_varargs.zc22
9 files changed, 280 insertions, 48 deletions
diff --git a/README.md b/README.md
index 1bd90b8..f5e47d9 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/std/io.zc b/std/io.zc
index 10ac712..5cfec3e 100644
--- a/std/io.zc
+++ b/std/io.zc
@@ -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;
+}