From d2602dafbb709934098ee9170784d446ab15166f Mon Sep 17 00:00:00 2001 From: Fernando Dev Date: Wed, 15 Jul 2026 20:21:39 -0300 Subject: [PATCH] Some corrections and improvements. --- .gitignore | 1 + examples/generics6.cx | 25 ++++++ examples/std/file.cx | 29 +++++++ examples/std/hashmap.cx | 26 ++++++ examples/std/hello.cx | 5 +- examples/ternary.cx | 6 ++ src/backend/codegen.d | 19 ++--- src/frontend/parser/ast.d | 31 +++++++ src/frontend/parser/parse_expr.d | 13 +++ src/frontend/type_expr.d | 3 +- src/frontend/type_resolve.d | 7 ++ src/utils.d | 11 +++ std/arena.cx | 19 ++++- std/file.cx | 85 +++++++++++++++++++ std/hashmap.cx | 137 +++++++++++++++++++++++++++++++ std/path.cx | 46 +++++++++++ 16 files changed, 442 insertions(+), 21 deletions(-) create mode 100644 examples/generics6.cx create mode 100644 examples/std/file.cx create mode 100644 examples/std/hashmap.cx create mode 100644 examples/ternary.cx create mode 100644 std/file.cx create mode 100644 std/hashmap.cx create mode 100644 std/path.cx diff --git a/.gitignore b/.gitignore index 65f443e..618fe6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /cx *.c +harpy diff --git a/examples/generics6.cx b/examples/generics6.cx new file mode 100644 index 0000000..6b93f13 --- /dev/null +++ b/examples/generics6.cx @@ -0,0 +1,25 @@ +//! F1\nchar*\n +import std.io; + +alias F1 = Foo + +struct Foo { + void bar(T val) { + if __is(F1, Foo) + printf("F1\n"); + else if __is(K, char*) + printf("char*\n"); + else + printf("%s %s\n", __type(K), __type(T)); + } +} + +int main() { + F1 f1; + f1.bar(60); + + Foo f2; + f2.bar(60); + + return 0; +} diff --git a/examples/std/file.cx b/examples/std/file.cx new file mode 100644 index 0000000..87dd66d --- /dev/null +++ b/examples/std/file.cx @@ -0,0 +1,29 @@ +//# 0 +import std.file; +import std.path; +import std.io; + +int main() { + char* testPath = "test.txt"; + + char* ext = Path.ext(testPath); + printf("Extension: %s\n", ext); + + bool!FileError writeRes = File.writeText(testPath, "Hello Cx World!"); + if !writeRes.valid { + printf("Failed to write: %d\n", writeRes.error); + return 1; + } + + char*!FileError readRes = File.readText(testPath); + if !readRes.valid { + printf("Failed to read: %d\n", readRes.error); + return 1; + } + + char* content = readRes.ok; + printf("File content: %s\n", content); + + free(content); + return 0; +} diff --git a/examples/std/hashmap.cx b/examples/std/hashmap.cx new file mode 100644 index 0000000..d60128a --- /dev/null +++ b/examples/std/hashmap.cx @@ -0,0 +1,26 @@ +//! age: 18\nkey 'fernando' not found\nupdated age: 19\nkeys: 3\n +import std.hashmap; +import std.io; + +int main() { + HashMap map = HashMap.new(16); + defer map.delete(); + + map.set("age", 18); + map.set("year", 2026); + map.set("port", 8080); + + Box age = map.get("age"); + if !age.isEmpty() + printf("age: %d\n", age.unwrap()); + + Box nonexistent = map.get("fernando"); + if nonexistent.isEmpty() + printf("key 'fernando' not found\n"); + + map.set("age", 19); // overwrite + printf("updated age: %d\n", map.get("age").unwrap()); + printf("keys: %d\n", map.size); + + return 0; +} diff --git a/examples/std/hello.cx b/examples/std/hello.cx index c2b0232..e0cffe3 100644 --- a/examples/std/hello.cx +++ b/examples/std/hello.cx @@ -1,11 +1,8 @@ //! Hello from Cx ;) \n -import std; // importa todos os módulos (sem subdiretórios) da stdlib +import std.io; int main() { printf("Hello from Cx ;) \n"); return 0; } -int main() { - return 1; -} diff --git a/examples/ternary.cx b/examples/ternary.cx new file mode 100644 index 0000000..f288550 --- /dev/null +++ b/examples/ternary.cx @@ -0,0 +1,6 @@ +//# 67 + +/// The Main Function +int main() { + return 0 == 0 ? 67 : 69; +} diff --git a/src/backend/codegen.d b/src/backend/codegen.d index b272610..4f69751 100644 --- a/src/backend/codegen.d +++ b/src/backend/codegen.d @@ -540,6 +540,10 @@ private: // writeln("right: ", tn.right.toStr()); return format("%s", tn.left.toStr() == tn.right.toStr() ? "true" : "false"); + case NodeKind.TernaryExpr: + TernaryExpr tn = cast(TernaryExpr) node; + return format("%s ? %s : %s", compileExpr(tn.expr), compileExpr(tn.left), compileExpr(tn.right)); + default: return "/* invalid expr */"; } @@ -763,17 +767,6 @@ private: actualType = a; } - string clearNameMangling(string name) - { - string buff; - for (ulong i; i < name.length; i++) - if (name[i] == '*') - buff ~= 'P'; - else - buff ~= name[i]; - return buff; - } - void compileFnDecl(FnDecl fn, uint ind, bool isMethod = false, string methodType = "", bool fromGeneric = false) { defer = []; // limpa @@ -817,7 +810,7 @@ private: if (fnErrorUnion) { TypeExprResult res = cast(TypeExprResult) fn.type_expr; - fnUnion = res.toString(); + fnUnion = clearNameMangling(res.toString()); fnError[0] = res.ok; fnError[1] = res.error; @@ -827,7 +820,7 @@ private: errorUnions[fnUnion] = true; typedefs ~= format("typedef struct %s %s;", fnUnion, fnUnion); unionErrors ~= format("struct %s { bool valid; union { %s ok; %s error; } val; };", fnUnion, - res.ok.toStr(), res.error.toStr()); + res.ok.toString(), res.error.toStr()); } } bool hasReturn; diff --git a/src/frontend/parser/ast.d b/src/frontend/parser/ast.d index 60c9b6f..acfafce 100644 --- a/src/frontend/parser/ast.d +++ b/src/frontend/parser/ast.d @@ -33,6 +33,7 @@ enum NodeKind : ubyte TypeNameExpr, // 1 2 IsExpr, // 1 2 TTypeExpr, // 1 2 + TernaryExpr, // 1 2 IncludeHeader, // 1 2 VarDecl, // 1 2 @@ -1685,6 +1686,36 @@ class IsExpr : Node } } +class TernaryExpr : Node +{ + Node expr, left, right; + + this(Node expr, Node left, Node right, Position pos) + { + super(NodeKind.TernaryExpr, pos); + this.expr = expr; + this.left = left; + this.right = right; + } + + override void print(uint indent) + { + iprint(indent, "TernaryExpr"); + } + + override TernaryExpr dup() + { + return new TernaryExpr(expr.dup(), left.dup(), right.dup(), pos); + } + + override void subGeneric(string[] names, TypeExpr[] types) + { + expr.subGeneric(names, types); + left.subGeneric(names, types); + right.subGeneric(names, types); + } +} + pragma(inline, true) private void iprint(uint indent, string s) { diff --git a/src/frontend/parser/parse_expr.d b/src/frontend/parser/parse_expr.d index 849f75f..48d3077 100644 --- a/src/frontend/parser/parse_expr.d +++ b/src/frontend/parser/parse_expr.d @@ -12,6 +12,7 @@ enum Precedence : ubyte { Low, Assign, // = += -= etc + Ternary, // ? Or, // || And, // && BitOr, // | @@ -407,6 +408,14 @@ public: return new IndexExpr(left, idx, p.getPos(left.pos, end)); } + Node parseTernaryExpr(Node expr) + { + Node left = parse(); + p.consume(TokenKind.Colon, "Expected ':'"); + Node right = parse(); + return new TernaryExpr(expr, left, right, p.getPos(expr.pos, right.pos)); + } + Node led(Node left) { Token tk = p.advance(); @@ -452,6 +461,8 @@ public: case TokenKind.PPlus: case TokenKind.MMinus: return new UnaryExpr(left, tk.kind, tk.pos, true); + case TokenKind.Question: + return parseTernaryExpr(left); default: return left; } @@ -472,6 +483,8 @@ public: case TokenKind.SHLEquals: case TokenKind.SHREquals: return Precedence.Assign; + case TokenKind.Question: + return Precedence.Ternary; case TokenKind.Or: return Precedence.Or; case TokenKind.And: diff --git a/src/frontend/type_expr.d b/src/frontend/type_expr.d index 0f7878d..38f00a3 100644 --- a/src/frontend/type_expr.d +++ b/src/frontend/type_expr.d @@ -2,6 +2,7 @@ module frontend.type_expr; import frontend.lexer : Position; +import utils; import std.format; import std.stdio; @@ -419,7 +420,7 @@ class TypeExprResult : TypeExpr override string toStrVar(string var = "") const { - return format("%s%s", ok.toStr(), error.toStr()) ~ (var == "" ? "" : " " ~ var); + return format("%s%s", clearNameMangling(ok.toString()), error.toString()) ~ (var == "" ? "" : " " ~ var); } override string toStr() const diff --git a/src/frontend/type_resolve.d b/src/frontend/type_resolve.d index e51b034..eb83c4a 100644 --- a/src/frontend/type_resolve.d +++ b/src/frontend/type_resolve.d @@ -173,6 +173,13 @@ private: resolveExprType((cast(TypeNameExpr) n).expr, scp); return n.type_expr; + case NodeKind.TernaryExpr: + TernaryExpr tn = cast(TernaryExpr) n; + resolveExprType(tn.expr, scp); + resolveExprType(tn.left, scp); + resolveExprType(tn.right, scp); + return tn.left.type_expr; + default: // já vêm com type_expr setado no próprio construtor return n.type_expr; diff --git a/src/utils.d b/src/utils.d index 0ff5012..72c3e71 100644 --- a/src/utils.d +++ b/src/utils.d @@ -30,3 +30,14 @@ string ext(string file) { return (file[$ - 3 .. $] != ".cx") ? file ~ ".cx" : file; } + +string clearNameMangling(string name) +{ + string buff; + for (ulong i; i < name.length; i++) + if (name[i] == '*') + buff ~= 'P'; + else + buff ~= name[i]; + return buff; +} diff --git a/std/arena.cx b/std/arena.cx index c4e9136..2483692 100644 --- a/std/arena.cx +++ b/std/arena.cx @@ -1,22 +1,33 @@ import std.lib; -struct Arena +/// A simple bump allocator. Grows by reallocating when capacity +/// is exceeded; never shrinks. Caller owns the lifetime and must +/// call `free()` when done. +struct Arena { char* data; u32 size; u32 cap; - static Arena new(u32 cap) + /// Creates a new Arena with the given initial capacity in bytes. + /// The underlying buffer is zero-initialized. + static Arena new(u32 cap) { char* data = calloc(1, cap); return (Arena) {data, 0, cap}; } + /// Rounds `size` up to the next multiple of 8, ensuring + /// allocations stay aligned. Internal helper used by `alloc`. u32 alignUp(u32 size) { return (size + 7) & ~7; } + /// Allocates `size` bytes from the arena, aligned to 8 bytes. + /// Grows the underlying buffer automatically if there isn't + /// enough room left. Returns a pointer valid until `free()` + /// is called or the arena is reallocated again. void* alloc(u32 size) { size = self.alignUp(size); @@ -32,7 +43,9 @@ struct Arena return (void*) data; } - void free() + /// Releases the underlying buffer. After calling this, every + /// pointer previously returned by `alloc()` is invalid. + void free() { free(self.data); } diff --git a/std/file.cx b/std/file.cx new file mode 100644 index 0000000..4568d83 --- /dev/null +++ b/std/file.cx @@ -0,0 +1,85 @@ +import std.array; +import std.lib; +include +include + +enum FileError { + NotFound, + PermissionDenied, + ReadError, + WriteError, + InvalidPath, +} + +struct File { + static bool exists(char* path) { + FILE* f = fopen(path, "r"); + if f == null return false; + fclose(f); + return true; + } + + static u64!FileError size(char* path) { + FILE* f = fopen(path, "rb"); + if f == null return FileError.NotFound; + defer fclose(f); + + if fseek(f, 0, SEEK_END) != 0 return FileError.ReadError; + long sz = ftell(f); + if sz < 0 return FileError.ReadError; + return (u64)sz; + } + + static Array!FileError readBytes(char* path) { + FILE* f = fopen(path, "rb"); + if f == null return FileError.NotFound; + defer fclose(f); + + if fseek(f, 0, SEEK_END) != 0 return FileError.ReadError; + long sz = ftell(f); + if sz < 0 return FileError.ReadError; + fseek(f, 0, SEEK_SET); + + Array buffer = Array.new((uint)sz); + + uint read_count = (uint)fread(buffer.data, 1, (size_t)sz, f); + if read_count != (uint)sz { + if buffer.data != null + free(buffer.data); + buffer.free(); + return FileError.ReadError; + } + + buffer.size = read_count; + return buffer; + } + + static char*!FileError readText(char* path) { + FILE* f = fopen(path, "r"); + if f == null return FileError.NotFound; + defer fclose(f); + + if fseek(f, 0, SEEK_END) != 0 return FileError.ReadError; + long sz = ftell(f); + if sz < 0 return FileError.ReadError; + fseek(f, 0, SEEK_SET); + + char* buffer = (char*)malloc((size_t)sz + 1); + if buffer == null return FileError.ReadError; + + uint read_count = (uint)fread(buffer, 1, (size_t)sz, f); + buffer[read_count] = '\0'; // Null-terminate + + return buffer; + } + + static bool!FileError writeText(char* path, char* content) { + FILE* f = fopen(path, "w"); + if f == null return FileError.PermissionDenied; + defer fclose(f); + + if fputs(content, f) == EOF + return FileError.WriteError; + return true; + } +} diff --git a/std/hashmap.cx b/std/hashmap.cx new file mode 100644 index 0000000..2de4c12 --- /dev/null +++ b/std/hashmap.cx @@ -0,0 +1,137 @@ +//! age: 18\nkey 'fernando' not found\nupdated age: 19\nkeys: 3\n +import std.string; +import std.lib; +import std.box; + +u32 murmur3(char* key, u32 len, u32 seed) { + u32 c1 = 0xcc9e2d51; + u32 c2 = 0x1b873593; + u32 h1 = seed; + u32 roundedEnd = (len & 0xfffffffc); // múltiplo de 4 mais próximo, pra baixo + + u32 i = 0; + while i < roundedEnd { + u32 k1 = (key[i] & 0xff) + | ((key[i + 1] & 0xff) << 8) + | ((key[i + 2] & 0xff) << 16) + | ((key[i + 3] & 0xff) << 24); + + k1 = k1 * c1; + k1 = (k1 << 15) | (k1 >> 17); + k1 = k1 * c2; + + h1 = h1 ^ k1; + h1 = (h1 << 13) | (h1 >> 19); + h1 = h1 * 5 + 0xe6546b64; + + i = i + 4; + } + + u32 k1 = 0; + u32 rem = len & 3; + if rem == 3 { + k1 = k1 ^ ((key[roundedEnd + 2] & 0xff) << 16); + } + if rem >= 2 { + k1 = k1 ^ ((key[roundedEnd + 1] & 0xff) << 8); + } + if rem >= 1 { + k1 = k1 ^ (key[roundedEnd] & 0xff); + k1 = k1 * c1; + k1 = (k1 << 15) | (k1 >> 17); + k1 = k1 * c2; + h1 = h1 ^ k1; + } + + h1 = h1 ^ len; + h1 = h1 ^ (h1 >> 16); + h1 = h1 * 0x85ebca6b; + h1 = h1 ^ (h1 >> 13); + h1 = h1 * 0xc2b2ae35; + h1 = h1 ^ (h1 >> 16); + + return h1; +} + +u32 hashKey(char* key) { + return murmur3(key, key.length, 0x9747b28c); +} + +struct Entry { + char* key; + V value; + Entry* next; + + static Entry* create(char* key, V value) { + Entry* e = malloc(sizeof(Entry)); + e.key = key; + e.value = value; + e.next = null; + return e; + } +} + +struct HashMap { + Entry** buckets; + u32 cap; + u32 size; + + static HashMap new(u32 cap) { + Entry** buckets = calloc(cap, sizeof(Entry*)); + return (HashMap) {buckets, cap, 0}; + } + + u32 bucketIndex(char* key) { + return hashKey(key) % self.cap; + } + + void set(char* key, V value) { + u32 idx = self.bucketIndex(key); + Entry* curr = self.buckets[idx]; + + while curr != null { + if strcmp(curr.key, key) == 0 { + curr.value = value; + return; + } + curr = curr.next; + } + + Entry* entry = Entry.create(key, value); + entry.next = self.buckets[idx]; + self.buckets[idx] = entry; + self.size = self.size + 1; + } + + Box get(char* key) { + u32 idx = self.bucketIndex(key); + Entry* curr = self.buckets[idx]; + + while curr != null { + if curr.key === key + return Box.of(curr.value); + curr = curr.next; + } + + return Box.empty(); + } + + bool has(char* key) { + Box res = self.get(key); + return !res.isEmpty(); + } + + void delete() { + u32 i = 0; + while i < self.cap { + Entry* curr = self.buckets[i]; + while curr != null { + Entry* next = curr.next; + free(curr); + curr = next; + } + i = i + 1; + } + free(self.buckets); + } +} diff --git a/std/path.cx b/std/path.cx new file mode 100644 index 0000000..5669b98 --- /dev/null +++ b/std/path.cx @@ -0,0 +1,46 @@ +include + +struct Path { + static char* ext(char* path) { + char* dot = strrchr(path, '.'); + if dot == null || dot == path return ""; + return dot + 1; + } + + static char* base(char* path) { + char* slash = strrchr(path, '/'); + if slash == null + // Fallback para Windows + slash = strrchr(path, '\\'); + if slash == null return path; + return slash + 1; + } + + static char* dir(char* path) { + char* slash = strrchr(path, '/'); + if slash == null + slash = strrchr(path, '\\'); + if slash != null { + *slash = '\0'; + return path; + } + return "."; + } + + static char* join(char* a, char* b) { + size_t lenA = strlen(a); + size_t lenB = strlen(b); + + // Verifica se 'a' já termina com '/' + bool needsSlash = (lenA > 0 && a[lenA - 1] != '/' && a[lenA - 1] != '\\'); + size_t extra = needsSlash ? 1 : 0; + + char* result = (char*)malloc(lenA + extra + lenB + 1); + if result == null return null; + + strcpy(result, a); + if needsSlash strcat(result, "/"); + strcat(result, b); + return result; + } +}