From ff30ef8153675a3b40735ebfd5013e81278a8b5c Mon Sep 17 00:00:00 2001 From: uan Date: Wed, 4 Feb 2026 21:04:58 +0100 Subject: [PATCH] basic One2C code gen --- .gitignore | 2 + generator.v | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lexer.v | 4 +- main.v | 14 +++++- parser.v | 76 ++++++++++++++++++++++-------- test | Bin 0 -> 12576 bytes test.one | 3 -- 7 files changed, 206 insertions(+), 25 deletions(-) create mode 100644 generator.v create mode 100755 test delete mode 100644 test.one diff --git a/.gitignore b/.gitignore index 2a3dc53..b9e3ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ onev *.so *.dylib *.dll +*.c +*.one # Ignore binary output folders bin/ diff --git a/generator.v b/generator.v new file mode 100644 index 0000000..ac119ab --- /dev/null +++ b/generator.v @@ -0,0 +1,132 @@ +module main + +import strings +import os + +struct Generator { +mut: + out strings.Builder +} + +fn (mut g Generator) get_c_type(typ string) string { + c_type := if typ == 'real' { 'double' } else { typ } + return c_type +} + +fn (mut g Generator) gen_stmt(stmt Stmt) { + match stmt { + VarDecl { + c_type := g.get_c_type(stmt.type) + g.out.write_string('${c_type} ${stmt.name} = ') + g.gen_expr(stmt.value) + g.out.writeln(';') + } + ExprStmt { + g.gen_expr(stmt.expr) + g.out.writeln(';') + } + ReturnStmt { + g.out.write_string('return ') + g.gen_expr(stmt.expr) + g.out.writeln(';') + } + Block { + g.out.writeln('{') + for inner_stmt in stmt.stmts { + g.gen_stmt(inner_stmt) + } + g.out.writeln('}') + } + FuncDecl { + c_type := g.get_c_type(stmt.ret_type) + g.out.write_string('${c_type} ${stmt.name}() ') + g.gen_stmt(stmt.block) + } + } +} + +fn (mut g Generator) gen_expr(expr Expr) { + match expr { + RealLiteral { + g.out.write_string(expr.val.str()) + } + IntegerLiteral { + g.out.write_string(expr.val.str()) + } + BoolLiteral { + g.out.write_string(expr.val.str()) + } + Variable { + g.out.write_string(expr.name) + } + UnaryExpr { + // Handle postfix/prefix logic if necessary + g.out.write_string('${expr.ident}${expr.op}') + } + BinaryExpr { + g.out.write_string('(') + g.gen_expr(expr.left) + g.out.write_string(' ${expr.op} ') + g.gen_expr(expr.right) + g.out.write_string(')') + } + PrintExpr { + g.out.write_string('printf(\"%') + format := match expr.type { + 'int' {'d'} + 'real' {'lf'} + 'bool' {'d'} + else {panic("Tried printing illegal type")} + } + g.out.write_string('${format}\\n\", ') + g.gen_expr(expr.expr) + g.out.write_string(')') + } + TypeCast { + c_type := g.get_c_type(expr.type) + g.out.write_string('(${c_type})') + g.gen_expr(expr.expr) + } + else {panic('unimplemented ${expr}')} + } +} + +fn (mut g Generator) gen_c(program []Stmt) string { + g.out.writeln('#include ') + g.out.writeln('#include ') // For your 'bool' types [cite: 4] + for stmt in program { + g.gen_stmt(stmt) + } + + return g.out.str() + +} + +fn compile_with_clang(c_code string, output_name string, keep_c bool) { + // 1. Write the C code to a temporary file + c_file := 'middle_c.c' + os.write_file(c_file, c_code) or { + eprintln('Failed to write C file: $err') + return + } + + // 2. Construct the clang command + // We'll use -O2 for optimization and -o to specify the output binary + clang_cmd := 'clang ${c_file} -o ${output_name} -O2' + + println('Executing: ${clang_cmd}') + + // 3. Run the command + result := os.execute(clang_cmd) + + if result.exit_code != 0 { + eprintln('Clang Compilation Failed:') + eprintln(result.output) + } else { + println('Compilation successful! Binary created: $output_name') + // Optional: Remove the temporary C file + if !keep_c { + os.rm(c_file) or { } + } + } +} diff --git a/lexer.v b/lexer.v index afe6565..a67fcd0 100644 --- a/lexer.v +++ b/lexer.v @@ -11,6 +11,7 @@ enum TokenType as u8 { kw_break kw_fn kw_return + kw_print integer real boolean @@ -99,6 +100,7 @@ fn toktype_from_kw(kw string) TokenType { 'fn' {.kw_fn} 'return' {.kw_return} 'true', 'false' {.boolean} + 'print' {.kw_print} else {.unknown} } } @@ -114,7 +116,7 @@ fn is_real(str string) bool { fn is_keyword(str string) bool { return [ - "void", "int", "real", "bool", "if", "else", "for", "break", "fn", "return", "let", "true", "false" + "void", "int", "real", "bool", "if", "else", "for", "break", "fn", "return", "let", "true", "false", "print" ].contains(str) } diff --git a/main.v b/main.v index aa8be7a..910a80a 100644 --- a/main.v +++ b/main.v @@ -1,9 +1,10 @@ module main import os +import strings fn main() { - content := os.read_file("test.one") or { return } + content := os.read_file("source.one") or { return } println("---------\n" + content + "---------") tokens := lex(content) or { return } println("-- TOK --") @@ -17,4 +18,15 @@ fn main() { println("-- AST --") println(statements) + + mut generator := Generator{ + out: strings.new_builder(100) + } + + out_c := generator.gen_c(statements) + + println("--- C ---") + println(out_c) + compile_with_clang(out_c, 'test', true) + } diff --git a/parser.v b/parser.v index 73fbc3e..65c1f9a 100644 --- a/parser.v +++ b/parser.v @@ -2,6 +2,28 @@ module main import term +// ------------------------------------------- Precedence + +enum Precedence { + lowest + assignment // = , +=, -= + comparison // ==, !=, <, > + sum // +, - + product // *, / + prefix // -x, !x + call // function() +} + +fn (p Parser) get_precedence(tok_type TokenType) Precedence { + return match tok_type { + .equals, .plus_eq, .minus_eq, .star_eq, .slash_eq { .assignment } + .eq_eq, .not_eq, .less_eq, .greater_eq { .comparison } + .plus, .minus { .sum } + .star, .slash { .product } + else { .lowest } + } +} + // ------------------------------------------- Symbol Table type SymbolInfo = VarSymbolInfo | FuncSymbolInfo @@ -70,7 +92,7 @@ fn (mut s SymbolTable) is_in_global_scope() bool { // ------------------------------------------- Expressions -type Expr = VoidExpr | UnaryExpr | BinaryExpr | IntegerLiteral | RealLiteral | BoolLiteral | Variable | TypeExpr | Function | TypeCast | ParenExpr +type Expr = VoidExpr | UnaryExpr | BinaryExpr | IntegerLiteral | RealLiteral | BoolLiteral | Variable | TypeExpr | Function | TypeCast | ParenExpr | PrintExpr struct VoidExpr {} @@ -118,6 +140,11 @@ struct ParenExpr { expr Expr } +struct PrintExpr { + expr Expr + type string +} + // ------------------------------------------- Statements type Stmt = VarDecl | ExprStmt | ReturnStmt | Block | FuncDecl @@ -139,7 +166,7 @@ struct ExprStmt { } struct ReturnStmt { - value Expr + expr Expr } struct Block { @@ -210,20 +237,20 @@ fn (mut p Parser) parse_primary() Expr { .identifier {p.parse_ident(token.text)} .type {p.parse_type(token.text)} .lparen {p.parse_paren()} + .kw_print {p.parse_print()} else {parse_error("Unexpected Token")} } } -fn (mut p Parser) parse_expr() Expr { - mut left := p.parse_primary() +fn (mut p Parser) parse_expr(prec Precedence) Expr { + mut expr := p.parse_primary() - match p.peek().type { - .plus, .minus, .star, .slash, .equals, .eq_eq, .not_eq, .less_eq, .greater_eq, - .plus_eq, .minus_eq, .star_eq, .slash_eq { - return p.parse_binary(left, p.peek().text) - } - else {return left} + for int(prec) < int(p.get_precedence(p.peek().type)) { + op_tok := p.next() + expr = p.parse_binary(expr, op_tok.text, p.get_precedence(op_tok.type)) } + + return expr } fn (mut p Parser) parse_ident(ident string) Expr { @@ -233,10 +260,19 @@ fn (mut p Parser) parse_ident(ident string) Expr { } } -fn (mut p Parser) parse_binary(left Expr, op string) BinaryExpr { - p.next() - right := p.parse_expr() +fn (mut p Parser) parse_print() PrintExpr { + p.expect(.lparen) + expr := p.parse_expr(.lowest) + p.expect(.rparen) + return PrintExpr{expr: expr, type: p.get_expr_type(expr)} +} + +fn (mut p Parser) parse_binary(left Expr, op string, prec Precedence) BinaryExpr { + //p.next() + right := p.parse_expr(prec) binary_expr := BinaryExpr{left, op, right} + + if !p.is_op_valid_for_type(p.get_expr_type(left), op) { parse_error("Illegal operation ${op} for type ${p.get_expr_type(left)}") } @@ -248,7 +284,7 @@ fn (mut p Parser) parse_type(type string) Expr { if p.peek().type == .lparen { p.next() - expr := p.parse_expr() + expr := p.parse_expr(.lowest) p.expect(.rparen) return TypeCast { @@ -261,7 +297,7 @@ fn (mut p Parser) parse_type(type string) Expr { } fn (mut p Parser) parse_paren() ParenExpr { - expr := p.parse_expr() + expr := p.parse_expr(.lowest) p.expect(.rparen) return ParenExpr{expr: expr} } @@ -356,7 +392,7 @@ fn (mut p Parser) parse_var_decl() VarDecl { } p.expect(.equals) - val := p.parse_expr() + val := p.parse_expr(.lowest) if type_tok.text == 'void' { parse_error("Cannot declare a variable of type void") @@ -402,9 +438,9 @@ fn (mut p Parser) parse_func_decl() FuncDecl { return_stmts := p.get_return_stmts_recursive(block) for return_stmt in return_stmts { - if p.get_expr_type(return_stmt.value) != type_tok.text { + if p.get_expr_type(return_stmt.expr) != type_tok.text { parse_error("Mismatch between declared return type (${type_tok.text}) \ - and actual return type (${p.get_expr_type(return_stmt.value)})") + and actual return type (${p.get_expr_type(return_stmt.expr)})") } } @@ -437,12 +473,12 @@ fn (mut p Parser) parse_return_stmt() ReturnStmt { p.expect(.semicolon) } return ReturnStmt { - value: expr + expr: expr } } fn (mut p Parser) parse_expr_stmt() ExprStmt { - expr := p.parse_expr() + expr := p.parse_expr(.lowest) p.expect(.semicolon) return ExprStmt { diff --git a/test b/test new file mode 100755 index 0000000000000000000000000000000000000000..f080ec685870f08430010b1a079ce36899aab41c GIT binary patch literal 12576 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