From e729a0eef0e2e0332202503a7de2f006698db21f Mon Sep 17 00:00:00 2001 From: Dylan Leifer-Ives Date: Tue, 9 Dec 2025 16:26:11 -0800 Subject: [PATCH 1/3] Start rework I added the fields that I think that I will need to basic blocks and made the arguments to jump functions exist properly. So that will help with the later development. While I am focusing on #12 foremost, there is always the idea of the other things in the background. I think that I'm going to refactor all the functions to have trailing types that way when you're writing out the function it has the consistent naming convention across different things. Predominantly this is of the form link_bb instead of bb_link. But I also changed it from add_bb to create_bb because it is making the bb and not adding an existing thing to the list of bbs. I think that at some level this makes more sense. I really want to clean up the code, it reached a really messy state... --- ir.v | 208 +++++++++++++++++++++++------------------------------------ 1 file changed, 81 insertions(+), 127 deletions(-) diff --git a/ir.v b/ir.v index ff62efc..dc6cc11 100644 --- a/ir.v +++ b/ir.v @@ -58,20 +58,29 @@ pub mut: type BBID = int -// TODO: resolve this lol +// TODO: we need to add scopes to basic blocks, so we can +// who own whom. and who we can access. +// Also need to determine pub struct IRBasicBlock { pub mut: - label string - id BBID - namespace NID - function FID - args []IRBasicBlockArg - vars_map map[string]RID - insts []IID - - predecessors []BBID - successors []BBID - stmts []Stmt // The list of statements from the IR that form this basic block + label string + id BBID + namespace NID + function FID + args []IRBasicBlockArg + insts []IID + local_defs map[string]RID // Values that are created in this block + + // Relationships + dominator ?BBID + dominance_level int + predecessors []BBID + successors []BBID + + // For construction + stmts []Stmt // The list of statements from the IR that form this basic block + is_sealed bool // Are all the predecessors known? + is_filled bool // Have we generated alled the instructions? } pub type BBAID = int @@ -105,19 +114,6 @@ pub mut: result RID } -pub fn (t TokenKind) to_assign_op() AssignOp { - return match t { - .assign { .assign } - .plus_assign { .add_assign } - .minus_assign { .sub_assign } - .star_assign { .mul_assign } - .slash_assign { .div_assign } - .percent_assign { .mod_assign } - .swap { .swap } - else { panic('illegal use of token ${t} as a Assign operation') } - } -} - pub enum AssignOp { assign add_assign @@ -128,32 +124,6 @@ pub enum AssignOp { swap } -pub fn (a AssignOp) print() { - match a { - .assign { - print('=') - } - .add_assign { - print('+=') - } - .sub_assign { - print('-=') - } - .mul_assign { - print('*=') - } - .div_assign { - print('/=') - } - .mod_assign { - print('%=') - } - .swap { - print('><') - } - } -} - pub struct IRAssign { pub mut: id IID @@ -183,47 +153,6 @@ pub enum BinaryOp { ge } -pub fn (t TokenKind) to_binary_op() BinaryOp { - return match t { - .plus { - .add - } - .minus { - .sub - } - .star { - .mul - } - .slash { - .div - } - .percent { - .mod - } - .eq { - .eq - } - .ne { - .ne - } - .lcarrot { - .lt - } - .rcarrot { - .gt - } - .lte { - .le - } - .gte { - .ge - } - else { - panic('Token to convert to binary operation ${t} is not supported as binary operation') - } - } -} - pub struct IRBinaryOp { pub mut: result RID @@ -233,15 +162,6 @@ pub mut: right OID } -pub fn (t TokenKind) to_unary_op() UnaryOp { - return match t { - .ampersand { .ref } - .at { .deref } - .ex_point { .neg } - else { panic('illegal use of token ${t} as a unary operation') } - } -} - pub enum UnaryOp { neg ref @@ -300,20 +220,22 @@ pub struct IRJump { pub mut: id IID target BBID - // TODO: add value here for calling the .mcfunction with args + args []OID } pub struct IRBranch { pub mut: - id IID - cond OID - then BBID - el BBID - // TODO: add values here for calling the .mcfunction with args + id IID + cond OID + then_bb BBID + then_args []OID + else_bb BBID + else_args []OID } pub struct IRReturn { pub mut: + id IID value ?OID } @@ -907,23 +829,26 @@ pub fn (mut b IRBuilder) stage1() bool { return result } -pub fn (mut b IRBuilder) bb_link(self BBID, child BBID) { +pub fn (mut b IRBuilder) link_bb(self BBID, child BBID) { // TODO: add some saftey checks lol b.basic_blocks[self].successors << child b.basic_blocks[child].predecessors << self } -pub fn (mut b IRBuilder) add_bb(fid FID, label string) BBID { - nid := b.functions[fid].namespace - bb := IRBasicBlock{ - label: label - id: b.basic_blocks.len - namespace: nid - function: fid - } - b.basic_blocks << bb - b.functions[fid].bbs << bb.id - return bb.id +pub fn (mut b IRBuilder) create_bb(fid FID, label string) BBID { + nid := b.functions[fid].namespace + bb := IRBasicBlock{ + label: label + id: b.basic_blocks.len + namespace: nid + function: fid + is_sealed: false + is_filled: false + } + bbid := bb.id + b.basic_blocks << bb + b.functions[fid].bbs << bbid + return bbid } pub fn (mut b IRBuilder) literal_has_macro(l Literal) bool { @@ -1128,6 +1053,7 @@ pub fn (mut b IRBuilder) expr_needs_block(e Expr) bool { // Start is the BBID to start parsing on // stmts are the statements that belong to this block or its children // Returns the BBID of the block that it has ended on +// TODO: rip out after I finish pub fn (mut b IRBuilder) bb_build_bb_cfg(start BBID, stmts []Stmt) (bool, BBID) { fid := b.basic_blocks[start].function mut current := start @@ -1271,15 +1197,34 @@ pub fn (mut b IRBuilder) bb_build_bb_cfg(start BBID, stmts []Stmt) (bool, BBID) return result, current } -pub fn (mut b IRBuilder) fn_build_bb_cfg(fid FID) bool { - println('extracting bbs from function ${b.functions[fid].name}') - block := b.functions[fid].block - // Now we are going to make our entry block for this function - entry := b.add_bb(fid, 'entry') - valid, _ := b.bb_build_bb_cfg(entry, block.stmts) - return valid +// TODO: implement +pub fn (mut b IRBuilder) build_cfg_recursive(fid FID, current_bb BBID, stmts []Stmt) ?(bool, BBID) { + return none } +pub fun (mut b IRBuilder) s2_phase1_build_cfg_skeleton(fid FID) bool { + func := b.functions[fid] + + // Create our entry block + entry_bb := b.create_bb(fid, 'entry') + b.functions[fid].entrybb = entry_bb + + _, end_bb := b.build_cfg_recursive(fid, entry_bb, func.block.stmts) or { + // TODO: change this to be a proper error + println("We could not build the CFG for ${func.name}") + return false + } + + // Adding a return here, so that we will always have a return at the end of our functions, so we just like. know that + b.basic_blocks[entry_bb].is_sealed = true + if b.basic_blocks[end_bb].insts.len == 0 { + b.basic_blocks[end_bb].stmts << Stmt(Return{value : none}) + } + + return true; +} + +// TODO: add the error classes so we can accumulate errors and print out helpfully. pub fn (mut b IRBuilder) stage2() bool { mut result := true // Here we are going to go through our functions and generate our basic blocks except for the terminal instructions within them. @@ -1307,7 +1252,16 @@ pub fn (mut b IRBuilder) stage2() bool { // Here we will create the basic blocks for a function for fid in 0 .. b.functions.len { - result = result && b.fn_build_bb_cfg(fid) + // Build the base + if !b.s2_phase1_build_cfg_skeleton(fid){ + return false + } + + // TODO: Compute the dominance + // TODO: Solve arguments + // TODO: Lower instructions + // TODO: Add terminators + } // Then we are going to generate the instructions for our blocks From 6f669f5a63e0d8e256f0596d080a3278615ba4d2 Mon Sep 17 00:00:00 2001 From: Dylan Leifer-Ives Date: Wed, 10 Dec 2025 21:31:39 -0800 Subject: [PATCH 2/3] Need to fix structure Was going to really quick go through and implement the skeleton generation for the cfg before going to bed. I had yet to think through the cases where we are going to have to do each expression expansion. I think that each statement should only produce a singular basic block. As increasing layers of internal recursion for macros are just going to push the requirements up to the block above them to then be parsed into this block. In this manner I think possibly a good idea would be to move around the statements or on the fly generate more statements before doing the instruction generation as this would allow us to split up the dependent macro case which is the entire reason for why I'm doing this rewrite of the exisitng cfg generation. --- ir.v | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/ir.v b/ir.v index dc6cc11..628aa3d 100644 --- a/ir.v +++ b/ir.v @@ -1197,8 +1197,42 @@ pub fn (mut b IRBuilder) bb_build_bb_cfg(start BBID, stmts []Stmt) (bool, BBID) return result, current } + + // TODO: implement pub fn (mut b IRBuilder) build_cfg_recursive(fid FID, current_bb BBID, stmts []Stmt) ?(bool, BBID) { + mut bb := current_bb + + for stmt in stmts { + match stmt{ + TypedDefine { + b.basic_blocks[bb].stmts << stmt + } + + // Need to add basic blocks for anything that can have a macro. This + // breaks down to each expression. So we're going to have to handle + // all of the those things. I think that this should be formed into + // one big expression run through that will generate the basic + // blocks per expression... or like if the expression needs a macro + // then it creates a basic block. + + Return { + b.basic_blocks[bb].stmts << stmt + b.basic_blocks[bb].is_sealed = true + return true, bb + } + + StructDefinition, FunctionDefinitino, NamespaceDefinition, NamespaceImport, NamespaceAlias, FunctionInlineDefinition, Block { + println("Error ${stmt} is not allowed in a basic_block_body") + return none + } + + else{ + println("Error: Unhandled statement ${stmt}") + return none + } + } + } return none } From bcec2d18d21090a2256cb8717ed45ace2fcb4015 Mon Sep 17 00:00:00 2001 From: Dylan Leifer-Ives Date: Wed, 7 Jan 2026 17:05:55 -0800 Subject: [PATCH 3/3] Been going crazy gir --- Readme.org | 20 +- codegen.v | 1117 +++++++++++++++++ errors.v | 419 +++++++ example.mcf | 40 - ir.v | 428 +++++-- ir_instruction_gen.v | 915 ++++++++++++-- ir_test.v | 16 +- lx.v | 40 + main.v | 57 +- parse.v | 21 +- semantic.v | 548 ++++++++ test.mcf | 13 - tests/codegen/arithmetic.mdl | 29 + tests/codegen/conditionals.mdl | 38 + tests/codegen/data_reg.mdl | 35 + tests/codegen/functions.mdl | 28 + tests/codegen/macros.mdl | 18 + tests/codegen/nested_ns.mdl | 20 + tests/codegen/structs.mdl | 34 + tests/errors/type_mismatch.mdl | 7 + tests/errors/undefined_function.mdl | 6 + tests/errors/undefined_var.mdl | 6 + tests/ir/bb_gen/simple/{main.mcf => main.mdl} | 2 +- .../bb_gen/stmts/{assign.mcf => assign.mdl} | 0 .../bb_gen/stmts/{define.mcf => define.mdl} | 0 .../{typed_define.mcf => typed_define.mdl} | 0 .../ir/namespace/child/{main.mcf => main.mdl} | 0 tests/ir/namespace/child_import/main.mcf | 3 - tests/ir/namespace/child_import/main.mdl | 3 + tests/ir/namespace/child_import/utils.mcf | 2 - tests/ir/namespace/child_import/utils.mdl | 2 + .../child_import_fail/{main.mcf => main.mdl} | 2 +- .../ir/namespace/child_import_fail/utils.mcf | 2 - .../ir/namespace/child_import_fail/utils.mdl | 2 + tests/ir/namespace/import/a.mcf | 2 - tests/ir/namespace/import/a.mdl | 2 + tests/ir/namespace/import/b.mcf | 2 - tests/ir/namespace/import/b.mdl | 2 + .../namespace/simple/{main.mcf => main.mdl} | 0 .../{attributes.mcf => attributes.mdl} | 0 .../structs/cross_file/{main.mcf => main.mdl} | 2 +- .../cross_file_fail/{main.mcf => main.mdl} | 2 +- .../{materials.mcf => materials.mdl} | 0 .../structs/self_ref/{main.mcf => main.mdl} | 0 .../ir/structs/simple/{main.mcf => main.mdl} | 0 tests/semantic/errors.mdl | 19 + tests/types/lists/append/append.mdl | 9 + utils_ir.v | 163 ++- 48 files changed, 3768 insertions(+), 308 deletions(-) create mode 100644 codegen.v create mode 100644 errors.v delete mode 100644 example.mcf create mode 100644 semantic.v delete mode 100644 test.mcf create mode 100644 tests/codegen/arithmetic.mdl create mode 100644 tests/codegen/conditionals.mdl create mode 100644 tests/codegen/data_reg.mdl create mode 100644 tests/codegen/functions.mdl create mode 100644 tests/codegen/macros.mdl create mode 100644 tests/codegen/nested_ns.mdl create mode 100644 tests/codegen/structs.mdl create mode 100644 tests/errors/type_mismatch.mdl create mode 100644 tests/errors/undefined_function.mdl create mode 100644 tests/errors/undefined_var.mdl rename tests/ir/bb_gen/simple/{main.mcf => main.mdl} (89%) rename tests/ir/bb_gen/stmts/{assign.mcf => assign.mdl} (100%) rename tests/ir/bb_gen/stmts/{define.mcf => define.mdl} (100%) rename tests/ir/bb_gen/stmts/{typed_define.mcf => typed_define.mdl} (100%) rename tests/ir/namespace/child/{main.mcf => main.mdl} (100%) delete mode 100644 tests/ir/namespace/child_import/main.mcf create mode 100644 tests/ir/namespace/child_import/main.mdl delete mode 100644 tests/ir/namespace/child_import/utils.mcf create mode 100644 tests/ir/namespace/child_import/utils.mdl rename tests/ir/namespace/child_import_fail/{main.mcf => main.mdl} (56%) delete mode 100644 tests/ir/namespace/child_import_fail/utils.mcf create mode 100644 tests/ir/namespace/child_import_fail/utils.mdl delete mode 100644 tests/ir/namespace/import/a.mcf create mode 100644 tests/ir/namespace/import/a.mdl delete mode 100644 tests/ir/namespace/import/b.mcf create mode 100644 tests/ir/namespace/import/b.mdl rename tests/ir/namespace/simple/{main.mcf => main.mdl} (100%) rename tests/ir/structs/cross_file/{attributes.mcf => attributes.mdl} (100%) rename tests/ir/structs/cross_file/{main.mcf => main.mdl} (71%) rename tests/ir/structs/cross_file_fail/{main.mcf => main.mdl} (67%) rename tests/ir/structs/cross_file_fail/{materials.mcf => materials.mdl} (100%) rename tests/ir/structs/self_ref/{main.mcf => main.mdl} (100%) rename tests/ir/structs/simple/{main.mcf => main.mdl} (100%) create mode 100644 tests/semantic/errors.mdl create mode 100644 tests/types/lists/append/append.mdl diff --git a/Readme.org b/Readme.org index 12460aa..38b6e47 100644 --- a/Readme.org +++ b/Readme.org @@ -15,7 +15,7 @@ Now what do I mean by this? One of the outcomes that is-- nearly-- impossible to *** In minecraft datapack `mcfunction` code /Concatenate output/ -=cat_and_say.mcfunction= +=cat_and_say.mdlunction= #+begin_src #inputs : # A : Some literal @@ -28,7 +28,7 @@ And then the calling code /Call Code/ -=main.mcfunction= +=main.mdlunction= #+begin_src data modify storage example:main cat_and_say_args set value {A:"Hello,",B:" World!"} function example:cat_and_say with storage example:main cat_and_say_args @@ -38,15 +38,15 @@ Which then will output =Hello, World!= and store it to =example:utils cat_output *** In mdl In a direct translation this could be #+begin_src -// utils.mcf +// utils.mdl data cat_output := ""; fn cat_and_say(eff A: String, eff B: String) : Void { utils.cat_output = ""; $say ""; } -// main.mcf -namespace utils = "utils.mcf' +// main.mdl +namespace utils = "utils.mdl' utils/cat_and_say("Hello,"," World!"); #+end_src @@ -89,14 +89,14 @@ This is my first time ever using the V programming language, it has been interes * Notes ** Namespaces #+begin_src -// a.mcf +// a.mdl namespace a; -namespace b = "b.mcf"; +namespace b = "b.mdl"; data x := 10; -// b.mcf +// b.mdl namespace b; -namespace a = "a.mcf"; +namespace a = "a.mdl"; fn addx(data y: Int) : Int { return y + a.x; @@ -110,7 +110,7 @@ fn add2x(data y: Int) : Int{ Should ultimately result in #+begin_src -//b.mcf +//b.mdl fn addx(data y : Int) : Int{ return y + a.x; diff --git a/codegen.v b/codegen.v new file mode 100644 index 0000000..98e76f8 --- /dev/null +++ b/codegen.v @@ -0,0 +1,1117 @@ +module main + +import os + +// Codegen - Generates .mcfunction files from IR +// +// === Storage Layout === +// Data variables (storage): +// - Function args: storage : .$args. +// - Function result: storage : .$result +// - Data variables: storage : . +// +// Register variables (scoreboard) - integers only: +// - Objective: +// - Player: __ +// +// === File Structure === +// Super nested structure: +// ns/func.mcfunction - Entry point (calls entry block) +// ns/func/entry_0.mcfunction - Entry basic block +// ns/func/if_then_1.mcfunction - Then branch +// ns/func/if_else_2.mcfunction - Else branch +// etc. +// +// === Macro Passing === +// Basic blocks that need macro values are called with: +// function : with storage : .$macro_args +// or with immediate dict: +// function : with {key: value, ...} +// Inside the function, $(key) expands to the value. +// +// Reference macros (&var) are inserted raw, non-ref macros use $(). + +pub struct Codegen { +pub mut: + builder &IRBuilder + output_dir string + project_name string // Scoreboard objective name +} + +pub fn Codegen.new(builder &IRBuilder, output_dir string, project_name string) Codegen { + return Codegen{ + builder: builder + output_dir: output_dir + project_name: project_name + } +} + +// Generate all mcfunction files +pub fn (mut c Codegen) generate() ! { + // Create base output directory + os.mkdir_all(c.output_dir) or { + return error('Could not create output directory: ${c.output_dir}') + } + + // Create pack.mcmeta + c.write_pack_mcmeta()! + + // Generate init function for scoreboards + c.generate_init_function()! + + // Generate functions for each namespace + for ns in c.builder.namespaces { + c.generate_namespace(ns)! + } +} + +// Write pack.mcmeta file +fn (mut c Codegen) write_pack_mcmeta() ! { + mcmeta := '{ + "pack": { + "pack_format": 48, + "description": "Generated by MDL compiler" + } +}' + os.write_file(os.join_path(c.output_dir, 'pack.mcmeta'), mcmeta)! +} + +// Generate init function that creates scoreboards +fn (mut c Codegen) generate_init_function() ! { + // Init function goes in output/data//function/ + init_path := os.join_path(c.output_dir, 'data', c.project_name, 'function') + os.mkdir_all(init_path)! + + mut lines := []string{} + lines << '# Initialize scoreboards for ${c.project_name}' + lines << 'scoreboard objectives add ${c.project_name} dummy' + + os.write_file(os.join_path(init_path, 'init.mcfunction'), lines.join('\n'))! +} + +// Generate mcfunction files for a namespace +pub fn (mut c Codegen) generate_namespace(ns IRNamespace) ! { + // Base path: output/data//function// + ns_parts := c.get_ns_parts(ns) + ns_path := os.join_path(c.output_dir, 'data', c.project_name, 'function', ns_parts.join('/')) + os.mkdir_all(ns_path)! + + for fid in ns.functions { + c.generate_function(fid, ns, ns_path)! + } +} + +// Generate mcfunction files for a function +pub fn (mut c Codegen) generate_function(fid FID, ns IRNamespace, ns_path string) ! { + func := c.builder.functions[fid] + + // Create function subdirectory for basic blocks + func_dir := os.join_path(ns_path, func.name) + os.mkdir_all(func_dir)! + + // Generate entry point: ns/func.mcfunction + // This sets up args and calls the entry block + c.generate_function_entry(fid, ns, ns_path)! + + // Generate each basic block: ns/func/block_id.mcfunction + for bbid in func.bbs { + c.generate_basic_block(bbid, ns, func_dir)! + } +} + +// Generate function entry point that calls the entry block +fn (mut c Codegen) generate_function_entry(fid FID, ns IRNamespace, fs_path string) ! { + func := c.builder.functions[fid] + mut lines := []string{} + + ns_parts := c.get_ns_parts(ns) + lines << '# Entry point for ${ns_parts.join(".")}.${func.name}' + + // Call the entry basic block + entry_bb := c.builder.basic_blocks[func.entrybb] + bb_func_path := c.build_func_path(ns, func.name, '${entry_bb.label}_${entry_bb.id}') + + if entry_bb.args.len > 0 { + // Need to pass macro arguments + args_storage := c.build_storage_path(ns, func.name, '\$args') + lines << 'function ${bb_func_path} with storage ${args_storage}' + } else { + lines << 'function ${bb_func_path}' + } + + filepath := os.join_path(fs_path, '${func.name}.mcfunction') + os.write_file(filepath, lines.join('\n'))! +} + +// Generate a single .mcfunction file for a basic block +pub fn (mut c Codegen) generate_basic_block(bbid BBID, ns IRNamespace, func_dir string) ! { + bb := c.builder.basic_blocks[bbid] + + filename := '${bb.label}_${bb.id}.mcfunction' + filepath := os.join_path(func_dir, filename) + + mut lines := []string{} + lines << '# Basic block: ${bb.label} (id: ${bb.id})' + + // Generate commands for each instruction + for iid in bb.insts { + inst_lines := c.generate_instruction(bb.function, bbid, iid, ns) + lines << inst_lines + } + + os.write_file(filepath, lines.join('\n'))! +} + +// Generate mcfunction commands for a single instruction +pub fn (mut c Codegen) generate_instruction(fid FID, bbid BBID, iid IID, ns IRNamespace) []string { + func := c.builder.functions[fid] + inst := func.insts[iid] + + mut lines := []string{} + + match inst { + IRDefine { + lines << c.gen_define(fid, bbid, inst, ns) + } + IRTypedDefine { + lines << c.gen_typed_define(fid, bbid, inst, ns) + } + IRAssign { + lines << c.gen_assign(fid, bbid, inst, ns) + } + IRStore { + lines << c.gen_store(fid, bbid, inst, ns) + } + IRBinaryOp { + lines << c.gen_binary_op(fid, bbid, inst, ns) + } + IRUnaryOp { + lines << c.gen_unary_op(fid, bbid, inst, ns) + } + IRCall { + lines << c.gen_call(fid, bbid, inst, ns) + } + IRJump { + lines << c.gen_jump(fid, bbid, inst, ns) + } + IRBranch { + lines << c.gen_branch(fid, bbid, inst, ns) + } + IRReturn { + lines << c.gen_return(fid, bbid, inst, ns) + } + IRMacroLiteralCmd { + lines << c.gen_macro_cmd(fid, bbid, inst, ns) + } + IRFieldAccess { + lines << c.gen_field_access(fid, bbid, inst, ns) + } + IRIndexAccess { + lines << c.gen_index_access(fid, bbid, inst, ns) + } + IRDeref { + lines << c.gen_deref(fid, bbid, inst, ns) + } + IRRefInst { + lines << c.gen_ref(fid, bbid, inst, ns) + } + IRStructInit { + lines << c.gen_struct_init(fid, bbid, inst, ns) + } + } + + return lines +} + +// ==================== Instruction Generators ==================== + +fn (mut c Codegen) gen_define(fid FID, bbid BBID, inst IRDefine, ns IRNamespace) []string { + mut lines := []string{} + + // Get storage kinds for dest and source + dest_kind := c.get_ref_storage(fid, inst.result) + src_kind := c.get_oid_storage(fid, inst.value) + + match dest_kind { + .register { + dest_player := c.scoreboard_player(fid, inst.result) + match src_kind { + .register { + // register <- register: use operation = + src_player := c.oid_to_score_player(fid, bbid, inst.value, ns) + lines << 'scoreboard players operation ${dest_player} ${c.project_name} = ${src_player} ${c.project_name}' + } + .data { + // register <- data: execute store result score ... run data get + src_path := c.oid_to_storage_path(fid, inst.value, ns) + lines << 'execute store result score ${dest_player} ${c.project_name} run data get storage ${src_path}' + } + .effemeral { + // register <- constant: scoreboard players set + value_cmd := c.oid_to_value_cmd(fid, bbid, inst.value, ns) + lines << 'scoreboard players set ${dest_player} ${c.project_name} ${value_cmd}' + } + } + } + .data { + dest_path := c.storage_path_rid(fid, inst.result, ns) + match src_kind { + .register { + // data <- register: execute store result storage ... run scoreboard players get + src_player := c.oid_to_score_player(fid, bbid, inst.value, ns) + lines << 'execute store result storage ${dest_path} int 1 run scoreboard players get ${src_player} ${c.project_name}' + } + .data { + // data <- data: data modify ... set from storage + value_cmd := c.oid_to_storage_cmd(fid, bbid, inst.value, ns) + lines << 'data modify storage ${dest_path} set ${value_cmd}' + } + .effemeral { + // data <- constant: data modify ... set value + value_cmd := c.oid_to_storage_cmd(fid, bbid, inst.value, ns) + lines << 'data modify storage ${dest_path} set ${value_cmd}' + } + } + } + .effemeral { + lines << '# ephemeral define (no storage)' + } + } + + return lines +} + +fn (mut c Codegen) gen_typed_define(fid FID, bbid BBID, inst IRTypedDefine, ns IRNamespace) []string { + // Typed define just declares, doesn't initialize + return ['# typed define (declaration only)'] +} + +fn (mut c Codegen) gen_assign(fid FID, bbid BBID, inst IRAssign, ns IRNamespace) []string { + mut lines := []string{} + + dest_kind := c.get_ref_storage(fid, inst.result) + src_kind := c.get_oid_storage(fid, inst.value) + + match dest_kind { + .register { + dest_player := c.scoreboard_player(fid, inst.result) + match inst.op { + .assign { + // Handle storage kind conversion for simple assignment + match src_kind { + .register { + src_player := c.oid_to_score_player(fid, bbid, inst.value, ns) + lines << 'scoreboard players operation ${dest_player} ${c.project_name} = ${src_player} ${c.project_name}' + } + .data { + src_path := c.oid_to_storage_path(fid, inst.value, ns) + lines << 'execute store result score ${dest_player} ${c.project_name} run data get storage ${src_path}' + } + .effemeral { + value_cmd := c.oid_to_value_cmd(fid, bbid, inst.value, ns) + lines << 'scoreboard players set ${dest_player} ${c.project_name} ${value_cmd}' + } + } + } + .add_assign { + value_cmd := c.oid_to_value_cmd(fid, bbid, inst.value, ns) + lines << 'scoreboard players add ${dest_player} ${c.project_name} ${value_cmd}' + } + .sub_assign { + value_cmd := c.oid_to_value_cmd(fid, bbid, inst.value, ns) + lines << 'scoreboard players remove ${dest_player} ${c.project_name} ${value_cmd}' + } + .mul_assign, .div_assign, .mod_assign { + other := c.oid_to_score_player(fid, bbid, inst.value, ns) + op_str := match inst.op { + .mul_assign { '*=' } + .div_assign { '/=' } + .mod_assign { '%=' } + else { '=' } + } + lines << 'scoreboard players operation ${dest_player} ${c.project_name} ${op_str} ${other} ${c.project_name}' + } + .swap { + other := c.oid_to_score_player(fid, bbid, inst.value, ns) + lines << 'scoreboard players operation ${dest_player} ${c.project_name} >< ${other} ${c.project_name}' + } + } + } + .data { + dest_path := c.storage_path_rid(fid, inst.result, ns) + match inst.op { + .assign { + // Handle storage kind conversion for simple assignment + match src_kind { + .register { + src_player := c.oid_to_score_player(fid, bbid, inst.value, ns) + lines << 'execute store result storage ${dest_path} int 1 run scoreboard players get ${src_player} ${c.project_name}' + } + .data, .effemeral { + value_cmd := c.oid_to_storage_cmd(fid, bbid, inst.value, ns) + lines << 'data modify storage ${dest_path} set ${value_cmd}' + } + } + } + else { + // Compound assignments on data require loading to scoreboard first + // This should ideally be handled by an IR pass + lines << '# TODO: compound assignment on data storage requires scoreboard temp' + } + } + } + .effemeral { + lines << '# ephemeral assign' + } + } + + return lines +} + +fn (mut c Codegen) gen_store(fid FID, bbid BBID, inst IRStore, ns IRNamespace) []string { + // Store copies from source to result (both are RIDs) + mut lines := []string{} + dest_path := c.storage_path_rid(fid, inst.result, ns) + src_path := c.storage_path_rid(fid, inst.source, ns) + lines << 'data modify storage ${dest_path} set from storage ${src_path}' + return lines +} + +fn (mut c Codegen) gen_binary_op(fid FID, bbid BBID, inst IRBinaryOp, ns IRNamespace) []string { + mut lines := []string{} + func := c.builder.functions[fid] + + // Binary ops on integers use scoreboard operations + result_player := c.scoreboard_player(fid, inst.result) + + // Get storage kinds for operands + left_kind := c.get_oid_storage(fid, inst.left) + right_kind := c.get_oid_storage(fid, inst.right) + + // Handle left operand - load into scoreboard if needed + left_player := c.oid_to_score_player(fid, bbid, inst.left, ns) + match left_kind { + .data { + // Load from storage into scoreboard + left_path := c.oid_to_storage_path(fid, inst.left, ns) + lines << 'execute store result score ${left_player} ${c.project_name} run data get storage ${left_path}' + } + .effemeral { + // Constant - set directly + if inst.left is CID { + cnst := func.consts[inst.left] + if cnst is IRIntConst { + lines << 'scoreboard players set ${left_player} ${c.project_name} ${cnst.value}' + } + } + } + .register { + // Already in scoreboard, nothing to do + } + } + + // Handle right operand - load into scoreboard if needed + right_player := c.oid_to_score_player(fid, bbid, inst.right, ns) + match right_kind { + .data { + // Load from storage into scoreboard + right_path := c.oid_to_storage_path(fid, inst.right, ns) + lines << 'execute store result score ${right_player} ${c.project_name} run data get storage ${right_path}' + } + .effemeral { + // Constant - set directly + if inst.right is CID { + cnst := func.consts[inst.right] + if cnst is IRIntConst { + lines << 'scoreboard players set ${right_player} ${c.project_name} ${cnst.value}' + } + } + } + .register { + // Already in scoreboard, nothing to do + } + } + + // First, copy left to result + lines << 'scoreboard players operation ${result_player} ${c.project_name} = ${left_player} ${c.project_name}' + + // Then apply operation with right + op_str := match inst.op { + .add { '+=' } + .sub { '-=' } + .mul { '*=' } + .div { '/=' } + .mod { '%=' } + .lt { '<' } + .le { '<=' } + .gt { '>' } + .ge { '>=' } + .eq { '=' } + .ne { '=' } // Will need special handling + } + + match inst.op { + .add, .sub, .mul, .div, .mod { + lines << 'scoreboard players operation ${result_player} ${c.project_name} ${op_str} ${right_player} ${c.project_name}' + } + .lt, .le, .gt, .ge, .eq, .ne { + // Comparison - set result to 1 if true, 0 if false + lines << 'scoreboard players set ${result_player} ${c.project_name} 0' + cmp_op := match inst.op { + .lt { '<' } + .le { '<=' } + .gt { '>' } + .ge { '>=' } + .eq { '=' } + .ne { '=' } + else { '=' } + } + if inst.op == .ne { + lines << 'execute unless score ${left_player} ${c.project_name} = ${right_player} ${c.project_name} run scoreboard players set ${result_player} ${c.project_name} 1' + } else { + lines << 'execute if score ${left_player} ${c.project_name} ${cmp_op} ${right_player} ${c.project_name} run scoreboard players set ${result_player} ${c.project_name} 1' + } + } + } + + return lines +} + +fn (mut c Codegen) gen_unary_op(fid FID, bbid BBID, inst IRUnaryOp, ns IRNamespace) []string { + mut lines := []string{} + + result_player := c.scoreboard_player(fid, inst.result) + operand_player := c.oid_to_score_player(fid, bbid, inst.operand, ns) + + match inst.op { + .neg { + // Negate: result = 0 - operand + lines << 'scoreboard players set ${result_player} ${c.project_name} 0' + lines << 'scoreboard players operation ${result_player} ${c.project_name} -= ${operand_player} ${c.project_name}' + } + else { + lines << '# unary op not applicable for scoreboard' + } + } + + return lines +} + +fn (mut c Codegen) gen_call(fid FID, bbid BBID, inst IRCall, ns IRNamespace) []string { + mut lines := []string{} + + called_func := c.builder.functions[inst.function] + called_ns := c.builder.namespaces[called_func.namespace] + + func_path := c.build_func_path(called_ns, called_func.name) + + if inst.args.len == 0 { + // No arguments + lines << 'function ${func_path}' + } else { + // Check if all args are constants (can inline) + mut all_constants := true + for arg_oid in inst.args { + if arg_oid is RID { + all_constants = false + break + } + } + + if all_constants { + // All constants - use inline dict (no 'with' keyword) + mut dict_parts := []string{} + for i, arg_oid in inst.args { + arg_def := called_func.args[i] + value := c.oid_to_nbt_value(fid, bbid, arg_oid, ns) + dict_parts << '${arg_def.name}: ${value}' + } + lines << 'function ${func_path} {${dict_parts.join(", ")}}' + } else { + // Has variables - need to copy to storage first, then call with storage + args_storage := c.build_storage_path(called_ns, called_func.name, '\$args') + + for i, arg_oid in inst.args { + arg_def := called_func.args[i] + arg_path := '${args_storage}.${arg_def.name}' + + match arg_oid { + CID { + // Constant - set value directly + value_cmd := c.oid_to_storage_cmd(fid, bbid, arg_oid, ns) + lines << 'data modify storage ${arg_path} set ${value_cmd}' + } + RID { + // Variable - check if register or data + storage_kind := c.get_ref_storage(fid, arg_oid) + match storage_kind { + .register { + // Register: execute store result storage ... run scoreboard players get + player := c.scoreboard_player(fid, arg_oid) + lines << 'execute store result storage ${arg_path} int 1 run scoreboard players get ${player} ${c.project_name}' + } + .data { + // Data: data modify storage ... set from storage + src_path := c.storage_path_rid(fid, arg_oid, ns) + lines << 'data modify storage ${arg_path} set from storage ${src_path}' + } + .effemeral { + // Ephemeral - shouldn't happen for RID + lines << '# ephemeral arg (unexpected)' + } + } + } + } + } + + lines << 'function ${func_path} with storage ${args_storage}' + } + } + + // Copy result if needed + if result_rid := inst.result { + result_path := c.storage_path_rid(fid, result_rid, ns) + called_result := c.build_storage_path(called_ns, called_func.name, '\$result') + lines << 'data modify storage ${result_path} set from storage ${called_result}' + } + + return lines +} + +// Check if an OID can be inlined directly (constants only, not variables) +fn (mut c Codegen) can_inline_oid(fid FID, oid OID) bool { + func := c.builder.functions[fid] + match oid { + CID { + cnst := func.consts[oid] + // Can inline simple constants + return cnst is IRIntConst || cnst is IRFloatConst || + cnst is IRCharConst || cnst is IRStringConst + } + RID { + // RIDs are variables - cannot inline, need storage + return false + } + } +} + +fn (mut c Codegen) gen_jump(fid FID, bbid BBID, inst IRJump, ns IRNamespace) []string { + mut lines := []string{} + + func := c.builder.functions[fid] + target_bb := c.builder.basic_blocks[inst.target] + target_path := c.build_func_path(ns, func.name, '${target_bb.label}_${target_bb.id}') + + if inst.args.len == 0 { + lines << 'function ${target_path}' + return lines + } + + // Check if all args are constants (can inline) + mut all_constants := true + for arg_oid in inst.args { + if arg_oid is RID { + all_constants = false + break + } + } + + if all_constants { + // All constants - use inline dict (no 'with' keyword) + mut dict_parts := []string{} + for i, arg_oid in inst.args { + arg_name := target_bb.args[i].name + value := c.oid_to_nbt_value(fid, bbid, arg_oid, ns) + dict_parts << '${arg_name}: ${value}' + } + lines << 'function ${target_path} {${dict_parts.join(", ")}}' + } else { + // Has variables - need to copy to storage first, then call with storage + target_name := '${target_bb.label}_${target_bb.id}' + args_storage := c.build_storage_path(ns, func.name, '\$${target_name}', '\$args') + + for i, arg_oid in inst.args { + arg_name := target_bb.args[i].name + arg_path := '${args_storage}.${arg_name}' + + match arg_oid { + CID { + // Constant - set value directly + value_cmd := c.oid_to_storage_cmd(fid, bbid, arg_oid, ns) + lines << 'data modify storage ${arg_path} set ${value_cmd}' + } + RID { + // Variable - check if register or data + storage_kind := c.get_ref_storage(fid, arg_oid) + match storage_kind { + .register { + // Register: execute store result storage ... run scoreboard players get + player := c.scoreboard_player(fid, arg_oid) + lines << 'execute store result storage ${arg_path} int 1 run scoreboard players get ${player} ${c.project_name}' + } + .data { + // Data: data modify storage ... set from storage + src_path := c.storage_path_rid(fid, arg_oid, ns) + lines << 'data modify storage ${arg_path} set from storage ${src_path}' + } + .effemeral { + // Ephemeral - shouldn't happen for RID + lines << '# ephemeral arg (unexpected)' + } + } + } + } + } + + lines << 'function ${target_path} with storage ${args_storage}' + } + + return lines +} + +fn (mut c Codegen) gen_branch(fid FID, bbid BBID, inst IRBranch, ns IRNamespace) []string { + mut lines := []string{} + + func := c.builder.functions[fid] + then_bb := c.builder.basic_blocks[inst.then_bb] + else_bb := c.builder.basic_blocks[inst.else_bb] + + then_path := c.build_func_path(ns, func.name, '${then_bb.label}_${then_bb.id}') + else_path := c.build_func_path(ns, func.name, '${else_bb.label}_${else_bb.id}') + + // Condition is a score (1 = true, 0 = false) + cond_player := c.oid_to_score_player(fid, bbid, inst.cond, ns) + + // Branch based on condition + lines << 'execute if score ${cond_player} ${c.project_name} matches 1.. run function ${then_path}' + lines << 'execute if score ${cond_player} ${c.project_name} matches ..0 run function ${else_path}' + + return lines +} + +fn (mut c Codegen) gen_return(fid FID, bbid BBID, inst IRReturn, ns IRNamespace) []string { + mut lines := []string{} + + func := c.builder.functions[fid] + + if value := inst.value { + // Copy return value to $result (which is always storage) + result_path := c.build_storage_path(ns, func.name, '\$result') + src_kind := c.get_oid_storage(fid, value) + + match src_kind { + .register { + // register -> storage: use execute store + src_player := c.oid_to_score_player(fid, bbid, value, ns) + lines << 'execute store result storage ${result_path} int 1 run scoreboard players get ${src_player} ${c.project_name}' + } + .data, .effemeral { + // data/constant -> storage: use data modify + value_cmd := c.oid_to_storage_cmd(fid, bbid, value, ns) + lines << 'data modify storage ${result_path} set ${value_cmd}' + } + } + } + + // Return is implicit (end of mcfunction) + lines << '# return' + + return lines +} + +fn (mut c Codegen) gen_macro_cmd(fid FID, bbid BBID, inst IRMacroLiteralCmd, ns IRNamespace) []string { + mut lines := []string{} + + // Build the command string with macro substitutions + mut cmd := '\$' // Start with $ for mcfunction macro command + + for part in inst.parts { + match part { + IRMacroCmdText { + cmd += part.text + } + IRMacroCmdMacro { + // Both ref and non-ref use $() substitution for macro commands + // The is_ref flag affects how the value is passed, not the syntax + name := c.get_ref_name(fid, part.value) + cmd += '\$(${name})' + } + IRMacroCmdString { + for str_part in part.parts { + match str_part { + IRStringText { + cmd += str_part.text + } + IRStringMacro { + // Both ref and non-ref use $() substitution + name := c.get_ref_name(fid, str_part.value) + cmd += '\$(${name})' + } + } + } + } + } + } + + lines << cmd + return lines +} + +fn (mut c Codegen) gen_field_access(fid FID, bbid BBID, inst IRFieldAccess, ns IRNamespace) []string { + mut lines := []string{} + src_path := c.storage_path_rid(fid, inst.source, ns) + dest_path := c.storage_path_rid(fid, inst.result, ns) + lines << 'data modify storage ${dest_path} set from storage ${src_path}.${inst.field}' + return lines +} + +fn (mut c Codegen) gen_index_access(fid FID, bbid BBID, inst IRIndexAccess, ns IRNamespace) []string { + mut lines := []string{} + src_path := c.storage_path_rid(fid, inst.source, ns) + dest_path := c.storage_path_rid(fid, inst.result, ns) + + // Index can be constant or variable + index_str := c.oid_to_index_str(fid, bbid, inst.index, ns) + lines << 'data modify storage ${dest_path} set from storage ${src_path}[${index_str}]' + return lines +} + +fn (mut c Codegen) gen_deref(fid FID, bbid BBID, inst IRDeref, ns IRNamespace) []string { + mut lines := []string{} + // Dereference - the source contains a path, copy from that path + src_path := c.storage_path_rid(fid, inst.source, ns) + dest_path := c.storage_path_rid(fid, inst.result, ns) + // This requires indirect access - use macro + lines << '# deref - indirect storage access' + lines << 'data modify storage ${dest_path} set from storage ${src_path}' + return lines +} + +fn (mut c Codegen) gen_ref(fid FID, bbid BBID, inst IRRefInst, ns IRNamespace) []string { + mut lines := []string{} + // Reference - store the path as a string + src_path := c.storage_path_rid(fid, inst.source, ns) + dest_path := c.storage_path_rid(fid, inst.result, ns) + lines << 'data modify storage ${dest_path} set value "${src_path}"' + return lines +} + +fn (mut c Codegen) gen_struct_init(fid FID, bbid BBID, inst IRStructInit, ns IRNamespace) []string { + mut lines := []string{} + dest_path := c.storage_path_rid(fid, inst.result, ns) + + // Initialize struct as compound + mut field_parts := []string{} + for field_name, field_oid in inst.field_values { + value := c.oid_to_nbt_value(fid, bbid, field_oid, ns) + field_parts << '${field_name}: ${value}' + } + lines << 'data modify storage ${dest_path} set value {${field_parts.join(", ")}}' + return lines +} + +// ==================== Helper Functions ==================== + +// Get namespace parts by traversing parent chain +// Returns array like ["root", "sub", "subsub"] (root first) +fn (mut c Codegen) get_ns_parts(ns IRNamespace) []string { + mut parts := []string{} + mut current := ns + for { + parts << current.name + if parent_nid := current.parent { + current = c.builder.namespaces[parent_nid] + } else { + break + } + } + // Reverse to get root first + mut result := []string{} + for i := parts.len - 1; i >= 0; i-- { + result << parts[i] + } + return result +} + +// Build storage path in format: : +// MC storage format is: first:second third.fourth.fifth +fn (mut c Codegen) build_storage_path(ns IRNamespace, components ...string) string { + ns_parts := c.get_ns_parts(ns) + + if ns_parts.len == 0 { + panic('Namespace has no parts') + } + + root_ns := ns_parts[0] + + // Build the dotted part: sub-namespaces + components + mut dotted := []string{} + for i := 1; i < ns_parts.len; i++ { + dotted << ns_parts[i] + } + for comp in components { + dotted << comp + } + + if dotted.len == 0 { + return '${c.project_name}:${root_ns}' + } + return '${c.project_name}:${root_ns} ${dotted.join(".")}' +} + +// Build function call path in format: : +// For function command: function example:macros/simple/command_1 +fn (mut c Codegen) build_func_path(ns IRNamespace, components ...string) string { + ns_parts := c.get_ns_parts(ns) + + // Build the slashed part: all namespace parts + components + mut slashed := []string{} + for part in ns_parts { + slashed << part + } + for comp in components { + slashed << comp + } + + if slashed.len == 0 { + return c.project_name + } + return '${c.project_name}:${slashed.join("/")}' +} + +// Get storage kind for a reference +fn (mut c Codegen) get_ref_storage(fid FID, rid RID) StorageKind { + func := c.builder.functions[fid] + ref := func.refs[rid] + + match ref.value { + VID { + return c.builder.variables[ref.value].storage + } + IRBasicBlockArg { + return ref.value.storage + } + IRFunctionArg { + return ref.value.storage + } + IID { + // Instruction results are registers by default + return .register + } + } +} + +// Get storage kind for an OID (constants are ephemeral/inline, RIDs depend on their ref) +fn (mut c Codegen) get_oid_storage(fid FID, oid OID) StorageKind { + match oid { + CID { + // Constants are inline values, not stored anywhere + return .effemeral + } + RID { + return c.get_ref_storage(fid, oid) + } + } +} + +// Get name for a reference (for macro substitution) +fn (mut c Codegen) get_ref_name(fid FID, rid RID) string { + func := c.builder.functions[fid] + ref := func.refs[rid] + + match ref.value { + VID { + return c.builder.variables[ref.value].name + } + IRBasicBlockArg { + return ref.value.name + } + IRFunctionArg { + return ref.value.name + } + IID { + return '_tmp_${ref.value}' + } + } +} + +// Get scoreboard player name for a reference +fn (mut c Codegen) scoreboard_player(fid FID, rid RID) string { + func := c.builder.functions[fid] + ns := c.builder.namespaces[func.namespace] + ns_parts := c.get_ns_parts(ns) + name := c.get_ref_name(fid, rid) + return '${ns_parts.join("_")}_${func.name}_${name}' +} + +// Get storage path for a reference +// Format: : . +// For function args: : .$args. +fn (mut c Codegen) storage_path_rid(fid FID, rid RID, ns IRNamespace) string { + func := c.builder.functions[fid] + ref := func.refs[rid] + + match ref.value { + IRFunctionArg { + // Function args come from $args storage + return c.build_storage_path(ns, func.name, '\$args', ref.value.name) + } + IRBasicBlockArg { + // BB args come from macro substitution, but their storage is in the BB's $args + return c.build_storage_path(ns, func.name, '\$args', ref.value.name) + } + VID { + return c.build_storage_path(ns, func.name, c.builder.variables[ref.value].name) + } + IID { + return c.build_storage_path(ns, func.name, '_tmp_${ref.value}') + } + } +} + +// Get storage path for an OID (only valid for RIDs with data storage) +fn (mut c Codegen) oid_to_storage_path(fid FID, oid OID, ns IRNamespace) string { + match oid { + CID { + panic('Cannot get storage path for constant') + } + RID { + return c.storage_path_rid(fid, oid, ns) + } + } +} + +// Convert OID to a value for scoreboard set command +fn (mut c Codegen) oid_to_value_cmd(fid FID, bbid BBID, oid OID, ns IRNamespace) string { + func := c.builder.functions[fid] + match oid { + CID { + cnst := func.consts[oid] + match cnst { + IRIntConst { + return '${cnst.value}' + } + else { + return '0' + } + } + } + RID { + // For scoreboard set, we can't use another score directly + // This should be handled by caller + return '0' + } + } +} + +// Convert OID to storage command (value or from) +fn (mut c Codegen) oid_to_storage_cmd(fid FID, bbid BBID, oid OID, ns IRNamespace) string { + func := c.builder.functions[fid] + match oid { + CID { + cnst := func.consts[oid] + match cnst { + IRIntConst { + return 'value ${cnst.value}' + } + IRFloatConst { + return 'value ${cnst.value}d' + } + IRCharConst { + return 'value "${cnst.value}"' + } + IRStringConst { + mut s := '' + for part in cnst.parts { + match part { + IRStringText { s += strip_quotes(part.text) } + IRStringMacro { s += '\$(${c.get_ref_name(fid, part.value)})' } + } + } + return 'value "${s}"' + } + else { + return 'value {}' + } + } + } + RID { + path := c.storage_path_rid(fid, oid, ns) + return 'from storage ${path}' + } + } +} + +// Convert OID to score player for operations +fn (mut c Codegen) oid_to_score_player(fid FID, bbid BBID, oid OID, ns IRNamespace) string { + match oid { + CID { + // Constants need to be loaded into a temp score first + return '#const ${c.project_name}' + } + RID { + return c.scoreboard_player(fid, oid) + } + } +} + +// Convert OID to NBT value for inline use +fn (mut c Codegen) oid_to_nbt_value(fid FID, bbid BBID, oid OID, ns IRNamespace) string { + func := c.builder.functions[fid] + match oid { + CID { + cnst := func.consts[oid] + match cnst { + IRIntConst { return '${cnst.value}' } + IRFloatConst { return '${cnst.value}d' } + IRCharConst { return '"${cnst.value}"' } + IRStringConst { + mut s := '' + for part in cnst.parts { + match part { + IRStringText { s += strip_quotes(part.text) } + IRStringMacro { s += '\$(${c.get_ref_name(fid, part.value)})' } + } + } + return '"${s}"' + } + else { return '{}' } + } + } + RID { + // Can't inline RID directly, would need macro + return '\$(${c.get_ref_name(fid, oid)})' + } + } +} + +// Convert OID to index string for array access +fn (mut c Codegen) oid_to_index_str(fid FID, bbid BBID, oid OID, ns IRNamespace) string { + func := c.builder.functions[fid] + match oid { + CID { + cnst := func.consts[oid] + match cnst { + IRIntConst { return '${cnst.value}' } + else { return '0' } + } + } + RID { + return '\$(${c.get_ref_name(fid, oid)})' + } + } +} + +// Get raw value for reference macro +fn (mut c Codegen) rid_to_macro_ref(fid FID, rid RID, ns IRNamespace) string { + // For &ref, we want the actual storage path + return c.storage_path_rid(fid, rid, ns) +} + +// Strip surrounding quotes from a string if present +fn strip_quotes(s string) string { + if s.len >= 2 && s[0] == `"` && s[s.len - 1] == `"` { + return s[1..s.len - 1] + } + return s +} diff --git a/errors.v b/errors.v new file mode 100644 index 0000000..0fb8fd9 --- /dev/null +++ b/errors.v @@ -0,0 +1,419 @@ +module main + +import os + +// ==================== Source Location ==================== + +pub struct SourceLocation { +pub: + file string + pos int // Byte offset + len int // Span length (for highlighting) +} + +pub fn (loc SourceLocation) is_valid() bool { + return loc.file.len > 0 && loc.pos >= 0 +} + +pub fn empty_location() SourceLocation { + return SourceLocation{ + file: '' + pos: 0 + len: 0 + } +} + +// ==================== Error Severity ==================== + +pub enum Severity { + error // Compilation fails + warning // Compilation continues, user should be aware + hint // Suggestion for improvement +} + +// ==================== Error Kinds ==================== + +pub enum ErrorKind { + // Lexer errors + invalid_character + unterminated_string + invalid_escape_sequence + invalid_number + // Parser errors + unexpected_token + expected_identifier + expected_type + expected_expression + expected_statement + malformed_struct + malformed_function + malformed_namespace + unclosed_delimiter + // Type errors + type_mismatch + invalid_operation + // Definition errors + undefined_variable + undefined_function + undefined_struct + undefined_field + undefined_namespace + // Declaration errors + duplicate_definition + shadowing_not_allowed + // Reference errors + invalid_reference + invalid_dereference + cannot_take_reference_of_constant + // Structure errors + missing_struct_field + unknown_struct_field + // Function errors + wrong_argument_count + wrong_argument_type + missing_return + invalid_return_type + // Namespace errors + namespace_collision + namespace_not_found + circular_import + // IR lowering errors + unsupported_statement_location + invalid_basic_block + empty_macro_chain + cannot_resolve_type + // File errors + file_not_found + file_read_error + // Internal errors + internal_compiler_error + not_implemented +} + +// ==================== Diagnostic Labels ==================== + +pub struct Label { +pub: + location SourceLocation + message string +} + +// ==================== Diagnostic ==================== + +pub struct Diagnostic { +pub: + kind ErrorKind + severity Severity + location SourceLocation + message string + hint string // Optional suggestion + labels []Label // Secondary spans + notes []string // Additional context +} + +// ==================== ANSI Colors ==================== + +struct Colors { + reset string = '\x1b[0m' + bold string = '\x1b[1m' + dim string = '\x1b[2m' + red string = '\x1b[91m' + yellow string = '\x1b[93m' + cyan string = '\x1b[96m' + blue string = '\x1b[94m' + white string = '\x1b[97m' + magenta string = '\x1b[95m' +} + +fn no_colors() Colors { + return Colors{ + reset: '' + bold: '' + dim: '' + red: '' + yellow: '' + cyan: '' + blue: '' + white: '' + magenta: '' + } +} + +// ==================== Error Manager ==================== + +@[heap] +pub struct ErrorManager { +pub mut: + diagnostics []Diagnostic + source_map map[string]string // file path -> source content + error_count int + warning_count int + hint_count int + // Configuration + max_errors int = 50 // Stop after this many errors + colored bool = true // Use ANSI colors + show_hints bool = true // Show hint-level diagnostics +} + +pub fn ErrorManager.new() ErrorManager { + return ErrorManager{ + diagnostics: []Diagnostic{} + source_map: map[string]string{} + } +} + +// Load source file content +pub fn (mut em ErrorManager) load_source(file string) { + if file !in em.source_map { + em.source_map[file] = os.read_file(file) or { '' } + } +} + +// Check if we should stop compilation +pub fn (em &ErrorManager) should_stop() bool { + return em.error_count >= em.max_errors +} + +pub fn (em &ErrorManager) has_errors() bool { + return em.error_count > 0 +} + +fn (em &ErrorManager) colors() Colors { + if em.colored { + return Colors{} + } + return no_colors() +} + +// ==================== Error Reporting Methods ==================== + +pub fn (mut em ErrorManager) report(severity Severity, kind ErrorKind, loc SourceLocation, msg string) { + em.add_diagnostic(severity, kind, loc, msg, '', [], []) +} + +pub fn (mut em ErrorManager) error(kind ErrorKind, loc SourceLocation, msg string) { + em.add_diagnostic(.error, kind, loc, msg, '', [], []) +} + +pub fn (mut em ErrorManager) error_with_hint(kind ErrorKind, loc SourceLocation, msg string, hint string) { + em.add_diagnostic(.error, kind, loc, msg, hint, [], []) +} + +pub fn (mut em ErrorManager) error_with_labels(kind ErrorKind, loc SourceLocation, msg string, labels []Label) { + em.add_diagnostic(.error, kind, loc, msg, '', labels, []) +} + +pub fn (mut em ErrorManager) error_full(kind ErrorKind, loc SourceLocation, msg string, hint string, labels []Label, notes []string) { + em.add_diagnostic(.error, kind, loc, msg, hint, labels, notes) +} + +pub fn (mut em ErrorManager) warning(kind ErrorKind, loc SourceLocation, msg string) { + em.add_diagnostic(.warning, kind, loc, msg, '', [], []) +} + +pub fn (mut em ErrorManager) warning_with_hint(kind ErrorKind, loc SourceLocation, msg string, hint string) { + em.add_diagnostic(.warning, kind, loc, msg, hint, [], []) +} + +pub fn (mut em ErrorManager) hint_msg(kind ErrorKind, loc SourceLocation, msg string) { + em.add_diagnostic(.hint, kind, loc, msg, '', [], []) +} + +fn (mut em ErrorManager) add_diagnostic(severity Severity, kind ErrorKind, loc SourceLocation, msg string, hint string, labels []Label, notes []string) { + match severity { + .error { em.error_count++ } + .warning { em.warning_count++ } + .hint { em.hint_count++ } + } + + em.diagnostics << Diagnostic{ + kind: kind + severity: severity + location: loc + message: msg + hint: hint + labels: labels + notes: notes + } +} + +// ==================== Location Utilities ==================== + +// Convert byte position to line and column (1-indexed) +pub fn (em &ErrorManager) pos_to_location(file string, pos int) (int, int) { + src := em.source_map[file] or { return 1, 1 } + mut line := 1 + mut col := 1 + for i := 0; i < pos && i < src.len; i++ { + if src[i] == `\n` { + line++ + col = 1 + } else { + col++ + } + } + return line, col +} + +// Get the source line containing a position +fn (em &ErrorManager) get_source_line(file string, pos int) (string, int) { + src := em.source_map[file] or { return '', 0 } + if pos >= src.len { + return '', 0 + } + + // Find start of line + mut start := pos + for start > 0 && src[start - 1] != `\n` { + start-- + } + + // Find end of line + mut end := pos + for end < src.len && src[end] != `\n` { + end++ + } + + return src[start..end], start +} + +// ==================== Pretty Printing ==================== + +pub fn (em &ErrorManager) print_all() { + for diag in em.diagnostics { + if diag.severity == .hint && !em.show_hints { + continue + } + em.print_diagnostic(diag) + } + em.print_summary() +} + +fn (em &ErrorManager) print_diagnostic(diag Diagnostic) { + c := em.colors() + + // Get line and column + mut line := 1 + mut col := 1 + if diag.location.is_valid() { + line, col = em.pos_to_location(diag.location.file, diag.location.pos) + } + + // Header line: error[kind]: message + severity_str, severity_color := match diag.severity { + .error { 'error', c.red } + .warning { 'warning', c.yellow } + .hint { 'hint', c.cyan } + } + + println('${c.bold}${severity_color}${severity_str}${c.reset}${c.bold}[${diag.kind}]${c.reset}: ${diag.message}') + + // Location line: --> file:line:col + if diag.location.is_valid() { + println(' ${c.blue}-->${c.reset} ${diag.location.file}:${line}:${col}') + + // Source context + em.print_source_context(diag, line, col, severity_color) + } + + // Print labels (secondary spans) + for label in diag.labels { + if label.location.is_valid() { + l_line, l_col := em.pos_to_location(label.location.file, label.location.pos) + println(' ${c.blue}:${c.reset}') + println(' ${c.blue}= note${c.reset}: ${label.message}') + println(' ${c.blue}-->${c.reset} ${label.location.file}:${l_line}:${l_col}') + + // Print source context for label + em.print_label_context(label, l_line, l_col) + } + } + + // Print hint + if diag.hint.len > 0 { + println(' ${c.blue}=${c.reset} ${c.cyan}hint${c.reset}: ${diag.hint}') + } + + // Print notes + for note in diag.notes { + println(' ${c.blue}= note${c.reset}: ${note}') + } + + println('') +} + +fn (em &ErrorManager) print_source_context(diag Diagnostic, line int, col int, severity_color string) { + c := em.colors() + src_line, _ := em.get_source_line(diag.location.file, diag.location.pos) + if src_line.len == 0 { + return + } + + line_num_width := max_int('${line}'.len, 3) + + // Empty line with gutter + gutter := ' '.repeat(line_num_width) + println('${c.blue}${gutter} |${c.reset}') + + // Source line with line number + line_str := str_rjust('${line}', line_num_width) + println('${c.blue}${line_str} |${c.reset} ${src_line}') + + // Underline with carets + span_len := if diag.location.len > 0 { diag.location.len } else { 1 } + padding := ' '.repeat(col - 1) + underline := '^'.repeat(span_len) + println('${c.blue}${gutter} |${c.reset} ${padding}${severity_color}${underline}${c.reset}') +} + +fn (em &ErrorManager) print_label_context(label Label, line int, col int) { + c := em.colors() + src_line, _ := em.get_source_line(label.location.file, label.location.pos) + if src_line.len == 0 { + return + } + + line_num_width := max_int('${line}'.len, 3) + gutter := ' '.repeat(line_num_width) + + println('${c.blue}${gutter} |${c.reset}') + line_str := str_rjust('${line}', line_num_width) + println('${c.blue}${line_str} |${c.reset} ${src_line}') + + span_len := if label.location.len > 0 { label.location.len } else { 1 } + padding := ' '.repeat(col - 1) + underline := '-'.repeat(span_len) + println('${c.blue}${gutter} |${c.reset} ${padding}${c.blue}${underline}${c.reset}') +} + +fn (em &ErrorManager) print_summary() { + c := em.colors() + if em.error_count > 0 { + mut parts := []string{} + if em.error_count > 0 { + s := if em.error_count == 1 { '' } else { 's' } + parts << '${em.error_count} error${s}' + } + if em.warning_count > 0 { + s := if em.warning_count == 1 { '' } else { 's' } + parts << '${em.warning_count} warning${s}' + } + println('${c.bold}${c.red}error${c.reset}: could not compile due to ${parts.join(', ')}') + } else if em.warning_count > 0 { + s := if em.warning_count == 1 { '' } else { 's' } + println('${c.bold}${c.yellow}warning${c.reset}: ${em.warning_count} warning${s} generated') + } +} + +// ==================== Helpers ==================== + +fn max_int(a int, b int) int { + return if a > b { a } else { b } +} + +fn str_rjust(s string, width int) string { + if s.len >= width { + return s + } + return ' '.repeat(width - s.len) + s +} diff --git a/example.mcf b/example.mcf deleted file mode 100644 index a0f9b7a..0000000 --- a/example.mcf +++ /dev/null @@ -1,40 +0,0 @@ -// file1.mcf -namespace string = { - data ascii_lut := ['\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\t', '\n', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0',... etc ]; - reg counter := 0; - - fn cat(eff a: String,eff b:String) : String { - counter += 1; - return ""; - } - - // effemeral values cannot have identification chains, they only exist for the lifetime of their block. as such they are soely an identifier for a macro - fn lookup_ascii(reg index: Int, data dest: &String) : Void { - eff idx := ; - = ascii_lut[idx]; - } -} - -data output := ""; - -fn add_token(reg id: Int):Void{ - data new_token := ""; - string/lookup_ascii(id, &new_token); - string/cat(output,new_token); -} - -fn get_counter() : Int{ - return string.counter; -} - - -// file2.mcf -namespace dinosaur; -namespace work = "file1.mcf" - -reg new_counter := work/get_counter(); -reg counter_other := work.string.counter; - -// file3.mcf -namespace file2 = "file2.mcf" -data tmp := file2.dinosaur.work.output diff --git a/ir.v b/ir.v index 628aa3d..ca223a3 100644 --- a/ir.v +++ b/ir.v @@ -63,13 +63,13 @@ type BBID = int // Also need to determine pub struct IRBasicBlock { pub mut: - label string - id BBID - namespace NID - function FID - args []IRBasicBlockArg - insts []IID - local_defs map[string]RID // Values that are created in this block + label string + id BBID + namespace NID + function FID + args []IRBasicBlockArg + insts []IID + vars_map map[string]RID // Variable name -> RID mapping for this block // Relationships dominator ?BBID @@ -100,18 +100,30 @@ pub type IRInstruction = IRMacroLiteralCmd | IRBinaryOp | IRUnaryOp | IRAssign + | IRStore + | IRCall + | IRStructInit + | IRFieldAccess + | IRIndexAccess + | IRDeref + | IRRefInst + | IRJump + | IRBranch + | IRReturn pub struct IRDefine { pub mut: id IID result RID value OID + pos int // Source position for error reporting } pub struct IRTypedDefine { pub mut: id IID result RID + pos int // Source position for error reporting } pub enum AssignOp { @@ -124,12 +136,25 @@ pub enum AssignOp { swap } +pub fn (op AssignOp) print() { + match op { + .assign { print(' = ') } + .add_assign { print(' += ') } + .sub_assign { print(' -= ') } + .mul_assign { print(' *= ') } + .div_assign { print(' /= ') } + .mod_assign { print(' %= ') } + .swap { print(' >< ') } + } +} + pub struct IRAssign { pub mut: id IID result RID op AssignOp value OID + pos int // Source position for error reporting } pub struct IRStore { @@ -137,6 +162,7 @@ pub mut: id IID result RID source RID + pos int // Source position for error reporting } pub enum BinaryOp { @@ -160,6 +186,7 @@ pub mut: op BinaryOp left OID right OID + pos int // Source position for error reporting } pub enum UnaryOp { @@ -174,6 +201,7 @@ pub mut: result RID op UnaryOp operand OID + pos int // Source position for error reporting } pub struct IRCall { @@ -182,6 +210,7 @@ pub mut: result ?RID function FID args []OID + pos int // Source position for error reporting } pub struct IRStructInit { @@ -190,13 +219,16 @@ pub mut: result RID struct_type SID field_values map[string]OID + pos int // Source position for error reporting } pub struct IRFieldAccess { +pub mut: id IID result RID source RID field string + pos int // Source position for error reporting } pub struct IRIndexAccess { @@ -207,6 +239,7 @@ pub mut: index OID is_slice bool end ?OID + pos int // Source position for error reporting } pub struct IRDeref { @@ -214,6 +247,16 @@ pub mut: id IID result RID source RID + pos int // Source position for error reporting +} + +// Takes the address/reference of a value +pub struct IRRefInst { +pub mut: + id IID + result RID + source RID + pos int // Source position for error reporting } pub struct IRJump { @@ -221,6 +264,7 @@ pub mut: id IID target BBID args []OID + pos int // Source position for error reporting } pub struct IRBranch { @@ -231,12 +275,14 @@ pub mut: then_args []OID else_bb BBID else_args []OID + pos int // Source position for error reporting } pub struct IRReturn { pub mut: id IID value ?OID + pos int // Source position for error reporting } // dunno @@ -249,6 +295,7 @@ pub mut: func FID id IID parts []IRMacroCmdPart + pos int // Source position for error reporting } pub type IRMacroCmdPart = IRMacroCmdText | IRMacroCmdMacro | IRMacroCmdString @@ -319,6 +366,7 @@ pub mut: storage StorageKind location IRLocation // where it is stored (This is our general reference thing) is_macro_dep bool // If it is the result of a macro call + pos int // Source position for error reporting } type RID = int @@ -329,6 +377,7 @@ pub mut: id RID value IRRefSum typ IRType + pos int // Source position for error reporting } // IR OPERAND @@ -360,8 +409,8 @@ pub fn (id OID) to_ir_type(f IRFunction) IRType { IRDictConst { return BuiltinType.dict_t } - else { - panic('Type not implemented in oid to ir type ${f.consts[id]}') + IRRangeConst { + return BuiltinType.list_t // Ranges are list-like } } } @@ -451,15 +500,27 @@ pub mut: basic_blocks []IRBasicBlock structs []IRStructDef variables []IRValue + errors &ErrorManager = unsafe { nil } // Error manager for reporting } -pub fn (mut b IRBuilder) solve_namespaces() { +pub fn (mut b IRBuilder) solve_namespaces() bool { mut ns_solver := NSSolver{} for file in b.files { - ns_solver.solve(file) or { panic('Could not solve') } + ns_solver.solve(file) or { + if b.errors != unsafe { nil } { + b.errors.error(.namespace_not_found, SourceLocation{ + file: file + pos: 0 + }, 'could not resolve namespace from file: ${file}') + } + return false + } } if !ns_solver.verify_legal() { - panic('Could not solve the namespaces... look at that') + if b.errors != unsafe { nil } { + b.errors.error(.namespace_collision, empty_location(), 'could not solve namespaces: circular dependency or naming conflict detected') + } + return false } mut s2b := map[NSNodeId]NID{} mut b2s := map[NID]NSNodeId{} @@ -480,7 +541,10 @@ pub fn (mut b IRBuilder) solve_namespaces() { } if b.namespaces.len == 0 { - panic('There is no namespaces?') + if b.errors != unsafe { nil } { + b.errors.error(.namespace_not_found, empty_location(), 'no namespaces found in source files') + } + return false } mut visited := map[NID]bool{} @@ -513,6 +577,7 @@ pub fn (mut b IRBuilder) solve_namespaces() { visited[nid] = true } + return true } pub fn (mut b IRBuilder) tranverse_namespace(nsa IRNamespace, path []string) ?NID { @@ -527,47 +592,31 @@ pub fn (mut b IRBuilder) tranverse_namespace(nsa IRNamespace, path []string) ?NI } pub fn (mut b IRBuilder) namespace_yeild_ir_type(ns IRNamespace, typ Type) ?IRType { - mut ft := typ - mut it := IRType{} - mut ctr := 0 - for mut ft is ReferenceType { - ft = ft.base - ctr++ - } match typ { BuiltinType { - it = IRType(typ as BuiltinType) - for _ in 0 .. ctr { - it = IRType(IRRefType{ - base: it - }) - } - return it + return IRType(typ) + } + ReferenceType { + // Recursively resolve the base type and wrap in IRRefType + base_ir := b.namespace_yeild_ir_type(ns, typ.base) or { return none } + return IRType(IRRefType{ + base: base_ir + }) } StructType { - sft := (typ as StructType) - ns_path := sft.name.to_list() + ns_path := typ.name.to_list() nnid := b.tranverse_namespace(ns, ns_path) or { - println('Could to traverse from ${ns.name} along ${ns_path} for type conversion ${typ} ') + println('Could not traverse from ${ns.name} along ${ns_path} for type conversion ${typ}') return none } // Need to check if name is a struct in that namespace - it = b.namespaces[nnid].struct_map[sft.name.name.name] or { - println('struct is not foundd in that namespace to use as a field') + sid := b.namespaces[nnid].struct_map[typ.name.name.name] or { + println('struct is not found in that namespace to use as a field') return none } - for _ in 0 .. ctr { - it = IRType(IRRefType{ - base: it - }) - } - return it - } - else { - panic('unreachable') + return IRType(sid) } } - return none } pub fn (mut b IRBuilder) namespace_extract_structs() bool { @@ -612,7 +661,13 @@ pub fn (mut b IRBuilder) namespace_extract_structs() bool { for ast_s in ast_structs { astpos := ast_s.Node.pos - mut s := &b.structs[smap[astpos]] or { panic('FUCK (IR STRUCT THING)') } + mut s := &b.structs[smap[astpos]] or { + if b.errors != unsafe { nil } { + b.errors.error(.internal_compiler_error, empty_location(), 'internal error: struct not found in mapping during field resolution') + } + result = false + continue + } for f in ast_s.fields { mut ft := f.field_type @@ -655,7 +710,14 @@ pub fn (mut b IRBuilder) namespace_extract_structs() bool { s.fields[f.name.name] = it } else { - panic('unreachable type ${ft}') + if b.errors != unsafe { nil } { + b.errors.error(.cannot_resolve_type, SourceLocation{ + file: '' + pos: ast_s.Node.pos + }, 'unsupported type in struct field: ${ft}') + } + result = false + continue } } } @@ -742,7 +804,13 @@ pub fn (mut b IRBuilder) namespace_extract_namesapce_defs() bool { } } else { - panic('unreachable effemeral as namespace value') + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, SourceLocation{ + pos: stmt.Node.pos + }, 'ephemeral storage is not allowed at namespace scope') + } + result = false + continue } } } @@ -810,7 +878,9 @@ pub fn (mut b IRBuilder) stage1() bool { // to just limit the scope of the problem so that it becomes easier to work // with. To start we are going to break up all the namespaces and make good // references for all them to each other. - b.solve_namespaces() + if !b.solve_namespaces() { + return false + } // Next we are going to pull out all the function definitions, struct // declarations, for all the namespaces. We have to start with the structs @@ -836,19 +906,19 @@ pub fn (mut b IRBuilder) link_bb(self BBID, child BBID) { } pub fn (mut b IRBuilder) create_bb(fid FID, label string) BBID { - nid := b.functions[fid].namespace - bb := IRBasicBlock{ - label: label - id: b.basic_blocks.len - namespace: nid - function: fid - is_sealed: false - is_filled: false - } - bbid := bb.id - b.basic_blocks << bb - b.functions[fid].bbs << bbid - return bbid + nid := b.functions[fid].namespace + bb := IRBasicBlock{ + label: label + id: b.basic_blocks.len + namespace: nid + function: fid + is_sealed: false + is_filled: false + } + bbid := bb.id + b.basic_blocks << bb + b.functions[fid].bbs << bbid + return bbid } pub fn (mut b IRBuilder) literal_has_macro(l Literal) bool { @@ -1095,36 +1165,45 @@ pub fn (mut b IRBuilder) bb_build_bb_cfg(start BBID, stmts []Stmt) (bool, BBID) println('Function blocks are not allowed to have namespace aliases in them ${stmt}') } FunctionInlineDefinition { - println('TODO FunctionInlineDefinition') - panic('Not handled') + if b.errors != unsafe { nil } { + b.errors.error(.not_implemented, SourceLocation{ + pos: stmt.Node.pos + }, 'inline function definitions are not yet implemented') + } + result = false } Block { - panic('Block in block??') + if b.errors != unsafe { nil } { + b.errors.error(.unsupported_statement_location, SourceLocation{ + pos: stmt.Node.pos + }, 'bare block statements are not allowed inside function bodies') + } + result = false } IfStmt { // Both the condition and of course the blocks... will create blocks if b.expr_needs_block(stmt.condition) { - cond := b.add_bb(fid, 'if_cond') - b.bb_link(current, cond) + cond := b.create_bb(fid, 'if_cond') + b.link_bb(current, cond) current = cond } b.basic_blocks[current].stmts << stmt // create then else and merge block - mut then := b.add_bb(fid, 'if_then') - b.bb_link(current, then) + mut then := b.create_bb(fid, 'if_then') + b.link_bb(current, then) state, then = b.bb_build_bb_cfg(then, stmt.then_block.stmts) result = result && state - mut el := b.add_bb(fid, 'if_else') - b.bb_link(current, el) + mut el := b.create_bb(fid, 'if_else') + b.link_bb(current, el) if stmt.else_block == none { } else { elb := stmt.else_block state, el = b.bb_build_bb_cfg(el, elb.stmts) result = result && state } - merge := b.add_bb(fid, 'if_merge') - b.bb_link(then, merge) - b.bb_link(el, merge) + merge := b.create_bb(fid, 'if_merge') + b.link_bb(then, merge) + b.link_bb(el, merge) current = merge } Return { @@ -1134,16 +1213,16 @@ pub fn (mut b IRBuilder) bb_build_bb_cfg(start BBID, stmts []Stmt) (bool, BBID) } if b.expr_needs_block(ret) { - ret_bbid := b.add_bb(fid, 'ret') - b.bb_link(current, ret_bbid) + ret_bbid := b.create_bb(fid, 'ret') + b.link_bb(current, ret_bbid) current = ret_bbid } b.basic_blocks[current].stmts << stmt } ExprStmt { if b.expr_needs_block(stmt.expr) { - emac := b.add_bb(fid, 'macro') - b.bb_link(current, emac) + emac := b.create_bb(fid, 'macro') + b.link_bb(current, emac) current = emac } b.basic_blocks[current].stmts << stmt @@ -1162,32 +1241,32 @@ pub fn (mut b IRBuilder) bb_build_bb_cfg(start BBID, stmts []Stmt) (bool, BBID) } } if need_block { - command := b.add_bb(fid, 'command') - b.bb_link(current, command) + command := b.create_bb(fid, 'command') + b.link_bb(current, command) current = command } b.basic_blocks[current].stmts << stmt } Define { if b.expr_needs_block(stmt.value) { - macro_use := b.add_bb(fid, 'macro_use') - b.bb_link(current, macro_use) + macro_use := b.create_bb(fid, 'macro_use') + b.link_bb(current, macro_use) current = macro_use } b.basic_blocks[current].stmts << stmt } Assignment { if b.expr_needs_block(stmt.left) || b.expr_needs_block(stmt.right) { - assignment := b.add_bb(fid, 'assignment') - b.bb_link(current, assignment) + assignment := b.create_bb(fid, 'assignment') + b.link_bb(current, assignment) current = assignment } b.basic_blocks[current].stmts << stmt } Store { if b.expr_needs_block(stmt.left) || b.expr_needs_block(stmt.right) { - store := b.add_bb(fid, 'store') - b.bb_link(current, store) + store := b.create_bb(fid, 'store') + b.link_bb(current, store) current = store } b.basic_blocks[current].stmts << stmt @@ -1197,46 +1276,150 @@ pub fn (mut b IRBuilder) bb_build_bb_cfg(start BBID, stmts []Stmt) (bool, BBID) return result, current } - - -// TODO: implement +// Build CFG recursively, creating new basic blocks when macro values are used. +// Each basic block maps to a .mcfunction file - when macros are used, we need +// a new function file to pass the macro arguments. pub fn (mut b IRBuilder) build_cfg_recursive(fid FID, current_bb BBID, stmts []Stmt) ?(bool, BBID) { mut bb := current_bb for stmt in stmts { - match stmt{ + match stmt { + // TypedDefine: No value expression, no macro possible - just attach TypedDefine { b.basic_blocks[bb].stmts << stmt } + // Define: Check if RHS has/is a macro + Define { + if b.expr_has_macro(stmt.value) { + def_bb := b.create_bb(fid, 'define_macro') + b.link_bb(bb, def_bb) + b.basic_blocks[bb].is_sealed = true + bb = def_bb + } + b.basic_blocks[bb].stmts << stmt + } + // Assignment: Check both sides for macros + Assignment { + if b.expr_has_macro(stmt.left) || b.expr_has_macro(stmt.right) { + assign_bb := b.create_bb(fid, 'assign_macro') + b.link_bb(bb, assign_bb) + b.basic_blocks[bb].is_sealed = true + bb = assign_bb + } + b.basic_blocks[bb].stmts << stmt + } + // Store: Same as assignment + Store { + if b.expr_has_macro(stmt.left) || b.expr_has_macro(stmt.right) { + store_bb := b.create_bb(fid, 'store_macro') + b.link_bb(bb, store_bb) + b.basic_blocks[bb].is_sealed = true + bb = store_bb + } + b.basic_blocks[bb].stmts << stmt + } + // ExprStmt: Expression that may contain macros + ExprStmt { + if b.expr_has_macro(stmt.expr) { + expr_bb := b.create_bb(fid, 'expr_macro') + b.link_bb(bb, expr_bb) + b.basic_blocks[bb].is_sealed = true + bb = expr_bb + } + b.basic_blocks[bb].stmts << stmt + } + // IfStmt: Creates branches in CFG + IfStmt { + // Check if condition has macro + if b.expr_has_macro(stmt.condition) { + cond_bb := b.create_bb(fid, 'if_cond') + b.link_bb(bb, cond_bb) + b.basic_blocks[bb].is_sealed = true + bb = cond_bb + } + + // Store the if statement (condition evaluation) in current block + b.basic_blocks[bb].stmts << stmt + b.basic_blocks[bb].is_sealed = true + + // Create then branch + then_bb := b.create_bb(fid, 'if_then') + b.link_bb(bb, then_bb) + _, then_end := b.build_cfg_recursive(fid, then_bb, stmt.then_block.stmts)? - // Need to add basic blocks for anything that can have a macro. This - // breaks down to each expression. So we're going to have to handle - // all of the those things. I think that this should be formed into - // one big expression run through that will generate the basic - // blocks per expression... or like if the expression needs a macro - // then it creates a basic block. + // Create else branch + else_bb := b.create_bb(fid, 'if_else') + b.link_bb(bb, else_bb) + mut else_end := else_bb + if else_block := stmt.else_block { + _, else_end = b.build_cfg_recursive(fid, else_bb, else_block.stmts)? + } else { + b.basic_blocks[else_bb].is_sealed = true + } + + // Create merge point + merge_bb := b.create_bb(fid, 'if_merge') + b.link_bb(then_end, merge_bb) + b.link_bb(else_end, merge_bb) + + bb = merge_bb + } + // Return: Terminal instruction Return { + // Check if return value has macro + if val := stmt.value { + if b.expr_has_macro(val) { + ret_bb := b.create_bb(fid, 'return_eval') + b.link_bb(bb, ret_bb) + b.basic_blocks[bb].is_sealed = true + bb = ret_bb + } + } b.basic_blocks[bb].stmts << stmt b.basic_blocks[bb].is_sealed = true return true, bb } + // MacroLiteralCommand: $ commands with potential macros + MacroLiteralCommand { + mut has_macro := false - StructDefinition, FunctionDefinitino, NamespaceDefinition, NamespaceImport, NamespaceAlias, FunctionInlineDefinition, Block { - println("Error ${stmt} is not allowed in a basic_block_body") - return none - } + for part in stmt.parts { + if part is MacroLiteralMacro { + has_macro = true + break + } + if part is MacroLiteralString { + // String could have interpolation with macros + if b.literal_has_macro(Literal(part.str_literal)) { + has_macro = true + break + } + } + } - else{ - println("Error: Unhandled statement ${stmt}") + if has_macro { + cmd_bb := b.create_bb(fid, 'command') + b.link_bb(bb, cmd_bb) + b.basic_blocks[bb].is_sealed = true + bb = cmd_bb + } + b.basic_blocks[bb].stmts << stmt + } + // Invalid statements in function bodies + StructDefinition, FunctionDefinition, NamespaceDefinition, NamespaceImport, + NamespaceAlias, FunctionInlineDefinition, Block { + println('Error: ${stmt} not allowed in function body') return none } } } - return none + + b.basic_blocks[bb].is_sealed = true + return true, bb } -pub fun (mut b IRBuilder) s2_phase1_build_cfg_skeleton(fid FID) bool { +pub fn (mut b IRBuilder) s2_phase1_build_cfg_skeleton(fid FID) bool { func := b.functions[fid] // Create our entry block @@ -1245,17 +1428,19 @@ pub fun (mut b IRBuilder) s2_phase1_build_cfg_skeleton(fid FID) bool { _, end_bb := b.build_cfg_recursive(fid, entry_bb, func.block.stmts) or { // TODO: change this to be a proper error - println("We could not build the CFG for ${func.name}") + println('We could not build the CFG for ${func.name}') return false } // Adding a return here, so that we will always have a return at the end of our functions, so we just like. know that b.basic_blocks[entry_bb].is_sealed = true if b.basic_blocks[end_bb].insts.len == 0 { - b.basic_blocks[end_bb].stmts << Stmt(Return{value : none}) + b.basic_blocks[end_bb].stmts << Stmt(Return{ + value: none + }) } - return true; + return true } // TODO: add the error classes so we can accumulate errors and print out helpfully. @@ -1284,21 +1469,22 @@ pub fn (mut b IRBuilder) stage2() bool { // needed to be parsed for each basic block into each basic block so that it // will be easier to handle later. - // Here we will create the basic blocks for a function + // Phase 1: Build CFG skeletons for all functions for fid in 0 .. b.functions.len { - // Build the base - if !b.s2_phase1_build_cfg_skeleton(fid){ + if !b.s2_phase1_build_cfg_skeleton(fid) { return false } + } - // TODO: Compute the dominance - // TODO: Solve arguments - // TODO: Lower instructions - // TODO: Add terminators - + // Phase 2: Solve basic block arguments (for macro value passing) + for fid in 0 .. b.functions.len { + b.solve_bb_arguments(fid) } - // Then we are going to generate the instructions for our blocks + // Phase 3: Lower instructions and add terminators + for fid in 0 .. b.functions.len { + b.fill_function_insts(fid) + } return result } @@ -1327,20 +1513,30 @@ pub fn (mut b IRBuilder) lower(files []string) !bool { return false } - // Next we are going to check that there is no overlap between names within any namespace + // Run semantic analysis after IR is built + mut checker := SemanticChecker.new(&b) + if !checker.check() { + // Errors are collected in the shared ErrorManager, main will print them + return false + } + return true } pub fn (mut b IRBuilder) lower_namespace_alias(nsa NamespaceAlias) { } -pub fn (mut b IRBuilder) lower_stmt(stmt Stmt) { +pub fn (mut b IRBuilder) lower_stmt(stmt Stmt) bool { match stmt { NamespaceAlias { b.lower_namespace_alias(stmt) + return true } else { - panic('Statement is not implemented ${stmt}') + if b.errors != unsafe { nil } { + b.errors.error(.not_implemented, empty_location(), 'statement lowering not implemented for: ${stmt}') + } + return false } } } diff --git a/ir_instruction_gen.v b/ir_instruction_gen.v index 28c1702..666648c 100644 --- a/ir_instruction_gen.v +++ b/ir_instruction_gen.v @@ -10,17 +10,17 @@ pub fn (mut b IRBuilder) bb_get_reference(name string, bbid BBID, fid FID) (bool // check if its in our arguments if name in bb.vars_map { - return true, fun.refs[bb.vars_map[name]] + return true, b.functions[fid].refs[bb.vars_map[name]] } for arg in bb.args { if arg.name == name { ref := IRRef{ - id: fun.refs.len + id: b.functions[fid].refs.len value: arg typ: arg.typ } - fun.refs << ref - bb.vars_map[name] = ref.id + b.functions[fid].refs << ref + b.basic_blocks[bbid].vars_map[name] = ref.id return true, ref } } @@ -35,7 +35,7 @@ pub fn (mut b IRBuilder) bb_get_reference(name string, bbid BBID, fid FID) (bool // check if its defined in any predecesor blocks // who exist in the same scope TODO for !to_visit.is_empty() { - new_id := to_visit.pop() or { panic("we really thought we'd have something lol") } + new_id := to_visit.pop() or { continue } if new_id in visited { continue } @@ -43,7 +43,7 @@ pub fn (mut b IRBuilder) bb_get_reference(name string, bbid BBID, fid FID) (bool if name in new.vars_map { ref := new.vars_map[name] b.basic_blocks[bbid].vars_map[name] = ref - return true, fun.refs[ref] + return true, b.functions[fid].refs[ref] } visited[new_id] = true for pred in new.predecessors { @@ -54,22 +54,23 @@ pub fn (mut b IRBuilder) bb_get_reference(name string, bbid BBID, fid FID) (bool // check if its in our owning namespace if name in ns.variable_map { ref := IRRef{ - id: fun.refs.len + id: b.functions[fid].refs.len value: IRRefSum(ns.variable_map[name]) } - fun.refs << ref + b.functions[fid].refs << ref b.basic_blocks[bbid].vars_map[name] = ref.id return true, ref } // check if its in our function's arguments - for arg in fun.args { + for arg in b.functions[fid].args { if arg.name == name { ref := IRRef{ - id: fun.refs.len + id: b.functions[fid].refs.len value: arg + typ: arg.typ } - fun.refs << ref + b.functions[fid].refs << ref b.basic_blocks[bbid].vars_map[name] = ref.id return true, ref } @@ -114,7 +115,7 @@ pub fn (mut b IRBuilder) bb_get_reference_no_creation(name string, bbid BBID, fi // check if its defined in any predecesor blocks // who exist in the same scope TODO for !to_visit.is_empty() { - new_id := to_visit.pop() or { panic("we really thought we'd have something lol") } + new_id := to_visit.pop() or { continue } if new_id in visited { continue } @@ -228,13 +229,17 @@ pub fn (mut b IRBuilder) bb_add_value_ast(bbid BBID, name string, typ Type, sour fid := bb.function func := b.functions[fid] ns := b.namespaces[func.namespace] - mut val := IRValue{ - name: name - id: b.variables.len - typ: b.namespace_yeild_ir_type(ns, typ) or { - panic('could not find type for typed define') + ir_typ := b.namespace_yeild_ir_type(ns, typ) or { + if b.errors != unsafe { nil } { + b.errors.error(.cannot_resolve_type, empty_location(), "could not resolve type for '${name}'") } - storage: source.to_ir() + return VID(-1) + } + mut val := IRValue{ + name: name + id: b.variables.len + typ: ir_typ + storage: source.to_ir() location: match source { .effemeral { IRLocation(IREffLocation{ @@ -259,67 +264,178 @@ pub fn (mut b IRBuilder) bb_add_value_ast(bbid BBID, name string, typ Type, sour return val.id } -// returns if its a refered macro, and the RID for accessing it! +// Handle macro expressions with full identifier chain support including nesting. +// Returns (is_referable, RID) - the reference ID can be used to access the macro value. +// Each element in the chain is processed, creating intermediate instructions as needed. pub fn (mut b IRBuilder) bb_handle_macro_expr(bbid BBID, mexpr MacroExpr) (bool, RID) { refed := mexpr.referable fid := b.basic_blocks[bbid].function - // TODO: Implement going down the identifier chain -> will have to make macro expasions and such.... - // which will create more basic blocks. chain := mexpr.ident_chain - if chain.elements.len > 1 { - panic('Not implemented identifier chains yet...') + + if chain.elements.len == 0 { + if b.errors != unsafe { nil } { + b.errors.error(.empty_macro_chain, empty_location(), 'empty macro expression chain') + } + return false, RID(-1) } - e := chain.elements[0] - match e { + + // Process the first element to get the base + mut current_rid := RID(-1) + mut current_type := IRType(BuiltinType.void_t) + + first := chain.elements[0] + match first { IdentifierChainName { - ok, ref := b.bb_get_reference(e.name.name, bbid, fid) + ok, ref := b.bb_get_reference(first.name.name, bbid, fid) if !ok { - panic('could not find value to macro expand in ${mexpr}') + if b.errors != unsafe { nil } { + b.errors.error_with_hint(.undefined_variable, SourceLocation{ + file: '' + pos: first.name.pos + len: first.name.name.len + }, "undefined variable '${first.name.name}' in macro expression", 'ensure the variable is defined before using it in a macro') + } + return false, RID(-1) } + // Check if already a BB argument match ref.value { IRBasicBlockArg { if ref.value.id == bbid { - // we don't have to add it as something that we want, someone has already done this for us. - return refed, ref.id + current_rid = ref.id + current_type = ref.typ + } else { + // Add as BB argument + current_rid = b.bb_add_macro_arg(bbid, fid, first.name.name, ref) + current_type = ref.typ } } - else {} + else { + // Add as BB argument + current_rid = b.bb_add_macro_arg(bbid, fid, first.name.name, ref) + current_type = ref.typ + } } + } + IdentifierChainMacro { + // Nested macro at start - recursively handle + _, nested_rid := b.bb_handle_macro_expr(bbid, first.macro_expr) + current_rid = nested_rid + current_type = b.functions[fid].refs[nested_rid].typ + } + IdentifierChainIndex { + // Index at start doesn't make sense without a base + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, empty_location(), 'cannot start macro chain with index access') + } + return false, RID(-1) + } + IdentifierChainDeref { + // Deref at start doesn't make sense without a base + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, empty_location(), 'cannot start macro chain with dereference') + } + return false, RID(-1) + } + } - // It is not an argument to ourself... We need to ask for this thing. - // As we are going to be using it as a macro value it has to be as a data -> we can set the storage type to data - // type IRRefSum = VID | IRBasicBlockArg | IRFunctionArg | IID - mut arg := IRBasicBlockArg{ - id: bbid - typ: ref.typ - storage: .data + // Process remaining chain elements + for i in 1 .. chain.elements.len { + elem := chain.elements[i] + match elem { + IdentifierChainName { + // Field access - create IRFieldAccess + mut inst := IRFieldAccess{ + id: b.functions[fid].insts.len + source: current_rid + field: elem.name.name + } + // Look up field type from struct definition if possible + field_type := b.get_field_type(current_type, elem.name.name) or { current_type } + inst.result = b.bb_add_anon_reference(bbid, inst.id, field_type) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_rid = inst.result + current_type = field_type } - match ref.value { - IRBasicBlockArg { - arg.name = ref.value.name + IdentifierChainMacro { + // Nested macro for dynamic field access - create index access with macro value + _, nested_rid := b.bb_handle_macro_expr(bbid, elem.macro_expr) + mut inst := IRIndexAccess{ + id: b.functions[fid].insts.len + source: current_rid + index: OID(nested_rid) + is_slice: false } - IRFunctionArg { - arg.name = ref.value.name + inst.result = b.bb_add_anon_reference(bbid, inst.id, current_type) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_rid = inst.result + } + IdentifierChainIndex { + // Array/list index - create IRIndexAccess + index_oid := b.lower_expression(bbid, fid, elem.index_expr) + mut inst := IRIndexAccess{ + id: b.functions[fid].insts.len + source: current_rid + index: index_oid + is_slice: false } - VID { - arg.name = b.variables[ref.value].name + inst.result = b.bb_add_anon_reference(bbid, inst.id, current_type) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_rid = inst.result + } + IdentifierChainDeref { + // Dereference - create IRDeref + mut inst := IRDeref{ + id: b.functions[fid].insts.len + source: current_rid } - IID { - panic('should be impossible for for an instruction to be the reference for a name...') - arg.name = '' + // Result type is the dereferenced type + mut result_type := current_type + if current_type is IRRefType { + ref_type := current_type as IRRefType + result_type = ref_type.base } + inst.result = b.bb_add_anon_reference(bbid, inst.id, result_type) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_rid = inst.result + current_type = result_type } - b.basic_blocks[bbid].args << arg - return refed, ref.id + } + } + + return refed, current_rid +} + +// Helper to add a macro argument to a basic block +fn (mut b IRBuilder) bb_add_macro_arg(bbid BBID, fid FID, name string, ref IRRef) RID { + mut arg := IRBasicBlockArg{ + id: bbid + typ: ref.typ + storage: .data + name: name + } + b.basic_blocks[bbid].args << arg + return ref.id +} + +// Helper to get field type from a struct type +fn (mut b IRBuilder) get_field_type(typ IRType, field_name string) ?IRType { + match typ { + SID { + struct_def := b.structs[typ] + return struct_def.fields[field_name] or { return none } + } + IRRefType { + return b.get_field_type(typ.base, field_name) } else { - panic('not implemented anything other than identiferi chain name for macroexpr ${e}') + return none } } - - panic('could not handle macro expr') - return refed, RID(-1) } // Returns an oid that we can set to something, later on we can merge the instructions together @@ -341,16 +457,61 @@ pub fn (mut b IRBuilder) lower_expression(bbid BBID, fid FID, expr Expr) OID { return OID(inst.result) } UnaryExpr { - // Will become not a literal, therefore we can have it not be a constant anymore - mut inst := IRUnaryOp{ - op: expr.operator.to_unary_op() - operand: b.lower_expression(bbid, fid, expr.right) + unary_op := expr.operator.to_unary_op() + operand_oid := b.lower_expression(bbid, fid, expr.right) + + match unary_op { + .ref { + // Reference operator (&) - creates a pointer to the operand + operand_rid := match operand_oid { + RID { operand_oid } + CID { panic('Cannot take reference of constant') } + } + mut inst := IRRefInst{ + id: b.functions[fid].insts.len + source: operand_rid + } + // Result type is a reference to the operand type + base_type := operand_oid.to_ir_type(func) + inst.result = b.bb_add_anon_reference(bbid, inst.id, IRType(IRRefType{ base: base_type })) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + return OID(inst.result) + } + .deref { + // Dereference operator (@) - gets the value pointed to + operand_rid := match operand_oid { + RID { operand_oid } + CID { panic('Cannot dereference constant') } + } + mut inst := IRDeref{ + id: b.functions[fid].insts.len + source: operand_rid + } + // Result type is the dereferenced type + base_type := operand_oid.to_ir_type(func) + result_type := match base_type { + IRRefType { base_type.base } + else { base_type } // Allow deref on non-ref for flexibility + } + inst.result = b.bb_add_anon_reference(bbid, inst.id, result_type) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + return OID(inst.result) + } + .neg { + // Negation - use regular unary op + mut inst := IRUnaryOp{ + op: unary_op + operand: operand_oid + } + inst.id = b.functions[fid].insts.len + inst.result = b.bb_add_anon_reference(bbid, inst.id, operand_oid.to_ir_type(func)) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + return OID(inst.result) + } } - inst.id = b.functions[fid].insts.len - inst.result = b.bb_add_anon_reference(bbid, inst.id, inst.operand.to_ir_type(func)) - b.functions[fid].insts << inst - b.basic_blocks[bbid].insts << inst.id - return OID(inst.result) } Literal { lit := expr @@ -407,39 +568,339 @@ pub fn (mut b IRBuilder) lower_expression(bbid BBID, fid FID, expr Expr) OID { return OID(cid) } ListLiteral { - panic('list literal not implemented') + mut list_const := IRListConst{} + for elem in lit.elements { + list_const.elements << b.lower_expression(bbid, fid, elem) + } + cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << list_const + return OID(cid) } DictionaryLiteral { - panic('dictionary literal not implemented') + mut dict_const := IRDictConst{} + for entry in lit.entries { + mut key_oid := OID(CID(-1)) + match entry.key_kind { + .integer_key { + key_cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << IRIntConst{ + value: entry.integer_key or { 0 } + } + key_oid = OID(key_cid) + } + .string_key { + str_key := entry.string_key or { StringLiteral{} } + key_cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << IRStringConst{ + parts: [IRStringPart(IRStringText{ text: str_key.value })] + } + key_oid = OID(key_cid) + } + .macro_key { + macro_key := entry.macro_key or { panic('expected macro key') } + _, mrid := b.bb_handle_macro_expr(bbid, macro_key) + key_oid = OID(mrid) + } + } + dict_const.entries << IRDictEntry{ + key: key_oid + value: b.lower_expression(bbid, fid, entry.value) + } + } + cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << dict_const + return OID(cid) } RangeLiteral { - panic('range literal not implemented') + mut range_const := IRRangeConst{} + if start := lit.start { + range_const.start = b.lower_expression(bbid, fid, start) + } + if end := lit.end { + range_const.end = b.lower_expression(bbid, fid, end) + } + cid := CID(b.functions[fid].consts.len) + b.functions[fid].consts << range_const + return OID(cid) } } } - Identifier { // Will have to be a ref, and it must exist! otherwise we're boned boys + Identifier { ok, ref := b.bb_get_reference(expr.name, bbid, fid) if ok { return OID(ref.id) } else { - panic('We could not find a reference to the identifier ${expr}') + if b.errors != unsafe { nil } { + b.errors.error(.undefined_variable, SourceLocation{ + pos: expr.pos + len: expr.name.len + }, "undefined variable '${expr.name}'") + } + return OID(CID(-1)) } } MacroExpr { - panic('not implemented macro expr yet for lowering expressoin') + // Macro expressions become basic block arguments + _, mrid := b.bb_handle_macro_expr(bbid, expr) + return OID(mrid) } AccessExpr { - panic('not implemente d access expressoin yet for loweing expression') + access := expr + match access { + MemberAccessExpr { + // Lower the target first + mut current_oid := b.lower_expression(bbid, fid, access.target) + + // Process the chain + for elem in access.chain { + match elem { + FieldAccessElement { + // Field access: create IRFieldAccess instruction + if current_oid is CID { + panic('Cannot access field on constant') + } + current_rid := current_oid as RID + mut inst := IRFieldAccess{ + id: b.functions[fid].insts.len + source: current_rid + field: elem.field.name + } + // Result type needs to be looked up from struct definition + inst.result = b.bb_add_anon_reference(bbid, inst.id, current_oid.to_ir_type(b.functions[fid])) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_oid = OID(inst.result) + } + IndexAccessElement { + // Index access: create IRIndexAccess instruction + if current_oid is CID { + panic('Cannot index into constant') + } + current_rid := current_oid as RID + mut inst := IRIndexAccess{ + id: b.functions[fid].insts.len + source: current_rid + index: b.lower_expression(bbid, fid, elem.index_expr) + is_slice: elem.is_slice + } + if elem.is_slice { + if end := elem.slice_end { + inst.end = b.lower_expression(bbid, fid, end) + } + } + inst.result = b.bb_add_anon_reference(bbid, inst.id, current_oid.to_ir_type(b.functions[fid])) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_oid = OID(inst.result) + } + MacroAccessElement { + // Macro in access chain - need to handle this specially + _, mrid := b.bb_handle_macro_expr(bbid, elem.macro_expr) + if current_oid is CID { + panic('Cannot access macro field on constant') + } + current_rid := current_oid as RID + mut inst := IRIndexAccess{ + id: b.functions[fid].insts.len + source: current_rid + index: OID(mrid) + is_slice: false + } + inst.result = b.bb_add_anon_reference(bbid, inst.id, current_oid.to_ir_type(b.functions[fid])) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_oid = OID(inst.result) + } + DerefAccessElement { + // Dereference: create IRDeref instruction + if current_oid is CID { + panic('Cannot dereference constant') + } + current_rid := current_oid as RID + mut inst := IRDeref{ + id: b.functions[fid].insts.len + source: current_rid + } + // Result type is the dereferenced type + base_type := current_oid.to_ir_type(b.functions[fid]) + mut result_type := base_type + if base_type is IRRefType { + ref_type := base_type as IRRefType + result_type = ref_type.base + } + inst.result = b.bb_add_anon_reference(bbid, inst.id, result_type) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + current_oid = OID(inst.result) + } + } + } + return current_oid + } + IndexAccessExpr { + // Direct index access + target_oid := b.lower_expression(bbid, fid, access.target) + target_rid := match target_oid { + RID { target_oid } + CID { panic('Cannot index into constant directly') } + } + mut inst := IRIndexAccess{ + id: b.functions[fid].insts.len + source: target_rid + index: b.lower_expression(bbid, fid, access.index.index_expr) + is_slice: access.index.is_slice + } + if access.index.is_slice { + if end := access.index.slice_end { + inst.end = b.lower_expression(bbid, fid, end) + } + } + inst.result = b.bb_add_anon_reference(bbid, inst.id, target_oid.to_ir_type(b.functions[fid])) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + return OID(inst.result) + } + FunctionCallExpr { + // Function calls - need to resolve the function and create IRCall + mut call_fid := FID(-1) + + // Resolve the function being called + base := access.base_target + match base { + Identifier { + // Look up in current namespace + call_fid = b.namespaces[ns.id].function_map[base.name] or { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_function, SourceLocation{ + pos: base.pos + len: base.name.len + }, "undefined function '${base.name}'") + } + return OID(CID(-1)) + } + } + QualifiedIdentifier { + // Traverse namespace path + ns_path := base.to_list() + target_nid := b.tranverse_namespace(ns, ns_path) or { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_namespace, SourceLocation{ + pos: base.pos + }, "could not resolve namespace path for function '${base.name.name}'") + } + return OID(CID(-1)) + } + call_fid = b.namespaces[target_nid].function_map[base.name.name] or { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_function, SourceLocation{ + pos: base.pos + }, "function '${base.name.name}' not found in namespace") + } + return OID(CID(-1)) + } + } + else { + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, empty_location(), 'cannot call non-identifier expression as function') + } + return OID(CID(-1)) + } + } + + // Lower arguments + mut arg_oids := []OID{} + for arg in access.args { + arg_oids << b.lower_expression(bbid, fid, arg) + } + + // Create IRCall instruction + mut inst := IRCall{ + id: b.functions[fid].insts.len + function: call_fid + args: arg_oids + } + + // Set result if function returns non-void + called_func := b.functions[call_fid] + if called_func.return_type != IRType(BuiltinType.void_t) { + inst.result = b.bb_add_anon_reference(bbid, inst.id, called_func.return_type) + } + + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + + if result_rid := inst.result { + return OID(result_rid) + } + // Void return - return a dummy + return OID(CID(-1)) + } + } } StructLiteral { - panic('not implemented struct literal expr yet for loweing expressions') + // Look up the struct type + ns_path := expr.struct_name.to_list() + target_nid := b.tranverse_namespace(ns, ns_path) or { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_namespace, SourceLocation{ + pos: expr.struct_name.pos + }, "could not resolve namespace path for struct '${expr.struct_name.name.name}'") + } + return OID(CID(-1)) + } + struct_id := b.namespaces[target_nid].struct_map[expr.struct_name.name.name] or { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_struct, SourceLocation{ + pos: expr.struct_name.pos + }, "struct '${expr.struct_name.name.name}' not found") + } + return OID(CID(-1)) + } + + // Create struct init instruction + mut inst := IRStructInit{ + id: b.functions[fid].insts.len + struct_type: struct_id + } + + // Lower field values + for field in expr.fields { + inst.field_values[field.name.name] = b.lower_expression(bbid, fid, field.value) + } + + inst.result = b.bb_add_anon_reference(bbid, inst.id, IRType(struct_id)) + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + return OID(inst.result) } QualifiedIdentifier { - panic('not yet implemented qualified identifier lowering for loweriing expresions yet') + // Resolve the qualified identifier to a variable + ns_path := expr.to_list() + target_nid := b.tranverse_namespace(ns, ns_path) or { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_namespace, SourceLocation{ + pos: expr.pos + }, "could not resolve namespace path for '${expr.name.name}'") + } + return OID(CID(-1)) + } + // Look up variable in target namespace + vid := b.namespaces[target_nid].variable_map[expr.name.name] or { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_variable, SourceLocation{ + pos: expr.pos + }, "variable '${expr.name.name}' not found in namespace") + } + return OID(CID(-1)) + } + rid := b.bb_add_reference(bbid, vid) + return OID(rid) } } - panic('could not lower expression') + if b.errors != unsafe { nil } { + b.errors.error(.not_implemented, empty_location(), 'could not lower expression') + } return OID(CID(-1)) } @@ -453,7 +914,12 @@ pub fn (mut b IRBuilder) fill_bb_insts_stmt(bbid BBID, fid FID, stmt Stmt) { if ok { // We have found a reference to this (Or made one) // This should not be allowed.... We are not allowed to have // this sort of shadowing! - panic('When defining ${stmt} we found a reference to it already existing.') + if b.errors != unsafe { nil } { + b.errors.error(.duplicate_definition, SourceLocation{ + pos: stmt.Node.pos + }, "variable '${stmt.name.name}' is already defined") + } + return } else { // No reference to this exists so we should like make one // need to create a value inst := IRTypedDefine{ @@ -469,7 +935,12 @@ pub fn (mut b IRBuilder) fill_bb_insts_stmt(bbid BBID, fid FID, stmt Stmt) { ok, ref := b.bb_get_reference(stmt.name.name, bbid, fid) if ok { // We have found a reference to this (or have made one) // This is not allowed, this is a definition. We are not allowed to have shadowing. - panic('When defining ${stmt} it would be shadowing something already in its scope. This is not allowed') + if b.errors != unsafe { nil } { + b.errors.error(.shadowing_not_allowed, SourceLocation{ + pos: stmt.Node.pos + }, "variable '${stmt.name.name}' would shadow an existing variable (shadowing is not allowed)") + } + return } else { // We are the first creation point for this thing! // We have to first figure out the type of the expression that we are coming from... // And generate the instructions that compose this expression... @@ -486,30 +957,39 @@ pub fn (mut b IRBuilder) fill_bb_insts_stmt(bbid BBID, fid FID, stmt Stmt) { Assignment { op := stmt.operator.to_assign_op() left_oid := b.lower_expression(bbid, fid, stmt.left) - left := match left_oid { - CID { - panic('Cannot assign to a constant value') - RID(-1) - } - RID { - left_oid + if left_oid is CID { + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, SourceLocation{ + pos: stmt.Node.pos + }, 'cannot assign to a constant value') } + return } + left := left_oid as RID right := b.lower_expression(bbid, fid, stmt.right) match op { .assign { // Check that left and right are same type. if OID(left).to_ir_type(b.functions[fid]) != right.to_ir_type(b.functions[fid]) { - panic('cannot assign accross types') + if b.errors != unsafe { nil } { + b.errors.error(.type_mismatch, SourceLocation{ + pos: stmt.Node.pos + }, 'cannot assign across different types') + } + return } - // TODO: handle checking if the sources are different to } else { lref := b.functions[fid].refs[left] if lref.typ is BuiltinType { if lref.typ != BuiltinType.int_t { - panic('cannot do operation ${op} onto type ${lref.typ}') + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, SourceLocation{ + pos: stmt.Node.pos + }, 'cannot perform operation ${op} on type ${lref.typ}') + } + return } } } @@ -523,8 +1003,127 @@ pub fn (mut b IRBuilder) fill_bb_insts_stmt(bbid BBID, fid FID, stmt Stmt) { b.functions[fid].insts << inst b.basic_blocks[bbid].insts << inst.id } - else { - println('not implemented ${stmt}') + Store { + // Store is like assignment but for storage operations (<-) + left_oid := b.lower_expression(bbid, fid, stmt.left) + if left_oid is CID { + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, SourceLocation{ + pos: stmt.Node.pos + }, 'cannot store to a constant value') + } + return + } + left := left_oid as RID + + right_oid := b.lower_expression(bbid, fid, stmt.right) + if right_oid is CID { + if b.errors != unsafe { nil } { + b.errors.error(.invalid_operation, SourceLocation{ + pos: stmt.Node.pos + }, 'cannot store from a constant value') + } + return + } + right := right_oid as RID + + mut inst := IRStore{ + id: b.functions[fid].insts.len + result: left + source: right + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } + ExprStmt { + // Expression statement - just evaluate the expression for side effects + b.lower_expression(bbid, fid, stmt.expr) + } + MacroLiteralCommand { + // $ command - lower to IRMacroLiteralCmd + mut parts := []IRMacroCmdPart{} + for part in stmt.parts { + match part { + MacroLiteralText { + parts << IRMacroCmdPart(IRMacroCmdText{ + text: part.text + }) + } + MacroLiteralMacro { + refed, mrid := b.bb_handle_macro_expr(bbid, part.macro_expr) + parts << IRMacroCmdPart(IRMacroCmdMacro{ + value: mrid + is_ref: refed + }) + } + MacroLiteralString { + mut str_parts := []IRStringPart{} + str_lit := part.str_literal + if !str_lit.interpolated { + str_parts << IRStringPart(IRStringText{ + text: str_lit.value + }) + } else { + for str_part in str_lit.parts { + if !str_part.is_macro { + str_parts << IRStringPart(IRStringText{ + text: str_part.text + }) + } else { + macro_expr := str_part.macro_expr or { continue } + refed, mrid := b.bb_handle_macro_expr(bbid, macro_expr) + str_parts << IRStringPart(IRStringMacro{ + value: mrid + is_ref: refed + }) + } + } + } + parts << IRMacroCmdPart(IRMacroCmdString{ + parts: str_parts + }) + } + } + } + inst := IRMacroLiteralCmd{ + func: fid + id: b.functions[fid].insts.len + parts: parts + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } + IfStmt { + // Lower the condition expression - actual branching is handled by terminators + b.lower_expression(bbid, fid, stmt.condition) + // Note: The then/else blocks are handled as separate basic blocks + // Terminators (IRBranch) are added in a later pass + } + Return { + // Return statement - handled as terminator instruction + if val := stmt.value { + ret_val := b.lower_expression(bbid, fid, val) + inst := IRReturn{ + id: b.functions[fid].insts.len + value: ret_val + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } else { + inst := IRReturn{ + id: b.functions[fid].insts.len + value: none + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } + } + StructDefinition, FunctionDefinition, NamespaceDefinition, + NamespaceImport, NamespaceAlias, FunctionInlineDefinition, Block { + // These shouldn't appear in basic blocks + if b.errors != unsafe { nil } { + b.errors.error(.unsupported_statement_location, empty_location(), 'invalid statement in basic block: ${stmt}') + } } } } @@ -536,3 +1135,147 @@ pub fn (mut b IRBuilder) fill_bb_insts(bbid BBID) { b.fill_bb_insts_stmt(bbid, fid, stmt) } } + +// Add terminator instructions to basic blocks based on CFG structure +pub fn (mut b IRBuilder) add_bb_terminator(bbid BBID) { + bb := b.basic_blocks[bbid] + fid := bb.function + + // Check if block already has a terminator (Return) + if bb.insts.len > 0 { + last_inst := b.functions[fid].insts[bb.insts[bb.insts.len - 1]] + if last_inst is IRReturn { + return // Already has terminator + } + } + + // Check what kind of terminator we need based on successors + match bb.successors.len { + 0 { + // No successors - this should have a return (implicit void return) + inst := IRReturn{ + id: b.functions[fid].insts.len + value: none + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } + 1 { + // One successor - unconditional jump + target := bb.successors[0] + // Collect arguments for the target block + mut args := []OID{} + for arg in b.basic_blocks[target].args { + // Look up the value in current scope and pass it + ok, ref := b.bb_get_reference(arg.name, bbid, fid) + if ok { + args << OID(ref.id) + } else { + if b.errors != unsafe { nil } { + b.errors.error(.undefined_variable, empty_location(), "could not find argument '${arg.name}' to pass to block") + } + continue + } + } + inst := IRJump{ + id: b.functions[fid].insts.len + target: target + args: args + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } + 2 { + // Two successors - conditional branch (if/else) + // The condition should be the last evaluated expression before branching + // Look for the IfStmt in the block's statements + mut cond_oid := OID(CID(-1)) + for stmt in bb.stmts { + if stmt is IfStmt { + cond_oid = b.lower_expression(bbid, fid, stmt.condition) + break + } + } + + then_bb := bb.successors[0] + else_bb := bb.successors[1] + + // Collect arguments for both branches + mut then_args := []OID{} + for arg in b.basic_blocks[then_bb].args { + ok, ref := b.bb_get_reference(arg.name, bbid, fid) + if ok { + then_args << OID(ref.id) + } + } + + mut else_args := []OID{} + for arg in b.basic_blocks[else_bb].args { + ok, ref := b.bb_get_reference(arg.name, bbid, fid) + if ok { + else_args << OID(ref.id) + } + } + + inst := IRBranch{ + id: b.functions[fid].insts.len + cond: cond_oid + then_bb: then_bb + then_args: then_args + else_bb: else_bb + else_args: else_args + } + b.functions[fid].insts << inst + b.basic_blocks[bbid].insts << inst.id + } + else { + if b.errors != unsafe { nil } { + b.errors.error(.internal_compiler_error, empty_location(), 'basic block has more than 2 successors, which is not supported') + } + } + } +} + +// Add terminators to all basic blocks in a function +pub fn (mut b IRBuilder) add_function_terminators(fid FID) { + for bbid in b.functions[fid].bbs { + b.add_bb_terminator(bbid) + } +} + +// Fill all instructions for all basic blocks in a function +pub fn (mut b IRBuilder) fill_function_insts(fid FID) { + for bbid in b.functions[fid].bbs { + b.fill_bb_insts(bbid) + } + // Add terminators after all instructions are filled + b.add_function_terminators(fid) +} + +// Solve basic block arguments - determine what values need to be passed between blocks +pub fn (mut b IRBuilder) solve_bb_arguments(fid FID) { + // For each basic block, check what variables it uses that are defined elsewhere + for bbid in b.functions[fid].bbs { + bb := b.basic_blocks[bbid] + + // Skip entry block - it gets arguments from function arguments + if bbid == b.functions[fid].entrybb { + continue + } + + // For blocks that need macro values, the args are already added by bb_handle_macro_expr + // This function ensures consistency and handles phi-like scenarios + + // Check each predecessor and ensure they can provide the required arguments + for pred_id in bb.predecessors { + pred := b.basic_blocks[pred_id] + for arg in bb.args { + ok, _, level := b.bb_get_reference_no_creation(arg.name, pred_id, fid) + if !ok { + // The predecessor doesn't have this value - it may need to get it from further up + // This is handled by the basic block argument passing in terminators + } + } + } + } +} diff --git a/ir_test.v b/ir_test.v index d0402c0..7348eee 100644 --- a/ir_test.v +++ b/ir_test.v @@ -2,35 +2,35 @@ module main pub fn test_nssolver_simple() { mut solver := NSSolver{} - solver.solve('tests/ir/namespace/simple/main.mcf')! + solver.solve('tests/ir/namespace/simple/main.mdl')! println(solver) assert solver.verify_legal() } pub fn test_nssolver_import() { mut solver := NSSolver{} - solver.solve('tests/ir/namespace/import/a.mcf')! + solver.solve('tests/ir/namespace/import/a.mdl')! println(solver) assert solver.verify_legal() } pub fn test_nssolver_child_import() { mut solver := NSSolver{} - solver.solve('tests/ir/namespace/child_import/main.mcf')! + solver.solve('tests/ir/namespace/child_import/main.mdl')! println(solver) assert solver.verify_legal() } pub fn test_nssolver_child_import_fail() { mut solver := NSSolver{} - solver.solve('tests/ir/namespace/child_import_fail/main.mcf')! + solver.solve('tests/ir/namespace/child_import_fail/main.mdl')! println(solver) assert !solver.verify_legal() } pub fn test_struct_single() { mut irb := IRBuilder{} - irb.lower(['tests/ir/structs/simple/main.mcf'])! + irb.lower(['tests/ir/structs/simple/main.mdl'])! p := irb.structs[0] assert p.name == 'Point' assert p.fields['x'] is BuiltinType @@ -38,7 +38,7 @@ pub fn test_struct_single() { pub fn test_struct_self_ref() { mut irb := IRBuilder{} - irb.lower(['tests/ir/structs/self_ref/main.mcf'])! + irb.lower(['tests/ir/structs/self_ref/main.mdl'])! print(irb.structs) assert irb.structs[0].fields['next'] == (IRType(IRRefType{ base: IRType(SID(0)) @@ -47,7 +47,7 @@ pub fn test_struct_self_ref() { pub fn test_struct_cross_file() { mut irb := IRBuilder{} - irb.lower(['tests/ir/structs/cross_file/main.mcf'])! + irb.lower(['tests/ir/structs/cross_file/main.mdl'])! person := irb.structs[0] assert person.name == 'Person' assert irb.namespaces[person.namespace].name == 'main' @@ -62,5 +62,5 @@ pub fn test_struct_cross_file() { pub fn test_struct_cross_file_fail() { mut irb := IRBuilder{} - assert false == irb.lower(['tests/ir/structs/cross_file_fail/main.mcf'])! + assert false == irb.lower(['tests/ir/structs/cross_file_fail/main.mdl'])! } diff --git a/lx.v b/lx.v index c98096a..25f7348 100644 --- a/lx.v +++ b/lx.v @@ -530,3 +530,43 @@ pub fn (mut l Lexer) next() ?Token { } return t } + +// Operator conversion methods for IR lowering +pub fn (t TokenKind) to_binary_op() BinaryOp { + return match t { + .plus { BinaryOp.add } + .minus { BinaryOp.sub } + .star { BinaryOp.mul } + .slash { BinaryOp.div } + .percent { BinaryOp.mod } + .eq { BinaryOp.eq } + .ne { BinaryOp.ne } + .lcarrot { BinaryOp.lt } + .rcarrot { BinaryOp.gt } + .lte { BinaryOp.le } + .gte { BinaryOp.ge } + else { panic('Token ${t} is not a binary operator') } + } +} + +pub fn (t TokenKind) to_unary_op() UnaryOp { + return match t { + .minus { UnaryOp.neg } + .ampersand { UnaryOp.ref } + .at { UnaryOp.deref } + else { panic('Token ${t} is not a unary operator') } + } +} + +pub fn (t TokenKind) to_assign_op() AssignOp { + return match t { + .assign { AssignOp.assign } + .plus_assign { AssignOp.add_assign } + .minus_assign { AssignOp.sub_assign } + .star_assign { AssignOp.mul_assign } + .slash_assign { AssignOp.div_assign } + .percent_assign { AssignOp.mod_assign } + .swap { AssignOp.swap } + else { panic('Token ${t} is not an assignment operator') } + } +} diff --git a/main.v b/main.v index 8699681..c5fb047 100644 --- a/main.v +++ b/main.v @@ -1,23 +1,46 @@ module main fn main() { - mut irb := IRBuilder{} - irb.lower(['tests/ir/bb_gen/stmts/assign.mcf'])! - irb.print_cfg_dot() - for mut bb in irb.basic_blocks { - irb.fill_bb_insts(bb.id) - print(irb.functions[bb.function].insts) - print('\n') - print(bb.insts) - print('\n') - for inst in bb.insts { - irb.fn_print_inst(bb.function, inst) - print('\n') - } - // print(irb.functions[bb.function].block) + // Initialize error manager + mut em := ErrorManager.new() + + // Get input files (for now using a test file) + files := ['tests/codegen/arithmetic.mdl'] + + // Load source files into error manager for error reporting + for f in files { + em.load_source(f) + } + + // Create IR builder with error manager + mut irb := IRBuilder{ + errors: &em + } + + // Run lowering + result := irb.lower(files) or { + em.print_all() + eprintln('Compilation failed.') + exit(1) } - // for func in irb.functions { - // println('func ${func.name}\n ${func.block}') - // } + // Check for errors + if em.has_errors() || !result { + em.print_all() + exit(1) + } + + // Print warnings if any + if em.warning_count > 0 { + em.print_all() + } + + // Generate mcfunction output + mut cg := Codegen.new(&irb, 'output', 'example') + cg.generate() or { + em.error(.internal_compiler_error, empty_location(), 'code generation failed: ${err}') + em.print_all() + exit(1) + } + println('Generated mcfunction files in output/') } diff --git a/parse.v b/parse.v index 73adeb8..7ef3b0d 100644 --- a/parse.v +++ b/parse.v @@ -1849,10 +1849,21 @@ fn (mut p Parser) parse_macro_literal_command() Stmt { p.consume(.dollar) mut parts := []MacroLiteralPart{} + mut needs_leading_space := false // Track if next text needs a leading space for p.current.kind != .semicolon && p.current.kind != .eof { match p.current.kind { .lcarrot { + // Add trailing space to previous text part if it exists and we're starting a macro + if parts.len > 0 { + last_idx := parts.len - 1 + if parts[last_idx] is MacroLiteralText { + mut last_text := parts[last_idx] as MacroLiteralText + last_text.text += ' ' + parts[last_idx] = MacroLiteralPart(last_text) + } + } + // Parse macro expression macro := p.parse_macro() parts << MacroLiteralPart(MacroLiteralMacro{ @@ -1861,6 +1872,7 @@ fn (mut p Parser) parse_macro_literal_command() Stmt { } macro_expr: macro }) + needs_leading_space = true // Next text should have a leading space } .lit_string { // Parse string literal (may contain macros) @@ -1897,13 +1909,20 @@ fn (mut p Parser) parse_macro_literal_command() Stmt { } str_literal: str_literal }) + needs_leading_space = false } else { // Raw literal text - accumulate all tokens until macro, string, or semicolon text_pos := p.current.pos mut text := '' - mut first := true + // Add leading space if we're coming after a macro + if needs_leading_space { + text = ' ' + } + needs_leading_space = false + + mut first := true for p.current.kind !in [.lcarrot, .lit_string, .semicolon, .eof] { if !first { text += ' ' diff --git a/semantic.v b/semantic.v new file mode 100644 index 0000000..74788d3 --- /dev/null +++ b/semantic.v @@ -0,0 +1,548 @@ +module main + +// ==================== Type Info for Scope Tracking ==================== + +pub struct TypeInfo { +pub: + typ IRType + storage StorageKind + pos int // Where it was defined +} + +// ==================== Semantic Context ==================== + +pub struct SemanticContext { +pub mut: + current_namespace NID + current_function ?FID + scope_stack []map[string]TypeInfo + return_type ?IRType + file string +} + +// ==================== Semantic Checker ==================== + +@[heap] +pub struct SemanticChecker { +pub mut: + builder &IRBuilder + errors &ErrorManager = unsafe { nil } // Shared error manager + context SemanticContext +} + +pub fn SemanticChecker.new(builder &IRBuilder) SemanticChecker { + return SemanticChecker{ + builder: builder + errors: builder.errors // Share error manager with IRBuilder + context: SemanticContext{ + scope_stack: []map[string]TypeInfo{} + } + } +} + +// ==================== Error Management ==================== + +pub fn (mut c SemanticChecker) add_error(kind ErrorKind, pos int, msg string) { + if c.errors != unsafe { nil } { + c.errors.error(kind, SourceLocation{ + file: c.context.file + pos: pos + }, msg) + } +} + +pub fn (mut c SemanticChecker) add_error_with_hint(kind ErrorKind, pos int, msg string, hint string) { + if c.errors != unsafe { nil } { + c.errors.error_with_hint(kind, SourceLocation{ + file: c.context.file + pos: pos + }, msg, hint) + } +} + +pub fn (mut c SemanticChecker) add_warning(kind ErrorKind, pos int, msg string) { + if c.errors != unsafe { nil } { + c.errors.warning(kind, SourceLocation{ + file: c.context.file + pos: pos + }, msg) + } +} + +pub fn (c &SemanticChecker) has_errors() bool { + if c.errors != unsafe { nil } { + return c.errors.has_errors() + } + return false +} + +pub fn (c &SemanticChecker) error_count() int { + if c.errors != unsafe { nil } { + return c.errors.error_count + } + return 0 +} + +pub fn (c &SemanticChecker) warning_count() int { + if c.errors != unsafe { nil } { + return c.errors.warning_count + } + return 0 +} + +// Print all accumulated errors (delegates to ErrorManager) +pub fn (c &SemanticChecker) print_errors() { + if c.errors != unsafe { nil } { + c.errors.print_all() + } +} + +// ==================== Scope Management ==================== + +pub fn (mut c SemanticChecker) scope_enter() { + c.context.scope_stack << map[string]TypeInfo{} +} + +pub fn (mut c SemanticChecker) scope_exit() { + if c.context.scope_stack.len > 0 { + c.context.scope_stack.pop() + } +} + +pub fn (mut c SemanticChecker) scope_define(name string, info TypeInfo) bool { + if c.context.scope_stack.len == 0 { + c.scope_enter() + } + + // Check if already defined in current scope + idx := c.context.scope_stack.len - 1 + if name in c.context.scope_stack[idx] { + return false // Already defined + } + + c.context.scope_stack[idx][name] = info + return true +} + +pub fn (c &SemanticChecker) scope_lookup(name string) ?TypeInfo { + // Search from innermost to outermost scope + for i := c.context.scope_stack.len - 1; i >= 0; i-- { + if name in c.context.scope_stack[i] { + return c.context.scope_stack[i][name] + } + } + return none +} + +// ==================== Type Utilities ==================== + +pub fn (c &SemanticChecker) types_compatible(expected IRType, actual IRType) bool { + // Exact match + if expected == actual { + return true + } + + // Check if both are reference types with compatible bases + match expected { + IRRefType { + if actual is IRRefType { + return c.types_compatible(expected.base, actual.base) + } + } + else {} + } + + return false +} + +pub fn (c &SemanticChecker) type_to_string(typ IRType) string { + match typ { + BuiltinType { + return match typ { + .int_t { 'Int' } + .float_t { 'Float' } + .char_t { 'Char' } + .string_t { 'String' } + .list_t { 'List' } + .dict_t { 'Dict' } + .void_t { 'Void' } + } + } + IRRefType { + return '&${c.type_to_string(typ.base)}' + } + SID { + if int(typ) < c.builder.structs.len { + return c.builder.structs[typ].name + } + return 'Struct#${typ}' + } + } +} + +// ==================== Stage 1 Checks ==================== + +pub fn (mut c SemanticChecker) check_stage1() bool { + initial_errors := c.error_count() + + // Set file context from builder + if c.builder.files.len > 0 { + c.context.file = c.builder.files[0] + } + + // Check each namespace + for ns in c.builder.namespaces { + c.context.current_namespace = ns.id + + // Check struct definitions + c.check_struct_definitions(ns) + + // Check function signatures + c.check_function_signatures(ns) + + // Check name collisions within namespace + c.check_namespace_names(ns) + } + + return c.error_count() == initial_errors +} + +fn (mut c SemanticChecker) check_struct_definitions(ns IRNamespace) { + for sid in ns.structs { + s := c.builder.structs[sid] + + // Check each field has a valid type + // s.fields is map[string]IRType + for field_name, field_type in s.fields { + if !c.is_valid_ir_type(ns, field_type) { + c.add_error(.undefined_struct, 0, + "field '${field_name}' in struct '${s.name}' has unknown type") + } + } + // Note: duplicate field names are impossible since fields is a map + } +} + +fn (mut c SemanticChecker) check_function_signatures(ns IRNamespace) { + for fid in ns.functions { + func := c.builder.functions[fid] + + // Check return type exists + if !c.is_valid_ir_type(ns, func.return_type) { + c.add_error(.undefined_struct, 0, + "function '${func.name}' has unknown return type") + } + + // Check argument types exist + mut seen_args := map[string]bool{} + for arg in func.args { + if !c.is_valid_ir_type(ns, arg.typ) { + c.add_error(.undefined_struct, 0, + "argument '${arg.name}' in function '${func.name}' has unknown type") + } + + // Check for duplicate argument names + if arg.name in seen_args { + c.add_error(.duplicate_definition, 0, + "duplicate argument '${arg.name}' in function '${func.name}'") + } + seen_args[arg.name] = true + } + } +} + +fn (mut c SemanticChecker) check_namespace_names(ns IRNamespace) { + mut all_names := map[string]bool{} + + // Collect function names + for fid in ns.functions { + func := c.builder.functions[fid] + if func.name in all_names { + c.add_error(.duplicate_definition, 0, + "'${func.name}' is already defined in namespace '${ns.name}'") + } + all_names[func.name] = true + } + + // Collect struct names + for sid in ns.structs { + s := c.builder.structs[sid] + if s.name in all_names { + c.add_error(.duplicate_definition, 0, + "'${s.name}' is already defined in namespace '${ns.name}'") + } + all_names[s.name] = true + } + + // Collect variable names + for name, _ in ns.variable_map { + if name in all_names { + c.add_error(.duplicate_definition, 0, + "'${name}' is already defined in namespace '${ns.name}'") + } + all_names[name] = true + } +} + +fn (c &SemanticChecker) is_valid_type(ns IRNamespace, typ Type) bool { + match typ { + BuiltinType { + return true + } + ReferenceType { + return c.is_valid_type(ns, typ.base) + } + StructType { + // Check if struct exists in namespace or parent namespaces + // For now, just check if the name is known + return true // TODO: full resolution + } + } +} + +fn (c &SemanticChecker) is_valid_ir_type(ns IRNamespace, typ IRType) bool { + match typ { + BuiltinType { + return true + } + IRRefType { + return c.is_valid_ir_type(ns, typ.base) + } + SID { + return int(typ) < c.builder.structs.len + } + } +} + +// ==================== Stage 2 Checks ==================== + +pub fn (mut c SemanticChecker) check_stage2() bool { + initial_errors := c.error_count() + + // Check each function body + for ns in c.builder.namespaces { + c.context.current_namespace = ns.id + + for fid in ns.functions { + c.check_function_body(fid) + } + } + + return c.error_count() == initial_errors +} + +fn (mut c SemanticChecker) check_function_body(fid FID) { + func := c.builder.functions[fid] + c.context.current_function = fid + c.context.return_type = func.return_type + + // Enter function scope + c.scope_enter() + + // Add function arguments to scope + for arg in func.args { + c.scope_define(arg.name, TypeInfo{ + typ: arg.typ + storage: arg.storage + pos: 0 + }) + } + + // Check each basic block's instructions + for bbid in func.bbs { + c.check_basic_block(bbid, fid) + } + + c.scope_exit() + c.context.current_function = none + c.context.return_type = none +} + +fn (mut c SemanticChecker) check_basic_block(bbid BBID, fid FID) { + bb := c.builder.basic_blocks[bbid] + func := c.builder.functions[fid] + + // Add BB args to scope + for arg in bb.args { + c.scope_define(arg.name, TypeInfo{ + typ: arg.typ + storage: arg.storage + pos: 0 + }) + } + + // Check each instruction + for iid in bb.insts { + c.check_instruction(func.insts[iid], fid) + } +} + +fn (mut c SemanticChecker) check_instruction(inst IRInstruction, fid FID) { + func := c.builder.functions[fid] + + match inst { + IRDefine { + // Define adds a variable - check RHS type + c.check_oid_valid(inst.value, fid) + } + IRTypedDefine { + // Just a declaration, type already checked in stage 1 + } + IRAssign { + // Check LHS and RHS types match + if left_type := c.get_rid_type(inst.result, fid) { + if right_type := c.get_oid_type(inst.value, fid) { + if !c.types_compatible(left_type, right_type) { + c.add_error(.type_mismatch, 0, + "cannot assign ${c.type_to_string(right_type)} to ${c.type_to_string(left_type)}") + } + } + } + } + IRStore { + // Store requires LHS to be a reference + if left_type := c.get_rid_type(inst.result, fid) { + if left_type !is IRRefType { + c.add_error(.invalid_dereference, 0, + "store target must be a reference type, got ${c.type_to_string(left_type)}") + } + } + } + IRBinaryOp { + // Check operands are numeric for arithmetic + if left_type := c.get_oid_type(inst.left, fid) { + if !c.is_numeric_type(left_type) { + c.add_error(.invalid_operation, 0, + "binary operator requires numeric type, got ${c.type_to_string(left_type)}") + } + } + if right_type := c.get_oid_type(inst.right, fid) { + if !c.is_numeric_type(right_type) { + c.add_error(.invalid_operation, 0, + "binary operator requires numeric type, got ${c.type_to_string(right_type)}") + } + } + } + IRCall { + // Check argument count + called_func := c.builder.functions[inst.function] + if inst.args.len != called_func.args.len { + c.add_error(.wrong_argument_count, 0, + "function '${called_func.name}' expects ${called_func.args.len} arguments, got ${inst.args.len}") + } else { + // Check argument types + for i, arg_oid in inst.args { + expected := called_func.args[i].typ + actual := c.get_oid_type(arg_oid, fid) or { continue } + + if !c.types_compatible(expected, actual) { + c.add_error(.wrong_argument_type, 0, + "argument ${i + 1} of '${called_func.name}': expected ${c.type_to_string(expected)}, got ${c.type_to_string(actual)}") + } + } + } + } + IRReturn { + // Check return type matches function + if ret_type := c.context.return_type { + if value := inst.value { + if actual := c.get_oid_type(value, fid) { + if !c.types_compatible(ret_type, actual) { + c.add_error(.type_mismatch, 0, + "return type mismatch: expected ${c.type_to_string(ret_type)}, got ${c.type_to_string(actual)}") + } + } + } else if ret_type != IRType(BuiltinType.void_t) { + c.add_error(.missing_return, 0, + "function must return a value of type ${c.type_to_string(ret_type)}") + } + } + } + IRDeref { + // Check operand is a reference type + if src_type := c.get_rid_type(inst.source, fid) { + if src_type !is IRRefType { + c.add_error(.invalid_dereference, 0, + "cannot dereference non-reference type ${c.type_to_string(src_type)}") + } + } + } + IRRefInst { + // Reference is always valid if source exists + c.check_rid_valid(inst.source, fid) + } + else { + // Other instructions don't need special checks + } + } +} + +fn (mut c SemanticChecker) check_oid_valid(oid OID, fid FID) { + match oid { + CID { + // Constants are always valid if they exist in the function + func := c.builder.functions[fid] + if int(oid) >= func.consts.len { + c.add_error(.undefined_variable, 0, 'invalid constant reference') + } + } + RID { + c.check_rid_valid(oid, fid) + } + } +} + +fn (mut c SemanticChecker) check_rid_valid(rid RID, fid FID) { + func := c.builder.functions[fid] + if int(rid) >= func.refs.len { + c.add_error(.undefined_variable, 0, 'invalid reference') + } +} + +fn (c &SemanticChecker) get_rid_type(rid RID, fid FID) ?IRType { + func := c.builder.functions[fid] + if int(rid) >= func.refs.len { + return none + } + return func.refs[rid].typ +} + +fn (c &SemanticChecker) get_oid_type(oid OID, fid FID) ?IRType { + func := c.builder.functions[fid] + match oid { + CID { + if int(oid) >= func.consts.len { + return none + } + cnst := func.consts[oid] + return match cnst { + IRIntConst { IRType(BuiltinType.int_t) } + IRFloatConst { IRType(BuiltinType.float_t) } + IRCharConst { IRType(BuiltinType.char_t) } + IRStringConst { IRType(BuiltinType.string_t) } + IRListConst { IRType(BuiltinType.list_t) } + IRDictConst { IRType(BuiltinType.dict_t) } + IRRangeConst { IRType(BuiltinType.list_t) } // Ranges are list-like + } + } + RID { + return c.get_rid_type(oid, fid) + } + } +} + +fn (c &SemanticChecker) is_numeric_type(typ IRType) bool { + return typ == IRType(BuiltinType.int_t) || typ == IRType(BuiltinType.float_t) +} + +// ==================== Main Entry Point ==================== + +pub fn (mut c SemanticChecker) check() bool { + // Source files are loaded by ErrorManager in main.v + + // Run both stages + stage1_ok := c.check_stage1() + stage2_ok := c.check_stage2() + + return stage1_ok && stage2_ok +} diff --git a/test.mcf b/test.mcf deleted file mode 100644 index 0f2469a..0000000 --- a/test.mcf +++ /dev/null @@ -1,13 +0,0 @@ -namespace my_namespace = "my_file"; -namespace my_other_namespace = { - fn my_function(data a: Int, data b: String): String { - return "HELlo World + \""; - } -}; - -$say $; -fn cat_strings(data first: *String,data second: String): String { - return ""; -} - -reg a = "hello"; diff --git a/tests/codegen/arithmetic.mdl b/tests/codegen/arithmetic.mdl new file mode 100644 index 0000000..6053f84 --- /dev/null +++ b/tests/codegen/arithmetic.mdl @@ -0,0 +1,29 @@ +// Test basic arithmetic operations with registers +namespace arithmetic; + +fn test_add() : Int { + reg a := 5; + reg b := 10; + reg c := a + b; + return c; +} + +fn test_sub() : Int { + reg x := 100; + reg y := 37; + return x - y; +} + +fn test_mul() : Int { + reg a := 7; + reg b := 8; + return a * b; +} + +fn test_compound() : Int { + reg result := 10; + result += 5; + result -= 3; + result *= 2; + return result; +} diff --git a/tests/codegen/conditionals.mdl b/tests/codegen/conditionals.mdl new file mode 100644 index 0000000..deef5fb --- /dev/null +++ b/tests/codegen/conditionals.mdl @@ -0,0 +1,38 @@ +// Test conditional statements +namespace cond; + +fn simple_if() : Int { + reg x := 10; + reg result := 0; + if (x > 5) { + result = 1; + } + return result; +} + +fn if_else() : Int { + reg a := 3; + reg b := 7; + reg max := 0; + if (a > b) { + max = a; + } else { + max = b; + } + return max; +} + +fn nested_if() : Int { + reg x := 50; + reg category := 0; + if (x > 100) { + category = 3; + } else { + if (x > 50) { + category = 2; + } else { + category = 1; + } + } + return category; +} diff --git a/tests/codegen/data_reg.mdl b/tests/codegen/data_reg.mdl new file mode 100644 index 0000000..d02384c --- /dev/null +++ b/tests/codegen/data_reg.mdl @@ -0,0 +1,35 @@ +// Test data and register variable interactions +namespace storage; + +fn data_to_reg() : Int { + data x := 42; + reg y := x; + return y; +} + +fn reg_to_data() : Int { + reg a := 100; + data b := a; + return b; +} + +fn mixed_arithmetic() : Int { + data a := 10; + reg b := 20; + reg result := a + b; + return result; +} + +fn all_data() : Int { + data x := 5; + data y := 10; + data z := x + y; + return z; +} + +fn all_reg() : Int { + reg a := 3; + reg b := 4; + reg c := a * b; + return c; +} diff --git a/tests/codegen/functions.mdl b/tests/codegen/functions.mdl new file mode 100644 index 0000000..edbca16 --- /dev/null +++ b/tests/codegen/functions.mdl @@ -0,0 +1,28 @@ +// Test function calls +namespace funcs; + +fn add(data a: Int, data b: Int) : Int { + return a + b; +} + +fn multiply(reg x: Int, reg y: Int) : Int { + return x * y; +} + +fn call_add() : Int { + data result := add(10, 20); + return result; +} + +fn call_multiply() : Int { + reg a := 5; + reg b := 6; + reg result := multiply(a, b); + return result; +} + +fn chain_calls() : Int { + data sum := add(1, 2); + data sum2 := add(sum, 3); + return sum2; +} diff --git a/tests/codegen/macros.mdl b/tests/codegen/macros.mdl new file mode 100644 index 0000000..cd00bb7 --- /dev/null +++ b/tests/codegen/macros.mdl @@ -0,0 +1,18 @@ +// Test macro commands +namespace macros; + +fn simple() : Void { + data name := "test"; + $say ; +} + +fn with_reg() : Void { + reg count := 64; + $say ; +} + +fn multiple_vars() : Void { + reg x := 10; + data y := "hello"; + $tp @p 64 ; +} diff --git a/tests/codegen/nested_ns.mdl b/tests/codegen/nested_ns.mdl new file mode 100644 index 0000000..720cfb4 --- /dev/null +++ b/tests/codegen/nested_ns.mdl @@ -0,0 +1,20 @@ +// Test nested namespaces +namespace outer; + +namespace inner = { + fn helper() : Int { + return 42; + } + + namespace deep = { + fn very_deep() : Int { + return 100; + } + }; +}; + +fn main() : Int { + data x := inner.helper(); + data y := inner.deep.very_deep(); + return x + y; +} diff --git a/tests/codegen/structs.mdl b/tests/codegen/structs.mdl new file mode 100644 index 0000000..2d6ff7b --- /dev/null +++ b/tests/codegen/structs.mdl @@ -0,0 +1,34 @@ +// Test struct usage +namespace structs; + +struct Point { + x: Int, + y: Int, + z: Int +} + +struct Player { + name: String, + health: Int, + pos: Point +} + +fn create_point() : Point { + data p := Point{x: 10, y: 64, z: -100}; + return p; +} + +fn access_field() : Int { + data p := Point{x: 5, y: 10, z: 15}; + data sum := p.x + p.y + p.z; + return sum; +} + +fn nested_struct() : Int { + data player := Player{ + name: "Steve", + health: 20, + pos: Point{x: 0, y: 64, z: 0} + }; + return player.pos.y; +} diff --git a/tests/errors/type_mismatch.mdl b/tests/errors/type_mismatch.mdl new file mode 100644 index 0000000..8927f93 --- /dev/null +++ b/tests/errors/type_mismatch.mdl @@ -0,0 +1,7 @@ +// Test file with type mismatch error +namespace test; + +fn main() : Void { + data x : Int; + x = "hello"; // Type mismatch: assigning String to Int +} diff --git a/tests/errors/undefined_function.mdl b/tests/errors/undefined_function.mdl new file mode 100644 index 0000000..a8974cb --- /dev/null +++ b/tests/errors/undefined_function.mdl @@ -0,0 +1,6 @@ +// Test file with undefined function error +namespace test; + +fn main() : Void { + nonexistent_function(); +} diff --git a/tests/errors/undefined_var.mdl b/tests/errors/undefined_var.mdl new file mode 100644 index 0000000..f9439d5 --- /dev/null +++ b/tests/errors/undefined_var.mdl @@ -0,0 +1,6 @@ +// Test file with an undefined variable error +namespace test; + +fn main() : Void { + $ say ; +} diff --git a/tests/ir/bb_gen/simple/main.mcf b/tests/ir/bb_gen/simple/main.mdl similarity index 89% rename from tests/ir/bb_gen/simple/main.mcf rename to tests/ir/bb_gen/simple/main.mdl index 28293e7..96a2038 100644 --- a/tests/ir/bb_gen/simple/main.mcf +++ b/tests/ir/bb_gen/simple/main.mdl @@ -10,7 +10,7 @@ fn tmp() : Void { } fn main() : Void { - data a := [10,20,30,b]; + data a := [10,20,30,30]; data a : Int; if (a == 2) { a.add(a,a); diff --git a/tests/ir/bb_gen/stmts/assign.mcf b/tests/ir/bb_gen/stmts/assign.mdl similarity index 100% rename from tests/ir/bb_gen/stmts/assign.mcf rename to tests/ir/bb_gen/stmts/assign.mdl diff --git a/tests/ir/bb_gen/stmts/define.mcf b/tests/ir/bb_gen/stmts/define.mdl similarity index 100% rename from tests/ir/bb_gen/stmts/define.mcf rename to tests/ir/bb_gen/stmts/define.mdl diff --git a/tests/ir/bb_gen/stmts/typed_define.mcf b/tests/ir/bb_gen/stmts/typed_define.mdl similarity index 100% rename from tests/ir/bb_gen/stmts/typed_define.mcf rename to tests/ir/bb_gen/stmts/typed_define.mdl diff --git a/tests/ir/namespace/child/main.mcf b/tests/ir/namespace/child/main.mdl similarity index 100% rename from tests/ir/namespace/child/main.mcf rename to tests/ir/namespace/child/main.mdl diff --git a/tests/ir/namespace/child_import/main.mcf b/tests/ir/namespace/child_import/main.mcf deleted file mode 100644 index b146470..0000000 --- a/tests/ir/namespace/child_import/main.mcf +++ /dev/null @@ -1,3 +0,0 @@ -namespace child = { -namespace utils = "utils.mcf"; -}; diff --git a/tests/ir/namespace/child_import/main.mdl b/tests/ir/namespace/child_import/main.mdl new file mode 100644 index 0000000..eefb204 --- /dev/null +++ b/tests/ir/namespace/child_import/main.mdl @@ -0,0 +1,3 @@ +namespace child = { +namespace utils = "utils.mdl"; +}; diff --git a/tests/ir/namespace/child_import/utils.mcf b/tests/ir/namespace/child_import/utils.mcf deleted file mode 100644 index 5ba9fe6..0000000 --- a/tests/ir/namespace/child_import/utils.mcf +++ /dev/null @@ -1,2 +0,0 @@ -namespace boingo; -namespace main = "main.mcf"; diff --git a/tests/ir/namespace/child_import/utils.mdl b/tests/ir/namespace/child_import/utils.mdl new file mode 100644 index 0000000..2b1a2bc --- /dev/null +++ b/tests/ir/namespace/child_import/utils.mdl @@ -0,0 +1,2 @@ +namespace boingo; +namespace main = "main.mdl"; diff --git a/tests/ir/namespace/child_import_fail/main.mcf b/tests/ir/namespace/child_import_fail/main.mdl similarity index 56% rename from tests/ir/namespace/child_import_fail/main.mcf rename to tests/ir/namespace/child_import_fail/main.mdl index a7f856a..14c2ad2 100644 --- a/tests/ir/namespace/child_import_fail/main.mcf +++ b/tests/ir/namespace/child_import_fail/main.mdl @@ -1,4 +1,4 @@ namespace child; namespace child = { -namespace utils = "utils.mcf"; +namespace utils = "utils.mdl"; }; diff --git a/tests/ir/namespace/child_import_fail/utils.mcf b/tests/ir/namespace/child_import_fail/utils.mcf deleted file mode 100644 index 5ba9fe6..0000000 --- a/tests/ir/namespace/child_import_fail/utils.mcf +++ /dev/null @@ -1,2 +0,0 @@ -namespace boingo; -namespace main = "main.mcf"; diff --git a/tests/ir/namespace/child_import_fail/utils.mdl b/tests/ir/namespace/child_import_fail/utils.mdl new file mode 100644 index 0000000..2b1a2bc --- /dev/null +++ b/tests/ir/namespace/child_import_fail/utils.mdl @@ -0,0 +1,2 @@ +namespace boingo; +namespace main = "main.mdl"; diff --git a/tests/ir/namespace/import/a.mcf b/tests/ir/namespace/import/a.mcf deleted file mode 100644 index 8df6cb2..0000000 --- a/tests/ir/namespace/import/a.mcf +++ /dev/null @@ -1,2 +0,0 @@ -namespace a; -namespace bin = "b.mcf"; diff --git a/tests/ir/namespace/import/a.mdl b/tests/ir/namespace/import/a.mdl new file mode 100644 index 0000000..b96b927 --- /dev/null +++ b/tests/ir/namespace/import/a.mdl @@ -0,0 +1,2 @@ +namespace a; +namespace bin = "b.mdl"; diff --git a/tests/ir/namespace/import/b.mcf b/tests/ir/namespace/import/b.mcf deleted file mode 100644 index f297501..0000000 --- a/tests/ir/namespace/import/b.mcf +++ /dev/null @@ -1,2 +0,0 @@ -namespace b; -namespace ain = "a.mcf"; diff --git a/tests/ir/namespace/import/b.mdl b/tests/ir/namespace/import/b.mdl new file mode 100644 index 0000000..a2876cb --- /dev/null +++ b/tests/ir/namespace/import/b.mdl @@ -0,0 +1,2 @@ +namespace b; +namespace ain = "a.mdl"; diff --git a/tests/ir/namespace/simple/main.mcf b/tests/ir/namespace/simple/main.mdl similarity index 100% rename from tests/ir/namespace/simple/main.mcf rename to tests/ir/namespace/simple/main.mdl diff --git a/tests/ir/structs/cross_file/attributes.mcf b/tests/ir/structs/cross_file/attributes.mdl similarity index 100% rename from tests/ir/structs/cross_file/attributes.mcf rename to tests/ir/structs/cross_file/attributes.mdl diff --git a/tests/ir/structs/cross_file/main.mcf b/tests/ir/structs/cross_file/main.mdl similarity index 71% rename from tests/ir/structs/cross_file/main.mcf rename to tests/ir/structs/cross_file/main.mdl index 161f26b..14994eb 100644 --- a/tests/ir/structs/cross_file/main.mcf +++ b/tests/ir/structs/cross_file/main.mdl @@ -1,4 +1,4 @@ -namespace attrs = "attributes.mcf" +namespace attrs = "attributes.mdl" struct Person{ age : Int name : Int diff --git a/tests/ir/structs/cross_file_fail/main.mcf b/tests/ir/structs/cross_file_fail/main.mdl similarity index 67% rename from tests/ir/structs/cross_file_fail/main.mcf rename to tests/ir/structs/cross_file_fail/main.mdl index e8bd92d..398e076 100644 --- a/tests/ir/structs/cross_file_fail/main.mcf +++ b/tests/ir/structs/cross_file_fail/main.mdl @@ -1,4 +1,4 @@ -namespace materials = "materials.mcf" +namespace materials = "materials.mdl" struct Bed{ thread_count : Int material : materials.Material diff --git a/tests/ir/structs/cross_file_fail/materials.mcf b/tests/ir/structs/cross_file_fail/materials.mdl similarity index 100% rename from tests/ir/structs/cross_file_fail/materials.mcf rename to tests/ir/structs/cross_file_fail/materials.mdl diff --git a/tests/ir/structs/self_ref/main.mcf b/tests/ir/structs/self_ref/main.mdl similarity index 100% rename from tests/ir/structs/self_ref/main.mcf rename to tests/ir/structs/self_ref/main.mdl diff --git a/tests/ir/structs/simple/main.mcf b/tests/ir/structs/simple/main.mdl similarity index 100% rename from tests/ir/structs/simple/main.mcf rename to tests/ir/structs/simple/main.mdl diff --git a/tests/semantic/errors.mdl b/tests/semantic/errors.mdl new file mode 100644 index 0000000..e1825c6 --- /dev/null +++ b/tests/semantic/errors.mdl @@ -0,0 +1,19 @@ +// Test file with intentional errors for semantic checking + +// Test 1: Wrong argument count +fn greet(data name: String) : Void { + $say "Hello"; +} + +fn test_wrong_args() : Void { + greet("Alice", "Bob"); // Error: too many arguments +} + +// Test 2: Type mismatch in function call +fn add(data a: Int, data b: Int) : Int { + return a; +} + +fn test_type_mismatch() : Void { + add("hello", 5); // Error: String passed where Int expected +} diff --git a/tests/types/lists/append/append.mdl b/tests/types/lists/append/append.mdl new file mode 100644 index 0000000..c281749 --- /dev/null +++ b/tests/types/lists/append/append.mdl @@ -0,0 +1,9 @@ +fn append(data test: &List, data value: Int) : Void { + $data modify append value ; +} + +fn doomer() : Void { + data l : List; + append(&l, 10); + $say ""; +} diff --git a/utils_ir.v b/utils_ir.v index 10fd013..4ea69d5 100644 --- a/utils_ir.v +++ b/utils_ir.v @@ -211,8 +211,167 @@ pub fn (mut b IRBuilder) fn_print_inst(fid FID, iid IID) { print(inst.op) b.fn_print_oid(fid, inst.right) } - else { - print('inst is not supported yes ${inst}') + IRUnaryOp { + print('unop ') + b.fn_print_rid(fid, inst.result) + print(' = ') + print(inst.op) + print(' ') + b.fn_print_oid(fid, inst.operand) + } + IRStore { + print('store ') + b.fn_print_rid(fid, inst.result) + print(' <- ') + b.fn_print_rid(fid, inst.source) + } + IRCall { + print('call ') + if result := inst.result { + b.fn_print_rid(fid, result) + print(' = ') + } + called_func := b.functions[inst.function] + print('${called_func.name}(') + for i, arg in inst.args { + if i > 0 { + print(', ') + } + b.fn_print_oid(fid, arg) + } + print(')') + } + IRStructInit { + print('struct_init ') + b.fn_print_rid(fid, inst.result) + print(' = ${b.structs[inst.struct_type].name}{') + mut first := true + for field_name, field_val in inst.field_values { + if !first { + print(', ') + } + first = false + print('${field_name}: ') + b.fn_print_oid(fid, field_val) + } + print('}') + } + IRFieldAccess { + print('field ') + b.fn_print_rid(fid, inst.result) + print(' = ') + b.fn_print_rid(fid, inst.source) + print('.${inst.field}') + } + IRIndexAccess { + print('index ') + b.fn_print_rid(fid, inst.result) + print(' = ') + b.fn_print_rid(fid, inst.source) + print('[') + b.fn_print_oid(fid, inst.index) + if inst.is_slice { + print('..') + if end := inst.end { + b.fn_print_oid(fid, end) + } + } + print(']') + } + IRDeref { + print('deref ') + b.fn_print_rid(fid, inst.result) + print(' = @') + b.fn_print_rid(fid, inst.source) + } + IRRefInst { + print('ref ') + b.fn_print_rid(fid, inst.result) + print(' = &') + b.fn_print_rid(fid, inst.source) + } + IRJump { + print('jump ') + target := b.basic_blocks[inst.target] + print('${target.label}_${target.id}') + if inst.args.len > 0 { + print('(') + for i, arg in inst.args { + if i > 0 { + print(', ') + } + b.fn_print_oid(fid, arg) + } + print(')') + } + } + IRBranch { + print('branch ') + b.fn_print_oid(fid, inst.cond) + then_bb := b.basic_blocks[inst.then_bb] + else_bb := b.basic_blocks[inst.else_bb] + print(' ? ${then_bb.label}_${then_bb.id}') + if inst.then_args.len > 0 { + print('(') + for i, arg in inst.then_args { + if i > 0 { + print(', ') + } + b.fn_print_oid(fid, arg) + } + print(')') + } + print(' : ${else_bb.label}_${else_bb.id}') + if inst.else_args.len > 0 { + print('(') + for i, arg in inst.else_args { + if i > 0 { + print(', ') + } + b.fn_print_oid(fid, arg) + } + print(')') + } + } + IRReturn { + print('return') + if value := inst.value { + print(' ') + b.fn_print_oid(fid, value) + } + } + IRMacroLiteralCmd { + print('macro_cmd $') + for part in inst.parts { + match part { + IRMacroCmdText { + print(part.text) + } + IRMacroCmdMacro { + if part.is_ref { + print('$(') + } else { + print('#(') + } + b.fn_print_rid(fid, part.value) + print(')') + } + IRMacroCmdString { + print('"') + for str_part in part.parts { + match str_part { + IRStringText { + print(str_part.text) + } + IRStringMacro { + print('#(rid)') + } + } + } + print('"') + } + } + } } } }