@@ -455,6 +455,8 @@ pub fn (mut g Gen) gen() string {
455455 g.sb.writeln ('' )
456456
457457 // Pass 4: Function forward declarations
458+ mut test_fn_names := []string {}
459+ mut has_main := false
458460 for file in g.files {
459461 g.set_file_module (file)
460462 for stmt in file.stmts {
@@ -473,6 +475,13 @@ pub fn (mut g Gen) gen() string {
473475 if fn_name == '' {
474476 continue
475477 }
478+ if fn_name == 'main' {
479+ has_main = true
480+ }
481+ if stmt.name.starts_with ('test_' ) && ! stmt.is_method
482+ && stmt.typ.params.len == 0 {
483+ test_fn_names << fn_name
484+ }
476485 if g.env != unsafe { nil } {
477486 if fn_scope := g.env.get_fn_scope (g.cur_module, fn_name) {
478487 g.cur_fn_scope = fn_scope
@@ -494,6 +503,25 @@ pub fn (mut g Gen) gen() string {
494503 g.gen_file (file)
495504 }
496505
506+ // Generate test runner main if this is a test file (has test_ functions but no main)
507+ if ! has_main && test_fn_names.len > 0 {
508+ g.sb.writeln ('' )
509+ g.sb.writeln ('int main(int ___argc, char** ___argv) {' )
510+ g.sb.writeln ('\t g_main_argc = ___argc;' )
511+ g.sb.writeln ('\t g_main_argv = (void*)___argv;' )
512+ for test_fn in test_fn_names {
513+ msg_run := 'Running test: ${test_fn }...'
514+ msg_ok := ' OK'
515+ g.sb.writeln ('\t println((string){"${msg_run }", sizeof("${msg_run }") - 1});' )
516+ g.sb.writeln ('\t ${test_fn }();' )
517+ g.sb.writeln ('\t println((string){"${msg_ok }", sizeof("${msg_ok }") - 1});' )
518+ }
519+ msg_all := 'All ${test_fn_names .len } tests passed.'
520+ g.sb.writeln ('\t println((string){"${msg_all }", sizeof("${msg_all }") - 1});' )
521+ g.sb.writeln ('\t return 0;' )
522+ g.sb.writeln ('}' )
523+ }
524+
497525 return g.sb.str ()
498526}
499527
@@ -1302,9 +1330,19 @@ fn (mut g Gen) gen_stmt(node ast.Stmt) {
13021330 ast.ExprStmt {
13031331 if node.expr is ast.UnsafeExpr {
13041332 unsafe_expr := node.expr as ast.UnsafeExpr
1333+ if unsafe_expr.stmts.len > 1 {
1334+ g.write_indent ()
1335+ g.sb.writeln ('{' )
1336+ g.indent++
1337+ }
13051338 for stmt in unsafe_expr.stmts {
13061339 g.gen_stmt (stmt)
13071340 }
1341+ if unsafe_expr.stmts.len > 1 {
1342+ g.indent--
1343+ g.write_indent ()
1344+ g.sb.writeln ('}' )
1345+ }
13081346 return
13091347 }
13101348 if node.expr is ast.IfExpr {
@@ -3452,6 +3490,12 @@ fn escape_c_string_literal_content(raw string) string {
34523490 if ch == `"` {
34533491 sb.write_u8 (`\\ ` )
34543492 sb.write_u8 (`"` )
3493+ } else if ch == `\n ` {
3494+ sb.write_u8 (`\\ ` )
3495+ sb.write_u8 (`n` )
3496+ } else if ch == `\r ` {
3497+ sb.write_u8 (`\\ ` )
3498+ sb.write_u8 (`r` )
34553499 } else {
34563500 sb.write_u8 (ch)
34573501 }
@@ -3662,10 +3706,20 @@ fn (mut g Gen) gen_expr(node ast.Expr) {
36623706 g.sb.write_string ('false' )
36633707 } else if node.kind == .char {
36643708 raw := strip_literal_quotes (node.value)
3665- escaped := escape_char_literal_content (raw)
3666- g.sb.write_u8 (`'` )
3667- g.sb.write_string (escaped)
3668- g.sb.write_u8 (`'` )
3709+ if raw.len > 1 && raw[0 ] != `\\ ` {
3710+ // Multi-byte UTF-8 character: emit as numeric codepoint
3711+ runes := raw.runes ()
3712+ if runes.len > 0 {
3713+ g.sb.write_string (int (runes[0 ]).str ())
3714+ } else {
3715+ g.sb.write_string ("'${raw }'" )
3716+ }
3717+ } else {
3718+ escaped := escape_char_literal_content (raw)
3719+ g.sb.write_u8 (`'` )
3720+ g.sb.write_string (escaped)
3721+ g.sb.write_u8 (`'` )
3722+ }
36693723 } else {
36703724 g.sb.write_string (sanitize_c_number_literal (node.value))
36713725 }
@@ -4427,6 +4481,12 @@ fn (mut g Gen) gen_expr(node ast.Expr) {
44274481 }
44284482 }
44294483 ast.SelectorExpr {
4484+ // typeof(x).name -> just emit the typeof string directly (already a string)
4485+ if node.lhs is ast.KeywordOperator && node.lhs.op == .key_typeof
4486+ && node.rhs.name == 'name' {
4487+ g.gen_keyword_operator (node.lhs)
4488+ return
4489+ }
44304490 // C.<ident> references C macros/constants directly (e.g. C.EOF -> EOF).
44314491 if node.lhs is ast.Ident && node.lhs.name == 'C' {
44324492 g.sb.write_string (node.rhs.name)
0 commit comments