From 40b47f5db4c5c51402c3e7b0e488326d9131cd69 Mon Sep 17 00:00:00 2001 From: Fernando Dev Date: Sun, 12 Jul 2026 20:13:34 -0300 Subject: [PATCH 1/6] Update env.d --- src/env.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/env.d b/src/env.d index ff4a534..0b50d3f 100644 --- a/src/env.d +++ b/src/env.d @@ -1,4 +1,4 @@ module env; -const string COMPILER_VERSION = "v0.1.0"; +const string COMPILER_VERSION = "0.1.0"; const string GITHUB_REPO = "https://github.com/FernandoTheDev/cx.git"; From af55e06a31cbdf771c5c58bb00eaf1db6f3a7d8d Mon Sep 17 00:00:00 2001 From: Fernando Dev Date: Mon, 13 Jul 2026 00:20:22 -0300 Subject: [PATCH 2/6] Some corrections and improvements. --- examples/array2.cx | 5 + examples/array3.cx | 5 + examples/calc/calc2.cx | 290 ++++++++++++++++++ examples/char.cx | 4 + examples/error_union3.cx | 23 ++ examples/error_union4.cx | 27 ++ examples/io/cx_io_test.txt | 3 + examples/io/file2.cx | 2 +- examples/langtoy.cx | 496 +++++++++++++++++++++++++++++++ examples/string_concat.cx | 9 + src/backend/codegen.d | 65 +++- src/env.d | 2 +- src/frontend/lexer/lexer.d | 57 +++- src/frontend/parser/parse_type.d | 46 ++- src/frontend/type_expr.d | 14 +- src/frontend/type_resolve.d | 5 + tests/unit.d | 4 + 17 files changed, 999 insertions(+), 58 deletions(-) create mode 100644 examples/array2.cx create mode 100644 examples/array3.cx create mode 100644 examples/calc/calc2.cx create mode 100644 examples/char.cx create mode 100644 examples/error_union3.cx create mode 100644 examples/error_union4.cx create mode 100644 examples/io/cx_io_test.txt create mode 100644 examples/langtoy.cx create mode 100644 examples/string_concat.cx diff --git a/examples/array2.cx b/examples/array2.cx new file mode 100644 index 0000000..3bcf275 --- /dev/null +++ b/examples/array2.cx @@ -0,0 +1,5 @@ +//# 67 +int main() { + int[2][1] arr = [[60], [7]]; + return arr[0][0] + arr[0][1]; +} diff --git a/examples/array3.cx b/examples/array3.cx new file mode 100644 index 0000000..8b69522 --- /dev/null +++ b/examples/array3.cx @@ -0,0 +1,5 @@ +//# 67 +int main() { + int[2][2][1] arr = [[[60], [7]]]; + return arr[0][0][0] + arr[0][0][1]; +} diff --git a/examples/calc/calc2.cx b/examples/calc/calc2.cx new file mode 100644 index 0000000..4cc0c41 --- /dev/null +++ b/examples/calc/calc2.cx @@ -0,0 +1,290 @@ +//# 0 +include +include +include + +enum EvalError { + UnexpectedChar, + UnexpectedToken, + DivisionByZero, + UnclosedParen, + EmptyExpression +} + +enum TokKind { + Num, + Plus, + Minus, + Star, + Slash, + LParen, + RParen, + Eof +} + +struct Tok { + TokKind kind; + double num; +} + +struct Lexer { + char* src; + u32 pos; + u32 len; + + static Lexer new(char* src) { + return (Lexer) {src, 0, src.length}; + } + + char current() { + if self.pos >= self.len { + return '\0'; + } + return self.src[self.pos]; + } + + void skipSpaces() { + while self.current() == ' ' { + self.pos = self.pos + 1; + } + } + + bool isDigit(char c) { + return c >= '0' && c <= '9'; + } + + double!EvalError nextNumber() { + u32 start = self.pos; + while self.isDigit(self.current()) { + self.pos = self.pos + 1; + } + if self.current() == '.' { + self.pos = self.pos + 1; + while self.isDigit(self.current()) { + self.pos = self.pos + 1; + } + } + u32 len = self.pos - start; + char[32] buff; + for (u32 i = 0; i < len; i++) { + buff[i] = self.src[start + i]; + } + buff[len] = '\0'; + return atof(buff); + } + + Tok!EvalError next() { + self.skipSpaces(); + char c = self.current(); + + if c == '\0' { + return (Tok) {TokKind.Eof, 0}; + } + if self.isDigit(c) { + double!EvalError n = self.nextNumber(); + if !n.valid { + return n.error; + } + return (Tok) {TokKind.Num, n.ok}; + } + if c == '+' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Plus, 0}; + } + if c == '-' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Minus, 0}; + } + if c == '*' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Star, 0}; + } + if c == '/' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Slash, 0}; + } + if c == '(' { + self.pos = self.pos + 1; + return (Tok) {TokKind.LParen, 0}; + } + if c == ')' { + self.pos = self.pos + 1; + return (Tok) {TokKind.RParen, 0}; + } + + return EvalError.UnexpectedChar; + } +} + +// gramatica: +// expr := term (("+" | "-") term)* +// term := factor (("*" | "/") factor)* +// factor := NUM | "(" expr ")" | "-" factor +struct Parser { + Lexer lex; + Tok tok; + + static Parser!EvalError new(char* src) { + Lexer l = Lexer.new(src); + Tok!EvalError first = l.next(); + if !first.valid { + return first.error; + } + return (Parser) {l, first.ok}; + } + + double!EvalError advance() { + Tok!EvalError t = self.lex.next(); + if !t.valid { + return t.error; + } + self.tok = t.ok; + return 0; + } + + double!EvalError factor() { + if self.tok.kind == TokKind.Num { + double val = self.tok.num; + double!EvalError adv = self.advance(); + if !adv.valid { + return adv.error; + } + return val; + } + + if self.tok.kind == TokKind.Minus { + double!EvalError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!EvalError inner = self.factor(); + if !inner.valid { + return inner.error; + } + return -inner.ok; + } + + if self.tok.kind == TokKind.LParen { + double!EvalError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!EvalError inner = self.expr(); + if !inner.valid { + return inner.error; + } + if self.tok.kind != TokKind.RParen { + return EvalError.UnclosedParen; + } + double!EvalError adv2 = self.advance(); + if !adv2.valid { + return adv2.error; + } + return inner.ok; + } + + return EvalError.UnexpectedToken; + } + + double!EvalError term() { + double!EvalError left = self.factor(); + if !left.valid { + return left.error; + } + double acc = left.ok; + + while self.tok.kind == TokKind.Star || self.tok.kind == TokKind.Slash { + TokKind op = self.tok.kind; + double!EvalError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!EvalError right = self.factor(); + if !right.valid { + return right.error; + } + if op == TokKind.Star { + acc = acc * right.ok; + } else { + if right.ok == 0.0 { + return EvalError.DivisionByZero; + } + acc = acc / right.ok; + } + } + + return acc; + } + + double!EvalError expr() { + double!EvalError left = self.term(); + if !left.valid { + return left.error; + } + double acc = left.ok; + + while self.tok.kind == TokKind.Plus || self.tok.kind == TokKind.Minus { + TokKind op = self.tok.kind; + double!EvalError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!EvalError right = self.term(); + if !right.valid { + return right.error; + } + if op == TokKind.Plus { + acc = acc + right.ok; + } else { + acc = acc - right.ok; + } + } + + return acc; + } +} + +double!EvalError evaluate(char* src) { + if src.length == 0 { + return EvalError.EmptyExpression; + } + Parser!EvalError p = Parser.new(src); + if !p.valid { + return p.error; + } + Parser parser = p.ok; + return parser.expr(); +} + +char* errorToStr(EvalError e) { + if e == EvalError.UnexpectedChar return "unexpected character"; + if e == EvalError.UnexpectedToken return "unexpected token"; + if e == EvalError.DivisionByZero return "division by zero"; + if e == EvalError.UnclosedParen return "unclosed parenthesis"; + if e == EvalError.EmptyExpression return "empty expression"; + return "unknown error"; +} + +void run(char* expr) { + double!EvalError result = evaluate(expr); + if !result.valid { + printf("%s => error: %s\n", expr, errorToStr(result.error)); + return; + } + printf("%s => %g\n", expr, result.ok); +} + +int main() { + run("2 + 3 * 4"); + run("(2 + 3) * 4"); + run("10 / 2 / 5"); + run("-5 + 3"); + run("2 * (3 + (4 - 1))"); + run("1 + 2 + 3 + 4 + 5"); + run("10 / 0"); + run("(1 + 2"); + run(""); + run("3 + + 2"); + + return 0; +} diff --git a/examples/char.cx b/examples/char.cx new file mode 100644 index 0000000..16d2734 --- /dev/null +++ b/examples/char.cx @@ -0,0 +1,4 @@ +//# 1 +int main() { + return '\0' == '\0'; +} diff --git a/examples/error_union3.cx b/examples/error_union3.cx new file mode 100644 index 0000000..395b190 --- /dev/null +++ b/examples/error_union3.cx @@ -0,0 +1,23 @@ +//# 0 +include + +enum Error { + SixSeven +} + +alias E = int!Error + +E bar() { + return Error.SixSeven; +} + +E foo() { + if true + return 0; + return bar(); +} + +int main() { + foo(); + return 0; +} diff --git a/examples/error_union4.cx b/examples/error_union4.cx new file mode 100644 index 0000000..ec65a42 --- /dev/null +++ b/examples/error_union4.cx @@ -0,0 +1,27 @@ +//# 0 +include + +enum Error { + SixSeven +} + +alias E = int!Error + +struct Seven { + E bar() { + return Error.SixSeven; + } + + E foo() { + if true + return 0; + return self.bar(); // It was throwing an error + } +} + + +int main() { + Seven s = {}; + s.foo(); + return 0; +} diff --git a/examples/io/cx_io_test.txt b/examples/io/cx_io_test.txt new file mode 100644 index 0000000..300ed6f --- /dev/null +++ b/examples/io/cx_io_test.txt @@ -0,0 +1,3 @@ +10 +20 +30 diff --git a/examples/io/file2.cx b/examples/io/file2.cx index a33d022..e63b0d3 100644 --- a/examples/io/file2.cx +++ b/examples/io/file2.cx @@ -51,7 +51,7 @@ int main() { } printf("Sum from file: %d\n", sum); - remove(filename); + // remove(filename); return 0; } diff --git a/examples/langtoy.cx b/examples/langtoy.cx new file mode 100644 index 0000000..5d3e6de --- /dev/null +++ b/examples/langtoy.cx @@ -0,0 +1,496 @@ +//# 0 +include +include +include + +enum LangError { + UnexpectedChar, + UnexpectedToken, + DivisionByZero, + UnclosedParen, + UndefinedVar, + ExpectedIdent, + ExpectedEquals, + ExpectedSemicolon, + TooManyVars +} + +enum TokKind { + Num, + Ident, + Let, + Print, + Plus, + Minus, + Star, + Slash, + Equals, + Semicolon, + LParen, + RParen, + Eof +} + +struct Tok { + TokKind kind; + double num; + char[16] name; +} + +struct Env { + char[16][32] names; + double[32] values; + u32 count; + + static Env new() { + Env e; + e.count = 0; + return e; + } + + void set(char* name, double val) { + for (u32 i = 0; i < self.count; i++) { + if self.names[i] === name { + self.values[i] = val; + return; + } + } + strcpy(self.names[self.count], name); + self.values[self.count] = val; + self.count = self.count + 1; + } + + double!LangError get(char* name) { + for (u32 i = 0; i < self.count; i++) { + if self.names[i] === name { + return self.values[i]; + } + } + return LangError.UndefinedVar; + } +} + +struct Lexer { + char* src; + u32 pos; + u32 len; + + static Lexer new(char* src) { + return (Lexer) {src, 0, src.length}; + } + + char current() { + if self.pos >= self.len { + return '\0'; + } + return self.src[self.pos]; + } + + void skipSpaces() { + while self.current() == ' ' || self.current() == '\n' || self.current() == '\t' { + self.pos = self.pos + 1; + } + } + + bool isDigit(char c) { + return c >= '0' && c <= '9'; + } + + bool isAlpha(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; + } + + double nextNumber() { + u32 start = self.pos; + while self.isDigit(self.current()) { + self.pos = self.pos + 1; + } + if self.current() == '.' { + self.pos = self.pos + 1; + while self.isDigit(self.current()) { + self.pos = self.pos + 1; + } + } + u32 len = self.pos - start; + char[32] buff; + for (u32 i = 0; i < len; i++) { + buff[i] = self.src[start + i]; + } + buff[len] = '\0'; + return atof(buff); + } + + Tok!LangError next() { + self.skipSpaces(); + char c = self.current(); + + if c == '\0' { + return (Tok) {TokKind.Eof, 0, ""}; + } + if self.isDigit(c) { + double n = self.nextNumber(); + return (Tok) {TokKind.Num, n, ""}; + } + if self.isAlpha(c) { + u32 start = self.pos; + while self.isAlpha(self.current()) || self.isDigit(self.current()) { + self.pos = self.pos + 1; + } + u32 len = self.pos - start; + char[16] name; + for (u32 i = 0; i < len; i++) { + name[i] = self.src[start + i]; + } + name[len] = '\0'; + + if name === "let" { + return (Tok) {TokKind.Let, 0, ""}; + } + if name === "print" { + return (Tok) {TokKind.Print, 0, ""}; + } + // TODO: fix that + Tok tmp_0; + tmp_0.kind = TokKind_Ident; + tmp_0.num = 0; + strcpy(tmp_0.name, name); // ou memcpy se não for char[] + return tmp_0; + // return (Tok) {TokKind.Ident, 0, name}; // TODO: fix + } + if c == '+' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Plus, 0, ""}; + } + if c == '-' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Minus, 0, ""}; + } + if c == '*' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Star, 0, ""}; + } + if c == '/' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Slash, 0, ""}; + } + if c == '=' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Equals, 0, ""}; + } + if c == ';' { + self.pos = self.pos + 1; + return (Tok) {TokKind.Semicolon, 0, ""}; + } + if c == '(' { + self.pos = self.pos + 1; + return (Tok) {TokKind.LParen, 0, ""}; + } + if c == ')' { + self.pos = self.pos + 1; + return (Tok) {TokKind.RParen, 0, ""}; + } + + return LangError.UnexpectedChar; + } +} + +// ---- Interpretador: parser + execucao, tudo direto (tree-walking) ---- +struct Interp { + Lexer lex; + Tok tok; + Env env; + + static Interp!LangError new(char* src) { + Lexer l = Lexer.new(src); + Tok!LangError first = l.next(); + if !first.valid { + return first.error; + } + return (Interp) {l, first.ok, Env.new()}; + } + + double!LangError advance() { + Tok!LangError t = self.lex.next(); + if !t.valid { + return t.error; + } + self.tok = t.ok; + return 0; + } + + double!LangError factor() { + if self.tok.kind == TokKind.Num { + double val = self.tok.num; + double!LangError adv = self.advance(); + if !adv.valid { + return adv.error; + } + return val; + } + if self.tok.kind == TokKind.Ident { + char[16] name; + strcpy(name, self.tok.name); + double!LangError adv = self.advance(); + if !adv.valid { + return adv.error; + } + return self.env.get(name); + } + if self.tok.kind == TokKind.Minus { + double!LangError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!LangError inner = self.factor(); + if !inner.valid { + return inner.error; + } + return -inner.ok; + } + if self.tok.kind == TokKind.LParen { + double!LangError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!LangError inner = self.expr(); + if !inner.valid { + return inner.error; + } + if self.tok.kind != TokKind.RParen { + return LangError.UnclosedParen; + } + double!LangError adv2 = self.advance(); + if !adv2.valid { + return adv2.error; + } + return inner.ok; + } + return LangError.UnexpectedToken; + } + + double!LangError term() { + double!LangError left = self.factor(); + if !left.valid { + return left.error; + } + double acc = left.ok; + while self.tok.kind == TokKind.Star || self.tok.kind == TokKind.Slash { + TokKind op = self.tok.kind; + double!LangError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!LangError right = self.factor(); + if !right.valid { + return right.error; + } + if op == TokKind.Star { + acc = acc * right.ok; + } else { + if right.ok == 0.0 { + return LangError.DivisionByZero; + } + acc = acc / right.ok; + } + } + return acc; + } + + double!LangError expr() { + double!LangError left = self.term(); + if !left.valid { + return left.error; + } + double acc = left.ok; + while self.tok.kind == TokKind.Plus || self.tok.kind == TokKind.Minus { + TokKind op = self.tok.kind; + double!LangError adv = self.advance(); + if !adv.valid { + return adv.error; + } + double!LangError right = self.term(); + if !right.valid { + return right.error; + } + if op == TokKind.Plus { + acc = acc + right.ok; + } else { + acc = acc - right.ok; + } + } + return acc; + } + + // let IDENT = expr ; + double!LangError letStmt() { + double!LangError adv = self.advance(); // consome 'let' + if !adv.valid { + return adv.error; + } + if self.tok.kind != TokKind.Ident { + return LangError.ExpectedIdent; + } + char[16] name; + strcpy(name, self.tok.name); + + double!LangError adv2 = self.advance(); // consome IDENT + if !adv2.valid { + return adv2.error; + } + if self.tok.kind != TokKind.Equals { + return LangError.ExpectedEquals; + } + double!LangError adv3 = self.advance(); // consome '=' + if !adv3.valid { + return adv3.error; + } + double!LangError val = self.expr(); + if !val.valid { + return val.error; + } + if self.tok.kind != TokKind.Semicolon { + return LangError.ExpectedSemicolon; + } + double!LangError adv4 = self.advance(); // consome ';' + if !adv4.valid { + return adv4.error; + } + self.env.set(name, val.ok); + return 0; + } + + // IDENT = expr ; + double!LangError assignStmt() { + char[16] name; + strcpy(name, self.tok.name); + + double!LangError adv = self.advance(); // consome IDENT + if !adv.valid { + return adv.error; + } + if self.tok.kind != TokKind.Equals { + return LangError.ExpectedEquals; + } + double!LangError adv2 = self.advance(); // consome '=' + if !adv2.valid { + return adv2.error; + } + double!LangError val = self.expr(); + if !val.valid { + return val.error; + } + if self.tok.kind != TokKind.Semicolon { + return LangError.ExpectedSemicolon; + } + double!LangError adv3 = self.advance(); // consome ';' + if !adv3.valid { + return adv3.error; + } + self.env.set(name, val.ok); + return 0; + } + + // print expr ; + double!LangError printStmt() { + double!LangError adv = self.advance(); // consome 'print' + if !adv.valid { + return adv.error; + } + double!LangError val = self.expr(); + if !val.valid { + return val.error; + } + if self.tok.kind != TokKind.Semicolon { + return LangError.ExpectedSemicolon; + } + double!LangError adv2 = self.advance(); // consome ';' + if !adv2.valid { + return adv2.error; + } + printf("%g\n", val.ok); + return 0; + } + + double!LangError statement() { + if self.tok.kind == TokKind.Let { + return self.letStmt(); + } + if self.tok.kind == TokKind.Print { + return self.printStmt(); + } + if self.tok.kind == TokKind.Ident { + return self.assignStmt(); + } + return LangError.UnexpectedToken; + } + + double!LangError run() { + while self.tok.kind != TokKind.Eof { + double!LangError r = self.statement(); + if !r.valid { + return r.error; + } + } + return 0; + } +} + +char* errorToStr(LangError e) { + if e == LangError.UnexpectedChar return "unexpected character"; + if e == LangError.UnexpectedToken return "unexpected token"; + if e == LangError.DivisionByZero return "division by zero"; + if e == LangError.UnclosedParen return "unclosed parenthesis"; + if e == LangError.UndefinedVar return "undefined variable"; + if e == LangError.ExpectedIdent return "expected identifier"; + if e == LangError.ExpectedEquals return "expected '='"; + if e == LangError.ExpectedSemicolon return "expected ';'"; + if e == LangError.TooManyVars return "too many variables"; + return "unknown error"; +} + +void execute(char* src) { + Interp!LangError i = Interp.new(src); + if !i.valid { + printf("Error: %s\n", errorToStr(i.error)); + return; + } + Interp interp = i.ok; + double!LangError r = interp.run(); + if !r.valid { + printf("Error: %s\n", errorToStr(r.error)); + } +} + +int main() { + printf("--- program 1 ---\n"); + execute( + "let x = 10;" + "let y = 20;" + "print x + y;" + "x = x * 2;" + "print x;" + "print x - y;" + ); + + printf("--- program 2 (fibonacci manual) ---\n"); + execute( + "let a = 0;" + "let b = 1;" + "print a;" + "print b;" + "let c = a + b;" + "print c;" + "a = b;" + "b = c;" + "c = a + b;" + "print c;" + ); + + printf("--- program 3 (erro: variavel indefinida) ---\n"); + execute("print z;"); + + printf("--- program 4 (erro: divisao por zero) ---\n"); + execute("let x = 10; print x / 0;"); + + return 0; +} diff --git a/examples/string_concat.cx b/examples/string_concat.cx new file mode 100644 index 0000000..c7f9a0c --- /dev/null +++ b/examples/string_concat.cx @@ -0,0 +1,9 @@ +//! Fernando Dev\n +import std.io; + +int main() { + char* str = "Fernando " + "Dev"; + printf("%s\n", str); + return 0; +} diff --git a/src/backend/codegen.d b/src/backend/codegen.d index 621e8a7..24a8584 100644 --- a/src/backend/codegen.d +++ b/src/backend/codegen.d @@ -176,7 +176,7 @@ private: return compileVarDecl(as!VarDecl(node), ind); case NodeKind.ReturnStmt: - return compileRetDecl(as!ReturnStmt(node), ind); + return compileRetStmt(as!ReturnStmt(node), ind); case NodeKind.CallExpr: return compileCallStmt(as!CallExpr(node), ind); @@ -307,6 +307,32 @@ private: } } + string escapeChar(char c) + { + switch (c) + { + case '\0': return "\\0"; + case '\n': return "\\n"; + case '\t': return "\\t"; + case '\r': return "\\r"; + case '\'': return "\\'"; + case '\\': return "\\\\"; + case '\a': return "\\a"; + case '\b': return "\\b"; + case '\f': return "\\f"; + case '\v': return "\\v"; + default: + return format("%c", c); + } + } + + string escapeString(string s) + { + string buff; + foreach (c; s) buff ~= escapeChar(c); + return buff; + } + string compileExpr(Node node) { switch (node.kind) @@ -327,8 +353,7 @@ private: BinaryExpr binary = cast(BinaryExpr) node; string left = compileExpr(binary.left); string right = compileExpr(binary.right); - if (isString(binary.left.type_expr) && isString(binary.right.type_expr) && binary.op == TokenKind - .EEEquals) + if (isString(binary.left.type_expr) && isString(binary.right.type_expr) && binary.op == TokenKind.EEEquals) return format("strcmp(%s, %s) == 0", left, right); return format("%s %s %s", left, getOp(binary.op), right); @@ -339,10 +364,10 @@ private: return format("%s%s", un.post ? val : op, un.post ? op : val); case NodeKind.StringLit: - return '"' ~ (cast(StringLit) node).val ~ '"'; + return format("\"%s\"", (cast(StringLit) node).val); case NodeKind.CharLit: - return "'" ~ (cast(CharLit) node).val ~ "'"; + return format("'%s'", escapeChar((cast(CharLit) node).val)); case NodeKind.NullLit: return "NULL"; @@ -473,8 +498,20 @@ private: id = (cast(IdentExpr) node.left).val; else if (MemberExpr m = cast(MemberExpr) node.left) { - id = format("tmp_%d", tmp++); - emit(format("%s %s = %s;", m.right.type_expr, id, compileMemberExpr(m)), 4); + // LValue? + string member = compileMemberExpr(m); + if (( + m.left.kind == NodeKind.IdentExpr + || m.left.kind == NodeKind.IndexExpr + || m.left.kind == NodeKind.MemberExpr) + && member[$-1] != ')' + ) + id = member; + else + { + id = format("tmp_%d", tmp++); + emit(format("%s %s = %s;", m.right.type_expr, id, member), 4); + } } if (id != "") { @@ -549,7 +586,7 @@ private: return indent(format("%s;", compileCallExpr(node)), ind); } - string compileRetDecl(ReturnStmt node, uint ind) + string compileRetStmt(ReturnStmt node, uint ind) { deferResolve(ind); string val = node.val is null ? "" : compileExpr(node.val); @@ -561,7 +598,11 @@ private: return "return 0;"; } // writeln(node.val); + // writeln(node.val.pos.toString()); // writeln(node.val.type_expr); + if (node.val.type_expr !is null) + if (node.val.type_expr.toString() == fnUnion) + return indent(format("return %s;", val), ind); // writeln(fnError[0]); // writeln(fnError[1], "\n"); bool ok; @@ -699,10 +740,12 @@ private: bool isString(TypeExpr type) { + TypeExprNamed t; if (TypeExprPointer p = cast(TypeExprPointer) type) - if (TypeExprNamed t = cast(TypeExprNamed) p.base) - return t.name == "char"; - return false; + t = cast(TypeExprNamed) p.base; + else if (TypeExprArray a = cast(TypeExprArray) type) + t = cast(TypeExprNamed) a.base; + return t is null ? false : t.name == "char"; } bool isResult(TypeExpr type) diff --git a/src/env.d b/src/env.d index 0b50d3f..9030d7f 100644 --- a/src/env.d +++ b/src/env.d @@ -1,4 +1,4 @@ module env; -const string COMPILER_VERSION = "0.1.0"; +const string COMPILER_VERSION = "0.1.1"; const string GITHUB_REPO = "https://github.com/FernandoTheDev/cx.git"; diff --git a/src/frontend/lexer/lexer.d b/src/frontend/lexer/lexer.d index 9c6e61e..55785ea 100644 --- a/src/frontend/lexer/lexer.d +++ b/src/frontend/lexer/lexer.d @@ -302,6 +302,46 @@ private: buffer ~= [advance()]; } + string lexString(uint start_o, uint start_l) + { + String buffer; + buffer.reserve(32); + + while (!isAtEnd() && !check('"')) + { + checkNewLine(peek()); + buffer ~= [advance()]; + } + + if (!match('"')) + { + err.error(getPos(start_o, start_l), "The string was not closed."); + return "/* err */"; + } + + /* + "Str1" + "Str2" "Str3" + */ + + while (!isAtEnd()) + { + if (peek() == ' ' || peek() == '\r' || peek() == '\t' || checkNewLine(peek())) + { + advance(); + continue; + } + break; + } + + string str = buffer.data; + + if (match('"')) + return str ~= lexString(loffset, line); + + return str; + } + public: this(string filename, string dir, string source, Diagnostics err, TypeRegistry t) { @@ -481,22 +521,7 @@ public: { uint start_o = loffset; uint start_l = line; - String buffer; - buffer.reserve(32); - - while (!isAtEnd() && !check('"')) - { - checkNewLine(peek()); - buffer ~= [advance()]; - } - - if (!match('"')) - { - err.error(getPos(start_o, start_l), "The string was not closed."); - continue; - } - - pushToken(Token.tk_string(TokenKind.String, buffer.data, getPos(start_o, start_l))); + pushToken(Token.tk_string(TokenKind.String, lexString(start_o, start_l), getPos(start_o, start_l))); continue; } diff --git a/src/frontend/parser/parse_type.d b/src/frontend/parser/parse_type.d index 2dc84ca..7ed305b 100644 --- a/src/frontend/parser/parse_type.d +++ b/src/frontend/parser/parse_type.d @@ -35,12 +35,7 @@ public: TypeExpr parsePointerType(TypeExpr base, Position pos) { - TypeExpr t = new TypeExprPointer(base, base.pos is null ? pos : p.getPos(base.pos, pos)); - if (p.match(TokenKind.Star)) - return parsePointerType(t, pos); - if (p.match(TokenKind.LBracket)) - return parseArrayType(t, pos); - return t; + return checkAfter(new TypeExprPointer(base, base.pos is null ? pos : p.getPos(base.pos, pos))); } TypeExpr parseFunctionType(TypeExpr base, Position pos) @@ -53,12 +48,12 @@ public: p.consume(TokenKind.Comma, "Expected ','."); } p.consume(TokenKind.RParen, "Expected ')'."); - return new TypeExprFunction(base, args, pos); + return checkAfter(new TypeExprFunction(base, args, pos)); } TypeExpr parseResultType(TypeExpr base, Position pos) { - return new TypeExprResult(base, parse(), pos); + return checkAfter(new TypeExprResult(base, parse(), pos)); } TypeExpr parseArrayType(TypeExpr base, Position pos) @@ -79,15 +74,9 @@ public: p.err.error(t.pos, "The array size is invalid."); type = new TypeExprArray(base, "", pos); // fallback pra não dar erro em comptime por segfault } - if (p.match(TokenKind.Star)) - return parsePointerType(type, pos); - if (p.match(TokenKind.Bang)) - return parseResultType(type, pos); - if (p.match(TokenKind.LBracket)) - return parseArrayType(type, pos); if (t.kind != TokenKind.RBracket) p.consume(TokenKind.RBracket, "Expected ']'."); - return type; + return checkAfter(type); } TypeExpr parseGenericType(TypeExpr name, Position pos) @@ -103,30 +92,31 @@ public: string n = name.toStr(); p.generic.add(n, args); TypeExprGeneric t = new TypeExprGeneric(n, args, pos); - if (p.match(TokenKind.Star)) - return parsePointerType(t, pos); - return t; + return checkAfter(t); } - TypeExpr parse() + TypeExpr checkAfter(TypeExpr type) { - TypeExpr primary = parsePrimary(); - if (p.match(TokenKind.Star)) - return parsePointerType(primary, p.previous().pos); + return parsePointerType(type, type.pos); if (p.match(TokenKind.LParen)) - return parseFunctionType(primary, p.previous().pos); + return parseFunctionType(type, type.pos); if (p.match(TokenKind.Bang)) - return parseResultType(primary, p.previous().pos); + return parseResultType(type, type.pos); if (p.match(TokenKind.LBracket)) - return parseArrayType(primary, p.previous().pos); + return parseArrayType(type, type.pos); if (p.match(TokenKind.LThan)) - return parseGenericType(primary, p.previous().pos); - - return primary; + return parseGenericType(type, type.pos); + + return type; + } + + TypeExpr parse() + { + return checkAfter(parsePrimary()); } } diff --git a/src/frontend/type_expr.d b/src/frontend/type_expr.d index 7beb576..2509290 100644 --- a/src/frontend/type_expr.d +++ b/src/frontend/type_expr.d @@ -171,7 +171,19 @@ class TypeExprArray : TypeExpr override string toStrVar(string var = "") const { - return base.toStrVar() ~ (var == "" ? "" : " " ~ var) ~ format("[%s]", idx); + return buildArray(var); + } + + private string buildArray(string var) const + { + if (auto arrBase = cast(TypeExprArray) base) + return arrBase.buildArray(var) ~ arrayType(); + return base.toStrVar() ~ (var == "" ? "" : " " ~ var) ~ arrayType(); + } + + string arrayType() const + { + return format("[%s]", idx); } override string toStr() const diff --git a/src/frontend/type_resolve.d b/src/frontend/type_resolve.d index 4457a49..12589b0 100644 --- a/src/frontend/type_resolve.d +++ b/src/frontend/type_resolve.d @@ -1,6 +1,8 @@ module frontend.type_resolve; import frontend; + +import std.format; import std.stdio; final class Scope @@ -192,6 +194,9 @@ private: methodName = (cast(IdentExpr) call.callee).val; FnDecl fn = findMethod(sName, methodName); + if (fn is null) + // fallback + fn = findMethod(sName, format("%s_%s", sName, methodName)); TypeExpr ret = fn !is null ? fn.type_expr : m.left.type_expr; // writeln("Name: ", sName, " ", methodName, " ", fn, " ", ret); diff --git a/tests/unit.d b/tests/unit.d index 8c4eeea..8a46cfb 100644 --- a/tests/unit.d +++ b/tests/unit.d @@ -28,6 +28,10 @@ string escape(string str) buffer ~= '\n'; offset++; continue; + case '\\': + buffer ~= '\\'; + offset++; + continue; default: break; } From 8744345f883908035ce2f1a3e84633f1e3cf125a Mon Sep 17 00:00:00 2001 From: Fernando Dev Date: Mon, 13 Jul 2026 10:59:55 -0300 Subject: [PATCH 3/6] Fix for the struct literal system. --- examples/langtoy.cx | 8 +--- examples/struct_with_comptime_array.cx | 14 ++++++ src/backend/codegen.d | 60 +++++++++++++++++++++++--- src/frontend/import_resolver.d | 2 +- src/frontend/resolve_symbols.d | 16 ++++--- src/frontend/type_resolve.d | 9 +++- std/stack.cx | 2 +- 7 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 examples/struct_with_comptime_array.cx diff --git a/examples/langtoy.cx b/examples/langtoy.cx index 5d3e6de..faf2641 100644 --- a/examples/langtoy.cx +++ b/examples/langtoy.cx @@ -149,13 +149,7 @@ struct Lexer { if name === "print" { return (Tok) {TokKind.Print, 0, ""}; } - // TODO: fix that - Tok tmp_0; - tmp_0.kind = TokKind_Ident; - tmp_0.num = 0; - strcpy(tmp_0.name, name); // ou memcpy se não for char[] - return tmp_0; - // return (Tok) {TokKind.Ident, 0, name}; // TODO: fix + return (Tok) {TokKind.Ident, 0, name}; } if c == '+' { self.pos = self.pos + 1; diff --git a/examples/struct_with_comptime_array.cx b/examples/struct_with_comptime_array.cx new file mode 100644 index 0000000..cc5504e --- /dev/null +++ b/examples/struct_with_comptime_array.cx @@ -0,0 +1,14 @@ +//! Fernando Dev\n +import std.io; + +struct Foo { + char[10] bar; +} + +int main() { + char[10] name = "Fernando"; + Foo f = {name}; + Foo b = {"Dev"}; + printf("%s %s\n", f.bar, b.bar); + return 0; +} diff --git a/src/backend/codegen.d b/src/backend/codegen.d index 24a8584..556985c 100644 --- a/src/backend/codegen.d +++ b/src/backend/codegen.d @@ -390,14 +390,64 @@ private: case NodeKind.StructLit: StructLit strc = cast(StructLit) node; - string values; + bool haveComptimeArray; + string[] values; + for (ulong i; i < strc.values.length; i++) { - values ~= compileExpr(strc.values[i]); - if ((i + 1) < strc.values.length) - values ~= ", "; + Node n = strc.values[i]; + if (n.type_expr !is null && n.type_expr.kind == TypeExprKind.Array) + haveComptimeArray = true; + values ~= compileExpr(n); } - return format("{%s}", values); + + TypeExpr type = strc.type_expr is null ? null : strc.type_expr; + string def = format("{%s}", values.join(", ")); + + if (!haveComptimeArray) + return def; + + if (haveComptimeArray && type is null) + return def; + + StructDecl decl = cast(StructDecl) context.symbols[type.toString()]; + + if (decl is null) + return def; + + string temp = format("temp_%d", tmp++); + emit(format("%s;", type.toStrVar(temp)), 4); + + foreach (size_t i, VarDecl field; decl.fields) + { + string member = format("%s.%s", temp, field.name); + if (field.type_expr !is null && field.type_expr.kind != TypeExprKind.Array) + emit(format("%s = %s;", member, values[i]), 4); + else { + // writeln("COMPTIME"); + string value; + if ( + strc.values[i].kind == NodeKind.IdentExpr + || strc.values[i].kind == NodeKind.MemberExpr + || strc.values[i].kind == NodeKind.IndexExpr + ) + value = values[i]; + else { + // cria uma variavel temporaria + string tempField = format("temp_%d", tmp++); + emit(format("%s = %s;", field.type_expr.toStrVar(tempField), values[i]), 4); + value = tempField; + } + emit(format("memcpy(%s, %s, sizeof(%s));", member, value, member), 4); + // memcpy(temp_0.bar, name, sizeof(temp_0.bar)); + // writeln(field.name); + // writeln(field.type_expr); + } + } + + // writeln(decl.fields); + // writeln(haveComptimeArray, " ", values); + return format("%s", temp); case NodeKind.ArrayLit: ArrayLit arr = cast(ArrayLit) node; diff --git a/src/frontend/import_resolver.d b/src/frontend/import_resolver.d index 80986c7..2bd6460 100644 --- a/src/frontend/import_resolver.d +++ b/src/frontend/import_resolver.d @@ -14,7 +14,7 @@ struct ImportResolverContext { string cxdir; string[string] mods; // módulos resolvidos - Position[string] symbols; // simbolos globais + Node[string] symbols; // simbolos globais bool[string] statics; } diff --git a/src/frontend/resolve_symbols.d b/src/frontend/resolve_symbols.d index b385bbf..2fcd5ec 100644 --- a/src/frontend/resolve_symbols.d +++ b/src/frontend/resolve_symbols.d @@ -7,7 +7,7 @@ import std.stdio; class ResolveSymbols { - static Node[] resolve(Diagnostics err, ImportResolverContext* context, Node[] body) + static Node[] resolve(Diagnostics err, ImportResolverContext* context, Node[] body, string base = "") { Node[] newBody; foreach (Node node; body) @@ -15,7 +15,10 @@ class ResolveSymbols string name; if (node.kind == NodeKind.FnDecl) { - name = (cast(FnDecl)node).name; + FnDecl fn = cast(FnDecl)node; + name = fn.name; + if (!(fn.flags & NodeFlags.Overload)) + name = base == "" ? name : format("%s_%s", base, name); // writeln("Sym: ", name); } else if (node.kind == NodeKind.StructDecl) @@ -24,7 +27,8 @@ class ResolveSymbols if (s.genericT.length == 0) { name = s.name; - resolve(err, context, cast(Node[]) s.functions); + // writeln("Name: ", name); + resolve(err, context, cast(Node[]) s.functions, name); } } else if (node.kind == NodeKind.EnumDecl) @@ -34,9 +38,9 @@ class ResolveSymbols else if (node.kind == NodeKind.AliasDecl) name = (cast(AliasDecl)node).name; if (name != "") - if (Position* p = name in context.symbols) + if (Node* p = name in context.symbols) { - if (p.filename == node.pos.filename) + if (p.pos.filename == node.pos.filename) { err.warning(node.pos, format("The symbol '%s' was declared twice in the same file, first declaration: %s", @@ -45,7 +49,7 @@ class ResolveSymbols } } newBody ~= node; - context.symbols[name] = node.pos; + context.symbols[name] = node; } return newBody; } diff --git a/src/frontend/type_resolve.d b/src/frontend/type_resolve.d index 12589b0..2e9020d 100644 --- a/src/frontend/type_resolve.d +++ b/src/frontend/type_resolve.d @@ -145,8 +145,10 @@ private: return lt2; case NodeKind.CastExpr: - resolveExprType((cast(CastExpr) n).expr, scp); - return n.type_expr; // já veio do parser + CastExpr c = (cast(CastExpr) n); + resolveExprType(c.expr, scp); + if (c.expr.type_expr is null) c.expr.type_expr = c.type_expr; + return c.type_expr; // já veio do parser case NodeKind.StructLit: foreach (v; (cast(StructLit) n).values) @@ -283,7 +285,10 @@ private: case NodeKind.VarDecl: VarDecl v = cast(VarDecl) n; if (v.val !is null) + { resolveExprType(v.val, scp); + if (v.val.type_expr is null) v.val.type_expr = v.type_expr; + } scp.declare(v.name, v.type_expr); return; diff --git a/std/stack.cx b/std/stack.cx index 13524a8..3e56900 100644 --- a/std/stack.cx +++ b/std/stack.cx @@ -21,7 +21,7 @@ struct Stack bool isEmpty() => self.size == 0 bool isFull() => self.size >= self.cap - int !StackError push(T val) + int!StackError push(T val) { if self.isFull() return StackError.Overflow; self.data[self.size++] = val; From ed996da798703284d0f2622b6fd6c4ee887d248b Mon Sep 17 00:00:00 2001 From: Fernando Dev Date: Mon, 13 Jul 2026 16:36:27 -0300 Subject: [PATCH 4/6] Addition of the __raw block, C directives, and minor fixes. --- examples/atomic.cx | 34 +++++ examples/cpp.cx | 16 +++ examples/io/cx_io_test.txt | 3 - examples/io/file2.cx | 2 +- examples/raw1.cx | 22 ++++ examples/restrict.cx | 39 ++++++ examples/volatile.cx | 31 +++++ src/backend/codegen.d | 15 ++- src/env.d | 2 +- src/frontend/lexer/lexer.d | 59 +++++++-- src/frontend/lexer/token.d | 6 + src/frontend/parser/ast.d | 26 ++++ src/frontend/parser/parse_expr.d | 38 +++++- src/frontend/parser/parse_stmt.d | 3 + src/frontend/parser/parse_type.d | 25 +++- src/frontend/parser/parser.d | 3 + src/frontend/type_expr.d | 208 +++++++++++++++++++++++++++++++ src/frontend/type_registry.d | 4 + src/main.d | 29 ++++- src/updater.d | 2 +- tests/unit.d | 6 +- 21 files changed, 549 insertions(+), 24 deletions(-) create mode 100644 examples/atomic.cx create mode 100644 examples/cpp.cx delete mode 100644 examples/io/cx_io_test.txt create mode 100644 examples/raw1.cx create mode 100644 examples/restrict.cx create mode 100644 examples/volatile.cx diff --git a/examples/atomic.cx b/examples/atomic.cx new file mode 100644 index 0000000..5278d46 --- /dev/null +++ b/examples/atomic.cx @@ -0,0 +1,34 @@ +//! counter: 5\nflag: 1\natomic ptr: 99\n +include +include +include + +struct SharedState { + _Atomic u32 counter; + _Atomic bool ready; +} + +void increment_counter(_Atomic u32* c, u32 times) { + for (u32 i = 0; i < times; i++) { + atomic_fetch_add(c, 1); + } +} + +bool check_flag(_Atomic bool* flag) { + return atomic_load(flag); +} + +int main() { + _Atomic u32 counter = 0; + increment_counter(&counter, 5); + printf("counter: %u\n", atomic_load(&counter)); + + _Atomic bool ready = true; + printf("flag: %d\n", check_flag(&ready)); + + _Atomic u32 value = 99; + _Atomic u32* atomic_ptr = &value; + printf("atomic ptr: %u\n", atomic_load(atomic_ptr)); + + return 0; +} diff --git a/examples/cpp.cx b/examples/cpp.cx new file mode 100644 index 0000000..046ffce --- /dev/null +++ b/examples/cpp.cx @@ -0,0 +1,16 @@ +//# 0 +include + +__raw { + using namespace std; +} + +int main() { + const char* name = "Fernando"; + + __raw { + cout << name << endl; + } + + return 0; +} diff --git a/examples/io/cx_io_test.txt b/examples/io/cx_io_test.txt deleted file mode 100644 index 300ed6f..0000000 --- a/examples/io/cx_io_test.txt +++ /dev/null @@ -1,3 +0,0 @@ -10 -20 -30 diff --git a/examples/io/file2.cx b/examples/io/file2.cx index e63b0d3..a33d022 100644 --- a/examples/io/file2.cx +++ b/examples/io/file2.cx @@ -51,7 +51,7 @@ int main() { } printf("Sum from file: %d\n", sum); - // remove(filename); + remove(filename); return 0; } diff --git a/examples/raw1.cx b/examples/raw1.cx new file mode 100644 index 0000000..edeea5c --- /dev/null +++ b/examples/raw1.cx @@ -0,0 +1,22 @@ +//! fast_add: 30\nclz: 29\n +include + +u32 fast_add(u32 a, u32 b) { + u32 result; + __raw { + __asm__ __volatile__( + "addl %%ebx, %%eax" + : "=a" (result) + : "a" (a), "b" (b) + ); + } + return result; +} + +u32 count_leading_zeros(u32 x) => __builtin_clz(x) + +int main() { + printf("fast_add: %u\n", fast_add(10, 20)); + printf("clz: %u\n", count_leading_zeros(7)); + return 0; +} diff --git a/examples/restrict.cx b/examples/restrict.cx new file mode 100644 index 0000000..3ed637d --- /dev/null +++ b/examples/restrict.cx @@ -0,0 +1,39 @@ +//! sum: 66\nproduct: 1600\ncopy: hello world\n +include +include + +u32 sum_arrays(u32* restrict a, u32* restrict b, u32 n) { + u32 total = 0; + for (u32 i = 0; i < n; i++) + total = total + a[i] + b[i]; + return total; +} + +u32 product_arrays(u32* restrict a, u32* restrict b, u32 n) { + u32 total = 1; + for (u32 i = 0; i < n; i++) + total = total * a[i] * b[i]; + return total; +} + +void fast_copy(char* restrict dst, char* restrict src, u32 n) { + for (u32 i = 0; i < n; i++) + dst[i] = src[i]; +} + +int main() { + u32[] a = [10, 20, 30]; + u32[] b = [1, 2, 3]; + printf("sum: %u\n", sum_arrays(a, b, 3)); + + u32[] p1 = [2, 4, 10]; + u32[] p2 = [1, 10, 2]; + printf("product: %u\n", product_arrays(p1, p2, 3)); + + char[] src = "hello world"; + char[20] dst; + fast_copy(dst, src, 12); + printf("copy: %s\n", dst); + + return 0; +} diff --git a/examples/volatile.cx b/examples/volatile.cx new file mode 100644 index 0000000..0b18dc3 --- /dev/null +++ b/examples/volatile.cx @@ -0,0 +1,31 @@ +//! volatile: 42\nvolatile ptr: 7\nconst volatile: 100\n +include +include + +struct HardwareReg { + volatile u32 status; + volatile u32 data; +} + +void write_to_device(volatile u32* reg, u32 value) { + *reg = value; +} + +u32 read_from_device(volatile u32* reg) { + return *reg; +} + +int main() { + volatile int sensor = 42; + printf("volatile: %d\n", sensor); + + volatile u32 hw_register = 0; + volatile u32* ptr = &hw_register; + write_to_device(ptr, 7); + printf("volatile ptr: %u\n", read_from_device(ptr)); + + const volatile u32 device_id = 100; + printf("const volatile: %u\n", device_id); + + return 0; +} diff --git a/src/backend/codegen.d b/src/backend/codegen.d index 556985c..3378283 100644 --- a/src/backend/codegen.d +++ b/src/backend/codegen.d @@ -137,6 +137,10 @@ private: case NodeKind.ImportStmt: return; + case NodeKind.RawStmt: + RawStmt n = cast(RawStmt) node; + return emit(indent("/* raw block */", ind) ~ n.code, 0); + default: emit("/* invalid decl */", ind); return; @@ -228,6 +232,10 @@ private: emit(compileStmt(_, ind), ind); return ""; + case NodeKind.RawStmt: + RawStmt n = cast(RawStmt) node; + return indent("/* raw block */", ind) ~ n.code; + default: return indent("/* invalid stmt */", ind); } @@ -726,6 +734,7 @@ private: } if (!fromGeneric) methodType = ""; + if (args.length == 0) args = "void"; bool cond = fn.type_expr.kind == TypeExprKind.Function; string name = cond ? format("%s(%s)", fn.name, args) : fn.name; name = clearNameMangling(name); @@ -809,7 +818,7 @@ private: public: this(Program program, TypeRegistry types, bool[string] staticFunctions, bool noHeader, bool genHeaderFile, - string headerFile, ImportResolverContext* context) + string headerFile, ImportResolverContext* context, bool isCpp) { this.program = program; this.types = types; @@ -829,8 +838,10 @@ public: #ifndef __STDDEF_H #include -#endif +#endif`; + if (!isCpp) + cxHeader ~= ` #ifndef NULL #define NULL (void*)0 #endif diff --git a/src/env.d b/src/env.d index 9030d7f..fac9d9c 100644 --- a/src/env.d +++ b/src/env.d @@ -1,4 +1,4 @@ module env; -const string COMPILER_VERSION = "0.1.1"; +const string COMPILER_VERSION = "0.1.2"; const string GITHUB_REPO = "https://github.com/FernandoTheDev/cx.git"; diff --git a/src/frontend/lexer/lexer.d b/src/frontend/lexer/lexer.d index 55785ea..e53f940 100644 --- a/src/frontend/lexer/lexer.d +++ b/src/frontend/lexer/lexer.d @@ -26,6 +26,11 @@ private: uint line = 1; TokenKind[string] keywords = [ + "register": TokenKind.Register, + "_Atomic": TokenKind.Atomic, + "restrict": TokenKind.Restrict, + "volatile": TokenKind.Volatile, + "const": TokenKind.Const, "return": TokenKind.Return, "static": TokenKind.Static, "inline": TokenKind.Inline, @@ -324,6 +329,18 @@ private: "Str2" "Str3" */ + skipWhiteSpace(); + string str = buffer.data; + + if (match('"')) + return str ~= lexString(loffset, line); + + return str; + } + + pragma(inline, true) + void skipWhiteSpace() + { while (!isAtEnd()) { if (peek() == ' ' || peek() == '\r' || peek() == '\t' || checkNewLine(peek())) @@ -333,13 +350,6 @@ private: } break; } - - string str = buffer.data; - - if (match('"')) - return str ~= lexString(loffset, line); - - return str; } public: @@ -367,6 +377,7 @@ public: if (isAlpha(ch)) { uint start_o = loffset; + uint l = line; String buffer; buffer.reserve(32); TokenKind kind = TokenKind.Id; @@ -385,6 +396,40 @@ public: bool isUnion = buffer.data == "union"; bool isEnum = buffer.data == "enum"; bool isAlias = buffer.data == "alias"; + bool isRaw = buffer.data == "__raw"; + + if (isRaw) + { + skipWhiteSpace(); + int closes = 1; + + if (!match('{')) + { + err.error(getPos(loffset, line), "Expected '{' after 'raw'."); + continue; + } + + String raw; + raw.reserve(64); + + while (!isAtEnd()) + { + ch = advance(); + if (ch == '{') + closes++; + else if (ch == '}') + closes--; + if (closes == 0) + break; + raw ~= [ch]; + } + + // skipWhiteSpace(); + // advance(); + + pushToken(Token.tk_string(TokenKind.Raw, raw.data, getPos(start_o, l))); + continue; + } if (isEnum || isStruct || isUnion || isAlias) { diff --git a/src/frontend/lexer/token.d b/src/frontend/lexer/token.d index 2583b5a..f1028e6 100644 --- a/src/frontend/lexer/token.d +++ b/src/frontend/lexer/token.d @@ -8,6 +8,12 @@ enum TokenKind : ubyte Include, // keywords + Register, + Raw, + Atomic, + Restrict, + Const, + Volatile, Inline, Overload, Import, diff --git a/src/frontend/parser/ast.d b/src/frontend/parser/ast.d index fcda60b..348df50 100644 --- a/src/frontend/parser/ast.d +++ b/src/frontend/parser/ast.d @@ -50,6 +50,7 @@ enum NodeKind : ubyte ImportStmt, // 1 2 AssignStmt, // 1 2 DeferStmt, // 1 2 + RawStmt, // 1 2 } abstract class Node @@ -1453,6 +1454,31 @@ class ArrayLit : Node } } +class RawStmt : Node +{ + string code; + this(string code, Position pos) + { + super(NodeKind.RawStmt, pos); + this.code = code; + } + + override void print(uint indent) + { + iprint(indent, "RawStmt"); + } + + override RawStmt dup() + { + return new RawStmt(code, pos); + } + + override void subGeneric(string[] names, TypeExpr[] 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 9955e16..7f04e39 100644 --- a/src/frontend/parser/parse_expr.d +++ b/src/frontend/parser/parse_expr.d @@ -46,6 +46,26 @@ public: case TokenKind.String: return new StringLit(tk.s, tk.pos); + case TokenKind.Const: + Node n = parse(); + n.type_expr = new TypeExprConst(n.type_expr, tk.pos); + return n; + + case TokenKind.Volatile: + Node n = parse(); + n.type_expr = new TypeExprVolatile(n.type_expr, tk.pos); + return n; + + case TokenKind.Atomic: + Node n = parse(); + n.type_expr = new TypeExprAtomic(n.type_expr, tk.pos); + return n; + + case TokenKind.Restrict: + Node n = parse(); + n.type_expr = new TypeExprRestrict(n.type_expr, tk.pos); + return n; + case TokenKind.Id: TypeExpr* t = p.types.get(tk.s); TypeExpr t2; @@ -137,6 +157,7 @@ public: return parseCastOrNode(tk.pos); default: + tk.print(); p.err.error(tk.pos, "An expression is expected."); return new IdentExpr("null", new TypeExprNamed("void", tk.pos), tk.pos); } @@ -145,6 +166,10 @@ public: bool looksLikeTypeStart(TokenKind k) { return k == TokenKind.Id + || k == TokenKind.Volatile + || k == TokenKind.Const + || k == TokenKind.Restrict + || k == TokenKind.Atomic || k == TokenKind.Star || k == TokenKind.LParen || k == TokenKind.LThan @@ -275,12 +300,23 @@ public: Node parseCastOrNode(Position pos) { - if (p.check(TokenKind.Id)) + if ( + p.check(TokenKind.Id) + || p.check(TokenKind.Volatile) + || p.check(TokenKind.Const) + || p.check(TokenKind.Atomic) + || p.check(TokenKind.Restrict) + ) { if ( p.future(TokenKind.Star, 1) || p.future(TokenKind.RParen, 1) || p.future(TokenKind.LThan, 1) + || p.future(TokenKind.Id, 1) + || p.future(TokenKind.Volatile, 1) + || p.future(TokenKind.Const, 1) + || p.future(TokenKind.Restrict, 1) + || p.future(TokenKind.Atomic, 1) || p.future(TokenKind.LBracket, 1) ) if (p.types.exists(p.peek().s)) diff --git a/src/frontend/parser/parse_stmt.d b/src/frontend/parser/parse_stmt.d index 2234173..a85df11 100644 --- a/src/frontend/parser/parse_stmt.d +++ b/src/frontend/parser/parse_stmt.d @@ -177,6 +177,9 @@ public: case TokenKind.Import: return parseImportStmt(tk.pos); + case TokenKind.Raw: + return new RawStmt(tk.s, tk.pos); + default: return new IdentExpr("null", new TypeExprNamed("void", tk.pos), tk.pos); } diff --git a/src/frontend/parser/parse_type.d b/src/frontend/parser/parse_type.d index 7ed305b..165ac79 100644 --- a/src/frontend/parser/parse_type.d +++ b/src/frontend/parser/parse_type.d @@ -112,11 +112,34 @@ public: if (p.match(TokenKind.LThan)) return parseGenericType(type, type.pos); + if (p.match(TokenKind.Restrict)) + return new TypeExprRestrict(type, type.pos); + return type; } + TypeExpr parseInit() + { + // import std.stdio; + // writeln(p.peek().s); + + if (p.match(TokenKind.Const)) + return new TypeExprConst(parseInit(), Position.init); + + if (p.match(TokenKind.Volatile)) + return new TypeExprVolatile(parseInit(), Position.init); + + if (p.match(TokenKind.Atomic)) + return new TypeExprAtomic(parseInit(), Position.init); + + if (p.match(TokenKind.Restrict)) + return new TypeExprRestrict(parseInit(), Position.init); + + return parsePrimary(); + } + TypeExpr parse() { - return checkAfter(parsePrimary()); + return checkAfter(parseInit()); } } diff --git a/src/frontend/parser/parser.d b/src/frontend/parser/parser.d index ed24f4a..708a6dd 100644 --- a/src/frontend/parser/parser.d +++ b/src/frontend/parser/parser.d @@ -48,6 +48,8 @@ class Parser { if (l is null) return r; + if (r is null) + return l; return new Position(l.filename, l.dir, l.start, r.end); } @@ -154,6 +156,7 @@ class Parser case TokenKind.Break: case TokenKind.Import: case TokenKind.Goto: + case TokenKind.Raw: return true; default: return false; diff --git a/src/frontend/type_expr.d b/src/frontend/type_expr.d index 2509290..0f7878d 100644 --- a/src/frontend/type_expr.d +++ b/src/frontend/type_expr.d @@ -17,6 +17,10 @@ enum TypeExprKind : ubyte Function, Result, Generic, + Const, + Volatile, + Restrict, + Atomic, } abstract class TypeExpr @@ -545,3 +549,207 @@ class TypeExprGeneric : TypeExpr return false; } } + +class TypeExprConst : TypeExpr +{ + TypeExpr base; + + this(TypeExpr base, Position p = Position.init) + { + this.kind = TypeExprKind.Const; + this.base = base; + this.pos = p; + } + + override string toString() const + { + return toStrVar(); + } + + override string toStrVar(string var = "") const + { + return "const " ~ base.toStrVar(var); + } + + override string toStr() const + { + return base.toStr(); + } + + override TypeExprConst dup() + { + auto n = new TypeExprConst(base is null ? null : base.dup(), pos); + n.kind = kind; + return n; + } + + override TypeExpr subGeneric(string[] names, TypeExpr[] types) + { + base = resolveGeneric(base, names, types); + return this; + } + + override void collectGenerics(void delegate(TypeExprGeneric) callback) + { + if (base !is null) + base.collectGenerics(callback); + } + + override bool containsOpenGeneric(bool delegate(string) isOpen) + { + return base !is null && base.containsOpenGeneric(isOpen); + } +} + +class TypeExprVolatile : TypeExpr +{ + TypeExpr base; + + this(TypeExpr base, Position p = Position.init) + { + this.kind = TypeExprKind.Volatile; + this.base = base; + this.pos = p; + } + + override string toString() const + { + return toStrVar(); + } + + override string toStrVar(string var = "") const + { + return "volatile " ~ base.toStrVar(var); + } + + override string toStr() const + { + return base.toStr(); + } + + override TypeExprVolatile dup() + { + auto n = new TypeExprVolatile(base is null ? null : base.dup(), pos); + n.kind = kind; + return n; + } + + override TypeExpr subGeneric(string[] names, TypeExpr[] types) + { + base = resolveGeneric(base, names, types); + return this; + } + + override void collectGenerics(void delegate(TypeExprGeneric) callback) + { + if (base !is null) + base.collectGenerics(callback); + } + + override bool containsOpenGeneric(bool delegate(string) isOpen) + { + return base !is null && base.containsOpenGeneric(isOpen); + } +} + +class TypeExprRestrict : TypeExpr +{ + TypeExpr base; + + this(TypeExpr base, Position p = Position.init) + { + this.kind = TypeExprKind.Restrict; + this.base = base; + this.pos = p; + } + + override string toString() const + { + return toStrVar(); + } + + override string toStrVar(string var = "") const + { + return base.toStrVar() ~ " restrict " ~ var; + } + + override string toStr() const + { + return base.toStr(); + } + + override TypeExprRestrict dup() + { + auto n = new TypeExprRestrict(base is null ? null : base.dup(), pos); + n.kind = kind; + return n; + } + + override TypeExpr subGeneric(string[] names, TypeExpr[] types) + { + base = resolveGeneric(base, names, types); + return this; + } + + override void collectGenerics(void delegate(TypeExprGeneric) callback) + { + if (base !is null) + base.collectGenerics(callback); + } + + override bool containsOpenGeneric(bool delegate(string) isOpen) + { + return base !is null && base.containsOpenGeneric(isOpen); + } +} + +class TypeExprAtomic : TypeExpr +{ + TypeExpr base; + + this(TypeExpr base, Position p = Position.init) + { + this.kind = TypeExprKind.Atomic; + this.base = base; + this.pos = p; + } + + override string toString() const + { + return toStrVar(); + } + + override string toStrVar(string var = "") const + { + return "_Atomic " ~ base.toStrVar(var); + } + + override string toStr() const + { + return base.toStr(); + } + + override TypeExprAtomic dup() + { + auto n = new TypeExprAtomic(base is null ? null : base.dup(), pos); + n.kind = kind; + return n; + } + + override TypeExpr subGeneric(string[] names, TypeExpr[] types) + { + base = resolveGeneric(base, names, types); + return this; + } + + override void collectGenerics(void delegate(TypeExprGeneric) callback) + { + if (base !is null) + base.collectGenerics(callback); + } + + override bool containsOpenGeneric(bool delegate(string) isOpen) + { + return base !is null && base.containsOpenGeneric(isOpen); + } +} diff --git a/src/frontend/type_registry.d b/src/frontend/type_registry.d index e52d038..5784fbf 100644 --- a/src/frontend/type_registry.d +++ b/src/frontend/type_registry.d @@ -31,6 +31,10 @@ public: types["bool"] = new TypeExprNamed("int"); types["void"] = new TypeExprNamed("void"); types["cstr"] = new TypeExprPointer(types["char"]); + types["volatile"] = new TypeExprNamed("volatile"); + types["const"] = new TypeExprNamed("const"); + types["_Atomic"] = new TypeExprNamed("_Atomic"); + types["restrict"] = new TypeExprNamed("restrict"); if (!noHeader) { diff --git a/src/main.d b/src/main.d index 3566b2f..f45b38c 100644 --- a/src/main.d +++ b/src/main.d @@ -44,6 +44,7 @@ void showHelp() writeln(" --no-header It will not automatically generate the Cx header."); writeln(" --gen-header It will generate a .h file and a .c file without compiling at the end."); writeln(" --cflags Pass compilation flags to the C compiler."); + writeln(" --cpp Compiles with a C++ compiler."); writeln(); writeln("Environment:"); writeln(" CC C compiler used to build the output (default: cc)"); @@ -64,6 +65,11 @@ void showVersion() writefln("Cx Compiler - Version (%s)", COMPILER_VERSION); } +bool which(string c) +{ + return executeShell(format("which %s", c)).status == 0; +} + int main(string[] argv) { string stdDir; @@ -112,7 +118,7 @@ int main(string[] argv) return runUpdate(); } - bool emitc, opt, dbg, verMessage, helpMessage, genHeader; + bool emitc, opt, dbg, verMessage, helpMessage, genHeader, cpp; string[] link, cflags; string output, target; @@ -129,6 +135,7 @@ int main(string[] argv) "no-header", &noHeader, "gen-header", &genHeader, "cflags", &cflags, + "cpp", &cpp, ); catch (GetOptException e) { @@ -202,9 +209,9 @@ int main(string[] argv) program.body = ResolveSymbols.resolve(err, ctx, program.body); check_diagnostic(err); - string fileh = output ~ ".h"; - string filec = output ~ ".c"; - string[2] src = new CodeGen(program, registry, ctx.statics, noHeader, genHeader, fileh, ctx).compile(); + string fileh = output ~ (cpp ? ".hpp" : ".h"); + string filec = output ~ (cpp ? ".cpp" : ".c"); + string[2] src = new CodeGen(program, registry, ctx.statics, noHeader, genHeader, fileh, ctx, cpp).compile(); check_diagnostic(err); write(filec, src[0]); @@ -221,7 +228,19 @@ int main(string[] argv) return 0; } - string c_compiler = environment.get("CC", "cc"); + string comp = "cc"; + if (cpp) + { + // decide o compilador a ser usado + comp = which("g++") ? "g++" : (which("clang++") ? "clang++" : ""); + if (!comp) + { + writeln("It was not possible to define a default C++ compiler. Use the CC variable to define one."); + return 1; + } + } + + string c_compiler = environment.get("CC", comp); string command = format("%s %s %s -o %s %s %s", c_compiler, filec, (opt ? "-O2" : ""), output, link.length > 0 ? (link.map!(l => format("-l%s", l).array).join(" ")) : "", cflags.join(" ")); if (dbg) diff --git a/src/updater.d b/src/updater.d index 5dcd060..553f165 100644 --- a/src/updater.d +++ b/src/updater.d @@ -47,7 +47,7 @@ int runUpdate() string tagName = json["tag_name"].str; string remoteVersion = tagName.startsWith("v") ? tagName[1 .. $] : tagName; - if (remoteVersion == COMPILER_VERSION) + if (remoteVersion < COMPILER_VERSION) { writefln("You are already on the latest version (%s).", COMPILER_VERSION); return 0; diff --git a/tests/unit.d b/tests/unit.d index 8a46cfb..7d0ddd0 100644 --- a/tests/unit.d +++ b/tests/unit.d @@ -97,10 +97,12 @@ TestResult runTest(string filename, string llvmLinkFlag) string binFile = filename ~ ".bin"; // 1) Cx -> C - string llvm; + string llvm, cpp; if (filename.length > 13 && filename[9..13] == "llvm") llvm = llvmLinkFlag; - Exec cxComp = executeShell(format("cx %s --output %s %s", filename, binFile, llvm)); + if (filename.length > 13 && filename[9..12] == "cpp") + cpp = "--cpp"; + Exec cxComp = executeShell(format("cx %s --output %s %s %s", filename, binFile, llvm, cpp)); if (cxComp.status != 0) { res.ok = false; From 7b77699828aa7ea223f77b552a659299ffc486b1 Mon Sep 17 00:00:00 2001 From: Fernando Dev Date: Mon, 13 Jul 2026 16:44:48 -0300 Subject: [PATCH 5/6] Fix ubuntu. --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f507f51..3481d72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: run: | export DEBIAN_FRONTEND=noninteractive apt-get update - apt-get install -y ldc dub gcc llvm-21-dev + apt-get install -y ldc dub gcc llvm-21-dev g++ - name: Install ldc2, dub, llvm (Arch) if: matrix.distro == 'arch' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b07bf60..ca226af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: run: | export DEBIAN_FRONTEND=noninteractive apt-get update - apt-get install -y ldc dub zip gcc llvm-21-dev + apt-get install -y ldc dub zip gcc llvm-21-dev g++ - name: Ensure llvm-c and llvm headers are on the default system include path run: | From 5f3517f30d95973e2f1f71efa448142622712d56 Mon Sep 17 00:00:00 2001 From: Fernando Dev Date: Mon, 13 Jul 2026 16:52:41 -0300 Subject: [PATCH 6/6] Fix fedora. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3481d72..e73a67a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: Install ldc2, dub (Fedora) if: matrix.distro == 'fedora' - run: dnf install -y ldc dub + run: dnf install -y ldc dub g++ - name: Install llvm-devel (Fedora) if: matrix.distro == 'fedora'