From a61c2e7b2afa685e76d259d06bec5cdee3739ae0 Mon Sep 17 00:00:00 2001 From: Squirrel Date: Tue, 27 Jan 2026 10:58:50 -0800 Subject: this kinda sucks but uhh it works --- std/regex.zc | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 std/regex.zc (limited to 'std') diff --git a/std/regex.zc b/std/regex.zc new file mode 100644 index 0000000..346985e --- /dev/null +++ b/std/regex.zc @@ -0,0 +1,194 @@ +include +include +import "./core.zc" +import "./string.zc" +import "./vec.zc" +import "./option.zc" + +struct Match { + text: char*; + start: int; + len: int; +} + +impl Match { + fn new(text: char*, start: int, len: int) -> Match { + return Match { text: text, start: start, len: len }; + } + + fn as_string(self) -> char* { + return self.text; + } + + fn end(self) -> int { + return self.start + self.len; + } +} + +struct Regex { + preg: void*; + pattern: char*; + flags: int; +} + +impl Regex { + fn compile(pattern: char*) -> Regex { + return Regex::compile_with_flags(pattern, 1); + } + + fn compile_with_flags(pattern: char*, flags: int) -> Regex { + let preg = malloc(128); + let status = regcomp(preg, pattern, flags); + if (status != 0) { + free(preg); + return Regex { preg: 0, pattern: 0, flags: flags }; + } + return Regex { preg: preg, pattern: pattern, flags: flags }; + } + + fn is_valid(self) -> bool { + return self.preg != 0; + } + + fn match(self, text: char*) -> bool { + if (self.preg == 0) { return false; } + return regexec(self.preg, text, 0, 0, 0) == 0; + } + + fn match_at(self, text: char*, offset: int) -> bool { + if (self.preg == 0) { return false; } + let len = strlen(text); + if (offset < 0 || offset > len) { return false; } + return regexec(self.preg, text + offset, 0, 0, 0) == 0; + } + + fn is_match(self, text: char*) -> bool { + return self.match(text); + } + + fn find(self, text: char*) -> Option { + if (self.preg == 0) { return Option::None(); } + let t_len = strlen(text); + for (let i = 0; i <= t_len; i = i + 1) { + let sub = text + i; + if (regexec(self.preg, sub, 0, 0, 0) == 0) { + let j = 0; + while (text[i + j] != 0 && regexec(self.preg, sub, 0, 0, 0) == 0) { + j = j + 1; + sub = text + i + j; + } + return Option::Some(Match::new(text + i, i, j)); + } + } + return Option::None(); + } + + fn find_at(self, text: char*, start: int) -> Option { + let len = strlen(text); + if (start < 0 || start >= len) { + return Option::None(); + } + return self.find(text + start); + } + + fn count(self, text: char*) -> int { + if (self.preg == 0) { return 0; } + let count = 0; + let pos = 0; + let t_len = strlen(text); + while (pos < t_len) { + let sub = text + pos; + if (regexec(self.preg, sub, 0, 0, 0) == 0) { + count = count + 1; + pos = pos + 1; + } else { + break; + } + } + return count; + } + + fn split(self, text: char*) -> Vec { + let parts = Vec::new(); + if (self.preg == 0) { + parts.push(String::from(text)); + return parts; + } + let t_len = strlen(text); + let last_pos = 0; + let pos = 0; + while (pos < t_len) { + let sub = text + pos; + if (regexec(self.preg, sub, 0, 0, 0) == 0) { + if (pos > last_pos) { + let before = text + last_pos; + let part_len = pos - last_pos; + let v = Vec::new(); + for (let i = 0; i < part_len; i = i + 1) { + v.push(before[i]); + } + v.push(0); + parts.push(String { vec: v }); + } + last_pos = pos + 1; + pos = pos + 1; + } else { + pos = pos + 1; + } + } + if (last_pos < t_len) { + parts.push(String::from(text + last_pos)); + } + return parts; + } + + fn pattern(self) -> char* { + return self.pattern; + } + + fn flags(self) -> int { + return self.flags; + } + + fn is_valid_pattern(pattern: char*) -> bool { + let test_regex = Regex::compile(pattern); + let valid = test_regex.is_valid(); + test_regex.destroy(); + return valid; + } + + fn destroy(self) { + if (self.preg != 0) { + regfree(self.preg); + free(self.preg); + } + } +} + +fn regex_match(pattern: char*, text: char*) -> bool { + let re = Regex::compile(pattern); + let result = re.match(text); + re.destroy(); + return result; +} + +fn regex_find(pattern: char*, text: char*) -> Option { + let re = Regex::compile(pattern); + let result = re.find(text); + re.destroy(); + return result; +} + +fn regex_count(pattern: char*, text: char*) -> int { + let re = Regex::compile(pattern); + let count = re.count(text); + re.destroy(); + return count; +} + +fn regex_split(pattern: char*, text: char*) -> Vec { + let re = Regex::compile(pattern); + let parts = re.split(text); + re.destroy(); + return parts; +} -- cgit v1.2.3 From 2bfa3286eda81375422d9dda38b110512749cef9 Mon Sep 17 00:00:00 2001 From: Squirrel Date: Tue, 27 Jan 2026 18:54:07 -0800 Subject: fix MAYBE..................... --- std/regex.zc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'std') diff --git a/std/regex.zc b/std/regex.zc index 346985e..92f1a54 100644 --- a/std/regex.zc +++ b/std/regex.zc @@ -1,5 +1,6 @@ include include + import "./core.zc" import "./string.zc" import "./vec.zc" @@ -33,11 +34,11 @@ struct Regex { impl Regex { fn compile(pattern: char*) -> Regex { - return Regex::compile_with_flags(pattern, 1); + return Regex::compile_with_flags(pattern, 1 | 2); } fn compile_with_flags(pattern: char*, flags: int) -> Regex { - let preg = malloc(128); + let preg = malloc(1024); let status = regcomp(preg, pattern, flags); if (status != 0) { free(preg); @@ -55,6 +56,10 @@ impl Regex { return regexec(self.preg, text, 0, 0, 0) == 0; } + fn match_full(self, text: char*) -> bool { + return self.match(text); + } + fn match_at(self, text: char*, offset: int) -> bool { if (self.preg == 0) { return false; } let len = strlen(text); -- cgit v1.2.3 From 18da1f89cc28c33d9621214158176b9531175f36 Mon Sep 17 00:00:00 2001 From: Squirrel Date: Tue, 27 Jan 2026 18:55:09 -0800 Subject: oh yea core.zc has string ok no need include string --- std/regex.zc | 1 - 1 file changed, 1 deletion(-) (limited to 'std') diff --git a/std/regex.zc b/std/regex.zc index 92f1a54..f64b36e 100644 --- a/std/regex.zc +++ b/std/regex.zc @@ -1,5 +1,4 @@ include -include import "./core.zc" import "./string.zc" -- cgit v1.2.3 From 472434301940015365f7ed303f52d71c505ac487 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 19:44:32 +0000 Subject: Improvements for the standard library + '@ctype'. --- Makefile | 2 +- README.md | 1 + README_ES.md | 1 + README_ZH_CN.md | 1 + README_ZH_TW.md | 1 + src/ast/ast.h | 2 ++ src/codegen/codegen_utils.c | 8 +++++- src/parser/parser.h | 3 ++- src/parser/parser_decl.c | 6 +++-- src/parser/parser_struct.c | 2 +- src/parser/parser_utils.c | 53 ++++++++++++++++++++++++++++++++++++-- std/core.zc | 6 ++--- std/cuda.zc | 2 ++ std/env.zc | 29 +++++++-------------- std/fs.zc | 62 ++++++++++++++++++--------------------------- std/io.zc | 43 ++++++++++++++++--------------- std/map.zc | 22 +++++++++------- std/net.zc | 25 ++++++++++-------- std/process.zc | 21 ++++++++------- std/set.zc | 20 +++++++-------- std/thread.zc | 5 ++++ std/time.zc | 3 +++ 22 files changed, 192 insertions(+), 126 deletions(-) (limited to 'std') diff --git a/Makefile b/Makefile index d15f556..b2d8e29 100644 --- a/Makefile +++ b/Makefile @@ -188,7 +188,7 @@ clean: @echo "=> Clean complete!" # Test -test: $(TARGET) +test: $(TARGET) $(PLUGINS) ./tests/run_tests.sh ./tests/run_codegen_tests.sh ./tests/run_example_transpile.sh diff --git a/README.md b/README.md index f03f605..001e590 100644 --- a/README.md +++ b/README.md @@ -939,6 +939,7 @@ Decorate functions and structs to modify compiler behavior. | `@host` | Fn | CUDA: Host function (`__host__`). | | `@comptime` | Fn | Helper function available for compile-time execution. | | `@derive(...)` | Struct | Auto-implement traits. Supports `Debug`, `Eq` (Smart Derive), `Copy`, `Clone`. | +| `@ctype("type")` | Fn Param | Overrides generated C type for a parameter. | | `@` | Any | Passes generic attributes to C (e.g. `@flatten`, `@alias("name")`). | #### Custom Attributes diff --git a/README_ES.md b/README_ES.md index 9040ea8..999a7b4 100644 --- a/README_ES.md +++ b/README_ES.md @@ -938,6 +938,7 @@ Decora funciones y structs para modificar el comportamiento del compilador. | `@host` | Fn | CUDA: Función de host (`__host__`). | | `@comptime` | Fn | Función auxiliar disponible para ejecución en tiempo de compilación. | | `@derive(...)` | Struct | Implementa traits automáticamente. Soporta `Debug`, `Eq` (Derivación Inteligente), `Copy`, `Clone`. | +| `@ctype("tipo")` | Parámetro Fn | Sobrescribe el tipo C generado para un parámetro. | | `@` | Cualquier | Pasa atributos genéricos a C (ej. `@flatten`, `@alias("nombre")`). | #### Atributos Personalizados diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 217e9ec..daa8a3d 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -938,6 +938,7 @@ let re = regex! { ^[a-z]+$ }; | `@host` | 函数 | CUDA: 主机函数 (`__host__`)。 | | `@comptime` | 函数 | 用于编译时执行的辅助函数。 | | `@derive(...)` | 结构体 | 自动实现 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@ctype("type")` | 函数参数 | 覆盖参数生成的 C 类型。 | | `@` | 任意 | 将泛型属性传递给 C (例如 `@flatten`, `@alias("name")`)。 | #### 自定义属性 diff --git a/README_ZH_TW.md b/README_ZH_TW.md index 8618540..2707caa 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -938,6 +938,7 @@ let re = regex! { ^[a-z]+$ }; | `@host` | 函數 | CUDA: 主機函數 (`__host__`)。 | | `@comptime` | 函數 | 用於編譯時執行的輔助函數。 | | `@derive(...)` | 結構體 | 自動實現 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@ctype("type")` | 函數參數 | 覆蓋參數生成的 C 類型。 | | `@` | 任意 | 將泛型屬性傳遞給 C (例如 `@flatten`, `@alias("name")`)。 | #### 自定義屬性 diff --git a/src/ast/ast.h b/src/ast/ast.h index 71d9943..4498d7c 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -229,6 +229,8 @@ struct ASTNode int cuda_device; // @device -> __device__ int cuda_host; // @host -> __host__ + char **c_type_overrides; // @ctype("...") per parameter + Attribute *attributes; // Custom attributes } func; diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 391ebd3..0d03661 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -714,7 +714,12 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } char *type_str = NULL; - if (func->func.arg_types && func->func.arg_types[i]) + // Check for @ctype override first + if (func->func.c_type_overrides && func->func.c_type_overrides[i]) + { + type_str = xstrdup(func->func.c_type_overrides[i]); + } + else if (func->func.arg_types && func->func.arg_types[i]) { type_str = codegen_type_to_string(func->func.arg_types[i]); } @@ -724,6 +729,7 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } const char *name = ""; + if (func->func.param_names && func->func.param_names[i]) { name = func->func.param_names[i]; diff --git a/src/parser/parser.h b/src/parser/parser.h index 262c359..23c2920 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -561,7 +561,8 @@ ASTNode *parse_arrow_lambda_multi(ParserContext *ctx, Lexer *l, char **param_nam * @brief Parses and converts arguments. */ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out, - Type ***types_out, char ***names_out, int *is_varargs_out); + Type ***types_out, char ***names_out, int *is_varargs_out, + char ***ctype_overrides_out); /** * @brief Checks if a file has been imported. diff --git a/src/parser/parser_decl.c b/src/parser/parser_decl.c index c96ca36..93a124d 100644 --- a/src/parser/parser_decl.c +++ b/src/parser/parser_decl.c @@ -99,10 +99,11 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async) int count; Type **arg_types; char **param_names; + char **ctype_overrides; int is_varargs = 0; - char *args = - parse_and_convert_args(ctx, l, &defaults, &count, &arg_types, ¶m_names, &is_varargs); + char *args = parse_and_convert_args(ctx, l, &defaults, &count, &arg_types, ¶m_names, + &is_varargs, &ctype_overrides); char *ret = "void"; Type *ret_type_obj = type_new(TYPE_VOID); @@ -191,6 +192,7 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async) node->func.defaults = defaults; node->func.ret_type_info = ret_type_obj; node->func.is_varargs = is_varargs; + node->func.c_type_overrides = ctype_overrides; if (gen_param) { diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c index 82dd346..109eeee 100644 --- a/src/parser/parser_struct.c +++ b/src/parser/parser_struct.c @@ -100,7 +100,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l) char **param_names = NULL; int is_varargs = 0; char *args = parse_and_convert_args(ctx, l, &defaults, &arg_count, &arg_types, ¶m_names, - &is_varargs); + &is_varargs, NULL); char *ret = xstrdup("void"); if (lexer_peek(l).type == TOK_ARROW) diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 48418b6..28d2c11 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -3392,7 +3392,8 @@ char *consume_and_rewrite(ParserContext *ctx, Lexer *l) } char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out, - Type ***types_out, char ***names_out, int *is_varargs_out) + Type ***types_out, char ***names_out, int *is_varargs_out, + char ***ctype_overrides_out) { Token t = lexer_next(l); if (t.type != TOK_LPAREN) @@ -3406,18 +3407,52 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, char **defaults = xmalloc(sizeof(char *) * 16); Type **types = xmalloc(sizeof(Type *) * 16); char **names = xmalloc(sizeof(char *) * 16); + char **ctype_overrides = xmalloc(sizeof(char *) * 16); for (int i = 0; i < 16; i++) { defaults[i] = NULL; types[i] = NULL; names[i] = NULL; + ctype_overrides[i] = NULL; } if (lexer_peek(l).type != TOK_RPAREN) { while (1) { + // Check for @ctype("...") before parameter + char *ctype_override = NULL; + if (lexer_peek(l).type == TOK_AT) + { + lexer_next(l); // eat @ + Token attr = lexer_next(l); + if (attr.type == TOK_IDENT && attr.len == 5 && strncmp(attr.start, "ctype", 5) == 0) + { + if (lexer_next(l).type != TOK_LPAREN) + { + zpanic_at(lexer_peek(l), "Expected ( after @ctype"); + } + Token ctype_tok = lexer_next(l); + if (ctype_tok.type != TOK_STRING) + { + zpanic_at(ctype_tok, "@ctype requires a string argument"); + } + // Extract string content (strip quotes) + ctype_override = xmalloc(ctype_tok.len - 1); + strncpy(ctype_override, ctype_tok.start + 1, ctype_tok.len - 2); + ctype_override[ctype_tok.len - 2] = 0; + if (lexer_next(l).type != TOK_RPAREN) + { + zpanic_at(lexer_peek(l), "Expected ) after @ctype string"); + } + } + else + { + zpanic_at(attr, "Unknown parameter attribute @%.*s", attr.len, attr.start); + } + } + Token t = lexer_next(l); // Handle 'self' if (t.type == TOK_IDENT && strncmp(t.start, "self", 4) == 0 && t.len == 4) @@ -3470,6 +3505,7 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, types[count] = type_new_ptr(type_new(TYPE_VOID)); add_symbol(ctx, "self", "void*", types[count]); } + ctype_overrides[count] = ctype_override; count++; } else @@ -3514,11 +3550,20 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, } else { - strcat(buf, type_str); + // Use @ctype override if present + if (ctype_override) + { + strcat(buf, ctype_override); + } + else + { + strcat(buf, type_str); + } strcat(buf, " "); strcat(buf, name); } + ctype_overrides[count] = ctype_override; count++; if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "=")) @@ -3593,6 +3638,10 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, *count_out = count; *types_out = types; *names_out = names; + if (ctype_overrides_out) + { + *ctype_overrides_out = ctype_overrides; + } return buf; } diff --git a/std/core.zc b/std/core.zc index f450517..7379db4 100644 --- a/std/core.zc +++ b/std/core.zc @@ -7,11 +7,11 @@ include let __zen_hash_seed: usize = 14695981039346656037; -raw { -void _zen_panic(const char* file, int line, const char* func, const char* msg) { +extern fn exit(code: int); + +fn _zen_panic(file: const char*, line: int, func: const char*, msg: const char*) { fprintf(stderr, "%s:%d (%s): Panic: %s\n", file, line, func, msg); exit(1); } -} #define panic(msg) _zen_panic(__FILE__, __LINE__, __func__, msg) \ No newline at end of file diff --git a/std/cuda.zc b/std/cuda.zc index c6a9403..8fc6545 100644 --- a/std/cuda.zc +++ b/std/cuda.zc @@ -101,6 +101,8 @@ fn cuda_ok() -> bool { } +// Minimal raw block: required for cudaDeviceProp struct field access +// The cudaDeviceProp struct cannot be declared in Zen-C without type conflicts raw { void _z_cuda_get_props(int dev, char* name, size_t* total_mem, int* sm_count, int* major, int* minor, int* max_threads, int* warp_size) { struct cudaDeviceProp prop; diff --git a/std/env.zc b/std/env.zc index 959784f..c63fd3d 100644 --- a/std/env.zc +++ b/std/env.zc @@ -2,23 +2,12 @@ import "./core.zc" import "./option.zc" import "./string.zc" -raw { - char *_z_env_get(char *name) { - return getenv(name); - } - - int _z_env_set(char *name, char *value, int overwrite) { - return setenv(name, value, overwrite); - } - - int _z_env_unset(char *name) { - return unsetenv(name); - } -} +include -extern fn _z_env_get(name: char*) -> char*; -extern fn _z_env_set(name: char*, value: char*, overwrite: int) -> int; -extern fn _z_env_unset(name: char*) -> int; +// Direct externs with const char* to match C stdlib declarations +extern fn getenv(name: const char*) -> char*; +extern fn setenv(name: const char*, value: const char*, overwrite: int) -> int; +extern fn unsetenv(name: const char*) -> int; @derive(Eq) enum EnvRes { @@ -30,7 +19,7 @@ struct Env {} impl Env { fn get(name: string) -> Option { - let value: string = _z_env_get(name); + let value: string = getenv(name); if (value == NULL) { return Option::None(); } @@ -39,7 +28,7 @@ impl Env { } fn get_dup(name: string) -> Option { - let value: string = _z_env_get(name); + let value: string = getenv(name); if (value == NULL) { return Option::None(); } @@ -52,13 +41,13 @@ impl Env { } fn set(name: string, value: string) -> EnvRes { - let ret: int = _z_env_set(name, value, 1); + let ret: int = setenv(name, value, 1); return (ret == 0) ? EnvRes::OK() : EnvRes::ERR(); } fn unset(name: string) -> EnvRes { - let ret: int = _z_env_unset(name); + let ret: int = unsetenv(name); return (ret == 0) ? EnvRes::OK() : EnvRes::ERR(); } diff --git a/std/fs.zc b/std/fs.zc index 4547b30..a00993b 100644 --- a/std/fs.zc +++ b/std/fs.zc @@ -14,12 +14,20 @@ include include include -// TODO: restructure this tomorrow (lol). +// Direct externs for simple functions with const char* parameters +extern fn access(pathname: const char*, mode: int) -> int; +extern fn unlink(pathname: const char*) -> int; +extern fn rmdir(pathname: const char*) -> int; +extern fn malloc(size: usize) -> void*; +extern fn free(ptr: void*); + +// Minimal raw block: required for opaque FILE*/DIR* types and C struct access +// These cannot be expressed in Zen-C extern declarations without type conflicts raw { typedef struct DirEntry* DirEntryPtr; - // Wrappers for FILE* handling due to opaque pointer casting - void* _z_fs_fopen(char* path, char* mode) { + // FILE* wrappers - fopen/fclose/etc use FILE* which conflicts with void* + void* _z_fs_fopen(const char* path, const char* mode) { return fopen(path, mode); } @@ -43,22 +51,8 @@ raw { return (int64_t)ftell((FILE*)stream); } - // Wrappers needed because C headers declare these with 'const char*' - // but Zen C externs generate 'char*', leading to conflicting types. - int _z_fs_access(char* pathname, int mode) { - return access(pathname, mode); - } - - int _z_fs_unlink(char* pathname) { - return unlink(pathname); - } - - int _z_fs_rmdir(char* pathname) { - return rmdir(pathname); - } - - // Wrappers for DIR* handling - void* _z_fs_opendir(char* name) { + // DIR* wrappers - opendir/closedir/readdir use DIR* which conflicts with void* + void* _z_fs_opendir(const char* name) { return opendir(name); } @@ -66,8 +60,8 @@ raw { return closedir((DIR*)dir); } - // struct stat / struct dirent helpers - int _z_fs_get_metadata(char* path, uint64_t* size, int* is_dir, int* is_file) { + // struct stat access - cannot define matching Zen-C struct for stat + int _z_fs_get_metadata(const char* path, uint64_t* size, int* is_dir, int* is_file) { struct stat st; if (stat(path, &st) != 0) return -1; *size = st.st_size; @@ -76,6 +70,7 @@ raw { return 0; } + // struct dirent access - readdir returns struct dirent* int _z_fs_read_entry(void* dir, char* out_name, int buf_size, int* is_dir) { struct dirent* ent = readdir((DIR*)dir); if (!ent) return 0; @@ -85,7 +80,8 @@ raw { return 1; } - int _z_fs_mkdir(char* path) { + // mkdir has different signatures on Windows vs POSIX + int _z_fs_mkdir(const char* path) { #ifdef _WIN32 return mkdir(path); #else @@ -94,26 +90,18 @@ raw { } } -// Direct externs -extern fn malloc(size: usize) -> void*; -extern fn free(ptr: void*); - -extern fn _z_fs_mkdir(path: char*) -> int; -extern fn _z_fs_get_metadata(path: char*, size: U64*, is_dir: int*, is_file: int*) -> int; +extern fn _z_fs_mkdir(path: const char*) -> int; +extern fn _z_fs_get_metadata(path: const char*, size: U64*, is_dir: int*, is_file: int*) -> int; extern fn _z_fs_read_entry(dir: void*, out_name: char*, buf_size: int, is_dir: int*) -> int; -extern fn _z_fs_fopen(path: char*, mode: char*) -> void*; +extern fn _z_fs_fopen(path: const char*, mode: const char*) -> void*; extern fn _z_fs_fclose(stream: void*) -> int; extern fn _z_fs_fread(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; extern fn _z_fs_fwrite(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; extern fn _z_fs_fseek(stream: void*, offset: I64, whence: int) -> int; extern fn _z_fs_ftell(stream: void*) -> I64; -extern fn _z_fs_opendir(name: char*) -> void*; +extern fn _z_fs_opendir(name: const char*) -> void*; extern fn _z_fs_closedir(dir: void*) -> int; -extern fn _z_fs_access(pathname: char*, mode: int) -> int; -extern fn _z_fs_unlink(pathname: char*) -> int; -extern fn _z_fs_rmdir(pathname: char*) -> int; - struct File { handle: void*; @@ -203,7 +191,7 @@ impl File { } fn exists(path: char*) -> bool { - return _z_fs_access(path, Z_F_OK) == 0; + return access(path, Z_F_OK) == 0; } fn metadata(path: char*) -> Result { @@ -230,14 +218,14 @@ impl File { } fn remove_file(path: char*) -> Result { - if (_z_fs_unlink(path) != 0) { + if (unlink(path) != 0) { return Result::Err("Failed to remove file"); } return Result::Ok(true); } fn remove_dir(path: char*) -> Result { - if (_z_fs_rmdir(path) != 0) { + if (rmdir(path) != 0) { return Result::Err("Failed to remove directory"); } return Result::Ok(true); diff --git a/std/io.zc b/std/io.zc index 2793ecf..cfb9179 100644 --- a/std/io.zc +++ b/std/io.zc @@ -2,28 +2,32 @@ import "./core.zc" import "./string.zc" -raw { - int _z_vprintf(char* fmt, va_list ap) { - return vprintf((const char*)fmt, ap); - } - - int _z_vsnprintf(char* str, size_t size, char* fmt, va_list ap) { - return vsnprintf(str, size, (const char*)fmt, ap); - } +include +include - void* _z_get_stdin() { return stdin; } - int _z_get_eof() { return EOF; } +// These work directly with const char* in extern declarations +extern fn vprintf(fmt: const char*, ap: va_list) -> int; +extern fn vsnprintf(str: char*, size: usize, fmt: const char*, ap: va_list) -> int; + +// EOF is typically -1, but we define it for portability +def Z_EOF = -1; + +// Minimal raw block: only for truly opaque FILE* types that can't be +// represented in Zen-C extern declarations without type conflicts. +// These wrappers use void* to avoid FILE* declaration conflicts. +raw { + void* _z_get_stdin(void) { return stdin; } int _z_fgetc(void* stream) { return fgetc((FILE*)stream); } } -extern fn _z_vprintf(fmt: char*, ap: va_list) -> int; -extern fn _z_vsnprintf(str: char*, size: usize, fmt: char*, ap: va_list) -> int; +extern fn _z_get_stdin() -> void*; +extern fn _z_fgetc(stream: void*) -> int; fn format(fmt: char*, ...) -> char* { static let buffer: char[1024]; let ap: va_list; va_start(ap, fmt); - _z_vsnprintf(buffer, 1024, fmt, ap); + vsnprintf(buffer, 1024, fmt, ap); va_end(ap); return (char*)buffer; } @@ -31,7 +35,7 @@ fn format(fmt: char*, ...) -> char* { fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vsnprintf(buffer, size, fmt, ap); + let ret = vsnprintf(buffer, size, fmt, ap); va_end(ap); return ret; } @@ -42,7 +46,7 @@ fn format_new(fmt: char*, ...) -> char* { let ap: va_list; va_start(ap, fmt); - _z_vsnprintf(buffer, 1024, fmt, ap); + vsnprintf(buffer, 1024, fmt, ap); va_end(ap); return buffer; } @@ -50,7 +54,7 @@ fn format_new(fmt: char*, ...) -> char* { fn print(fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vprintf(fmt, ap); + let ret = vprintf(fmt, ap); va_end(ap); return ret; } @@ -58,7 +62,7 @@ fn print(fmt: char*, ...) -> int { fn println(fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vprintf(fmt, ap); + let ret = vprintf(fmt, ap); va_end(ap); puts(""); return ret + 1; @@ -72,11 +76,10 @@ fn readln() -> char* { let c: int; let std_in = _z_get_stdin(); - let eof_val = _z_get_eof(); while (true) { c = _z_fgetc(std_in); - if (c == eof_val) break; + if (c == Z_EOF) break; if (c == 10) break; // '\n' if (len + 1 >= cap) { @@ -93,7 +96,7 @@ fn readln() -> char* { len = len + 1; } - if (len == 0 && c == eof_val) { + if (len == 0 && c == Z_EOF) { free(line); return NULL; } diff --git a/std/map.zc b/std/map.zc index 70d6ad2..8376da2 100644 --- a/std/map.zc +++ b/std/map.zc @@ -3,16 +3,20 @@ import "./core.zc" import "./option.zc" import "./mem.zc" -raw { - extern size_t __zen_hash_seed; - size_t _map_hash_str(const char* str) { - size_t hash = __zen_hash_seed; - while (*str) { - hash ^= (unsigned char)*str++; - hash *= 1099511628211UL; - } - return hash; +// Pure Zen-C string hash using FNV-1a algorithm +fn _map_hash_str(str: const char*) -> usize { + let hash = __zen_hash_seed; + let i: usize = 0; + + while (str[i] != 0) { + // Cast char to U8 for unsigned byte value + let b: U8 = (U8)str[i]; + hash = hash ^ (usize)b; + hash = hash * (usize)1099511628211; + i = i + 1; } + + return hash; } struct Map { diff --git a/std/net.zc b/std/net.zc index dd10642..d410a4d 100644 --- a/std/net.zc +++ b/std/net.zc @@ -13,8 +13,17 @@ import "./mem.zc" def Z_AF_INET = 2; def Z_SOCK_STREAM = 1; +// Direct externs for simple socket functions +extern fn socket(domain: int, type: int, proto: int) -> int; +extern fn close(fd: int) -> int; +extern fn read(fd: int, buf: void*, count: usize) -> isize; + +// Minimal raw block: required for struct sockaddr_in usage +// These functions encapsulate sockaddr_in setup because the struct layout +// cannot be declared in Zen-C without type conflicts with C headers. +// Also includes inet_pton, htons, bind, connect, listen, accept wrappers. raw { - static int _z_net_bind(int fd, char *host, int port) { + static int _z_net_bind(int fd, const char *host, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -28,7 +37,7 @@ raw { return 0; } - static int _z_net_connect(int fd, char *host, int port) { + static int _z_net_connect(int fd, const char *host, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -42,19 +51,15 @@ raw { return accept(fd, NULL, NULL); } - static ssize_t _z_net_write(int fd, char* buf, size_t n) { + static ssize_t _z_net_write(int fd, const char* buf, size_t n) { return write(fd, (const void*)buf, n); } } -extern fn socket(domain: int, type: int, proto: int) -> int; -extern fn close(fd: int) -> int; -extern fn read(fd: int, buf: void*, count: usize) -> isize; - -extern fn _z_net_bind(fd: int, host: char*, port: int) -> int; -extern fn _z_net_connect(fd: int, host: char*, port: int) -> int; +extern fn _z_net_bind(fd: int, host: const char*, port: int) -> int; +extern fn _z_net_connect(fd: int, host: const char*, port: int) -> int; extern fn _z_net_accept(fd: int) -> int; -extern fn _z_net_write(fd: int, buf: char*, n: usize) -> isize; +extern fn _z_net_write(fd: int, buf: const char*, n: usize) -> isize; struct TcpStream { diff --git a/std/process.zc b/std/process.zc index d0b09a9..3ce43b6 100644 --- a/std/process.zc +++ b/std/process.zc @@ -5,8 +5,16 @@ import "./mem.zc"; import "./string.zc"; import "./option.zc"; -raw { - void *_z_popen(char *command, char *type) { +include +include + +// system() can be externed directly with const char* +extern fn system(command: const char*) -> int; + +// Minimal raw block: only for opaque FILE* types +// popen/pclose/fgets use FILE* which conflicts with void* +raw { + void *_z_popen(const char *command, const char *type) { return (void *)popen(command, type); } @@ -17,16 +25,11 @@ raw { char *_z_fgets(char *s, int size, void *stream) { return fgets(s, size, (FILE *)stream); } - - int _z_system(char *command) { - return system(command); - } } -extern fn _z_popen(command: char*, type: char*) -> void*; +extern fn _z_popen(command: const char*, type: const char*) -> void*; extern fn _z_pclose(stream: void*) -> int; extern fn _z_fgets(s: char*, size: int, stream: void*) -> char*; -extern fn _z_system(command: char*) -> int; struct Output { stdout: String; @@ -105,7 +108,7 @@ impl Command { fn status(self) -> int { let cmd_str = self._build_cmd(); - let code = _z_system(cmd_str.c_str()); + let code = system(cmd_str.c_str()); cmd_str.free(); return code; } diff --git a/std/set.zc b/std/set.zc index ba6c93f..e1faab3 100644 --- a/std/set.zc +++ b/std/set.zc @@ -2,17 +2,17 @@ import "./core.zc" import "./option.zc" -raw { - extern size_t __zen_hash_seed; - size_t _set_hash(const void* data, size_t len) { - size_t hash = __zen_hash_seed; - const unsigned char* bytes = (const unsigned char*)data; - for (size_t i = 0; i < len; i++) { - hash ^= bytes[i]; - hash *= 1099511628211UL; - } - return hash; +// Pure Zen-C generic hash using FNV-1a algorithm +fn _set_hash(data: const void*, len: usize) -> usize { + let hash = __zen_hash_seed; + let bytes: U8* = (U8*)data; + + for (let i: usize = 0; i < len; i = i + 1) { + hash = hash ^ (usize)bytes[i]; + hash = hash * (usize)1099511628211; } + + return hash; } struct Set { diff --git a/std/thread.zc b/std/thread.zc index 98f080e..16f3ca1 100644 --- a/std/thread.zc +++ b/std/thread.zc @@ -7,6 +7,11 @@ import "./core.zc" import "./result.zc" import "./mem.zc" +// Essential raw block: required for pthread operations and closure trampolining +// This block cannot be eliminated because: +// 1. z_closure_T is an internal compiler type for Zen-C closures +// 2. pthread_t, pthread_mutex_t are opaque types that can't be extern'd with void* +// 3. The trampoline function needs to cast and execute Zen-C closures raw { typedef void (*ZenThreadFunc)(void*); diff --git a/std/time.zc b/std/time.zc index 865dd6d..fa764ed 100644 --- a/std/time.zc +++ b/std/time.zc @@ -5,6 +5,9 @@ include include include +// Minimal raw block: required because gettimeofday() uses struct timeval +// which can't be declared in Zen-C without type conflicts, and time() +// has conflicting type signature (time_t* vs void*) raw { static uint64_t _time_now_impl(void) { struct timeval tv; -- cgit v1.2.3 From 6f95b74465f792ac9a600d5a41855f14173cd476 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 20:37:14 +0000 Subject: Fix portability issue macOS (vsnprintf) --- std/io.zc | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'std') diff --git a/std/io.zc b/std/io.zc index cfb9179..a5a7359 100644 --- a/std/io.zc +++ b/std/io.zc @@ -7,7 +7,10 @@ include // These work directly with const char* in extern declarations extern fn vprintf(fmt: const char*, ap: va_list) -> int; -extern fn vsnprintf(str: char*, size: usize, fmt: const char*, ap: va_list) -> int; + +// vsnprintf is problematic on macOS because it's a macro that expands to a builtin with a different signature +// so we wrap it in a C function to avoid the conflict +extern fn _z_vsnprintf(str: char*, size: usize, fmt: const char*, ap: va_list) -> int; // EOF is typically -1, but we define it for portability def Z_EOF = -1; @@ -18,6 +21,9 @@ def Z_EOF = -1; raw { void* _z_get_stdin(void) { return stdin; } int _z_fgetc(void* stream) { return fgetc((FILE*)stream); } + int _z_vsnprintf(char* str, size_t size, const char* fmt, va_list ap) { + return vsnprintf(str, size, fmt, ap); + } } extern fn _z_get_stdin() -> void*; @@ -27,16 +33,20 @@ fn format(fmt: char*, ...) -> char* { static let buffer: char[1024]; let ap: va_list; va_start(ap, fmt); - vsnprintf(buffer, 1024, fmt, ap); + + _z_vsnprintf(buffer, 1024, fmt, ap); va_end(ap); + return (char*)buffer; } fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = vsnprintf(buffer, size, fmt, ap); + + let ret = _z_vsnprintf(buffer, size, fmt, ap); va_end(ap); + return ret; } @@ -46,8 +56,10 @@ fn format_new(fmt: char*, ...) -> char* { let ap: va_list; va_start(ap, fmt); - vsnprintf(buffer, 1024, fmt, ap); + + _z_vsnprintf(buffer, 1024, fmt, ap); va_end(ap); + return buffer; } -- cgit v1.2.3 From 422616a43b4f9b7659c96bfffd2f3095461dbea5 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 23:41:21 +0000 Subject: JSON serialization --- docs/std/json.md | 26 ++++++ docs/std/string.md | 4 + std/json.zc | 67 ++++++++++++++++ std/string.zc | 22 ++++++ tests/std/test_json_serialization.zc | 149 +++++++++++++++++++++++++++++++++++ 5 files changed, 268 insertions(+) create mode 100644 tests/std/test_json_serialization.zc (limited to 'std') diff --git a/docs/std/json.md b/docs/std/json.md index fba2ad8..ce35d64 100644 --- a/docs/std/json.md +++ b/docs/std/json.md @@ -51,6 +51,32 @@ Represents a node in a JSON document. - **`fn at(self, index: usize) -> Option`** Retrieves a value from an array by index. +#### Serialization + +- **`fn to_string(self) -> String`** + Serializes the JSON value to a string representation. + +- **`fn stringify(self, buf: String*)`** + Internal recursive serialization method that appends to a string buffer. + +**Example:** +```zc +let obj = JsonValue::object(); +obj.set("name", JsonValue::string("Alice")); +obj.set("age", JsonValue::number(30.0)); + +let json_str = obj.to_string(); +println "{json_str.c_str()}"; // {"name":"Alice","age":30} +json_str.free(); +obj.free(); +``` + +**Features:** +- Proper escaping of special characters: `\"`, `\\`, `\n`, `\t`, `\r`, `\b`, `\f` +- Numbers formatted with `%.15g` for precision +- Recursive serialization for nested objects and arrays +- Round-trip compatible with `parse()` + #### Memory Management - **`fn free(self)`** diff --git a/docs/std/string.md b/docs/std/string.md index 1f89e0f..a2f63f5 100644 --- a/docs/std/string.md +++ b/docs/std/string.md @@ -49,8 +49,12 @@ struct String { | Method | Signature | Description | | :--- | :--- | :--- | | **append** | `append(self, other: String*)` | Appends another string to this one. | +| **append_c** | `append_c(self, s: char*)` | Appends a C string literal. Uses value receiver. | +| **append_c_ptr** | `append_c_ptr(ptr: String*, s: char*)` | Appends a C string literal using pointer receiver for guaranteed mutation. | | **add** | `add(self, other: String*) -> String` | Concatenates this string and another into a new String. | +**Note:** When passing `String*` to functions that need to mutate, use `append_c_ptr` instead of `append_c` for reliable mutation. + ### Access & Query | Method | Signature | Description | diff --git a/std/json.zc b/std/json.zc index d373ab9..70f7cf2 100644 --- a/std/json.zc +++ b/std/json.zc @@ -457,3 +457,70 @@ impl Drop for JsonValue { self.free(); } } + +extern fn sprintf(s: char*, fmt: const char*, ...) -> int; + +impl JsonValue { + fn to_string(self) -> String { + let s = String::new(""); + self.stringify(&s); + return s; + } + + fn stringify(self, buf: String*) { + if (self.kind.tag == JsonType::JSON_NULL().tag) { + buf.append_c_ptr("null"); + } else if (self.kind.tag == JsonType::JSON_BOOL().tag) { + if (self.bool_val) { buf.append_c_ptr("true"); } else { buf.append_c_ptr("false"); } + } else if (self.kind.tag == JsonType::JSON_NUMBER().tag) { + let tmp: char[64]; + sprintf((char*)tmp, "%.15g", self.number_val); // Use %.15g for precision + buf.append_c_ptr((char*)tmp); + } else if (self.kind.tag == JsonType::JSON_STRING().tag) { + buf.append_c_ptr("\""); + let p = self.string_val; + let len = strlen(p); + for (let i = 0; i < len; i = i + 1) { + let c = p[i]; + if (c == '"') buf.append_c_ptr("\\\""); + else if (c == '\\') buf.append_c_ptr("\\\\"); + else if (c == '\n') buf.append_c_ptr("\\n"); + else if (c == '\t') buf.append_c_ptr("\\t"); + else if (c == '\r') buf.append_c_ptr("\\r"); + else if (c == '\b') buf.append_c_ptr("\\b"); + else if (c == '\f') buf.append_c_ptr("\\f"); + else { + let tmp: char[2]; tmp[0] = c; tmp[1] = 0; + buf.append_c_ptr((char*)tmp); + } + } + buf.append_c_ptr("\""); + } else if (self.kind.tag == JsonType::JSON_ARRAY().tag) { + buf.append_c_ptr("["); + let v = self.array_val; + for (let i: usize = 0; i < v.length(); i = i + 1) { + if (i > 0) buf.append_c_ptr(","); + let item = v.get(i); + (*item).stringify(buf); + } + buf.append_c_ptr("]"); + } else if (self.kind.tag == JsonType::JSON_OBJECT().tag) { + buf.append_c_ptr("{{"); + let m = self.object_val; + let first = true; + for (let i: usize = 0; i < m.capacity(); i = i + 1) { + if (m.is_slot_occupied(i)) { + if (!first) buf.append_c_ptr(","); + first = false; + let key = m.key_at(i); + buf.append_c_ptr("\""); + buf.append_c_ptr(key); // Assuming keys are simple for now, but really should escape them too + buf.append_c_ptr("\":"); + let val = m.val_at(i); + val.stringify(buf); + } + } + buf.append_c_ptr("}"); + } + } +} diff --git a/std/string.zc b/std/string.zc index fe5b0ad..0bc9539 100644 --- a/std/string.zc +++ b/std/string.zc @@ -55,6 +55,28 @@ impl String { } } + fn append_c(self, s: char*) { + if (self.vec.len > 0) { + self.vec.len = self.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + self.vec.push(s[i]); + } + self.vec.push(0); + } + + fn append_c_ptr(ptr: String*, s: char*) { + if (ptr.vec.len > 0) { + ptr.vec.len = ptr.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + ptr.vec.push(s[i]); + } + ptr.vec.push(0); + } + fn add(self, other: String*) -> String { let new_s = String::from(self.c_str()); new_s.append(other); diff --git a/tests/std/test_json_serialization.zc b/tests/std/test_json_serialization.zc new file mode 100644 index 0000000..9fd5b32 --- /dev/null +++ b/tests/std/test_json_serialization.zc @@ -0,0 +1,149 @@ +import "std/json.zc" +import "std/io.zc" + +test "primitives" { + // Null + let v = JsonValue::null(); + let s = v.to_string(); + let expected = String::from("null"); + if (!s.eq(&expected)) { + panic("Null serialization failed"); + } + expected.free(); + s.free(); + + // Bool True + v = JsonValue::bool(true); + s = v.to_string(); + expected = String::from("true"); + if (!s.eq(&expected)) { + panic("Bool true serialization failed"); + } + expected.free(); + s.free(); + + // Bool False + v = JsonValue::bool(false); + s = v.to_string(); + expected = String::from("false"); + if (!s.eq(&expected)) { + panic("Bool false serialization failed"); + } + expected.free(); + s.free(); + + // Number Int + v = JsonValue::number(123.0); + s = v.to_string(); + expected = String::from("123"); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("Number 123 serialization failed"); + } + expected.free(); + s.free(); + + // Number Float + v = JsonValue::number(12.5); + s = v.to_string(); + expected = String::from("12.5"); + if (!s.eq(&expected)) { + panic("Number 12.5 serialization failed"); + } + expected.free(); + s.free(); + + // String Simple + v = JsonValue::string("hello"); + s = v.to_string(); + expected = String::from("\"hello\""); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("String hello serialization failed"); + } + expected.free(); + s.free(); + + // String Escaped + v = JsonValue::string("hello \"world\""); + s = v.to_string(); + expected = String::from("\"hello \\\"world\\\"\""); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("String escaped serialization failed"); + } + expected.free(); + s.free(); +} + +test "array" { + let v = JsonValue::array(); + v.push(JsonValue::number(1.0)); + v.push(JsonValue::bool(true)); + v.push(JsonValue::string("a")); + + let s = v.to_string(); + let expected = String::from("[1,true,\"a\"]"); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("Array serialization failed"); + } + expected.free(); + s.free(); +} + +test "object" { + let v = JsonValue::object(); + v.set("key", JsonValue::string("value")); + + let s = v.to_string(); + // Round trip verification to avoid parser bug with literals + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Object round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip not object"); + + let val_opt = (*parsed).get_string("key"); + if (val_opt.is_none()) panic("Round trip missing 'key'"); + + let val_str = val_opt.unwrap(); + if (strcmp(val_str, "value") != 0) panic("Round trip wrong value"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +} + +test "nested" { + // {"arr":[1,2]} + let v = JsonValue::object(); + let arr = JsonValue::array(); + arr.push(JsonValue::number(1.0)); + arr.push(JsonValue::number(2.0)); + v.set("arr", arr); + + let s = v.to_string(); + + // Round trip + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip type mismatch"); + + let arr_opt = (*parsed).get_array("arr"); + if (arr_opt.is_none()) panic("Round trip missing arr"); + + let arr_ptr = arr_opt.unwrap(); + if (!(*arr_ptr).is_array()) panic("Inner not array"); + if ((*arr_ptr).len() != 2) panic("Wrong array length"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +} \ No newline at end of file -- cgit v1.2.3 From 03a6a57f500ee4230ce2cee887866b3850ed7ed9 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 23:49:49 +0000 Subject: Fix portability issue macOS --- std/json.zc | 2 -- 1 file changed, 2 deletions(-) (limited to 'std') diff --git a/std/json.zc b/std/json.zc index 70f7cf2..9f5cf73 100644 --- a/std/json.zc +++ b/std/json.zc @@ -458,8 +458,6 @@ impl Drop for JsonValue { } } -extern fn sprintf(s: char*, fmt: const char*, ...) -> int; - impl JsonValue { fn to_string(self) -> String { let s = String::new(""); -- cgit v1.2.3 From 856c9fe56b412779e045ef86a767b93d5c7f563b Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 01:15:25 +0000 Subject: Improvements for slice + better iteration for arrays --- README.md | 11 ++- README_ES.md | 12 ++- README_ZH_CN.md | 27 ++++-- README_ZH_TW.md | 11 ++- docs/std/slice.md | 90 ++++++++++++++++++ src/codegen/codegen.c | 88 +++++++++++++++++- src/codegen/codegen_utils.c | 25 ++++- src/parser/parser_stmt.c | 152 ++++++++++++++++++++++++++++--- std/slice.zc | 35 +++++++ tests/std/test_direct_array_iteration.zc | 37 ++++++++ tests/std/test_slice_iteration.zc | 29 ++++++ 11 files changed, 479 insertions(+), 38 deletions(-) create mode 100644 docs/std/slice.md create mode 100644 tests/std/test_direct_array_iteration.zc create mode 100644 tests/std/test_slice_iteration.zc (limited to 'std') diff --git a/README.md b/README.md index ab51b83..ecdf8fc 100644 --- a/README.md +++ b/README.md @@ -494,8 +494,15 @@ for i in 0..<10 { ... } // Exclusive (Explicit) for i in 0..=10 { ... } // Inclusive (0 to 10) for i in 0..10 step 2 { ... } -// Iterator (Vec, Array, or custom Iterable) -for item in collection { ... } +// Iterator (Vec or custom Iterable) +for item in vec { ... } + +// Iterate over fixed-size arrays directly +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val is int + println "{val}"; +} // While while x < 10 { ... } diff --git a/README_ES.md b/README_ES.md index d2cfbbb..d73e9ca 100644 --- a/README_ES.md +++ b/README_ES.md @@ -493,8 +493,15 @@ for i in 0..<10 { ... } // Exclusivo (Explícito) for i in 0..=10 { ... } // Inclusivo (0 al 10) for i in 0..10 step 2 { ... } -// Iterador (Vec, Array, o Iterable personalizado) -for item in coleccion { ... } +// Iterador (Vec o Iterable personalizado) +for item in vec { ... } + +// Iterar sobre arrays de tamaño fijo directamente +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val es int + println "{val}"; +} // While while x < 10 { ... } @@ -508,6 +515,7 @@ externo: loop { for _ in 0..5 { ... } ``` + #### Control Avanzado ```zc // Guard: Ejecuta else y retorna si la condición es falsa diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 2ac38a2..51689f6 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -485,26 +485,33 @@ match opt { } ``` -#### 循环 +#### 循環 ```zc -// 区间迭代 -for i in 0..10 { ... } // 左闭右开 (0 到 9) -for i in 0..<10 { ... } // 左闭右开 (显式) -for i in 0..=10 { ... } // 全闭 (0 到 10) +// 區間迭代 +for i in 0..10 { ... } // 左閉右開 (0 到 9) +for i in 0..<10 { ... } // 左閉右開 (顯式) +for i in 0..=10 { ... } // 全閉 (0 到 10) for i in 0..10 step 2 { ... } -// 迭代器 (Vec, Array, 或自定义 Iterable) -for item in collection { ... } +// 迭代器 (Vec 或自定義 Iterable) +for item in vec { ... } -// While 循环 +// 直接迭代固定大小数组 +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val 是 int + println "{val}"; +} + +// While 循環 while x < 10 { ... } -// 带标签的无限循环 +// 帶標籤的無限循環 outer: loop { if done { break outer; } } -// 重复 N 次 +// 重複 N 次 for _ in 0..5 { ... } ``` diff --git a/README_ZH_TW.md b/README_ZH_TW.md index 13591cf..6fa0dbd 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -493,8 +493,15 @@ for i in 0..<10 { ... } // 左閉右開 (顯式) for i in 0..=10 { ... } // 全閉 (0 到 10) for i in 0..10 step 2 { ... } -// 迭代器 (Vec, Array, 或自定義 Iterable) -for item in collection { ... } +// 迭代器 (Vec 或自定義 Iterable) +for item in vec { ... } + +// 直接迭代固定大小數組 +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val 是 int + println "{val}"; +} // While 循環 while x < 10 { ... } diff --git a/docs/std/slice.md b/docs/std/slice.md new file mode 100644 index 0000000..b70c5fe --- /dev/null +++ b/docs/std/slice.md @@ -0,0 +1,90 @@ +# Standard Library: Slice (`std/slice.zc`) + +`Slice` is a lightweight, non-owning view into a contiguous sequence of elements. It's particularly useful for working with fixed-size arrays and enabling iteration. + +## Usage + +```zc +import "std/slice.zc" + +fn main() { + let arr: int[5] = [1, 2, 3, 4, 5]; + + // Direct iteration (Recommended) + for val in arr { + println "{val}"; + } + + // Manual slice creation (for partial views or specific needs) + let slice = Slice::from_array((int*)(&arr), 5); + for val in slice { + println "{val}"; + } +} +``` + +## Structure + +```zc +struct Slice { + data: T*; + len: usize; +} +``` + +## Methods + +### Construction + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **from_array** | `Slice::from_array(arr: T*, len: usize) -> Slice` | Creates a slice view over an array. | + +### Iteration + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **iterator** | `iterator(self) -> SliceIter` | Returns an iterator for `for-in` loops. | + +`SliceIter` implements the iterator protocol with a `next() -> Option` method. + +### Access & Query + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **length** | `length(self) -> usize` | Returns the number of elements. | +| **is_empty** | `is_empty(self) -> bool` | Returns true if length is 0. | +| **get** | `get(self, idx: usize) -> Option` | Returns the element at index, or None if out of bounds. | +| **at** | `at(self, idx: usize) -> Option` | Alias for `get`. | + +## Examples + +### Iterating over fixed-size arrays + +```zc +let numbers: int[3] = [10, 20, 30]; +let slice = Slice::from_array((int*)(&numbers), 3); + +for n in slice { + println "Number: {n}"; +} +``` + +### Safe indexed access + +```zc +let arr: int[3] = [1, 2, 3]; +let slice = Slice::from_array((int*)(&arr), 3); + +let opt = slice.get(1); +if (!opt.is_none()) { + println "Value: {opt.unwrap()}"; // Prints: Value: 2 +} +``` + +## Notes + +- `Slice` does not own its data - it's just a view +- No memory management needed (no `free()` method) +- Must specify the generic type explicitly: `Slice`, `Slice`, etc. +- The array pointer cast `(T*)(&arr)` is required for fixed-size arrays diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index a66f179..7a67428 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -65,6 +65,52 @@ static void codegen_var_expr(ParserContext *ctx, ASTNode *node, FILE *out) zwarn_at(node->token, "%s\n = help: %s", msg, help); } } + + // Check for static method call pattern: Type::method or Slice::method + char *double_colon = strstr(node->var_ref.name, "::"); + if (double_colon) + { + // Extract type name and method name + int type_len = double_colon - node->var_ref.name; + char *type_name = xmalloc(type_len + 1); + strncpy(type_name, node->var_ref.name, type_len); + type_name[type_len] = 0; + + char *method_name = double_colon + 2; // Skip :: + + // Handle generic types: Slice -> Slice_int + char mangled_type[256]; + if (strchr(type_name, '<')) + { + // Generic type - need to mangle it + char *lt = strchr(type_name, '<'); + char *gt = strchr(type_name, '>'); + + if (lt && gt) + { + // Extract base type and type argument + *lt = 0; + char *type_arg = lt + 1; + *gt = 0; + + sprintf(mangled_type, "%s_%s", type_name, type_arg); + } + else + { + strcpy(mangled_type, type_name); + } + } + else + { + strcpy(mangled_type, type_name); + } + + // Output as Type__method + fprintf(out, "%s__%s", mangled_type, method_name); + free(type_name); + return; + } + fprintf(out, "%s", node->var_ref.name); } @@ -348,6 +394,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } // Check for Static Enum Variant Call: Enum.Variant(...) + if (target->type == NODE_EXPR_VAR) { ASTNode *def = find_struct_def(ctx, target->var_ref.name); @@ -418,11 +465,43 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) base += 7; } + char *mangled_base = base; + char base_buf[256]; + + // Mangle generic types: Slice -> Slice_int, Vec -> Vec_Point + char *lt = strchr(base, '<'); + if (lt) + { + char *gt = strchr(lt, '>'); + if (gt) + { + int prefix_len = lt - base; + int arg_len = gt - lt - 1; + snprintf(base_buf, 255, "%.*s_%.*s", prefix_len, base, arg_len, lt + 1); + mangled_base = base_buf; + } + } + if (!strchr(type, '*') && target->type == NODE_EXPR_CALL) { - fprintf(out, "({ %s _t = ", type); + char *type_mangled = type; + char type_buf[256]; + char *t_lt = strchr(type, '<'); + if (t_lt) + { + char *t_gt = strchr(t_lt, '>'); + if (t_gt) + { + int p_len = t_lt - type; + int a_len = t_gt - t_lt - 1; + snprintf(type_buf, 255, "%.*s_%.*s", p_len, type, a_len, t_lt + 1); + type_mangled = type_buf; + } + } + + fprintf(out, "({ %s _t = ", type_mangled); codegen_expression(ctx, target, out); - fprintf(out, "; %s__%s(&_t", base, method); + fprintf(out, "; %s__%s(&_t", mangled_base, method); ASTNode *arg = node->call.args; while (arg) { @@ -435,10 +514,11 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else { // Mixin Lookup Logic - char *call_base = base; + char *call_base = mangled_base; + int need_cast = 0; char mixin_func_name[128]; - sprintf(mixin_func_name, "%s__%s", base, method); + sprintf(mixin_func_name, "%s__%s", call_base, method); char *resolved_method_suffix = NULL; diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 0d03661..8d9cb28 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -64,13 +64,28 @@ void emit_c_decl(FILE *out, const char *type_str, const char *name) } else if (generic && (!bracket || generic < bracket)) { - // Strip generic part for C output - int base_len = generic - type_str; - fprintf(out, "%.*s %s", base_len, type_str, name); + char *gt = strchr(generic, '>'); + if (gt) + { + int base_len = generic - type_str; + int arg_len = gt - generic - 1; + + fprintf(out, "%.*s_%.*s %s", base_len, type_str, arg_len, generic + 1, name); - if (bracket) + if (bracket) + { + fprintf(out, "%s", bracket); + } + } + else { - fprintf(out, "%s", bracket); + int base_len = generic - type_str; + fprintf(out, "%.*s %s", base_len, type_str, name); + + if (bracket) + { + fprintf(out, "%s", bracket); + } } } else if (bracket) diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index a471fe6..4c24de3 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -1133,6 +1133,7 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *obj_expr = start_expr; char *iter_method = "iterator"; + ASTNode *slice_decl = NULL; // Track if we need to add a slice declaration // Check for reference iteration: for x in &vec if (obj_expr->type == NODE_EXPR_UNARY && obj_expr->unary.op && @@ -1142,6 +1143,78 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) iter_method = "iter_ref"; } + // Check for array iteration: wrap with Slice::from_array + if (obj_expr->type_info && obj_expr->type_info->kind == TYPE_ARRAY && + obj_expr->type_info->array_size > 0) + { + // Create a var decl for the slice + slice_decl = ast_create(NODE_VAR_DECL); + slice_decl->var_decl.name = xstrdup("__zc_arr_slice"); + + // Build type string: Slice + char *elem_type_str = type_to_string(obj_expr->type_info->inner); + char slice_type[256]; + sprintf(slice_type, "Slice<%s>", elem_type_str); + slice_decl->var_decl.type_str = xstrdup(slice_type); + + ASTNode *from_array_call = ast_create(NODE_EXPR_CALL); + ASTNode *static_method = ast_create(NODE_EXPR_VAR); + + // The function name for static methods is Type::method format + char func_name[512]; + snprintf(func_name, 511, "%s::from_array", slice_type); + static_method->var_ref.name = xstrdup(func_name); + + from_array_call->call.callee = static_method; + + // Create arguments + ASTNode *arr_addr = ast_create(NODE_EXPR_UNARY); + arr_addr->unary.op = xstrdup("&"); + arr_addr->unary.operand = obj_expr; + + ASTNode *arr_cast = ast_create(NODE_EXPR_CAST); + char cast_type[256]; + sprintf(cast_type, "%s*", elem_type_str); + arr_cast->cast.target_type = xstrdup(cast_type); + arr_cast->cast.expr = arr_addr; + + ASTNode *size_arg = ast_create(NODE_EXPR_LITERAL); + size_arg->literal.type_kind = LITERAL_INT; + size_arg->literal.int_val = obj_expr->type_info->array_size; + char size_buf[32]; + sprintf(size_buf, "%d", obj_expr->type_info->array_size); + size_arg->literal.string_val = xstrdup(size_buf); + + arr_cast->next = size_arg; + from_array_call->call.args = arr_cast; + from_array_call->call.arg_count = 2; + + slice_decl->var_decl.init_expr = from_array_call; + + // Manually trigger generic instantiation for Slice + // This ensures that Slice_int, Slice_float, etc. structures are generated + Token dummy_tok = {0}; + instantiate_generic(ctx, "Slice", elem_type_str, elem_type_str, dummy_tok); + + // Instantiate SliceIter and Option too for the loop logic + char iter_type[256]; + sprintf(iter_type, "SliceIter<%s>", elem_type_str); + instantiate_generic(ctx, "SliceIter", elem_type_str, elem_type_str, dummy_tok); + + char option_type[256]; + sprintf(option_type, "Option<%s>", elem_type_str); + instantiate_generic(ctx, "Option", elem_type_str, elem_type_str, dummy_tok); + + // Replace obj_expr with a reference to the slice variable + ASTNode *slice_ref = ast_create(NODE_EXPR_VAR); + slice_ref->var_ref.name = xstrdup("__zc_arr_slice"); + slice_ref->resolved_type = + xstrdup(slice_type); // Explicitly set type for codegen + obj_expr = slice_ref; + + free(elem_type_str); + } + // var __it = obj.iterator(); ASTNode *it_decl = ast_create(NODE_VAR_DECL); it_decl->var_decl.name = xstrdup("__it"); @@ -1182,6 +1255,34 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) stmts_tail = node; \ } + char *iter_type_ptr = NULL; + char *option_type_ptr = NULL; + + if (slice_decl) + { + char *slice_t = slice_decl->var_decl.type_str; + char *start = strchr(slice_t, '<'); + if (start) + { + char *end = strrchr(slice_t, '>'); + if (end) + { + int len = end - start - 1; + char *elem = xmalloc(len + 1); + strncpy(elem, start + 1, len); + elem[len] = 0; + + iter_type_ptr = xmalloc(256); + sprintf(iter_type_ptr, "SliceIter<%s>", elem); + + option_type_ptr = xmalloc(256); + sprintf(option_type_ptr, "Option<%s>", elem); + + free(elem); + } + } + } + // var __opt = __it.next(); ASTNode *opt_decl = ast_create(NODE_VAR_DECL); opt_decl->var_decl.name = xstrdup("__opt"); @@ -1192,6 +1293,10 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_next = ast_create(NODE_EXPR_MEMBER); ASTNode *it_ref = ast_create(NODE_EXPR_VAR); it_ref->var_ref.name = xstrdup("__it"); + if (iter_type_ptr) + { + it_ref->resolved_type = xstrdup(iter_type_ptr); + } memb_next->member.target = it_ref; memb_next->member.field = xstrdup("next"); call_next->call.callee = memb_next; @@ -1204,15 +1309,22 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_is_none = ast_create(NODE_EXPR_MEMBER); ASTNode *opt_ref1 = ast_create(NODE_EXPR_VAR); opt_ref1->var_ref.name = xstrdup("__opt"); + if (option_type_ptr) + { + opt_ref1->resolved_type = xstrdup(option_type_ptr); + } memb_is_none->member.target = opt_ref1; memb_is_none->member.field = xstrdup("is_none"); call_is_none->call.callee = memb_is_none; + call_is_none->call.args = NULL; + call_is_none->call.arg_count = 0; - ASTNode *break_stmt = ast_create(NODE_BREAK); - + // if (__opt.is_none()) break; ASTNode *if_break = ast_create(NODE_IF); if_break->if_stmt.condition = call_is_none; + ASTNode *break_stmt = ast_create(NODE_BREAK); if_break->if_stmt.then_body = break_stmt; + if_break->if_stmt.else_body = NULL; APPEND_STMT(if_break); // var = __opt.unwrap(); @@ -1225,25 +1337,28 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_unwrap = ast_create(NODE_EXPR_MEMBER); ASTNode *opt_ref2 = ast_create(NODE_EXPR_VAR); opt_ref2->var_ref.name = xstrdup("__opt"); + if (option_type_ptr) + { + opt_ref2->resolved_type = xstrdup(option_type_ptr); + } memb_unwrap->member.target = opt_ref2; memb_unwrap->member.field = xstrdup("unwrap"); call_unwrap->call.callee = memb_unwrap; + call_unwrap->call.args = NULL; + call_unwrap->call.arg_count = 0; user_var_decl->var_decl.init_expr = call_unwrap; APPEND_STMT(user_var_decl); - // User Body + // User body statements enter_scope(ctx); add_symbol(ctx, var_name, NULL, NULL); - ASTNode *user_body_node; - if (lexer_peek(l).type == TOK_LBRACE) - { - user_body_node = parse_block(ctx, l); - } - else + // Body block + ASTNode *stmt = parse_statement(ctx, l); + ASTNode *user_body_node = stmt; + if (stmt && stmt->type != NODE_BLOCK) { - ASTNode *stmt = parse_statement(ctx, l); ASTNode *blk = ast_create(NODE_BLOCK); blk->block.statements = stmt; user_body_node = blk; @@ -1256,10 +1371,21 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) loop_body->block.statements = stmts_head; while_loop->while_stmt.body = loop_body; - // Wrap entire thing in a block to scope _it + // Wrap entire thing in a block to scope __it (and __zc_arr_slice if present) ASTNode *outer_block = ast_create(NODE_BLOCK); - it_decl->next = while_loop; - outer_block->block.statements = it_decl; + if (slice_decl) + { + // Chain: slice_decl -> it_decl -> while_loop + slice_decl->next = it_decl; + it_decl->next = while_loop; + outer_block->block.statements = slice_decl; + } + else + { + // Chain: it_decl -> while_loop + it_decl->next = while_loop; + outer_block->block.statements = it_decl; + } return outer_block; } diff --git a/std/slice.zc b/std/slice.zc index 778c6ed..7ace396 100644 --- a/std/slice.zc +++ b/std/slice.zc @@ -1,10 +1,45 @@ +import "./option.zc" + struct Slice { data: T*; len: usize; } +struct SliceIter { + data: T*; + count: usize; + idx: usize; +} + +impl SliceIter { + fn next(self) -> Option { + if (self.idx < self.count) { + let item = self.data[self.idx]; + self.idx = self.idx + 1; + return Option::Some(item); + } + return Option::None(); + } + + fn iterator(self) -> SliceIter { + return *self; + } +} + impl Slice { + fn from_array(arr: T*, len: usize) -> Slice { + return Slice { data: arr, len: len }; + } + + fn iterator(self) -> SliceIter { + return SliceIter { + data: self.data, + count: self.len, + idx: 0 + }; + } + fn length(self) -> usize { return self.len; } diff --git a/tests/std/test_direct_array_iteration.zc b/tests/std/test_direct_array_iteration.zc new file mode 100644 index 0000000..359951f --- /dev/null +++ b/tests/std/test_direct_array_iteration.zc @@ -0,0 +1,37 @@ +import "std/slice.zc" + +test "direct array iteration" { + let arr: int[5] = [1, 2, 3, 4, 5]; + + let sum = 0; + for val in arr { + sum = sum + val; + } + + assert(sum == 15, "Sum should be 1+2+3+4+5 = 15"); +} + +test "direct array iteration with different types" { + let floats: float[3] = [1.5, 2.5, 3.0]; + let count = 0; + + for f in floats { + count = count + 1; + } + + assert(count == 3, "Should iterate over all 3 elements"); +} + +// TODO: Nested array iteration needs special handling +// test "nested array iteration" { +// let matrix: int[2][3] = [[1, 2, 3], [4, 5, 6]]; +// let total = 0; +// +// for row in matrix { +// for val in row { +// total = total + val; +// } +// } +// +// assert(total == 21, "Sum should be 1+2+3+4+5+6 = 21"); +// } diff --git a/tests/std/test_slice_iteration.zc b/tests/std/test_slice_iteration.zc new file mode 100644 index 0000000..b7eddf4 --- /dev/null +++ b/tests/std/test_slice_iteration.zc @@ -0,0 +1,29 @@ +import "std/slice.zc" +import "std/io.zc" + +test "slice from array iteration" { + let ints: int[5] = [1, 2, 3, 4, 5]; + let slice = Slice::from_array((int*)(&ints), 5); + + // Test iteration + let sum = 0; + for val in slice { + sum = sum + val; + } + + if (sum != 15) { + panic("Slice iteration failed: expected sum 15"); + } +} + +test "slice methods" { + let arr: int[3] = [10, 20, 30]; + let slice = Slice::from_array((int*)(&arr), 3); + + if (slice.length() != 3) panic("Slice length wrong"); + if (slice.is_empty()) panic("Slice should not be empty"); + + let opt = slice.get(1); + if (opt.is_none()) panic("Slice get failed"); + if (opt.unwrap() != 20) panic("Slice get returned wrong value"); +} -- cgit v1.2.3 From ccc53b11a0e273f46cb40e5f0eb32a74ab6750bf Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 15:31:41 +0000 Subject: Fix for #159 --- docs/std/slice.md | 15 +++-- src/codegen/codegen_main.c | 33 +++++++++++ src/parser/parser_stmt.c | 114 ++++++++++++++++++++++++++++++++++++ src/parser/parser_struct.c | 115 +++++++++++++++++++++++++++++++++++++ std/mem.zc | 23 +------- std/slice.zc | 5 ++ tests/memory/test_memory_safety.zc | 9 ++- 7 files changed, 283 insertions(+), 31 deletions(-) (limited to 'std') diff --git a/docs/std/slice.md b/docs/std/slice.md index b70c5fe..f029995 100644 --- a/docs/std/slice.md +++ b/docs/std/slice.md @@ -10,12 +10,12 @@ import "std/slice.zc" fn main() { let arr: int[5] = [1, 2, 3, 4, 5]; - // Direct iteration (Recommended) + // Direct iteration (auto-imports std/slice.zc) for val in arr { println "{val}"; } - // Manual slice creation (for partial views or specific needs) + // Manual slice creation let slice = Slice::from_array((int*)(&arr), 5); for val in slice { println "{val}"; @@ -39,6 +39,7 @@ struct Slice { | Method | Signature | Description | | :--- | :--- | :--- | | **from_array** | `Slice::from_array(arr: T*, len: usize) -> Slice` | Creates a slice view over an array. | +| **new** | `Slice::new(data: T*, len: usize) -> Slice` | Alias for `from_array` (backwards compat). | ### Iteration @@ -62,10 +63,10 @@ struct Slice { ### Iterating over fixed-size arrays ```zc +// std/slice.zc is auto-imported when using for-in on arrays let numbers: int[3] = [10, 20, 30]; -let slice = Slice::from_array((int*)(&numbers), 3); -for n in slice { +for n in numbers { println "Number: {n}"; } ``` @@ -73,6 +74,8 @@ for n in slice { ### Safe indexed access ```zc +import "std/slice.zc" + let arr: int[3] = [1, 2, 3]; let slice = Slice::from_array((int*)(&arr), 3); @@ -84,7 +87,7 @@ if (!opt.is_none()) { ## Notes -- `Slice` does not own its data - it's just a view +- `Slice` does not own its data — it's just a view - No memory management needed (no `free()` method) -- Must specify the generic type explicitly: `Slice`, `Slice`, etc. +- **Auto-import**: `std/slice.zc` is automatically imported when using `for val in arr` on a fixed-size array - The array pointer cast `(T*)(&arr)` is required for fixed-size arrays diff --git a/src/codegen/codegen_main.c b/src/codegen/codegen_main.c index b298700..82fc3ce 100644 --- a/src/codegen/codegen_main.c +++ b/src/codegen/codegen_main.c @@ -448,6 +448,39 @@ void codegen_node(ParserContext *ctx, ASTNode *node, FILE *out) emit_type_aliases(kids, out); // Emit local aliases (redundant but safe) emit_trait_defs(kids, out); + // Also emit traits from parsed_globals_list (from auto-imported files like std/mem.zc) + // but only if they weren't already emitted from kids + StructRef *trait_ref = ctx->parsed_globals_list; + while (trait_ref) + { + if (trait_ref->node && trait_ref->node->type == NODE_TRAIT) + { + // Check if this trait was already in kids (explicitly imported) + int already_in_kids = 0; + ASTNode *k = kids; + while (k) + { + if (k->type == NODE_TRAIT && k->trait.name && trait_ref->node->trait.name && + strcmp(k->trait.name, trait_ref->node->trait.name) == 0) + { + already_in_kids = 1; + break; + } + k = k->next; + } + + if (!already_in_kids) + { + // Create a temporary single-node list for emit_trait_defs + ASTNode *saved_next = trait_ref->node->next; + trait_ref->node->next = NULL; + emit_trait_defs(trait_ref->node, out); + trait_ref->node->next = saved_next; + } + } + trait_ref = trait_ref->next; + } + // Track emitted raw statements to prevent duplicates EmittedContent *emitted_raw = NULL; diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index 7758ae3..0677cf5 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -14,6 +14,118 @@ char *curr_func_ret = NULL; char *run_comptime_block(ParserContext *ctx, Lexer *l); +extern char *g_current_filename; + +/** + * @brief Auto-imports std/slice.zc if not already imported. + * + * This is called when array iteration is detected in for-in loops, + * to ensure the Slice, SliceIter, and Option templates are available. + */ +static void auto_import_std_slice(ParserContext *ctx) +{ + // Check if already imported via templates + GenericTemplate *t = ctx->templates; + while (t) + { + if (strcmp(t->name, "Slice") == 0) + { + return; // Already have the Slice template + } + t = t->next; + } + + // Try to find and import std/slice.zc + static const char *std_paths[] = {"std/slice.zc", "./std/slice.zc", NULL}; + static const char *system_paths[] = {"/usr/local/share/zenc", "/usr/share/zenc", NULL}; + + char resolved_path[1024]; + int found = 0; + + // First, try relative to current file + if (g_current_filename) + { + char *current_dir = xstrdup(g_current_filename); + char *last_slash = strrchr(current_dir, '/'); + if (last_slash) + { + *last_slash = 0; + snprintf(resolved_path, sizeof(resolved_path), "%s/std/slice.zc", current_dir); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + free(current_dir); + } + + // Try relative paths + if (!found) + { + for (int i = 0; std_paths[i] && !found; i++) + { + if (access(std_paths[i], R_OK) == 0) + { + strncpy(resolved_path, std_paths[i], sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + found = 1; + } + } + } + + // Try system paths + if (!found) + { + for (int i = 0; system_paths[i] && !found; i++) + { + snprintf(resolved_path, sizeof(resolved_path), "%s/std/slice.zc", system_paths[i]); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + } + + if (!found) + { + return; // Could not find std/slice.zc, instantiate_generic will error + } + + // Canonicalize path + char *real_fn = realpath(resolved_path, NULL); + if (real_fn) + { + strncpy(resolved_path, real_fn, sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + free(real_fn); + } + + // Check if already imported + if (is_file_imported(ctx, resolved_path)) + { + return; + } + mark_file_imported(ctx, resolved_path); + + // Load and parse the file + char *src = load_file(resolved_path); + if (!src) + { + return; // Could not load file + } + + Lexer i; + lexer_init(&i, src); + + // Save and restore filename context + char *saved_fn = g_current_filename; + g_current_filename = resolved_path; + + // Parse the slice module contents + parse_program_nodes(ctx, &i); + + g_current_filename = saved_fn; +} static void check_assignment_condition(ASTNode *cond) { @@ -1193,6 +1305,8 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) // Manually trigger generic instantiation for Slice // This ensures that Slice_int, Slice_float, etc. structures are generated + // First, ensure std/slice.zc is imported (auto-import if needed) + auto_import_std_slice(ctx); Token dummy_tok = {0}; instantiate_generic(ctx, "Slice", elem_type_str, elem_type_str, dummy_tok); diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c index 109eeee..e53b56c 100644 --- a/src/parser/parser_struct.c +++ b/src/parser/parser_struct.c @@ -12,6 +12,114 @@ #include "zprep_plugin.h" #include "../codegen/codegen.h" +extern char *g_current_filename; + +/** + * @brief Auto-imports std/mem.zc if not already imported. + * + * This is called when the Drop trait is used (impl Drop for X). + */ +static void auto_import_std_mem(ParserContext *ctx) +{ + // Check if Drop trait is already registered (means mem.zc was imported) + if (check_impl(ctx, "Drop", "__trait_marker__")) + { + // Check_impl returns 0 if not found, but we need a different check + // Let's check if we can find any indicator that mem.zc was loaded + } + + // Try to find and import std/mem.zc + static const char *std_paths[] = {"std/mem.zc", "./std/mem.zc", NULL}; + static const char *system_paths[] = {"/usr/local/share/zenc", "/usr/share/zenc", NULL}; + + char resolved_path[1024]; + int found = 0; + + // First, try relative to current file + if (g_current_filename) + { + char *current_dir = xstrdup(g_current_filename); + char *last_slash = strrchr(current_dir, '/'); + if (last_slash) + { + *last_slash = 0; + snprintf(resolved_path, sizeof(resolved_path), "%s/std/mem.zc", current_dir); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + free(current_dir); + } + + // Try relative paths + if (!found) + { + for (int i = 0; std_paths[i] && !found; i++) + { + if (access(std_paths[i], R_OK) == 0) + { + strncpy(resolved_path, std_paths[i], sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + found = 1; + } + } + } + + // Try system paths + if (!found) + { + for (int i = 0; system_paths[i] && !found; i++) + { + snprintf(resolved_path, sizeof(resolved_path), "%s/std/mem.zc", system_paths[i]); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + } + + if (!found) + { + return; // Could not find std/mem.zc + } + + // Canonicalize path + char *real_fn = realpath(resolved_path, NULL); + if (real_fn) + { + strncpy(resolved_path, real_fn, sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + free(real_fn); + } + + // Check if already imported + if (is_file_imported(ctx, resolved_path)) + { + return; + } + mark_file_imported(ctx, resolved_path); + + // Load and parse the file + char *src = load_file(resolved_path); + if (!src) + { + return; // Could not load file + } + + Lexer i; + lexer_init(&i, src); + + // Save and restore filename context + char *saved_fn = g_current_filename; + g_current_filename = resolved_path; + + // Parse the mem module contents + parse_program_nodes(ctx, &i); + + g_current_filename = saved_fn; +} + // Trait Parsing ASTNode *parse_trait(ParserContext *ctx, Lexer *l) { @@ -149,6 +257,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l) } register_trait(name); + add_to_global_list(ctx, n_node); // Track for codegen (VTable emission) return n_node; } @@ -206,6 +315,12 @@ ASTNode *parse_impl(ParserContext *ctx, Lexer *l) register_generic(ctx, target_gen_param); } + // Auto-import std/mem.zc if implementing Drop, Copy, or Clone traits + if (strcmp(name1, "Drop") == 0 || strcmp(name1, "Copy") == 0 || strcmp(name1, "Clone") == 0) + { + auto_import_std_mem(ctx); + } + register_impl(ctx, name1, name2); // RAII: Check for "Drop" trait implementation diff --git a/std/mem.zc b/std/mem.zc index 6ee96e8..f1a5f5a 100644 --- a/std/mem.zc +++ b/std/mem.zc @@ -49,28 +49,7 @@ impl Box { } } -struct Slice { - data: T*; - len: usize; -} - -impl Slice { - fn new(data: T*, len: usize) -> Self { - return Self { data: data, len: len }; - } - - fn get(self, i: usize) -> T { - return self.data[i]; - } - - fn set(self, i: usize, val: T) { - self.data[i] = val; - } - - fn is_empty(self) -> bool { - return self.len == 0; - } -} +// Note: Slice is defined in std/slice.zc with iteration support fn mem_zero(ptr: T*, count: usize) { memset(ptr, 0, sizeof(T) * count); diff --git a/std/slice.zc b/std/slice.zc index 7ace396..3c317ca 100644 --- a/std/slice.zc +++ b/std/slice.zc @@ -32,6 +32,11 @@ impl Slice { return Slice { data: arr, len: len }; } + // Alias for backwards compatibility with std/mem.zc + fn new(data: T*, len: usize) -> Slice { + return Slice { data: data, len: len }; + } + fn iterator(self) -> SliceIter { return SliceIter { data: self.data, diff --git a/tests/memory/test_memory_safety.zc b/tests/memory/test_memory_safety.zc index a5cc960..b672cc9 100644 --- a/tests/memory/test_memory_safety.zc +++ b/tests/memory/test_memory_safety.zc @@ -1,5 +1,6 @@ import "std/mem.zc" +import "std/slice.zc" // ** Globals ** let DROP_COUNT = 0; @@ -127,11 +128,13 @@ test "test_slice" { let data: int[5] = [1, 2, 3, 4, 5]; let s = Slice::new(&data[0], 5); f" Slice len: {(int)s.len}"; - let v2 = s.get(2); + let opt_v2 = s.get(2); + let v2 = opt_v2.unwrap(); f" Slice[2]: {v2}"; assert(v2 == 3, "Slice get failed"); - s.set(0, 99); - let v0 = s.get(0); + s.data[0] = 99; + let opt_v0 = s.get(0); + let v0 = opt_v0.unwrap(); f" After set: Slice[0] = {v0}"; assert(v0 == 99, "Slice set failed"); " ✓ Slice works!"; -- cgit v1.2.3 From aced94a89dd732d8ae8fddd27de9e1f1094c449a Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 15:48:07 +0000 Subject: Fix for #158 --- src/parser/parser_expr.c | 10 ++++++++++ std/slice.zc | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'std') diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 28dc465..7c53d96 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -5644,7 +5644,17 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) char *t1 = type_to_string(lhs->type_info); char *t2 = type_to_string(rhs->type_info); // Skip type check if either operand is void* (escape hatch type) + // or if either operand is a generic type parameter (T, K, V, etc.) int skip_check = (strcmp(t1, "void*") == 0 || strcmp(t2, "void*") == 0); + if (lhs->type_info->kind == TYPE_GENERIC || rhs->type_info->kind == TYPE_GENERIC) + { + skip_check = 1; + } + // Also check if type name is a single uppercase letter (common generic param) + if ((strlen(t1) == 1 && isupper(t1[0])) || (strlen(t2) == 1 && isupper(t2[0]))) + { + skip_check = 1; + } // Allow comparing pointers/strings with integer literal 0 (NULL) if (!skip_check) diff --git a/std/slice.zc b/std/slice.zc index 3c317ca..c757fbd 100644 --- a/std/slice.zc +++ b/std/slice.zc @@ -28,8 +28,8 @@ impl SliceIter { } impl Slice { - fn from_array(arr: T*, len: usize) -> Slice { - return Slice { data: arr, len: len }; + fn from_array(ptr: T*, len: usize) -> Slice { + return Slice { data: ptr, len: len }; } // Alias for backwards compatibility with std/mem.zc -- cgit v1.2.3 From 45165b0192325867771acc0e27a443100b700b3e Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 22:35:26 +0000 Subject: Improved networking example --- examples/networking/echo_server.zc | 32 +++++++++++++++++++++++--------- std/net.zc | 32 +++++++++++++++++--------------- 2 files changed, 40 insertions(+), 24 deletions(-) (limited to 'std') diff --git a/examples/networking/echo_server.zc b/examples/networking/echo_server.zc index 2934923..64c60da 100644 --- a/examples/networking/echo_server.zc +++ b/examples/networking/echo_server.zc @@ -4,6 +4,7 @@ import "std/net.zc" def SIZE = 1024; fn main() { + raw { setbuf(stdout, NULL); } "Starting Echo Server on 127.0.0.1:8080..."; let listener_res = TcpListener::bind("127.0.0.1", 8080); @@ -13,24 +14,37 @@ fn main() { } let listener = listener_res.unwrap(); - defer listener.close(); loop { let client_res = listener.accept(); if client_res.is_ok() { let stream = client_res.unwrap(); - defer stream.close(); - let buf = (char*)malloc(SIZE); - defer free(buf); - let read_res = stream.read(buf, SIZE); + "New Connection!"; - if read_res.is_ok() { + let buf: char[SIZE]; + + while true { + let read_res = stream.read(&buf[0], SIZE); + + if read_res.is_err() { + !"Read error: {read_res.err}"; + break; + } + let bytes = read_res.unwrap(); - if bytes > 0 { - stream.write(buf, bytes); - "Echoed {bytes} bytes."; + if bytes == 0 { + "Client disconnected."; + break; } + + let write_res = stream.write(&buf[0], bytes); + if write_res.is_err() { + !"Write error: {write_res.err}"; + break; + } + + "Echoed {bytes} bytes."; } } } diff --git a/std/net.zc b/std/net.zc index d410a4d..826b795 100644 --- a/std/net.zc +++ b/std/net.zc @@ -63,26 +63,28 @@ extern fn _z_net_write(fd: int, buf: const char*, n: usize) -> isize; struct TcpStream { - fd: int; + handle: int; } +extern fn strerror(errnum: int) -> char*; + impl TcpStream { fn read(self, buf: char*, len: usize) -> Result { - let n = read(self.fd, (void*)buf, len); - if (n < 0) return Result::Err("Read failed"); + let n = read(self.handle - 1, (void*)buf, len); + if (n < 0) return Result::Err(strerror(errno)); return Result::Ok((usize)n); } fn write(self, buf: char*, len: usize) -> Result { - let n = _z_net_write(self.fd, buf, len); + let n = _z_net_write(self.handle - 1, buf, len); if (n < 0) return Result::Err("Write failed"); return Result::Ok((usize)n); } fn close(self) { - if (self.fd >= 0) { - close(self.fd); - self.fd = -1; + if (self.handle > 0) { + close(self.handle - 1); + self.handle = 0; } } @@ -94,7 +96,7 @@ impl TcpStream { if (res == -1) { close(fd); return Result::Err("Invalid address"); } if (res == -2) { close(fd); return Result::Err("Connection failed"); } - return Result::Ok(TcpStream { fd: fd }); + return Result::Ok(TcpStream { handle: fd + 1 }); } } @@ -105,7 +107,7 @@ impl Drop for TcpStream { } struct TcpListener { - fd: int; + handle: int; } impl TcpListener { @@ -118,19 +120,19 @@ impl TcpListener { if (res == -2) { close(fd); return Result::Err("Bind failed"); } if (res == -3) { close(fd); return Result::Err("Listen failed"); } - return Result::Ok(TcpListener { fd: fd }); + return Result::Ok(TcpListener { handle: fd + 1 }); } fn accept(self) -> Result { - let client_fd = _z_net_accept(self.fd); + let client_fd = _z_net_accept(self.handle - 1); if (client_fd < 0) return Result::Err("Accept failed"); - return Result::Ok(TcpStream { fd: client_fd }); + return Result::Ok(TcpStream { handle: client_fd + 1 }); } fn close(self) { - if (self.fd >= 0) { - close(self.fd); - self.fd = -1; + if (self.handle > 0) { + close(self.handle - 1); + self.handle = 0; } } } -- cgit v1.2.3 From fcc9210aa32d671e16b392cf48546c4e2001ff8f Mon Sep 17 00:00:00 2001 From: Lam Wei Lun Date: Sun, 1 Feb 2026 14:46:46 +0800 Subject: Added detach/cancel and equality check for std/thread --- std/thread.zc | 56 ++++++++++++++++++++++++++++++++++++++++++------ tests/std/test_thread.zc | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 tests/std/test_thread.zc (limited to 'std') diff --git a/std/thread.zc b/std/thread.zc index 16f3ca1..0ab02e4 100644 --- a/std/thread.zc +++ b/std/thread.zc @@ -25,9 +25,13 @@ raw { z_closure_T *closure = (z_closure_T*)c; void (*f)(void*) = (void(*)(void*))closure->func; f(closure->ctx); - free(c); + free(c); return NULL; } + + static int _z_thread_equal(void* handle1, void* handle2) { + return pthread_equal((pthread_t)handle1, (pthread_t)handle2); + } static int _z_thread_spawn(void *ctx_copy, size_t *out_handle) { pthread_t pt; @@ -41,6 +45,14 @@ raw { static int _z_thread_join(void *handle) { return pthread_join((pthread_t)handle, NULL); } + + static int _z_thread_detach(void* handle) { + return pthread_detach((pthread_t)handle); + } + + static int _z_thread_cancel(void* handle) { + return pthread_cancel((pthread_t)handle); + } static void _z_mutex_init(void *ptr) { pthread_mutex_init((pthread_mutex_t*)ptr, NULL); @@ -63,8 +75,11 @@ raw { } } +extern fn _z_thread_equal(handle1: void*, handle2: void*) -> int; extern fn _z_thread_spawn(ctx: void*, out: usize*) -> int; extern fn _z_thread_join(handle: void*) -> int; +extern fn _z_thread_detach(handle: void*) -> int; +extern fn _z_thread_cancel(handle: void*) -> int; extern fn _z_mutex_init(ptr: void*); extern fn _z_mutex_lock(ptr: void*); extern fn _z_mutex_unlock(ptr: void*); @@ -74,21 +89,30 @@ extern fn _z_usleep(micros: int); struct Thread { - handle: void*; + handle: void*; } impl Thread { + fn eq(self, other: const Thread) -> bool { + return _z_thread_equal(self.handle, other.handle); + } + fn neq(self, other: const Thread) -> bool { + return !(self == other); + } + fn spawn(func: fn()) -> Result { let t: usize = 0; let ctx_copy = malloc(16); // z_closure_T is 16 bytes - if (ctx_copy == NULL) return Result::Err("OOM"); + if ctx_copy == NULL { + return Result::Err("OOM"); + } memcpy(ctx_copy, &func, 16); let ret = _z_thread_spawn(ctx_copy, &t); - if (ret != 0) { + if ret != 0 { free(ctx_copy); return Result::Err("Failed to create thread"); } @@ -97,8 +121,26 @@ impl Thread { } fn join(self) -> Result { - let ret = _z_thread_join(self.handle); - if (ret != 0) return Result::Err("Join failed"); + let err = _z_thread_join(self.handle); + if err { + return Result::Err("Join failed"); + } + return Result::Ok(true); + } + + fn detach(self) -> Result { + let err = _z_thread_detach(self.handle); + if err { + return Result::Err("Detach failed"); + } + return Result::Ok(true); + } + + fn cancel(self) -> Result { + let err = _z_thread_cancel(self.handle); + if err { + return Result::Err("Cancel failed"); + } return Result::Ok(true); } } @@ -123,7 +165,7 @@ impl Mutex { } fn free(self) { - if (self.handle) { + if self.handle { _z_mutex_destroy(self.handle); free(self.handle); self.handle = NULL; diff --git a/tests/std/test_thread.zc b/tests/std/test_thread.zc new file mode 100644 index 0000000..42fa82e --- /dev/null +++ b/tests/std/test_thread.zc @@ -0,0 +1,42 @@ +import "std/thread.zc" + +test "Thread Spawn and Join" { + "Testing thread spawn and join"; + + let spawn_result = Thread::spawn(fn(){ + "Running on a separate thread"; + }); + assert(spawn_result.is_ok(), "Thread spawn has failed"); + + let thr = spawn_result.unwrap(); + let join_result = thr.join(); + assert(join_result.is_ok(), "Thread join has failed"); +} + +test "Thread Spawn and Detach" { + "Testing thread spawn and detach"; + + let spawn_result = Thread::spawn(fn(){ + "Detached thread, this line might print later."; + }); + assert(spawn_result.is_ok(), "Thread spawn has failed"); + + let thr = spawn_result.unwrap(); + let detach_result = thr.detach(); + assert(detach_result.is_ok(), "Thread detach has failed"); +} + +test "Thread Spawn and Cancel" { + "Testing thread spawn and cancel"; + + let spawn_result = Thread::spawn(fn(){ + "Thread running indefinitely..."; + while true { + } + }); + assert(spawn_result.is_ok(), "Thread spawn has failed"); + + let thr = spawn_result.unwrap(); + let cancel_result = thr.cancel(); + assert(cancel_result.is_ok(), "Thread cancel has failed"); +} -- cgit v1.2.3 From fbfce63744882d48ea2fc514ab1594000254db80 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sun, 1 Feb 2026 14:01:51 +0000 Subject: Related to #138 --- README.md | 58 +++++++++++++++++++- README_ES.md | 57 +++++++++++++++++++- README_IT.md | 55 ++++++++++++++++++- README_ZH_CN.md | 58 +++++++++++++++++++- README_ZH_TW.md | 60 ++++++++++++++++++++- src/ast/ast.c | 56 +++++++++++++++++-- src/ast/ast.h | 58 +++++++++++--------- src/codegen/codegen.c | 92 ++++++++++++++++++++++++++++++- src/codegen/codegen_decl.c | 19 +++++-- src/parser/parser_expr.c | 4 +- src/parser/parser_struct.c | 6 +-- src/parser/parser_type.c | 62 ++++++++++++++++++--- src/parser/parser_utils.c | 22 ++++++-- std/fs.zc | 53 ++++++++++-------- std/io.zc | 23 ++++---- std/net.zc | 98 +++++++++++++++++++++------------- std/process.zc | 8 +-- std/string.zc | 9 ++-- std/thread.zc | 35 ++++++------ tests/collections/test_string_suite.zc | 4 +- tests/features/test_portable_types.zc | 46 ++++++++++++++++ 21 files changed, 737 insertions(+), 146 deletions(-) create mode 100644 tests/features/test_portable_types.zc (limited to 'std') diff --git a/README.md b/README.md index 87f95af..fc1aa27 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [Named Constraints](#named-constraints) - [15. Build Directives](#15-build-directives) - [16. Keywords](#16-keywords) + - [17. C Interoperability](#17-c-interoperability) - [Standard Library](#standard-library) - [Tooling](#tooling) - [Language Server (LSP)](#language-server-lsp) @@ -204,7 +205,11 @@ let y: const int = 10; // Read-only (Type qualified) | Type | C Equivalent | Description | |:---|:---|:---| -| `int`, `uint` | `int`, `unsigned int` | Platform standard integer | +| `int`, `uint` | `int32_t`, `uint32_t` | 32-bit signed/unsigned integer | +| `c_char`, `c_uchar` | `char`, `unsigned char` | C char / unsigned char (Interop) | +| `c_short`, `c_ushort` | `short`, `unsigned short` | C short / unsigned short (Interop) | +| `c_int`, `c_uint` | `int`, `unsigned int` | C int / unsigned int (Interop) | +| `c_long`, `c_ulong` | `long`, `unsigned long` | C long / unsigned long (Interop) | | `I8` .. `I128` or `i8` .. `i128` | `int8_t` .. `__int128_t` | Signed fixed-width integers | | `U8` .. `U128` or `u8` .. `u128` | `uint8_t` .. `__uint128_t` | Unsigned fixed-width integers | | `isize`, `usize` | `ptrdiff_t`, `size_t` | Pointer-sized integers | @@ -217,6 +222,12 @@ let y: const int = 10; // Read-only (Type qualified) | `iN` (for example, `i256`) | `_BitInt(N)` | Arbitrary bit-width signed integer (C23) | | `uN` (for example, `u42`) | `unsigned _BitInt(N)` | Arbitrary bit-width unsigned integer (C23) | +> **Best Practices for Portable Code** +> +> - Use **Portable Types** (`int`, `uint`, `i64`, `u8`, etc.) for all pure Zen C logic. `int` is guaranteed to be 32-bit signed on all architectures. +> - Use **C Interop Types** (`c_int`, `c_char`, `c_long`) **only** when interacting with C libraries (FFI). Their size varies by platform and C compiler (e.g. `c_long` size differs between Windows and Linux). +> - Use `isize` and `usize` for array indexing and memory pointer arithmetic. + ### 3. Aggregate Types #### Arrays @@ -1092,6 +1103,51 @@ The following identifiers are reserved because they are keywords in C11: #### Operators `and`, `or` +### 17. C Interoperability + +Zen C offers two ways to interact with C code: **Trusted Imports** (Convenient) and **Explicit FFI** (Safe/Precise). + +#### Method 1: Trusted Imports (Convenient) + +You can import a C header directly using the `import` keyword with the `.h` extension. This treats the header as a module and assumes all symbols accessed through it exist. + +```zc +//> link: -lm +import "math.h" as c_math; + +fn main() { + // Compiler trusts correctness; emits 'cos(...)' directly + let x = c_math::cos(3.14159); +} +``` + +> **Pros**: Zero boilerplate. Access everything in the header immediately. +> **Cons**: No type safety from Zen C (errors caught by C compiler later). + +#### Method 2: Explicit FFI (Safe) + +For strict type checking or when you don't want to include the text of a header, use `extern fn`. + +```zc +include // Emits #include in generated C + +// Define strict signature +extern fn printf(fmt: char*, ...) -> c_int; + +fn main() { + printf("Hello FFI: %d\n", 42); // Type checked by Zen C +} +``` + +> **Pros**: Zen C ensures types match. +> **Cons**: Requires manual declaration of functions. + +#### `import` vs `include` + +- **`import "file.h"`**: Registers the header as a named module. Enables implicit access to symbols (for example, `file::function()`). +- **`include `**: Purely emits `#include ` in the generated C code. Does not introduce any symbols to the Zen C compiler; you must use `extern fn` to access them. + + --- ## Standard Library diff --git a/README_ES.md b/README_ES.md index 11c4d89..07234f0 100644 --- a/README_ES.md +++ b/README_ES.md @@ -100,6 +100,7 @@ - [Restricciones con Nombre](#restricciones-con-nombre) - [15. Directivas de Construcción](#15-directivas-de-construcción) - [16. Palabras Clave](#16-palabras-clave) + - [17. Interoperabilidad C](#17-interoperabilidad-c) - [Biblioteca Estándar](#biblioteca-estándar) - [Herramientas](#herramientas) - [Servidor de Lenguaje (LSP)](#servidor-de-lenguaje-lsp) @@ -203,7 +204,11 @@ let y: const int = 10; // Solo lectura (Calificado por tipo) | Tipo | Equivalente en C | Descripción | |:---|:---|:---| -| `int`, `uint` | `int`, `unsigned int` | Entero estándar de la plataforma | +| `int`, `uint` | `int32_t`, `uint32_t` | Entero de 32 bits con signo/sin signo | +| `c_char`, `c_uchar` | `char`, `unsigned char` | C char (Interoperabilidad) | +| `c_short`, `c_ushort` | `short`, `unsigned short` | C short (Interoperabilidad) | +| `c_int`, `c_uint` | `int`, `unsigned int` | C int (Interoperabilidad) | +| `c_long`, `c_ulong` | `long`, `unsigned long` | C long (Interoperabilidad) | | `I8` .. `I128` o `i8` .. `i128` | `int8_t` .. `__int128_t` | Enteros con signo de ancho fijo | | `U8` .. `U128` o `u8` .. `u128` | `uint8_t` .. `__uint128_t` | Enteros sin signo de ancho fijo | | `isize`, `usize` | `ptrdiff_t`, `size_t` | Enteros del tamaño de un puntero | @@ -216,6 +221,12 @@ let y: const int = 10; // Solo lectura (Calificado por tipo) | `iN` (ej. `i256`) | `_BitInt(N)` | Entero con signo de ancho arbitrario (C23) | | `uN` (ej. `u42`) | `unsigned _BitInt(N)` | Entero sin signo de ancho arbitrario (C23) | +> **Mejores Prácticas para Código Portable** +> +> - Usa **Tipos Portables** (`int`, `uint`, `i64`, `u8`, etc.) para toda la lógica pura de Zen C. `int` garantiza ser 32-bits con signo en todas las arquitecturas. +> - Usa **Tipos de Interoperabilidad C** (`c_int`, `c_char`, `c_long`) **sólo** al interactuar con bibliotecas C (FFI). Su tamaño varía según la plataforma y el compilador C. +> - Usa `isize` y `usize` para indexado de arrays y aritmética de punteros. + ### 3. Tipos Agregados #### Arrays @@ -1092,6 +1103,50 @@ Los siguientes identificadores están reservados porque son palabras clave en C1 #### Operadores `and`, `or` +### 17. Interoperabilidad C +Zen C ofrece dos formas de interactuar con código C: **Importaciones de Confianza** (Conveniente) y **FFI Explícita** (Seguro/Preciso). + +#### Método 1: Importaciones de Confianza (Conveniente) + +Puedes importar una cabecera C directamente usando la palabra clave `import` con la extensión `.h`. Esto trata la cabecera como un módulo y asume que todos los símbolos accedidos existen. + +```zc +//> link: -lm +import "math.h" as c_math; + +fn main() { + // El compilador confía en la corrección; emite 'cos(...)' directamente + let x = c_math::cos(3.14159); +} +``` + +> **Pros**: Cero código repetitivo. Acceso a todo el contenido de la cabecera inmediato. +> **Cons**: Sin seguridad de tipos desde Zen C (errores capturados por el compilador C después). + +#### Método 2: FFI Explícita (Seguro) + +Para una comprobación estricta de tipos o cuando no quieres incluir el texto de una cabecera, usa `extern fn`. + +```zc +include // Emite #include en el C generado + +// Define firma estricta +extern fn printf(fmt: char*, ...) -> c_int; + +fn main() { + printf("Hola FFI: %d\n", 42); // Comprobado por tipos por Zen C +} +``` + +> **Pros**: Zen C asegura que los tipos coincidan. +> **Cons**: Requiere declaración manual de funciones. + +#### `import` vs `include` + +- **`import "file.h"`**: Registra la cabecera como un módulo con nombre. Habilita el acceso implícito a símbolos (ej. `file::function()`). +- **`include `**: Puramente emite `#include ` en el código C generado. No introduce ningún símbolo al compilador de Zen C; debes usar `extern fn` para acceder a ellos. + + --- ## Biblioteca Estándar diff --git a/README_IT.md b/README_IT.md index 9bf804e..be48cda 100644 --- a/README_IT.md +++ b/README_IT.md @@ -101,6 +101,7 @@ Unisciti alla conversazione, condividi delle demo, fai domande o segnala dei bug - [Vincoli Nominati](#vincoli-nominati) - [15. Direttive della Buil](#15-direttive-della-build) - [16. Keyword](#16-keyword) + - [17. Interoperabilità C](#17-interoperabilità-c) - [Libreria Standard](#liberia-standard) - [Tooling](#tooling) - [Language Server (LSP)](#language-server-lsp) @@ -205,7 +206,11 @@ let y: const int = 10; // Sola lettura (Tipo qualificato) | Tipo | C Equivalent | Descrizione | |:---|:---|:---| -| `int`, `uint` | `int`, `unsigned int` | Intero standard della piattaforma | +| `int`, `uint` | `int32_t`, `uint32_t` | Intero a 32 bit con segno/senza segno | +| `c_char`, `c_uchar` | `char`, `unsigned char` | C char (Interop) | +| `c_short`, `c_ushort` | `short`, `unsigned short` | C short (Interop) | +| `c_int`, `c_uint` | `int`, `unsigned int` | C int (Interop) | +| `c_long`, `c_ulong` | `long`, `unsigned long` | C long (Interop) | | `I8` .. `I128` or `i8` .. `i128` | `int8_t` .. `__int128_t` | Interi a grandezza fissa con segno | | `U8` .. `U128` or `u8` .. `u128` | `uint8_t` .. `__uint128_t` | Interi a grandezza fissa senza segno | | `isize`, `usize` | `ptrdiff_t`, `size_t` | Interi con grandezza di un puntatore | @@ -218,6 +223,12 @@ let y: const int = 10; // Sola lettura (Tipo qualificato) | `iN` (Per esempio, `i256`) | `_BitInt(N)` | Intero con segno a larghezza arbitraria di bit (C23) | | `uN` (Per esempio, `u42`) | `unsigned _BitInt(N)` | Intero senza segno a larghezza arbitraria di bit (C23) | +> **Best Practice per Codice Portabile** +> +> - Usa **Tipi Portabili** (`int`, `uint`, `i64`, `u8`, ecc.) per tutta la logica Zen C pura. `int` è garantito essere a 32-bit con segno su tutte le architetture. +> - Usa **Tipi di Interop C** (`c_int`, `c_char`, `c_long`) **solo** quando interagisci con librerie C (FFI). La loro dimensione varia in base alla piattaforma e al compilatore C. +> - Usa `isize` e `usize` per indicizzazione di array e aritmetica dei puntatori. + ### 3. Tipi Aggregati #### Array @@ -1089,6 +1100,48 @@ Gli identifiers seguenti sono riservati poiché sono keyword nello standard C11: #### Operatori `and`, `or` +### 17. Interoperabilità C +Zen C offre due modi per interagire con il codice C: **Import Trusted** (Conveniente) e **FFI Esplicita** (Sicuro/Preciso). + +#### Metodo 1: Import Trusted (Conveniente) +Puoi importare un header C direttamente usando la parola chiave `import` con l'estensione `.h`. Questo tratta l'header come un modulo e assume che tutti i simboli acceduti esistano. + +```zc +//> link: -lm +import "math.h" as c_math; + +fn main() { + // Il compilatore si fida della correttezza; emette 'cos(...)' direttamente + let x = c_math::cos(3.14159); +} +``` + +> **Pro**: Zero boilerplate. Accesso immediato a tutto nell'header. +> **Contro**: Nessuna sicurezza dei tipi da Zen C (errori catturati dal compilatore C dopo). + +#### Metodo 2: FFI Esplicita (Sicuro) +Per un controllo rigoroso dei tipi o quando non vuoi includere il testo di un header, usa `extern fn`. + +```zc +include // Emette #include nel C generato + +// Definisci firma rigorosa +extern fn printf(fmt: char*, ...) -> c_int; + +fn main() { + printf("Ciao FFI: %d\n", 42); // Controllato nei tipi da Zen C +} +``` + +> **Pro**: Zen C assicura che i tipi corrispondano. +> **Contro**: Richiede dichiarazione manuale delle funzioni. + +#### `import` vs `include` + +- **`import "file.h"`**: Registra l'header come un modulo con nome. Abilita l'accesso implicito ai simboli (es. `file::function()`). +- **`include `**: Emette puramente `#include ` nel codice C generato. Non introduce alcun simbolo nel compilatore Zen C; devi usare `extern fn` per accedervi. + + --- ## Libreria Standard diff --git a/README_ZH_CN.md b/README_ZH_CN.md index aa1130b..af83e8c 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -100,6 +100,7 @@ - [命名约束](#命名约束) - [15. 构建指令](#15-构建指令) - [16. 关键字](#16-关键字) + - [17. C 互操作性](#17-c-互操作性) - [标准库](#标准库) - [工具链](#工具链) - [语言服务器 (LSP)](#语言服务器-lsp) @@ -203,7 +204,11 @@ let y: const int = 10; // 只读 (类型修饰) | 类型 | C 等效类型 | 描述 | |:---|:---|:---| -| `int`, `uint` | `int`, `unsigned int` | 平台标准整数 | +| `int`, `uint` | `int32_t`, `uint32_t` | 32位有符号/无符号整数 | +| `c_char`, `c_uchar` | `char`, `unsigned char` | C char (互操作) | +| `c_short`, `c_ushort` | `short`, `unsigned short` | C short (互操作) | +| `c_int`, `c_uint` | `int`, `unsigned int` | C int (互操作) | +| `c_long`, `c_ulong` | `long`, `unsigned long` | C long (互操作) | | `I8` .. `I128` 或 `i8` .. `i128` | `int8_t` .. `__int128_t` | 有符号固定宽度整数 | | `U8` .. `U128` 或 `u8` .. `u128` | `uint8_t` .. `__uint128_t` | 无符号固定宽度整数 | | `isize`, `usize` | `ptrdiff_t`, `size_t` | 指针大小的整数 | @@ -216,6 +221,12 @@ let y: const int = 10; // 只读 (类型修饰) | `iN` (例 `i256`) | `_BitInt(N)` | 任意位宽有符号整数 (C23) | | `uN` (例 `u42`) | `unsigned _BitInt(N)` | 任意位宽无符号整数 (C23) | +> **可移植代码最佳实践** +> +> - 对于所有纯 Zen C 逻辑,请使用 **可移植类型** (`int`、`uint`、`i64`、`u8` 等)。`int` 保证在所有架构上都是 32 位有符号整数。 +> - 仅在与 C 库 (FFI) 交互时使用 **C 互操作类型** (`c_int`、`c_char`、`c_long`)。它们的大小因平台和 C 编译器而异。 +> - 使用 `isize` 和 `usize` 进行数组索引和内存指针运算。 + ### 3. 复合类型 #### 数组 @@ -1091,6 +1102,51 @@ fn main() { ... } #### 运算符 `and`, `or` +### 17. C 互操作性 + +Zen C 提供了两种与 C 代码交互的方式:**信任导入 (Trusted Imports)** (方便) 和 **显式 FFI** (安全/精确)。 + +#### 方法 1: 信任导入 (方便) + +你可以使用 `import` 关键字直接导入 `.h` 扩展名的 C 头文件。这会将头文件视为一个模块,并假设通过它访问的所有符号都存在。 + +```zc +//> link: -lm +import "math.h" as c_math; + +fn main() { + // 编译器信任不仅正确;直接生成 'cos(...)' + let x = c_math::cos(3.14159); +} +``` + +> **优点**: 零样板代码。立即访问头文件中的所有内容。 +> **缺点**: Zen C 不提供类型安全 (错误将在稍后由 C 编译器捕获)。 + +#### 方法 2: 显式 FFI (安全) + +对于严格的类型检查,或当你不想包含头文件文本时,请使用 `extern fn`. + +```zc +include // 在生成的 C 代码中发出 #include + +// 定义严格签名 +extern fn printf(fmt: char*, ...) -> c_int; + +fn main() { + printf("Hello FFI: %d\n", 42); // 由 Zen C 进行类型检查 +} +``` + +> **优点**: Zen C 确保类型匹配。 +> **缺点**: 需要手动声明函数。 + +#### `import` vs `include` + +- **`import "file.h"`**: 将头文件注册为命名模块。启用对符号的隐式访问 (例如 `file::function()`)。 +- **`include `**: 纯粹在生成的 C 代码中发出 `#include `。不向 Zen C 编译器引入任何符号;必须使用 `extern fn` 才能访问它们。 + + --- ## 标准库 diff --git a/README_ZH_TW.md b/README_ZH_TW.md index 5d85a76..3a18a53 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -100,6 +100,7 @@ - [命名約束](#命名約束) - [15. 構建指令](#15-構建指令) - [16. 關鍵字](#16-關鍵字) + - [17. C 互操作性](#17-c-互操作性) - [標準庫](#標準庫) - [工具鏈](#工具鏈) - [語言服務器 (LSP)](#語言服務器-lsp) @@ -203,7 +204,11 @@ let y: const int = 10; // 只讀 (類型修飾) | 類型 | C 等效類型 | 描述 | |:---|:---|:---| -| `int`, `uint` | `int`, `unsigned int` | 平台標準整數 | +| `int`, `uint` | `int32_t`, `uint32_t` | 32位元有號/無號整數 | +| `c_char`, `c_uchar` | `char`, `unsigned char` | C char (互操作) | +| `c_short`, `c_ushort` | `short`, `unsigned short` | C short (互操作) | +| `c_int`, `c_uint` | `int`, `unsigned int` | C int (互操作) | +| `c_long`, `c_ulong` | `long`, `unsigned long` | C long (互操作) | | `I8` .. `I128` 或 `i8` .. `i128` | `int8_t` .. `__int128_t` | 有符號固定寬度整數 | | `U8` .. `U128` 或 `u8` .. `u128` | `uint8_t` .. `__uint128_t` | 無符號固定寬度整數 | | `isize`, `usize` | `ptrdiff_t`, `size_t` | 指針大小的整數 | @@ -214,7 +219,13 @@ let y: const int = 10; // 只讀 (類型修飾) | `string` | `char*` | C-string (以 null 結尾) | | `U0`, `u0`, `void` | `void` | 空類型 | | `iN` (例 `i256`) | `_BitInt(N)` | 任意位元寬度有號整數 (C23) | -| `uN` (例 `u42`) | `unsigned _BitInt(N)` | 任意位元寬度無號整數 (C23) | +| `uN` (例 `u42`) | `unsigned _BitInt(N)` | 任意位寬無號整數 (C23) | + +> **可移植代碼最佳實踐** +> +> - 對於所有純 Zen C 邏輯,請使用 **可移植類型** (`int`、`uint`、`i64`、`u8` 等)。`int` 保證在所有架構上都是 32 位元有號整數。 +> - 僅在與 C 庫 (FFI) 交互時使用 **C 互操作類型** (`c_int`、`c_char`、`c_long`)。它們的大小因平台和 C 編譯器而異。 +> - 使用 `isize` 和 `usize` 進行數組索引和內存指針運算。 ### 3. 複合類型 @@ -1091,6 +1102,51 @@ fn main() { ... } #### 運算符 `and`, `or` +### 17. C 互操作性 + +Zen C 提供了兩種與 C 代碼交互的方式:**信任導入 (Trusted Imports)** (方便) 和 **顯式 FFI** (安全/精確)。 + +#### 方法 1: 信任導入 (方便) + +你可以使用 `import` 關鍵字直接導入 `.h` 擴展名的 C 頭文件。這會將頭文件視為一個模塊,並假設通過它訪問的所有符號都存在。 + +```zc +//> link: -lm +import "math.h" as c_math; + +fn main() { + // 編譯器信任不僅正確;直接生成 'cos(...)' + let x = c_math::cos(3.14159); +} +``` + +> **優點**: 零樣板代碼。立即訪問頭文件中的所有內容。 +> **缺點**: Zen C 不提供類型安全 (錯誤將在稍後由 C 編譯器捕獲)。 + +#### 方法 2: 顯式 FFI (安全) + +對於嚴格的類型檢查,或當你不想包含頭文件文本時,請使用 `extern fn`. + +```zc +include // 在生成的 C 代碼中發出 #include + +// 定義嚴格簽名 +extern fn printf(fmt: char*, ...) -> c_int; + +fn main() { + printf("Hello FFI: %d\n", 42); // 由 Zen C 進行類型檢查 +} +``` + +> **優點**: Zen C 確保類型匹配。 +> **缺點**: 需要手動聲明函數。 + +#### `import` vs `include` + +- **`import "file.h"`**: 將頭文件註冊為命名模塊。啟用對符號的隱式訪問 (例如 `file::function()`)。 +- **`include `**: 純粹在生成的 C 代碼中發出 `#include `。不向 Zen C 編譯器引入任何符號;必須使用 `extern fn` 才能訪問它們。 + + --- ## 標準庫 diff --git a/src/ast/ast.c b/src/ast/ast.c index 439a9f5..1b35500 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -259,6 +259,25 @@ static char *type_to_string_impl(Type *t) return xstrdup("int32_t"); case TYPE_UINT: return xstrdup("unsigned int"); + + // Portable C Types + case TYPE_C_INT: + return xstrdup("c_int"); + case TYPE_C_UINT: + return xstrdup("c_uint"); + case TYPE_C_LONG: + return xstrdup("c_long"); + case TYPE_C_ULONG: + return xstrdup("c_ulong"); + case TYPE_C_SHORT: + return xstrdup("c_short"); + case TYPE_C_USHORT: + return xstrdup("c_ushort"); + case TYPE_C_CHAR: + return xstrdup("c_char"); + case TYPE_C_UCHAR: + return xstrdup("c_uchar"); + case TYPE_INT: return xstrdup("int"); case TYPE_FLOAT: @@ -461,8 +480,29 @@ static char *type_to_c_string_impl(Type *t) return xstrdup("int32_t"); case TYPE_UINT: return xstrdup("unsigned int"); - case TYPE_INT: + + // Portable C Types (Map directly to C types) + case TYPE_C_INT: return xstrdup("int"); + case TYPE_C_UINT: + return xstrdup("unsigned int"); + case TYPE_C_LONG: + return xstrdup("long"); + case TYPE_C_ULONG: + return xstrdup("unsigned long"); + case TYPE_C_SHORT: + return xstrdup("short"); + case TYPE_C_USHORT: + return xstrdup("unsigned short"); + case TYPE_C_CHAR: + return xstrdup("char"); + case TYPE_C_UCHAR: + return xstrdup("unsigned char"); + + case TYPE_INT: + // 'int' in Zen C maps to 'i32' now for portability. + // FFI should use c_int. + return xstrdup("int32_t"); case TYPE_FLOAT: return xstrdup("float"); case TYPE_BITINT: @@ -519,8 +559,11 @@ static char *type_to_c_string_impl(Type *t) return res; } - char *res = xmalloc(strlen(inner) + 7); - sprintf(res, "Slice_%s", inner); + char *inner_zens = type_to_string(t->inner); + char *res = xmalloc(strlen(inner_zens) + 7); + sprintf(res, "Slice_%s", inner_zens); + free(inner_zens); + free(inner); return res; } @@ -561,7 +604,12 @@ static char *type_to_c_string_impl(Type *t) return xstrdup("z_closure_T"); case TYPE_GENERIC: - return xstrdup(t->name); + // Use type_to_string to get the mangled name (e.g. Option_int) instead of raw C string + // composition This ensures consistency with struct definitions. + { + char *s = type_to_string(t); + return s; + } case TYPE_ALIAS: return type_to_c_string(t->inner); diff --git a/src/ast/ast.h b/src/ast/ast.h index 4498d7c..fa67043 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -28,30 +28,40 @@ typedef enum */ typedef enum { - TYPE_VOID, ///< `void` type. - TYPE_BOOL, ///< `bool` type. - TYPE_CHAR, ///< `char` type. - TYPE_STRING, ///< `string` type. - TYPE_U0, ///< `u0` type. - TYPE_I8, ///< `i8` type. - TYPE_U8, ///< `u8` type. - TYPE_I16, ///< `i16` type. - TYPE_U16, ///< `u16` type. - TYPE_I32, ///< `i32` type. - TYPE_U32, ///< `u32` type. - TYPE_I64, ///< `i64` type. - TYPE_U64, ///< `u64` type. - TYPE_I128, ///< `i128` type. - TYPE_U128, ///< `u128` type. - TYPE_F32, ///< `f32` type. - TYPE_F64, ///< `f64` type. - TYPE_INT, ///< `int` (alias, usually i32). - TYPE_FLOAT, ///< `float` (alias). - TYPE_USIZE, ///< `usize` (pointer size unsigned). - TYPE_ISIZE, ///< `isize` (pointer size signed). - TYPE_BYTE, ///< `byte`. - TYPE_RUNE, ///< `rune`. - TYPE_UINT, ///< `uint` (alias). + TYPE_VOID, ///< `void` type. + TYPE_BOOL, ///< `bool` type. + TYPE_CHAR, ///< `char` type. + TYPE_STRING, ///< `string` type. + TYPE_U0, ///< `u0` type. + TYPE_I8, ///< `i8` type. + TYPE_U8, ///< `u8` type. + TYPE_I16, ///< `i16` type. + TYPE_U16, ///< `u16` type. + TYPE_I32, ///< `i32` type. + TYPE_U32, ///< `u32` type. + TYPE_I64, ///< `i64` type. + TYPE_U64, ///< `u64` type. + TYPE_I128, ///< `i128` type. + TYPE_U128, ///< `u128` type. + TYPE_F32, ///< `f32` type. + TYPE_F64, ///< `f64` type. + TYPE_INT, ///< `int` (alias, usually i32). + TYPE_FLOAT, ///< `float` (alias). + TYPE_USIZE, ///< `usize` (pointer size unsigned). + TYPE_ISIZE, ///< `isize` (pointer size signed). + TYPE_BYTE, ///< `byte`. + TYPE_RUNE, ///< `rune`. + TYPE_UINT, ///< `uint` (alias). + // Portable C Types (FFI) + TYPE_C_INT, ///< `c_int` (int). + TYPE_C_UINT, ///< `c_uint` (unsigned int). + TYPE_C_LONG, ///< `c_long` (long). + TYPE_C_ULONG, ///< `c_ulong` (unsigned long). + TYPE_C_SHORT, ///< `c_short` (short). + TYPE_C_USHORT, ///< `c_ushort` (unsigned short). + TYPE_C_CHAR, ///< `c_char` (char). + TYPE_C_UCHAR, ///< `c_uchar` (unsigned char). + TYPE_STRUCT, ///< Struct type. TYPE_ENUM, ///< Enum type. TYPE_POINTER, ///< Pointer type (*). diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 0496a46..384820b 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -1150,14 +1150,102 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) break; } case NODE_EXPR_CAST: - fprintf(out, "((%s)(", node->cast.target_type); + { + const char *t = node->cast.target_type; + const char *mapped = t; + if (strcmp(t, "c_int") == 0) + { + mapped = "int"; + } + else if (strcmp(t, "c_uint") == 0) + { + mapped = "unsigned int"; + } + else if (strcmp(t, "c_long") == 0) + { + mapped = "long"; + } + else if (strcmp(t, "c_ulong") == 0) + { + mapped = "unsigned long"; + } + else if (strcmp(t, "c_short") == 0) + { + mapped = "short"; + } + else if (strcmp(t, "c_ushort") == 0) + { + mapped = "unsigned short"; + } + else if (strcmp(t, "c_char") == 0) + { + mapped = "char"; + } + else if (strcmp(t, "c_uchar") == 0) + { + mapped = "unsigned char"; + } + else if (strcmp(t, "int") == 0) + { + mapped = "int32_t"; + } + else if (strcmp(t, "uint") == 0) + { + mapped = "unsigned int"; + } + + fprintf(out, "((%s)(", mapped); codegen_expression(ctx, node->cast.expr, out); fprintf(out, "))"); break; + } case NODE_EXPR_SIZEOF: if (node->size_of.target_type) { - fprintf(out, "sizeof(%s)", node->size_of.target_type); + const char *t = node->size_of.target_type; + const char *mapped = t; + if (strcmp(t, "c_int") == 0) + { + mapped = "int"; + } + else if (strcmp(t, "c_uint") == 0) + { + mapped = "unsigned int"; + } + else if (strcmp(t, "c_long") == 0) + { + mapped = "long"; + } + else if (strcmp(t, "c_ulong") == 0) + { + mapped = "unsigned long"; + } + else if (strcmp(t, "c_short") == 0) + { + mapped = "short"; + } + else if (strcmp(t, "c_ushort") == 0) + { + mapped = "unsigned short"; + } + else if (strcmp(t, "c_char") == 0) + { + mapped = "char"; + } + else if (strcmp(t, "c_uchar") == 0) + { + mapped = "unsigned char"; + } + else if (strcmp(t, "int") == 0) + { + mapped = "int32_t"; // Strict mapping + } + else if (strcmp(t, "uint") == 0) + { + mapped = "unsigned int"; // uint alias + } + + fprintf(out, "sizeof(%s)", mapped); } else { diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 31bd2ee..1623ffc 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -1130,12 +1130,23 @@ void print_type_defs(ParserContext *ctx, FILE *out, ASTNode *nodes) fprintf(out, "typedef struct Tuple_%s Tuple_%s;\nstruct Tuple_%s { ", t->sig, t->sig, t->sig); char *s = xstrdup(t->sig); - char *p = strtok(s, "_"); + char *current = s; + char *next_sep = strstr(current, "__"); int i = 0; - while (p) + while (current) { - fprintf(out, "%s v%d; ", p, i++); - p = strtok(NULL, "_"); + if (next_sep) + { + *next_sep = 0; + fprintf(out, "%s v%d; ", current, i++); + current = next_sep + 2; + next_sep = strstr(current, "__"); + } + else + { + fprintf(out, "%s v%d; ", current, i++); + break; + } } free(s); fprintf(out, "};\n"); diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 7c53d96..5bf0089 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -1908,7 +1908,7 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) { Type *formal_type = parse_type_formal(ctx, l); concrete_types[arg_count] = type_to_string(formal_type); - unmangled_types[arg_count] = type_to_c_string(formal_type); + unmangled_types[arg_count] = type_to_string(formal_type); arg_count++; if (lexer_peek(l).type == TOK_COMMA) @@ -2944,7 +2944,7 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) { if (i > 0) { - strcat(sig, "_"); + strcat(sig, "__"); } strcat(sig, type_strs[i]); } diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c index e53b56c..c89ad34 100644 --- a/src/parser/parser_struct.c +++ b/src/parser/parser_struct.c @@ -863,7 +863,7 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union, int is_opaque) Token field_name = lexer_next(l); lexer_next(l); // eat : Type *ft = parse_type_formal(ctx, l); - char *field_type_str = type_to_string(ft); + char *field_type_str = type_to_c_string(ft); expect(l, TOK_SEMICOLON, "Expected ;"); ASTNode *nf = ast_create(NODE_FIELD); @@ -947,7 +947,7 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union, int is_opaque) Token f_name = lexer_next(l); expect(l, TOK_COLON, "Expected :"); Type *ft = parse_type_formal(ctx, l); - char *f_type = type_to_string(ft); + char *f_type = type_to_c_string(ft); ASTNode *f = ast_create(NODE_FIELD); f->field.name = token_strdup(f_name); @@ -1120,7 +1120,7 @@ ASTNode *parse_enum(ParserContext *ctx, Lexer *l) while (lexer_peek(l).type == TOK_COMMA) { lexer_next(l); // eat , - strcat(sig, "_"); + strcat(sig, "__"); Type *next_t = parse_type_obj(ctx, l); char *ns = type_to_string(next_t); if (strlen(sig) + strlen(ns) + 2 > 510) diff --git a/src/parser/parser_type.c b/src/parser/parser_type.c index 49e961c..fcbe12d 100644 --- a/src/parser/parser_type.c +++ b/src/parser/parser_type.c @@ -427,13 +427,13 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) if (strcmp(name, "uint") == 0) { free(name); - return type_new(TYPE_UINT); + return type_new(TYPE_U32); // Strict uint32_t } if (strcmp(name, "int") == 0) { free(name); - return type_new(TYPE_INT); + return type_new(TYPE_I32); // Strict int32_t } if (strcmp(name, "float") == 0) { @@ -467,23 +467,31 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) } if (strcmp(name, "long") == 0) { + zwarn_at(t, "'long' is treated as portable 'int64_t' in Zen C. Use 'c_long' for " + "platform-dependent C long."); free(name); return type_new(TYPE_I64); } if (strcmp(name, "short") == 0) { + zwarn_at(t, "'short' is treated as portable 'int16_t' in Zen C. Use 'c_short' for " + "platform-dependent C short."); free(name); return type_new(TYPE_I16); } if (strcmp(name, "unsigned") == 0) { + zwarn_at(t, "'unsigned' is treated as portable 'uint32_t' in Zen C. Use 'c_uint' for " + "platform-dependent C unsigned int."); free(name); - return type_new(TYPE_UINT); + return type_new(TYPE_U32); } if (strcmp(name, "signed") == 0) { + zwarn_at(t, "'signed' is treated as portable 'int32_t' in Zen C. Use 'c_int' for " + "platform-dependent C int."); free(name); - return type_new(TYPE_INT); + return type_new(TYPE_I32); } if (strcmp(name, "int8_t") == 0) { @@ -536,6 +544,48 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) return type_new(TYPE_ISIZE); } + // Portable C Types + if (strcmp(name, "c_int") == 0) + { + free(name); + return type_new(TYPE_C_INT); + } + if (strcmp(name, "c_uint") == 0) + { + free(name); + return type_new(TYPE_C_UINT); + } + if (strcmp(name, "c_long") == 0) + { + free(name); + return type_new(TYPE_C_LONG); + } + if (strcmp(name, "c_ulong") == 0) + { + free(name); + return type_new(TYPE_C_ULONG); + } + if (strcmp(name, "c_short") == 0) + { + free(name); + return type_new(TYPE_C_SHORT); + } + if (strcmp(name, "c_ushort") == 0) + { + free(name); + return type_new(TYPE_C_USHORT); + } + if (strcmp(name, "c_char") == 0) + { + free(name); + return type_new(TYPE_C_CHAR); + } + if (strcmp(name, "c_uchar") == 0) + { + free(name); + return type_new(TYPE_C_UCHAR); + } + // Relaxed Type Check: If explicit 'struct Name', trust the user. if (explicit_struct) { @@ -677,7 +727,7 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) zpanic_at(t, "Expected > after generic"); } - char *unmangled_arg = type_to_c_string(first_arg); + char *unmangled_arg = type_to_string(first_arg); int is_single_dep = 0; for (int k = 0; k < ctx->known_generics_count; ++k) @@ -791,7 +841,7 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) if (lexer_peek(l).type == TOK_COMMA) { lexer_next(l); - strcat(sig, "_"); + strcat(sig, "__"); } else { diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 28d2c11..8ea2934 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -691,16 +691,22 @@ void register_tuple(ParserContext *ctx, const char *sig) s_def->strct.name = xstrdup(struct_name); char *s_sig = xstrdup(sig); - char *tok = strtok(s_sig, "_"); + char *current = s_sig; + char *next_sep = strstr(current, "__"); ASTNode *head = NULL, *tail = NULL; int i = 0; - while (tok) + while (current) { + if (next_sep) + { + *next_sep = 0; + } + ASTNode *f = ast_create(NODE_FIELD); char fname[32]; sprintf(fname, "v%d", i++); f->field.name = xstrdup(fname); - f->field.type = xstrdup(tok); + f->field.type = xstrdup(current); if (!head) { @@ -712,7 +718,15 @@ void register_tuple(ParserContext *ctx, const char *sig) } tail = f; - tok = strtok(NULL, "_"); + if (next_sep) + { + current = next_sep + 2; + next_sep = strstr(current, "__"); + } + else + { + break; + } } free(s_sig); s_def->strct.fields = head; diff --git a/std/fs.zc b/std/fs.zc index a00993b..5b2cb21 100644 --- a/std/fs.zc +++ b/std/fs.zc @@ -15,9 +15,9 @@ include include // Direct externs for simple functions with const char* parameters -extern fn access(pathname: const char*, mode: int) -> int; -extern fn unlink(pathname: const char*) -> int; -extern fn rmdir(pathname: const char*) -> int; +extern fn access(pathname: const char*, mode: c_int) -> c_int; +extern fn unlink(pathname: const char*) -> c_int; +extern fn rmdir(pathname: const char*) -> c_int; extern fn malloc(size: usize) -> void*; extern fn free(ptr: void*); @@ -90,17 +90,17 @@ raw { } } -extern fn _z_fs_mkdir(path: const char*) -> int; -extern fn _z_fs_get_metadata(path: const char*, size: U64*, is_dir: int*, is_file: int*) -> int; -extern fn _z_fs_read_entry(dir: void*, out_name: char*, buf_size: int, is_dir: int*) -> int; +extern fn _z_fs_mkdir(path: const char*) -> c_int; +extern fn _z_fs_get_metadata(path: const char*, size: U64*, is_dir: c_int*, is_file: c_int*) -> c_int; +extern fn _z_fs_read_entry(dir: void*, out_name: char*, buf_size: c_int, is_dir: c_int*) -> c_int; extern fn _z_fs_fopen(path: const char*, mode: const char*) -> void*; -extern fn _z_fs_fclose(stream: void*) -> int; +extern fn _z_fs_fclose(stream: void*) -> c_int; extern fn _z_fs_fread(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; extern fn _z_fs_fwrite(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; -extern fn _z_fs_fseek(stream: void*, offset: I64, whence: int) -> int; +extern fn _z_fs_fseek(stream: void*, offset: I64, whence: c_int) -> c_int; extern fn _z_fs_ftell(stream: void*) -> I64; extern fn _z_fs_opendir(name: const char*) -> void*; -extern fn _z_fs_closedir(dir: void*) -> int; +extern fn _z_fs_closedir(dir: void*) -> c_int; struct File { @@ -191,41 +191,50 @@ impl File { } fn exists(path: char*) -> bool { - return access(path, Z_F_OK) == 0; + let zero: c_int = 0; + return access(path, Z_F_OK) == zero; } fn metadata(path: char*) -> Result { let size: uint64_t; - let is_d: int; - let is_f: int; + let is_d: c_int; + let is_f: c_int; - if (_z_fs_get_metadata(path, &size, &is_d, &is_f) != 0) { + let res = _z_fs_get_metadata(path, &size, &is_d, &is_f); + let non_zero: c_int = 0; + if (res != non_zero) { return Result::Err("Failed to get metadata"); } return Result::Ok(Metadata { size: (U64)size, - is_dir: is_d != 0, - is_file: is_f != 0 + is_dir: is_d != non_zero, + is_file: is_f != non_zero }); } fn create_dir(path: char*) -> Result { - if (_z_fs_mkdir(path) != 0) { + let res = _z_fs_mkdir(path); + let zero: c_int = 0; + if (res != zero) { return Result::Err("Failed to create directory"); } return Result::Ok(true); } fn remove_file(path: char*) -> Result { - if (unlink(path) != 0) { + let res = unlink(path); + let zero: c_int = 0; + if (res != zero) { return Result::Err("Failed to remove file"); } return Result::Ok(true); } fn remove_dir(path: char*) -> Result { - if (rmdir(path) != 0) { + let res = rmdir(path); + let zero: c_int = 0; + if (res != zero) { return Result::Err("Failed to remove directory"); } return Result::Ok(true); @@ -245,17 +254,19 @@ impl File { return Result< Vec >::Err("Out of memory"); } - let is_d: int = 0; + let is_d: c_int = 0; + let is_d_zero: c_int = 0; while (_z_fs_read_entry(dir, name_buf, 256, &is_d)) { - if (strcmp(name_buf, ".") == 0 || strcmp(name_buf, "..") == 0) { + let zero_cmp: c_int = 0; + if (strcmp(name_buf, ".") == zero_cmp || strcmp(name_buf, "..") == zero_cmp) { continue; } let s = String::new(name_buf); let ent = DirEntry { name: s, - is_dir: is_d != 0 + is_dir: is_d != is_d_zero }; // Transfer ownership: String -> DirEntry diff --git a/std/io.zc b/std/io.zc index a5a7359..d9829dd 100644 --- a/std/io.zc +++ b/std/io.zc @@ -6,11 +6,11 @@ include include // These work directly with const char* in extern declarations -extern fn vprintf(fmt: const char*, ap: va_list) -> int; +extern fn vprintf(fmt: const char*, ap: va_list) -> c_int; // vsnprintf is problematic on macOS because it's a macro that expands to a builtin with a different signature // so we wrap it in a C function to avoid the conflict -extern fn _z_vsnprintf(str: char*, size: usize, fmt: const char*, ap: va_list) -> int; +extern fn _z_vsnprintf(str: char*, size: usize, fmt: const char*, ap: va_list) -> c_int; // EOF is typically -1, but we define it for portability def Z_EOF = -1; @@ -27,7 +27,7 @@ raw { } extern fn _z_get_stdin() -> void*; -extern fn _z_fgetc(stream: void*) -> int; +extern fn _z_fgetc(stream: void*) -> c_int; fn format(fmt: char*, ...) -> char* { static let buffer: char[1024]; @@ -40,7 +40,7 @@ fn format(fmt: char*, ...) -> char* { return (char*)buffer; } -fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> int { +fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> c_int { let ap: va_list; va_start(ap, fmt); @@ -63,7 +63,7 @@ fn format_new(fmt: char*, ...) -> char* { return buffer; } -fn print(fmt: char*, ...) -> int { +fn print(fmt: char*, ...) -> c_int { let ap: va_list; va_start(ap, fmt); let ret = vprintf(fmt, ap); @@ -71,7 +71,7 @@ fn print(fmt: char*, ...) -> int { return ret; } -fn println(fmt: char*, ...) -> int { +fn println(fmt: char*, ...) -> c_int { let ap: va_list; va_start(ap, fmt); let ret = vprintf(fmt, ap); @@ -86,13 +86,15 @@ fn readln() -> char* { let line: char* = malloc(cap); if (line == NULL) return NULL; - let c: int; + let c: c_int; let std_in = _z_get_stdin(); while (true) { c = _z_fgetc(std_in); - if (c == Z_EOF) break; - if (c == 10) break; // '\n' + let eof_c: c_int = Z_EOF; + if (c == eof_c) break; + let nl_c: c_int = 10; + if (c == nl_c) break; // '\n' if (len + 1 >= cap) { cap = cap * 2; @@ -108,7 +110,8 @@ fn readln() -> char* { len = len + 1; } - if (len == 0 && c == Z_EOF) { + let eof_final: c_int = Z_EOF; + if (len == 0 && c == eof_final) { free(line); return NULL; } diff --git a/std/net.zc b/std/net.zc index 826b795..dce1a01 100644 --- a/std/net.zc +++ b/std/net.zc @@ -14,9 +14,9 @@ def Z_AF_INET = 2; def Z_SOCK_STREAM = 1; // Direct externs for simple socket functions -extern fn socket(domain: int, type: int, proto: int) -> int; -extern fn close(fd: int) -> int; -extern fn read(fd: int, buf: void*, count: usize) -> isize; +extern fn socket(domain: c_int, type: c_int, proto: c_int) -> c_int; +extern fn close(fd: c_int) -> c_int; +extern fn read(fd: c_int, buf: void*, count: usize) -> isize; // Minimal raw block: required for struct sockaddr_in usage // These functions encapsulate sockaddr_in setup because the struct layout @@ -56,47 +56,63 @@ raw { } } -extern fn _z_net_bind(fd: int, host: const char*, port: int) -> int; -extern fn _z_net_connect(fd: int, host: const char*, port: int) -> int; -extern fn _z_net_accept(fd: int) -> int; -extern fn _z_net_write(fd: int, buf: const char*, n: usize) -> isize; +extern fn _z_net_bind(fd: c_int, host: const char*, port: c_int) -> c_int; +extern fn _z_net_connect(fd: c_int, host: const char*, port: c_int) -> c_int; +extern fn _z_net_accept(fd: c_int) -> c_int; +extern fn _z_net_write(fd: c_int, buf: const char*, n: usize) -> isize; struct TcpStream { - handle: int; + handle: c_int; } -extern fn strerror(errnum: int) -> char*; +extern fn strerror(errnum: c_int) -> char*; impl TcpStream { fn read(self, buf: char*, len: usize) -> Result { let n = read(self.handle - 1, (void*)buf, len); - if (n < 0) return Result::Err(strerror(errno)); + let zero: c_int = 0; + if (n < (isize)zero) return Result::Err(strerror(errno)); return Result::Ok((usize)n); } - fn write(self, buf: char*, len: usize) -> Result { - let n = _z_net_write(self.handle - 1, buf, len); - if (n < 0) return Result::Err("Write failed"); + fn write(self, buf: u8*, len: usize) -> Result { + let one: c_int = 1; + let n: isize = _z_net_write(self.handle - one, buf, len); + let zero: isize = 0; + if (n < zero) return Result::Err("Write failed"); return Result::Ok((usize)n); } fn close(self) { - if (self.handle > 0) { - close(self.handle - 1); - self.handle = 0; + let zero: c_int = 0; + if (self.handle > zero) { + let one: c_int = 1; + close(self.handle - one); + self.handle = zero; } } - fn connect(host: char*, port: int) -> Result { - let fd = socket(Z_AF_INET, Z_SOCK_STREAM, 0); - if (fd < 0) return Result::Err("Failed to create socket"); + fn connect(host: char*, port: c_int) -> Result { + let zero: c_int = 0; + let fd = socket(Z_AF_INET, Z_SOCK_STREAM, zero); + if (fd < zero) return Result::Err("Failed to create socket"); + + // C constants like -1 + let neg_one: c_int = -1; let res = _z_net_connect(fd, host, port); - if (res == -1) { close(fd); return Result::Err("Invalid address"); } - if (res == -2) { close(fd); return Result::Err("Connection failed"); } + if (res == neg_one) { close(fd); return Result::Err("Invalid address"); } + // _z_net_connect might return -2? The original code had it. + // Assuming -2 is also possible... check implementation or just assume logic was correct. + // But wait, the original code had: + // if (res == -1) ... if (res == -2) ... + // I will keep it but cast strict. + let neg_two: c_int = -2; + if (res == neg_two) { close(fd); return Result::Err("Connection failed"); } - return Result::Ok(TcpStream { handle: fd + 1 }); + let one: c_int = 1; + return Result::Ok(TcpStream { handle: fd + one }); } } @@ -107,32 +123,42 @@ impl Drop for TcpStream { } struct TcpListener { - handle: int; + handle: c_int; } impl TcpListener { - fn bind(host: char*, port: int) -> Result { - let fd = socket(Z_AF_INET, Z_SOCK_STREAM, 0); - if (fd < 0) return Result::Err("Failed to create socket"); + fn bind(host: char*, port: c_int) -> Result { + let zero: c_int = 0; + let fd = socket(Z_AF_INET, Z_SOCK_STREAM, zero); + if (fd < zero) return Result::Err("Failed to create socket"); let res = _z_net_bind(fd, host, port); - if (res == -1) { close(fd); return Result::Err("Invalid address"); } - if (res == -2) { close(fd); return Result::Err("Bind failed"); } - if (res == -3) { close(fd); return Result::Err("Listen failed"); } + let neg_one: c_int = -1; + let neg_two: c_int = -2; + let neg_three: c_int = -3; + + if (res == neg_one) { close(fd); return Result::Err("Invalid address"); } + if (res == neg_two) { close(fd); return Result::Err("Bind failed"); } + if (res == neg_three) { close(fd); return Result::Err("Listen failed"); } - return Result::Ok(TcpListener { handle: fd + 1 }); + let one: c_int = 1; + return Result::Ok(TcpListener { handle: fd + one }); } fn accept(self) -> Result { - let client_fd = _z_net_accept(self.handle - 1); - if (client_fd < 0) return Result::Err("Accept failed"); - return Result::Ok(TcpStream { handle: client_fd + 1 }); + let one: c_int = 1; + let client_fd = _z_net_accept(self.handle - one); + let zero: c_int = 0; + if (client_fd < zero) return Result::Err("Accept failed"); + return Result::Ok(TcpStream { handle: client_fd + one }); } fn close(self) { - if (self.handle > 0) { - close(self.handle - 1); - self.handle = 0; + let zero: c_int = 0; + if (self.handle > zero) { + let one: c_int = 1; + close(self.handle - one); + self.handle = zero; } } } diff --git a/std/process.zc b/std/process.zc index 3ce43b6..9f432c0 100644 --- a/std/process.zc +++ b/std/process.zc @@ -9,7 +9,7 @@ include include // system() can be externed directly with const char* -extern fn system(command: const char*) -> int; +extern fn system(command: const char*) -> c_int; // Minimal raw block: only for opaque FILE* types // popen/pclose/fgets use FILE* which conflicts with void* @@ -28,8 +28,8 @@ raw { } extern fn _z_popen(command: const char*, type: const char*) -> void*; -extern fn _z_pclose(stream: void*) -> int; -extern fn _z_fgets(s: char*, size: int, stream: void*) -> char*; +extern fn _z_pclose(stream: void*) -> c_int; +extern fn _z_fgets(s: char*, size: c_int, stream: void*) -> char*; struct Output { stdout: String; @@ -88,7 +88,7 @@ impl Command { let buf = (char*)malloc(buf_size); while (true) { - let res = _z_fgets(buf, (int)buf_size, fp); + let res = _z_fgets(buf, (c_int)buf_size, fp); if (res == 0) break; let chunk = String::from(buf); diff --git a/std/string.zc b/std/string.zc index 0bc9539..54f11b2 100644 --- a/std/string.zc +++ b/std/string.zc @@ -90,7 +90,8 @@ impl String { } fn eq(self, other: String*) -> bool { - return strcmp(self.c_str(), (*other).c_str()) == 0; + let zero: c_int = 0; + return strcmp(self.c_str(), (*other).c_str()) == zero; } fn length(self) -> usize { @@ -146,7 +147,8 @@ impl String { fn starts_with(self, prefix: char*) -> bool { let plen = strlen(prefix); if plen > self.length() { return false; } - return strncmp(self.c_str(), prefix, plen) == 0; + let zero: c_int = 0; + return strncmp(self.c_str(), prefix, plen) == zero; } fn ends_with(self, suffix: char*) -> bool { @@ -154,7 +156,8 @@ impl String { let len = self.length(); if slen > len { return false; } let offset = (int)(len - slen); - return strcmp(self.c_str() + offset, suffix) == 0; + let zero: c_int = 0; + return strcmp(self.c_str() + offset, suffix) == zero; } fn free(self) { diff --git a/std/thread.zc b/std/thread.zc index 16f3ca1..78d2547 100644 --- a/std/thread.zc +++ b/std/thread.zc @@ -35,11 +35,11 @@ raw { if (ret == 0) { *out_handle = (size_t)pt; } - return ret; + return (int)ret; } static int _z_thread_join(void *handle) { - return pthread_join((pthread_t)handle, NULL); + return (int)pthread_join((pthread_t)handle, NULL); } static void _z_mutex_init(void *ptr) { @@ -63,13 +63,13 @@ raw { } } -extern fn _z_thread_spawn(ctx: void*, out: usize*) -> int; -extern fn _z_thread_join(handle: void*) -> int; +extern fn _z_thread_spawn(ctx: void*, out: usize*) -> c_int; +extern fn _z_thread_join(handle: void*) -> c_int; extern fn _z_mutex_init(ptr: void*); extern fn _z_mutex_lock(ptr: void*); extern fn _z_mutex_unlock(ptr: void*); extern fn _z_mutex_destroy(ptr: void*); -extern fn _z_usleep(micros: int); +extern fn _z_usleep(micros: c_int); @@ -79,26 +79,28 @@ struct Thread { impl Thread { fn spawn(func: fn()) -> Result { - let t: usize = 0; + let out_handle: usize = 0; - let ctx_copy = malloc(16); // z_closure_T is 16 bytes - if (ctx_copy == NULL) return Result::Err("OOM"); + let ctx = malloc(16); // z_closure_T is 16 bytes + if (ctx == NULL) return Result::Err("OOM"); - memcpy(ctx_copy, &func, 16); + memcpy(ctx, &func, 16); - let ret = _z_thread_spawn(ctx_copy, &t); - - if (ret != 0) { - free(ctx_copy); + let ret = _z_thread_spawn(ctx, &out_handle); + let zero: c_int = 0; + if (ret != zero) { + // Failed to spawn + free(ctx); return Result::Err("Failed to create thread"); } - return Result::Ok(Thread { handle: (void*)t }); + return Result::Ok(Thread { handle: (void*)out_handle }); } fn join(self) -> Result { let ret = _z_thread_join(self.handle); - if (ret != 0) return Result::Err("Join failed"); + let zero: c_int = 0; + if (ret != zero) return Result::Err("Join failed"); return Result::Ok(true); } } @@ -138,5 +140,6 @@ impl Drop for Mutex { } fn sleep_ms(ms: int) { - _z_usleep(ms * 1000); + let micros: c_int = (c_int)(ms * 1000); + _z_usleep(micros); } diff --git a/tests/collections/test_string_suite.zc b/tests/collections/test_string_suite.zc index afe08af..64ed9d8 100644 --- a/tests/collections/test_string_suite.zc +++ b/tests/collections/test_string_suite.zc @@ -91,7 +91,9 @@ test "test_fstrings_return" { let inner = f"Inner({x})"; let outer = f"Outer({inner})"; println "Composed: {outer}"; - assert(strcmp(outer, "Outer(Inner(100))") == 0, "Composed f-string failed"); + let outer_res = strcmp(outer, "Outer(Inner(100))"); + let zero: c_int = 0; + assert(outer_res == zero, "Composed f-string failed"); } test "test_string_std_ops" { diff --git a/tests/features/test_portable_types.zc b/tests/features/test_portable_types.zc new file mode 100644 index 0000000..8f54fcb --- /dev/null +++ b/tests/features/test_portable_types.zc @@ -0,0 +1,46 @@ + +import "std/io.zc"; + +// This test verifies the new portable integer types and C interop types. + +extern fn abs(x: c_int) -> c_int; +extern fn labs(x: c_long) -> c_long; + +fn main() -> int { + // Portable types + let a: i32 = -42; + let b: u32 = 42; + let c: i64 = -1000000; + let d: u64 = 1000000; + + if (a != -42) return 1; + if (b != 42) return 2; + if (c != -1000000) return 3; + if (d != 1000000) return 4; + + // C Types + let ca: c_int = -10; + let cb: c_long = -20; + let cc: c_short = -5; + let cd: c_char = 65; // 'A' + + // Test C interaction + let abs_val = abs(ca); + let expected_abs: c_int = 10; + if (abs_val != expected_abs) return 5; + + let labs_val = labs(cb); + let expected_labs: c_long = 20; + if (labs_val != expected_labs) return 6; + + // Size checks (these are platform dependent but we can check relations) + // sizeof(c_char) is always 1 + if (sizeof(c_char) != 1) return 7; + + // sizeof(c_short) <= sizeof(c_int) <= sizeof(c_long) + if (sizeof(c_short) > sizeof(c_int)) return 8; + if (sizeof(c_int) > sizeof(c_long)) return 9; + + printf("Portable types test passed.\n"); + return 0; +} -- cgit v1.2.3 From dadabf8b9d11d099777acc261068a3ed8ca06f24 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Mon, 2 Feb 2026 00:33:12 +0000 Subject: Fix concurrency suite compilation errors involving C types --- src/ast/ast.c | 3 +++ src/parser/parser_expr.c | 3 +-- std/thread.zc | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'std') diff --git a/src/ast/ast.c b/src/ast/ast.c index 1b35500..28fe678 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -101,6 +101,9 @@ int is_integer_type(Type *t) t->kind == TYPE_ISIZE || t->kind == TYPE_BYTE || t->kind == TYPE_RUNE || t->kind == TYPE_UINT || t->kind == TYPE_I128 || t->kind == TYPE_U128 || t->kind == TYPE_BITINT || t->kind == TYPE_UBITINT || + t->kind == TYPE_C_INT || t->kind == TYPE_C_UINT || t->kind == TYPE_C_LONG || + t->kind == TYPE_C_ULONG || t->kind == TYPE_C_SHORT || t->kind == TYPE_C_USHORT || + t->kind == TYPE_C_CHAR || t->kind == TYPE_C_UCHAR || (t->kind == TYPE_STRUCT && t->name && (0 == strcmp(t->name, "int8_t") || 0 == strcmp(t->name, "uint8_t") || 0 == strcmp(t->name, "int16_t") || 0 == strcmp(t->name, "uint16_t") || diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index a732448..f27e2c3 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -370,8 +370,7 @@ static void check_format_string(ASTNode *call, Token t) if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x' || spec == 'X' || spec == 'o') { - if (vt && vt->kind != TYPE_INT && vt->kind != TYPE_I64 && !type_is_unsigned(vt) && - vt->kind != TYPE_CHAR) + if (vt && !is_integer_type(vt)) { warn_format_string(t, arg_num, "integer", got_type); } diff --git a/std/thread.zc b/std/thread.zc index da21772..0722b60 100644 --- a/std/thread.zc +++ b/std/thread.zc @@ -108,10 +108,10 @@ impl Thread { memcpy(ctx, &func, 16); - let ret = _z_thread_spawn(ctx_copy, &t); + let ret = _z_thread_spawn(ctx, &out_handle); if ret != 0 { - free(ctx_copy); + free(ctx); return Result::Err("Failed to create thread"); } -- cgit v1.2.3