summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-18 15:57:40 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-18 15:57:40 +0000
commit43c4f78eb3d2af7778bab6e51d77588bec62930d (patch)
treef8614e429ef913467420af7d08d104030ec6d434
parenta7eceb21faf04762379f2ce4d23d21bbc8c11929 (diff)
Update docs + add '..<' + add typed embed.
-rw-r--r--README.md63
-rw-r--r--src/lexer/token.c5
-rw-r--r--src/parser/parser.h2
-rw-r--r--src/parser/parser_expr.c6
-rw-r--r--src/parser/parser_stmt.c22
-rw-r--r--src/parser/parser_type.c110
-rw-r--r--src/zprep.h1
-rw-r--r--tests/control_flow/test_loops.zc8
-rw-r--r--tests/control_flow/test_match.zc3
-rw-r--r--tests/features/embed_data.txt1
-rw-r--r--tests/features/test_embed.zc26
11 files changed, 213 insertions, 34 deletions
diff --git a/README.md b/README.md
index fcf1634..3fd3872 100644
--- a/README.md
+++ b/README.md
@@ -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";
}