diff options
| -rw-r--r-- | README.md | 63 | ||||
| -rw-r--r-- | src/lexer/token.c | 5 | ||||
| -rw-r--r-- | src/parser/parser.h | 2 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 6 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 22 | ||||
| -rw-r--r-- | src/parser/parser_type.c | 110 | ||||
| -rw-r--r-- | src/zprep.h | 1 | ||||
| -rw-r--r-- | tests/control_flow/test_loops.zc | 8 | ||||
| -rw-r--r-- | tests/control_flow/test_match.zc | 3 | ||||
| -rw-r--r-- | tests/features/embed_data.txt | 1 | ||||
| -rw-r--r-- | tests/features/test_embed.zc | 26 |
11 files changed, 213 insertions, 34 deletions
@@ -194,7 +194,8 @@ match val { 2 || 3 => print("Two or Three"), // OR with || 4 or 5 => print("Four or Five"), // OR with 'or' 6, 7, 8 => print("Six to Eight"), // OR with comma - 10..15 => print("10 to 14"), // Exclusive range + 10..15 => print("10 to 14"), // Exclusive range (Legacy) + 10..<15 => print("10 to 14"), // Exclusive range (Explicit) 20..=25 => print("20 to 25"), // Inclusive range _ => print("Other") } @@ -211,6 +212,7 @@ match shape { ```zc // Range for i in 0..10 { ... } // Exclusive (0 to 9) +for i in 0..<10 { ... } // Exclusive (Explicit) for i in 0..=10 { ... } // Inclusive (0 to 10) for i in 0..10 step 2 { ... } @@ -240,16 +242,45 @@ unless is_valid { return; } ### 6. Operators -| Operator | Description | Function Mapping | +Zen C supports operator overloading for user-defined structs by implementing specific method names. + +#### Overloadable Operators + +| Category | Operator | Method Name | |:---|:---|:---| -| `+`, `-`, `*`, `/`, `%` | Arithmetic | `add`, `sub`, `mul`, `div`, `rem` | -| `==`, `!=`, `<`, `>` | Comparison | `eq`, `neq`, `lt`, `gt` | -| `[]` | Indexing | `get`, `set` | -| `\|>` | Pipeline (`x \|> f(y)` -> `f(x, y)`) | - | -| `??` | Null Coalescing (`val ?? default`) | - | -| `??=` | Null Assignment (`val ??= init`) | - | -| `?.` | Safe Navigation (`ptr?.field`) | - | -| `?` | Try Operator (`res?` returns error if present) | - | +| **Arithmetic** | `+`, `-`, `*`, `/`, `%` | `add`, `sub`, `mul`, `div`, `rem` | +| **Comparison** | `==`, `!=` | `eq`, `neq` | +| | `<`, `>`, `<=`, `>=` | `lt`, `gt`, `le`, `ge` | +| **Bitwise** | `&`, `\|`, `^` | `bitand`, `bitor`, `bitxor` | +| | `<<`, `>>` | `shl`, `shr` | +| **Unary** | `-` | `neg` | +| | `!` | `not` | +| | `~` | `bitnot` | +| **Index** | `a[i]` | `get(a, i)` | +| | `a[i] = v` | `set(a, i, v)` | + +**Example:** +```zc +impl Point { + fn add(self, other: Point) -> Point { + return Point{x: self.x + other.x, y: self.y + other.y}; + } +} + +var p3 = p1 + p2; // Calls p1.add(p2) +``` + +#### Syntactic Sugar + +These operators are built-in language features and cannot be overloaded directly. + +| Operator | Name | Description | +|:---|:---|:---| +| `\|>` | Pipeline | `x \|> f(y)` desugars to `f(x, y)` | +| `??` | Null Coalescing | `val ?? default` returns `default` if `val` is NULL (pointers) | +| `??=` | Null Assignment | `val ??= init` assigns if `val` is NULL | +| `?.` | Safe Navigation | `ptr?.field` accesses field only if `ptr` is not NULL | +| `?` | Try Operator | `res?` returns error if present (Result/Option types) | ### 7. Printing and String Interpolation @@ -329,7 +360,7 @@ Define methods on types using `impl`. ```zc impl Point { // Static method (constructor convention) - fn new(x: int, y: int) -> Point { + fn new(x: int, y: int) -> Self { return Point{x: x, y: y}; } @@ -428,9 +459,15 @@ println "Build Date: {build_date}"; ``` #### Embed -Embed files as byte arrays. +Embed files as specified types. ```zc -var png = embed "assets/logo.png"; +// Default (Slice_char) +var data = embed "assets/logo.png"; + +// Typed Embed +var text = embed "shader.glsl" as string; // Embbed as C-string +var rom = embed "bios.bin" as u8[1024]; // Embed as fixed array +var wav = embed "sound.wav" as u8[]; // Embed as Slice_u8 ``` #### Plugins diff --git a/src/lexer/token.c b/src/lexer/token.c index 9cc29a6..b58be3e 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -361,6 +361,11 @@ Token lexer_next(Lexer *l) len = 3; type = TOK_DOTDOT_EQ; } + else if (s[2] == '<') + { + len = 3; + type = TOK_DOTDOT_LT; + } else { len = 2; diff --git a/src/parser/parser.h b/src/parser/parser.h index f54bae0..cfe486d 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -394,7 +394,7 @@ ASTNode *parse_lambda(ParserContext *ctx, Lexer *l); char *parse_condition_raw(ParserContext *ctx, Lexer *l); char *parse_array_literal(ParserContext *ctx, Lexer *l, const char *st); char *parse_tuple_literal(ParserContext *ctx, Lexer *l, const char *tn); -char *parse_embed(ParserContext *ctx, Lexer *l); +ASTNode *parse_embed(ParserContext *ctx, Lexer *l); ASTNode *parse_macro_call(ParserContext *ctx, Lexer *l, char *name); ASTNode *parse_statement(ParserContext *ctx, Lexer *l); diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 99ce7d1..b9f022a 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -3075,10 +3075,10 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } // Case: [..] or [..end] - if (lexer_peek(l).type == TOK_DOTDOT) + if (lexer_peek(l).type == TOK_DOTDOT || lexer_peek(l).type == TOK_DOTDOT_LT) { is_slice = 1; - lexer_next(l); // consume .. + lexer_next(l); // consume .. or ..< if (lexer_peek(l).type != TOK_RBRACKET) { end = parse_expression(ctx, l); @@ -3088,7 +3088,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) { // Case: [start] or [start..] or [start..end] start = parse_expression(ctx, l); - if (lexer_peek(l).type == TOK_DOTDOT) + if (lexer_peek(l).type == TOK_DOTDOT || lexer_peek(l).type == TOK_DOTDOT_LT) { is_slice = 1; lexer_next(l); // consume .. diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index 6a0f50d..5307768 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -242,11 +242,12 @@ ASTNode *parse_match(ParserContext *ctx, Lexer *l) p_str = tmp; } - // Check for range pattern: value..end or value..=end - if (lexer_peek(l).type == TOK_DOTDOT || lexer_peek(l).type == TOK_DOTDOT_EQ) + // Check for range pattern: value..end, value..<end or value..=end + if (lexer_peek(l).type == TOK_DOTDOT || lexer_peek(l).type == TOK_DOTDOT_EQ || + lexer_peek(l).type == TOK_DOTDOT_LT) { int is_inclusive = (lexer_peek(l).type == TOK_DOTDOT_EQ); - lexer_next(l); // eat .. or ..= + lexer_next(l); // eat operator Token end_tok = lexer_next(l); char *end_str = token_strdup(end_tok); @@ -1071,9 +1072,12 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l) Token next = lexer_peek(l); if (next.type == TOK_IDENT && strncmp(next.start, "embed", 5) == 0) { - char *e = parse_embed(ctx, l); - init = ast_create(NODE_RAW_STMT); - init->raw_stmt.content = e; + init = parse_embed(ctx, l); + + if (!type && init->type_info) + { + type = type_to_string(init->type_info); + } if (!type) { register_slice(ctx, "char"); @@ -1583,9 +1587,9 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) { ASTNode *start_expr = parse_expression(ctx, l); int is_inclusive = 0; - if (lexer_peek(l).type == TOK_DOTDOT) + if (lexer_peek(l).type == TOK_DOTDOT || lexer_peek(l).type == TOK_DOTDOT_LT) { - lexer_next(l); // consume .. + lexer_next(l); // consume .. or ..< } else if (lexer_peek(l).type == TOK_DOTDOT_EQ) @@ -3327,7 +3331,7 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) continue; } - // --- HANDLE 'use' (Struct Embedding) --- + // Handle 'use' (Struct Embedding) if (t.type == TOK_USE) { lexer_next(l); // eat use diff --git a/src/parser/parser_type.c b/src/parser/parser_type.c index 27328fe..3d5bff8 100644 --- a/src/parser/parser_type.c +++ b/src/parser/parser_type.c @@ -874,7 +874,7 @@ char *parse_tuple_literal(ParserContext *ctx, Lexer *l, const char *tn) free(c); return o; } -char *parse_embed(ParserContext *ctx, Lexer *l) +ASTNode *parse_embed(ParserContext *ctx, Lexer *l) { lexer_next(l); Token t = lexer_next(l); @@ -886,6 +886,15 @@ char *parse_embed(ParserContext *ctx, Lexer *l) strncpy(fn, t.start + 1, t.len - 2); fn[t.len - 2] = 0; + // Check for optional "as Type" + Type *target_type = NULL; + if (lexer_peek(l).type == TOK_IDENT && lexer_peek(l).len == 2 && + strncmp(lexer_peek(l).start, "as", 2) == 0) + { + lexer_next(l); // consume 'as' + target_type = parse_type_formal(ctx, l); + } + FILE *f = fopen(fn, "rb"); if (!f) { @@ -898,16 +907,103 @@ char *parse_embed(ParserContext *ctx, Lexer *l) fread(b, 1, len, f); fclose(f); - register_slice(ctx, "char"); - size_t oc = len * 6 + 128; + size_t oc = len * 6 + 256; char *o = xmalloc(oc); - sprintf(o, "(Slice_char){.data=(char[]){"); + + // Default Type if none + if (!target_type) + { + // Default: Slice_char + register_slice(ctx, "char"); + + Type *slice_type = type_new(TYPE_STRUCT); + slice_type->name = xstrdup("Slice_char"); + target_type = slice_type; + + sprintf(o, "(Slice_char){.data=(char[]){"); + } + else + { + // Handle specific type + char *ts = type_to_string(target_type); + + if (target_type->kind == TYPE_ARRAY) + { + char *inner_ts = type_to_string(target_type->inner); + if (target_type->array_size > 0) + { + Type *ptr_type = type_new_ptr(target_type->inner); // Reuse inner + target_type = ptr_type; + sprintf(o, "(%s[]){", inner_ts); + } + else + { + // Slice -> Slice_T struct + register_slice(ctx, inner_ts); + char slice_name[256]; + sprintf(slice_name, "Slice_%s", inner_ts); + Type *slice_t = type_new(TYPE_STRUCT); + slice_t->name = xstrdup(slice_name); + target_type = slice_t; + sprintf(o, "(%s){.data=(%s[]){", slice_name, inner_ts); + } + free(inner_ts); + } + else + { + if (strcmp(ts, "string") == 0 || strcmp(ts, "char*") == 0) + { + sprintf(o, "(char*)\""); + } + else + { + sprintf(o, "(%s){", ts); + } + } + free(ts); + } + char *p = o + strlen(o); + + // Check if string mode + int is_string = (target_type && (strcmp(type_to_string(target_type), "string") == 0 || + strcmp(type_to_string(target_type), "char*") == 0)); + for (int i = 0; i < len; i++) { - p += sprintf(p, "0x%02X,", b[i]); + if (is_string) + { + // Hex escape for string + p += sprintf(p, "\\x%02X", b[i]); + } + else + { + p += sprintf(p, "0x%02X,", b[i]); + } + } + + if (is_string) + { + sprintf(p, "\""); } - sprintf(p, "},.len=%ld,.cap=%ld}", len, len); + else + { + int is_slice = (strncmp(o, "(Slice_", 7) == 0); + + if (is_slice) + { + sprintf(p, "},.len=%ld,.cap=%ld}", len, len); + } + else + { + sprintf(p, "}"); + } + } + free(b); - return o; + + ASTNode *n = ast_create(NODE_RAW_STMT); + n->raw_stmt.content = o; + n->type_info = target_type; + return n; } diff --git a/src/zprep.h b/src/zprep.h index a242548..f9bb6b6 100644 --- a/src/zprep.h +++ b/src/zprep.h @@ -53,6 +53,7 @@ typedef enum TOK_AT, TOK_DOTDOT, TOK_DOTDOT_EQ, + TOK_DOTDOT_LT, TOK_ARROW, TOK_PIPE, TOK_TEST, diff --git a/tests/control_flow/test_loops.zc b/tests/control_flow/test_loops.zc index 882ae7b..60ccc50 100644 --- a/tests/control_flow/test_loops.zc +++ b/tests/control_flow/test_loops.zc @@ -164,3 +164,11 @@ test "exclusive range regression check" { } assert(count3 == 5, "Expected 5 iterations for 0..5") } + +test "explicit exclusive range (..<)" { + var count4 = 0 + for i in 0..<5 { + count4 += 1 + } + assert(count4 == 5, "Expected 5 iterations for 0..<5") +} diff --git a/tests/control_flow/test_match.zc b/tests/control_flow/test_match.zc index bf757b1..ca185ae 100644 --- a/tests/control_flow/test_match.zc +++ b/tests/control_flow/test_match.zc @@ -24,7 +24,8 @@ fn classify_extended(n: int) -> int { match n { 1 || 2 => { return 100; }, // OR pattern with || 3 or 4 => { return 200; }, // OR pattern with 'or' - 5..8 => { return 300; }, // Range exclusive (5, 6, 7) + 5..<8 => { return 300; }, // Use ..< for exclusive range (originally 5..8) + 8..8 => { return 305; }, // Single valid? (empty range) 10..=15 => { return 400; }, // Range inclusive (10-15) _ => { return -1; } } diff --git a/tests/features/embed_data.txt b/tests/features/embed_data.txt new file mode 100644 index 0000000..980a0d5 --- /dev/null +++ b/tests/features/embed_data.txt @@ -0,0 +1 @@ +Hello World! diff --git a/tests/features/test_embed.zc b/tests/features/test_embed.zc index f6d9468..33acef2 100644 --- a/tests/features/test_embed.zc +++ b/tests/features/test_embed.zc @@ -53,5 +53,31 @@ test "test_embed" { ""; "Analysis successfully completed."; +} + +test "typed_embed" { + // String + var s = embed "tests/features/embed_data.txt" as string; + // "Hello World!\n" ? No newline in my file creation? + // echo "Hello World!" > ... likely adds newline. + // My previous tool 'write_to_file' wrote "Hello World!". It usually doesn't add newline unless specified? + // Let's verify start content. + if (s[0] != 'H') exit(101); + + // Fixed array + var arr = embed "tests/features/embed_data.txt" as u8[5]; + if (arr[0] != 'H') exit(102); + if (arr[4] != 'o') exit(103); + + // Slice + var sl = embed "tests/features/embed_data.txt" as u8[]; + if (sl.len < 5) exit(104); + if (sl.data[0] != 'H') exit(105); + + // Untyped regression + var raw = embed "tests/features/embed_data.txt"; + if (raw.len < 5) exit(106); + if (raw.data[0] != 'H') exit(107); + println "Typed embed tests passed"; } |
