diff options
| author | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-24 00:09:29 +0000 |
|---|---|---|
| committer | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-24 00:09:29 +0000 |
| commit | 27d041865d17a4055e7e6b3e297656d6f35a0f3b (patch) | |
| tree | 8fe0cbd2fecf24a6da3150dc4a2666a1c6dec0c3 | |
| parent | 1991cb62d26b954e54cf13c2d765fb3a0bbaa3ca (diff) | |
Welcome to 'def' + changed 'const'
| -rw-r--r-- | README.md | 32 | ||||
| -rw-r--r-- | src/codegen/codegen.c | 2 | ||||
| -rw-r--r-- | src/lexer/token.c | 4 | ||||
| -rw-r--r-- | src/parser/parser.h | 9 | ||||
| -rw-r--r-- | src/parser/parser_core.c | 10 | ||||
| -rw-r--r-- | src/parser/parser_decl.c | 21 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 60 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 7 | ||||
| -rw-r--r-- | src/zprep.h | 1 | ||||
| -rw-r--r-- | std/fs.zc | 6 | ||||
| -rw-r--r-- | std/net.zc | 4 | ||||
| -rw-r--r-- | tests/features/test_concurrency_suite.zc | 2 | ||||
| -rw-r--r-- | tests/features/test_const_def.zc | 32 | ||||
| -rw-r--r-- | tests/features/test_match_ref.zc | 2 | ||||
| -rw-r--r-- | tests/features/test_operators_suite.zc | 2 | ||||
| -rw-r--r-- | tests/memory/test_memory_safety.zc | 6 |
16 files changed, 148 insertions, 52 deletions
@@ -140,12 +140,25 @@ export ZC_ROOT=/path/to/Zen-C ### 1. Variables and Constants -Zen C uses type inference by default. +Zen C distinguishes between compile-time constants and runtime variables. + +#### Manifest Constants (`def`) +Values that exist only at compile-time (folded into code). Use these for array sizes, fixed configuration, and magic numbers. ```zc -var x = 42; // Inferred as int -const PI = 3.14159; // Compile-time constant -var explicit: float = 1.0; // Explicit type +def MAX_SIZE = 1024; +var buffer: char[MAX_SIZE]; // Valid array size +``` + +#### Variables (`var`) +Storage locations in memory. Can be mutable or read-only (`const`). + +```zc +var x = 10; // Mutable +x = 20; // OK + +var y: const int = 10; // Read-only (Type qualified) +// y = 20; // Error: cannot assign to const ``` ### 2. Primitive Types @@ -168,8 +181,9 @@ var explicit: float = 1.0; // Explicit type #### Arrays Fixed-size arrays with value semantics. ```zc -var ints: int[5] = {1, 2, 3, 4, 5}; -var zeros: [int; 5]; // Zero-initialized +def SIZE = 5; +var ints: int[SIZE] = {1, 2, 3, 4, 5}; +var zeros: [int; SIZE]; // Zero-initialized ``` #### Tuples @@ -262,7 +276,8 @@ add(a: 10, b: 20); > **Note**: Named arguments must strictly follow the defined parameter order. `add(b: 20, a: 10)` is invalid. #### Const Arguments -Function arguments can be marked as `const` to enforce read-only semantics. +Function arguments can be marked as `const` to enforce read-only semantics. This is a type qualifier, not a manifest constant. + ```zc fn print_val(v: const int) { // v = 10; // Error: Cannot assign to const variable @@ -782,6 +797,9 @@ var re = regex! { ^[a-z]+$ }; #### Generic C Macros Pass preprocessor macros through to C. + +> **Tip**: For simple constants, use `def` instead. Use `#define` when you need C-preprocessor macros or conditional compilation flags. + ```zc #define MAX_BUFFER 1024 ``` diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 95e0ccd..ebbfdb6 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -42,8 +42,6 @@ static void codegen_literal_expr(ASTNode *node, FILE *out) // Emit variable reference expression static void codegen_var_expr(ParserContext *ctx, ASTNode *node, FILE *out) { - (void)ctx; // May be used for context lookup in future - if (g_current_lambda) { for (int i = 0; i < g_current_lambda->lambda.num_captures; i++) diff --git a/src/lexer/token.c b/src/lexer/token.c index ea636b1..decabbe 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -145,6 +145,10 @@ Token lexer_next(Lexer *l) { return (Token){TOK_DEFER, s, 5, start_line, start_col}; } + if (len == 3 && strncmp(s, "def", 3) == 0) + { + return (Token){TOK_DEF, s, 3, start_line, start_col}; + } if (len == 8 && strncmp(s, "autofree", 8) == 0) { return (Token){TOK_AUTOFREE, s, 8, start_line, start_col}; diff --git a/src/parser/parser.h b/src/parser/parser.h index 6ac55bf..8857641 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -41,6 +41,7 @@ typedef struct Symbol int is_autofree; Token decl_token; int is_const_value; + int is_def; int const_int_val; int is_moved; struct Symbol *next; @@ -56,14 +57,14 @@ typedef struct Scope typedef struct FuncSig { char *name; - Token decl_token; // For LSP + Token decl_token; int total_args; char **defaults; Type **arg_types; Type *ret_type; int is_varargs; - int is_async; // Async function flag - int must_use; // Attribute: warn if return value discarded + int is_async; + int must_use; struct FuncSig *next; } FuncSig; @@ -438,7 +439,7 @@ ASTNode *parse_defer(ParserContext *ctx, Lexer *l); ASTNode *parse_asm(ParserContext *ctx, Lexer *l); ASTNode *parse_plugin(ParserContext *ctx, Lexer *l); ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l); -ASTNode *parse_const(ParserContext *ctx, Lexer *l); +ASTNode *parse_def(ParserContext *ctx, Lexer *l); ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l); ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async); diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c index 464c905..8410203 100644 --- a/src/parser/parser_core.c +++ b/src/parser/parser_core.c @@ -267,6 +267,10 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) s = ast_create(NODE_RAW_STMT); s->raw_stmt.content = content; } + else if (t.type == TOK_DEF) + { + s = parse_def(ctx, l); + } else if (t.type == TOK_IDENT) { // Inline function: inline fn name(...) { } @@ -337,9 +341,13 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) { s = parse_var_decl(ctx, l); } + else if (t.len == 3 && strncmp(t.start, "def", 3) == 0) + { + s = parse_def(ctx, l); + } else if (t.len == 5 && strncmp(t.start, "const", 5) == 0) { - s = parse_const(ctx, l); + zpanic_at(t, "'const' for declarations is deprecated. Use 'def' for constants or 'var x: const T' for read-only variables."); } else if (t.len == 6 && strncmp(t.start, "extern", 6) == 0) { diff --git a/src/parser/parser_decl.c b/src/parser/parser_decl.c index 5cac0b4..87b15ad 100644 --- a/src/parser/parser_decl.c +++ b/src/parser/parser_decl.c @@ -701,9 +701,9 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l) return n; } -ASTNode *parse_const(ParserContext *ctx, Lexer *l) +ASTNode *parse_def(ParserContext *ctx, Lexer *l) { - lexer_next(l); // eat const + lexer_next(l); // eat def Token n = lexer_next(l); char *type_str = NULL; @@ -723,7 +723,14 @@ ASTNode *parse_const(ParserContext *ctx, Lexer *l) type_obj = type_new(TYPE_UNKNOWN); // Ensure we have an object } type_obj->is_const = 1; + + // Use is_def flag for manifest constants add_symbol(ctx, ns, type_str ? type_str : "unknown", type_obj); + Symbol *sym_entry = find_symbol_entry(ctx, ns); + if (sym_entry) { + sym_entry->is_def = 1; + // is_const_value set only if literal + } ASTNode *i = 0; if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "=")) @@ -741,6 +748,7 @@ ASTNode *parse_const(ParserContext *ctx, Lexer *l) { s->is_const_value = 1; s->const_int_val = val; + s->is_def = 1; // Double ensure if (!s->type_name || strcmp(s->type_name, "unknown") == 0) { @@ -754,6 +762,7 @@ ASTNode *parse_const(ParserContext *ctx, Lexer *l) free(s->type_info); } s->type_info = type_new(TYPE_INT); + s->type_info->is_const = 1; } } } @@ -771,7 +780,7 @@ ASTNode *parse_const(ParserContext *ctx, Lexer *l) } else { - lexer_next(l); + zpanic_at(n, "'def' constants must be initialized"); } if (lexer_peek(l).type == TOK_SEMICOLON) @@ -783,6 +792,7 @@ ASTNode *parse_const(ParserContext *ctx, Lexer *l) o->var_decl.name = ns; o->var_decl.type_str = type_str; o->var_decl.init_expr = i; + // Store extra metadata if needed, but NODE_CONST usually suffices if (!ctx->current_scope || !ctx->current_scope->parent) { @@ -805,10 +815,7 @@ ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l) char *o = parse_type(ctx, l); - lexer_next(l); // consume ';' (parse_type doesn't consume it? parse_type calls parse_type_formal - // which doesn't consume ;?) - // Note: parse_type_stmt usually expects ; but parse_type just parses type expression. - // Check previous implementation: it had lexer_next(l) at end. This assumes ;. + lexer_next(l); ASTNode *node = ast_create(NODE_TYPE_ALIAS); node->type_alias.alias = xmalloc(n.len + 1); diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 9251f98..9d21e77 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -2327,35 +2327,48 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) } else { - node = ast_create(NODE_EXPR_VAR); - node->token = t; // Set source token - node->var_ref.name = acc; - node->type_info = find_symbol_type_info(ctx, acc); - Symbol *sym = find_symbol_entry(ctx, acc); - if (sym) - { - sym->is_used = 1; - node->definition_token = sym->decl_token; - } - - char *type_str = find_symbol_type(ctx, acc); - - if (type_str) + if (sym && sym->is_def && sym->is_const_value) { - node->resolved_type = type_str; - node->var_ref.suggestion = NULL; + // Constant Folding for 'def', emits literal + node = ast_create(NODE_EXPR_LITERAL); + node->token = t; + node->literal.type_kind = 0; // INT (assumed for now from const_int_val) + node->literal.int_val = sym->const_int_val; + node->type_info = type_new(TYPE_INT); + // No need for resolution } else { - node->resolved_type = xstrdup("unknown"); - if (should_suppress_undef_warning(ctx, acc)) + node = ast_create(NODE_EXPR_VAR); + node->token = t; // Set source token + node->var_ref.name = acc; + node->type_info = find_symbol_type_info(ctx, acc); + + if (sym) + { + sym->is_used = 1; + node->definition_token = sym->decl_token; + } + + char *type_str = find_symbol_type(ctx, acc); + + if (type_str) { + node->resolved_type = type_str; node->var_ref.suggestion = NULL; } else { - node->var_ref.suggestion = find_similar_symbol(ctx, acc); + node->resolved_type = xstrdup("unknown"); + if (should_suppress_undef_warning(ctx, acc)) + { + node->var_ref.suggestion = NULL; + } + else + { + node->var_ref.suggestion = find_similar_symbol(ctx, acc); + } } } } @@ -3405,6 +3418,15 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) lexer_next(l); // consume op ASTNode *operand = parse_expr_prec(ctx, l, PREC_UNARY); + if (is_token(t, "&") && operand->type == NODE_EXPR_VAR) + { + Symbol *s = find_symbol_entry(ctx, operand->var_ref.name); + if (s && s->is_def) + { + zpanic_at(t, "Cannot take address of manifest constant '%s' (use 'var' if you need an address)", operand->var_ref.name); + } + } + char *method = NULL; if (is_token(t, "-")) { diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index dfc99db..bc1849d 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -1869,6 +1869,10 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) { return parse_asm(ctx, l); } + if (tk.type == TOK_DEF) + { + return parse_def(ctx, l); + } // Identifiers (Keywords or Expressions) if (tk.type == TOK_IDENT) @@ -1956,9 +1960,10 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) } zpanic_at(next, "Expected 'var' after 'static'"); } + if (strncmp(tk.start, "const", 5) == 0 && tk.len == 5) { - return parse_const(ctx, l); + zpanic_at(tk, "'const' for declarations is deprecated. Use 'def' for constants or 'var x: const T' for read-only variables."); } if (strncmp(tk.start, "return", 6) == 0 && tk.len == 6) { diff --git a/src/zprep.h b/src/zprep.h index 18c4c51..03c17fc 100644 --- a/src/zprep.h +++ b/src/zprep.h @@ -59,6 +59,7 @@ typedef enum TOK_TEST, TOK_ASSERT, TOK_SIZEOF, + TOK_DEF, TOK_DEFER, TOK_AUTOFREE, TOK_QUESTION, @@ -3,9 +3,9 @@ import "./result.zc" import "./string.zc" import "./vec.zc" -const Z_SEEK_SET: int = 0; -const Z_SEEK_END: int = 2; -const Z_F_OK: int = 0; +def Z_SEEK_SET = 0; +def Z_SEEK_END = 2; +def Z_F_OK = 0; // TODO: restructure this tomorrow. @@ -9,8 +9,8 @@ import "./core.zc" import "./result.zc" import "./string.zc" -const Z_AF_INET = 2; -const Z_SOCK_STREAM = 1; +def Z_AF_INET = 2; +def Z_SOCK_STREAM = 1; raw { static int _z_net_bind(int fd, char *host, int port) { diff --git a/tests/features/test_concurrency_suite.zc b/tests/features/test_concurrency_suite.zc index 836594d..aa7512a 100644 --- a/tests/features/test_concurrency_suite.zc +++ b/tests/features/test_concurrency_suite.zc @@ -59,7 +59,7 @@ test "test_thread" { c.val = 0; c.lock = Mutex::new(); - const N = 10000; + def N = 10000; var t1_res = Thread::spawn(fn() { for (var i=0; i<N; ++i) { diff --git a/tests/features/test_const_def.zc b/tests/features/test_const_def.zc new file mode 100644 index 0000000..b104196 --- /dev/null +++ b/tests/features/test_const_def.zc @@ -0,0 +1,32 @@ + +test "def_constants" { + def PI = 3.14159; + def MAX = 100; + + assert(MAX == 100, "def constant value mismatch"); + // PI check (float) - exact match might be tricky but 3.14159 is literal + + var x = MAX; + assert(x == 100, "Assign from def"); +} + +test "def_scoping" { + def VAL = 1; + { + def VAL = 2; // Shadowing + assert(VAL == 2, "Shadowed def"); + } + assert(VAL == 1, "Original def preserved"); +} + +test "const_type_qualifier" { + var x: const int = 10; + assert(x == 10, "const Var init"); + + // Address of const var should be allowed + var p: const int* = &x; + assert(*p == 10, "Pointer to const var"); +} + +// Note: Negative tests (compilation failures) are hard to test in this harness currently +// but we verified the logic in parser_expr.c diff --git a/tests/features/test_match_ref.zc b/tests/features/test_match_ref.zc index cdd0835..0442dc7 100644 --- a/tests/features/test_match_ref.zc +++ b/tests/features/test_match_ref.zc @@ -50,7 +50,7 @@ enum Test { } test "match ref binding concrete" { - const t = Test::ONE(Hello{ world: 123 }); + var t = Test::ONE(Hello{ world: 123 }); var val = 0; match t { diff --git a/tests/features/test_operators_suite.zc b/tests/features/test_operators_suite.zc index 68b73fe..cf0f4c2 100644 --- a/tests/features/test_operators_suite.zc +++ b/tests/features/test_operators_suite.zc @@ -93,7 +93,7 @@ test "test_operator_overloading" { f1 |= f2; "Result bits: {f1.bits} (Expected 5)"; - const MAX_LIMIT = 100; + def MAX_LIMIT = 100; } test "test_extended_overloading" { diff --git a/tests/memory/test_memory_safety.zc b/tests/memory/test_memory_safety.zc index 020a7ba..c7ba01d 100644 --- a/tests/memory/test_memory_safety.zc +++ b/tests/memory/test_memory_safety.zc @@ -189,9 +189,9 @@ test "test_immutable" { } test "test_permissions" { - const READ : U8 = 0b100; - const WRITE : U8 = 0b010; - const EXEC : U8 = 0b001; + def READ : U8 = 0b100; + def WRITE : U8 = 0b010; + def EXEC : U8 = 0b001; var p1 = Permissions::new(READ); "Start: {p1.mask} (Read)"; |
