diff --git a/.idea/dsa2.iml b/.idea/dsa2.iml index d44b7fe..3f76210 100644 --- a/.idea/dsa2.iml +++ b/.idea/dsa2.iml @@ -12,12 +12,14 @@ - + + + diff --git a/tooling/assembler/example.dsa b/doc/DSC/dscx.md similarity index 100% rename from tooling/assembler/example.dsa rename to doc/DSC/dscx.md diff --git a/doc/notes/functional language syntax.txt b/doc/notes/functional language syntax.txt new file mode 100644 index 0000000..f310e91 --- /dev/null +++ b/doc/notes/functional language syntax.txt @@ -0,0 +1,47 @@ +# idk + +basic stuff. +don't like haskell's reverse order of applying operations inside out, makes code hard to read. +piping >> + +// this would be the syntax to filter a list of numbers to get the evens +evens x List(Int) => x + |> filter_map (div 2 |> equals 0) + +// similarly you can use lambdas (same syntax as ordinary functions) +evens x List(Int) => x + |> filter_map (x => div 2 |> equals 0) + +is_even x Int => x |> div 2 |> equals 0 + +add_y_if_even_else_z x Int, y Int, z Int => x + |> is_even + |> then (add y) + |> else (add z) + +then Bool x, (-> T) val -> Option(T) => + // language internal + +else Option(T) val (-> T) alternative -> T => + // language internal + +type Result(T, E) => Ok(T) | Err(E) +type Option(T) => Some(T) | None +type Shape => Square(Int) | Rectangle(Int, Int) | Circle(Int) + +// we can pattern match +area_doubled Shape s -> Int => s + |> case Square (s => multiply s s) + |> case Rectangle multiply // multiply takes two integer arguments and Rectangle provides this! + |> case Circle (r => multiply r r |> multiply PI) + |> case Circle (r => r |> multiply r |> multiply PI) + |> case Circle (r => r, r |> multiply |> multiply PI) + |> case Circle (r => r, r |> (multiply >> multiply PI)) + |> multiply 2 + +chained_computation Int s -> Result(Int, ErrType) => s + |> try fallible_computation_on_int + |> try fallible_computation_on_int + +case T item, T matched_variant, (-> R) -> Option(R) => + // language internal \ No newline at end of file diff --git a/resources/bf_out.dsa b/resources/bf_out.dsa new file mode 100644 index 0000000..dd56f3d --- /dev/null +++ b/resources/bf_out.dsa @@ -0,0 +1,704 @@ + +include print: "./print.dsa" + +resb program: 0x1000 +dw stack: 0x10000 +dw input: 0x30000 + +_init: + ldw stack, bpr + mov bpr, spr +main: + lwi program, rga + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + jez acc, loop_end_99 +loop_start_99: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + jez acc, loop_end_116 +loop_start_116: + nop + subi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + subi acc, 1 + jez acc, loop_end_122 +loop_start_122: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + jnz acc, loop_start_122 +loop_end_122: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_129 +loop_start_129: + nop + addi acc, 1 + jez acc, loop_end_131 +loop_start_131: + nop + subi acc, 1 + stw acc, rga + subi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + jnz acc, loop_start_131 +loop_end_131: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + jnz acc, loop_start_129 +loop_end_129: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + jnz acc, loop_start_116 +loop_end_116: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_150 +loop_start_150: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + subi acc, 1 + jnz acc, loop_start_150 +loop_end_150: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_157 +loop_start_157: + nop + subi acc, 1 + jnz acc, loop_start_157 +loop_end_157: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + stw acc, rga + subi rga, 4 + ldw rga, acc + jez acc, loop_end_174 +loop_start_174: + nop + subi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + subi acc, 1 + jez acc, loop_end_178 +loop_start_178: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + jnz acc, loop_start_178 +loop_end_178: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_185 +loop_start_185: + nop + addi acc, 1 + jez acc, loop_end_187 +loop_start_187: + nop + subi acc, 1 + stw acc, rga + subi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + jnz acc, loop_start_187 +loop_end_187: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + jnz acc, loop_start_185 +loop_end_185: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + jnz acc, loop_start_174 +loop_end_174: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_205 +loop_start_205: + nop + subi acc, 1 + jnz acc, loop_start_205 +loop_end_205: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_210 +loop_start_210: + nop + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + push acc + call print::print_byte + pop zero + jez acc, loop_end_260 +loop_start_260: + nop + subi acc, 1 + jnz acc, loop_start_260 +loop_end_260: + nop + jnz acc, loop_start_210 +loop_end_210: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + jez acc, loop_end_265 +loop_start_265: + nop + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + push acc + call print::print_byte + pop zero + jez acc, loop_end_315 +loop_start_315: + nop + subi acc, 1 + jnz acc, loop_start_315 +loop_end_315: + nop + jnz acc, loop_start_265 +loop_end_265: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + addi acc, 1 + push acc + call print::print_byte + pop zero + jez acc, loop_end_371 +loop_start_371: + nop + subi acc, 1 + jnz acc, loop_start_371 +loop_end_371: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + push acc + call print::print_byte + pop zero + stw acc, rga + addi rga, 4 + ldw rga, acc + push acc + call print::print_byte + pop zero + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_386 +loop_start_386: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + subi acc, 1 + jnz acc, loop_start_386 +loop_end_386: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_395 +loop_start_395: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + subi acc, 1 + jnz acc, loop_start_395 +loop_end_395: + nop + stw acc, rga + addi rga, 4 + ldw rga, acc + jez acc, loop_end_405 +loop_start_405: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + addi acc, 1 + stw acc, rga + addi rga, 4 + ldw rga, acc + subi acc, 1 + jnz acc, loop_start_405 +loop_end_405: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + subi acc, 1 + jnz acc, loop_start_99 +loop_end_99: + nop + stw acc, rga + subi rga, 4 + ldw rga, acc + stw acc, rga + subi rga, 4 + ldw rga, acc + addi acc, 1 + addi acc, 1 + push acc + call print::print_byte + pop zero + push acc + call print::print_byte + pop zero + push acc + call print::print_byte + pop zero + hlt diff --git a/resources/bf_out.dsb b/resources/bf_out.dsb new file mode 100644 index 0000000..e5a022d Binary files /dev/null and b/resources/bf_out.dsb differ diff --git a/resources/brainf.bf b/resources/brainf.bf new file mode 100644 index 0000000..4adf535 --- /dev/null +++ b/resources/brainf.bf @@ -0,0 +1,29 @@ +++++++++++++++++++++++++++++++++++++++++++++ +>++++++++++++++++++++++++++++++++ +>++++++++++++++++ +> +>+ +<< +[ +>> +> +>++++++++++ +<< +[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<] +>[<+>-] +>[-] +>> +>++++++++++ +< +[->-[>+>>]>[+[-<+>]>+>>]<<<<<] +>[-] +>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] +<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] +<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-] +<<<<<<<.>. +>>[>>+<<-] +>[>+<<+>-] +>[<+>-] +<<<- +] +<<++... diff --git a/resources/dsc.dsa b/resources/dsc.dsa new file mode 100644 index 0000000..15819ed --- /dev/null +++ b/resources/dsc.dsa @@ -0,0 +1,135 @@ +// GENERATED BY DSC COMPILER +// Generated at 2026-08-09 01:17:46 + +// Imports +include alloc: "./lib/memory/block_alloc.dsa" +include print: "./print.dsa" + +// Globals & Reserved Memory + +// Entry Point +dw stack: 0x010000 +db message: "Process Exited with code:" +_init: + ldw stack, bpr, 0 + mov bpr, spr + push zero + call main + call print::newline + lwi message, rg0 + push rg0 + call print::print + pop zero + call print::print_hex_word + pop zero + hlt + +// Return +_ret: + mov bpr, spr + pop bpr + ret + +db str_4: "successful free of ptr" +// fn main() -> u32 +main: + push bpr + mov spr, bpr + lli 32, rg0 + lli 64, rg1 + // push arg 1 + push rg0 + // push arg 0 + push rg1 + call alloc::init + pop rg2 + pop zero + push rg2 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + call print::newline + ldw spr, rg0, 0 + stw rg0, spr, 0 + // push arg 0 + push rg0 + call alloc::alloc + pop rg1 + push rg1 + // push arg 0 + push rg1 + call print::print_hex_word + pop zero + lli 200, rg0 + ldw spr, rg1, 0 + stw rg0, rg1, 0 + stw rg1, spr, 0 + call print::newline + ldw spr, rg0, 4 + stw rg0, spr, 4 + // push arg 0 + push rg0 + call alloc::alloc + pop rg2 + push rg2 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + call print::newline + ldw spr, rg0, 4 + ldw rg0, rg2, 0 + stw rg0, spr, 4 + // push arg 0 + ldw spr, rg2, 4 + stw rg2, spr, 4 + addi spr, 4, rg3 + ldw spr, rg2, 8 + stw rg2, spr, 8 + // push arg 1 + push rg3 + // push arg 0 + push rg2 + call alloc::free + pop zero + pop zero + ldw spr, rg2, 8 + stw rg2, spr, 8 + // push arg 0 + push rg2 + call alloc::alloc + pop rg3 + push rg3 + call print::newline + ldw spr, rg2, 0 + stw rg2, spr, 0 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + call print::newline + ldw spr, rg2, 8 + stw rg2, spr, 8 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + ldw spr, rg2, 8 + lli 0, rg4 + ieq rg2, rg4, rg5 + jez rg5, _else_2 +_then_1: + lwi str_4, rg4 + stw rg2, spr, 8 + // push arg 0 + push rg4 + call print::print + pop zero + jmp _end_3 +_else_2: + nop +_end_3: + lli 0, rg4 + stw rg4, bpr, 8 + jmp _ret \ No newline at end of file diff --git a/resources/dsfl.dsb b/resources/dsfl.dsb new file mode 100644 index 0000000..77dcf0b Binary files /dev/null and b/resources/dsfl.dsb differ diff --git a/resources/example.dsc b/resources/example.dsc new file mode 100644 index 0000000..47ebd9d --- /dev/null +++ b/resources/example.dsc @@ -0,0 +1,30 @@ +include print: "./lib/io/print.dsa"; +include arena: "./lib/memory/arena_alloc.dsa"; + +fn main() -> u32 { + + let x: u32 = 0; + let y: u32 = &x; + + let alloc: u32 = arena::new(512); + let ptr1: u32 = arena::alloc(alloc, 32); + let ptr2: u32 = arena::alloc(alloc, 32); + + print::print_hex_word(alloc); + print::print_newline(); + print::print_hex_word(ptr1); + print::print_newline(); + print::print_hex_word(ptr2); + print::print_newline(); + print::print_num(*ptr2); + print::print_newline(); + *ptr2 = 42; + + print::print_hex_word(ptr2); + print::print_newline(); + print::print_num(*ptr2); + print::print_newline(); + print::println("end"); + + return 0; +} diff --git a/resources/hello_world.dsc b/resources/hello_world.dsc new file mode 100644 index 0000000..2f82e59 --- /dev/null +++ b/resources/hello_world.dsc @@ -0,0 +1,5 @@ +include print: "./lib/io/print.dsa"; + +fn main() { + print::print("Hello, world!"); +} diff --git a/resources/lib/memory/block_alloc.dsa b/resources/lib/memory/block_alloc.dsa new file mode 100644 index 0000000..bd1fe5b --- /dev/null +++ b/resources/lib/memory/block_alloc.dsa @@ -0,0 +1,212 @@ +// block_alloc.dsa +// Fixed-size block allocator +// +// Memory layout: +// [base + 0]: free list head pointer (pointer to first free block, or 0 if none) +// [base + 4]: block size +// [base + 8]: total blocks +// [base + 12]: base address of block pool +// [base + 16+]: block pool (each block starts with a 4-byte next pointer) +// +// Usage: +// include block_alloc "./lib/memory/block_alloc.dsa" +// +// For init: +// push num_blocks (e.g., 32) +// push block_size (e.g., 64 bytes) +// call block_alloc::init +// pop block_size +// pop num_blocks +// ; result in spr+8 (allocator handle) +// +// For alloc: +// push allocator_handle +// call block_alloc::alloc +// pop allocator_handle +// ; result in spr+8 (pointer to block, or 0 if out of memory) +// +// For free: +// push block_pointer +// push allocator_handle +// call block_alloc::free +// pop allocator_handle +// pop block_pointer + +dw heap_start: 0x30000 // Start of our heap area + +// Initialize the allocator +// Args: block_size, num_blocks +// Returns: allocator handle (pointer to metadata) +init: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // block_size + ldw bpr, rg1, 12 // num_blocks + + // Allocate metadata (16 bytes) + pool space + ldw heap_start, rg2 // base address for this allocator + mov rg2, rg3 // save base in rg3 + + // Calculate total size needed: 16 + (block_size * num_blocks) + // We'll use a simple multiplication by repeated addition + mov rg0, rg4 // block_size to rg4 + mov rg1, rg5 // num_blocks to rg5 + lli 0, acc // accumulator for multiplication + +_multiply_loop: + jez rg5 _multiply_done + add acc, rg4, acc + subi rg5, 1 + jmp _multiply_loop + +_multiply_done: + // acc now contains block_size * num_blocks + addi acc, 16 // add metadata size + + // Update heap_start for next allocation + add rg2, acc, acc + stw acc, heap_start + + // Now set up metadata at rg3 (base) + // [base + 0]: free list head (will point to first block) + // [base + 4]: block_size + // [base + 8]: total blocks + // [base + 12]: pool base address + + addi rg3, 16, rg6 // rg6 = pool base + + stw rg6, rg3 // store pool base as free list head initially + stw rg0, rg3, 4 // store block_size + stw rg1, rg3, 8 // store total blocks + stw rg6, rg3, 12 // store pool base address + + // Now initialize the free list + // Each block's first 4 bytes point to the next block + // rg6 = current block pointer + // rg0 = block_size + // rg1 = num_blocks (counter) + + subi rg1 1 // we'll count down from num_blocks-1 + +_init_loop: + jez rg1, _init_loop_done + + // Calculate next block address: current + block_size + add rg6, rg0, rg7 // rg7 = next block address + + // Store next pointer at current block + stw rg7, rg6 + + // Move to next block + mov rg7, rg6 + subi rg1, 1 + jmp _init_loop + +_init_loop_done: + // Last block points to null (0) + lli 0, acc + stw acc, rg6 + + // Return allocator handle (base address - 16 to get back to metadata start) + stw rg3, bpr, 8 + + mov bpr, spr + pop bpr + ret + + +// Allocate a block +// Args: allocator_handle +// Returns: pointer to block (or 0 if out of memory) +alloc: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // allocator handle (metadata base) + + // Load free list head + ldw rg0, rg1 // rg1 = free list head + + // Check if free list is empty + jez, rg1 _alloc_fail + + // Free list is not empty, pop the first block + // Load the next pointer from the block we're allocating + ldw rg1, rg2 // rg2 = next free block + + // Update free list head to point to next block + stw rg2, rg0 + + // Return the allocated block (rg1) + stw rg1, bpr, 8 + jmp _alloc_done + +_alloc_fail: + // No free blocks, return 0 + lli 0, acc + stw acc, bpr, 8 + +_alloc_done: + mov bpr, spr + pop bpr + ret + + +// Free a block +// Args: allocator_handle, block_pointer +// Returns: nothing (but could return error code if block is invalid) +free: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // allocator handle + ldw bpr, rg6, 12 // pointer to the block pointer to free + ldw rg6, rg1 // rg1 = block pointer to free + + // Load current free list head + ldw rg0, rg2 // rg2 = current head + + // Set the freed block's next pointer to current head + stw rg2, rg1 + + // Update free list head to point to freed block + stw rg1, rg0 + + // Update the freed block's previous pointer to NULL + lli 0, rg1 + stw rg1, rg6 + + mov bpr, spr + pop bpr + ret + + +// Debug function: get stats +// Args: allocator_handle +// Returns: nothing (but could populate a stats structure) +get_stats: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // allocator handle + + // Count free blocks by traversing the free list + ldw rg0, rg1 // rg1 = free list head + lli 0, rg2 // rg2 = counter + +count_loop: + jez, rg1 count_done + + addi rg2, 1 + ldw rg1, rg1 // move to next block + jmp count_loop + +count_done: + // rg2 now contains number of free blocks + // Could store this somewhere or return it + stw rg2, bpr, 8 + + mov bpr, spr + pop bpr + ret diff --git a/resources/main.dsc b/resources/main.dsc new file mode 100644 index 0000000..72cfd71 --- /dev/null +++ b/resources/main.dsc @@ -0,0 +1,37 @@ +include print: "./lib/io/print.dsa"; +include alloc: "./lib/memory/block_alloc.dsa"; + +fn main() -> u32 { + let allocator: u32 = alloc::init(64, 32); + + print::print_hex_word(allocator); + + print::print_newline(); + + let ptr: u32 = alloc::alloc(allocator); + print::print_hex_word(ptr); + *ptr = 200; + + print::print_newline(); + + let p2: u32 = alloc::alloc(allocator); + print::print_hex_word(p2); + print::print_newline(); + print::print_num(*ptr); + + alloc::free(allocator, &ptr); + let ptr3: u32 = alloc::alloc(allocator); + + print::print_newline(); + + print::print_hex_word(ptr3); + + print::print_newline(); + print::print_hex_word(ptr); + + if ptr == 0 { + print::print("successful free of ptr"); + } + + return 0; +} \ No newline at end of file diff --git a/resources/print.dsa b/resources/print.dsa index 24be7d8..5398fad 100644 --- a/resources/print.dsa +++ b/resources/print.dsa @@ -129,42 +129,67 @@ print_byte: func // ------------------------------------------ // prints the value of arg[0] to the screen in hex. -// print_hex_word: -// push bpr -// mov spr, bpr +print_hex_word: func + push rg0 + push rg1 + push rg2 + push rg3 + push rg4 -// ldw current, rg1 + ldw current, rg1 + ldb bpr, rg0, 11 + push rg0 + call _print_hex_byte + addi spr, 4 -// ldb bpr, rg0, 8 -// push rg0 -// call _print_hex_byte -// addi spr, 4 + ldb bpr, rg0, 10 + push rg0 + call _print_hex_byte + addi spr, 4 -// ldb bpr, rg0, 9 -// push rg0 -// call _print_hex_byte -// addi spr, 4 + ldb bpr, rg0, 9 + push rg0 + call _print_hex_byte + addi spr, 4 -// ldb bpr, rg0, 10 -// push rg0 -// call _print_hex_byte -// addi spr, 4 + ldb bpr, rg0, 8 + push rg0 + call _print_hex_byte + addi spr, 4 -// ldb bpr, rg0, 11 -// push rg0 -// call _print_hex_byte -// addi spr, 4 + // save cursor + stw rg1, current -// jmp _end + pop rg4 + pop rg3 + pop rg2 + pop rg1 + pop rg0 + return // ------------------------------------------ // prints the last byte of arg[0] to the screen in hex. print_hex_byte: func + push rg0 + push rg1 + push rg2 + push rg3 + push rg4 + ldw bpr, rg0, 8 ldw current, rg1 call _print_hex_byte - jmp _end + + // save cursor + stw rg1, current + + pop rg4 + pop rg3 + pop rg2 + pop rg1 + pop rg0 + return // function body _print_hex_byte: @@ -183,14 +208,18 @@ _print_hex_byte: // print a hex digit _print_hex_nibble: lli 10, rg3 + ilt rg0, rg3, rg4 jnz rg4, _print_hex_nibble_number + + // print a character 'A'..='F' by applying offset 0x37 ('A' - 10) addi rg0, 0x37, rg0 stb rg0, rg1 addi rg1, 1 ret // helper function. +// prints a decimal digit by adding 0x30 ('0') _print_hex_nibble_number: addi rg0, 0x30, rg0 stb rg0, rg1 @@ -200,11 +229,21 @@ _print_hex_nibble_number: // ------------------------------------------ // print whitespace print_whitespace: func + push rg0 + push rg1 + ldw current, rg1 lli 0x20, rg0 stb rg0, rg1 addi rg1, 1 - jmp _end + + // save cursor + stw rg1, current + + pop rg1 + pop rg0 + return + // ------------------------------------------ // print newline diff --git a/resources/program.dsa b/resources/program.dsa new file mode 100644 index 0000000..d833342 --- /dev/null +++ b/resources/program.dsa @@ -0,0 +1,76 @@ + +include print: "./print.dsa" +dw stack: 0x10000 + +_init: + ldw stack, bpr + mov bpr, spr +main: + push zero + call program + call print::print_hex_word + pop zero + hlt + +program: func + push rg0 + push rg1 + lli 5, rg0 + push rg0 + lli 4, rg0 + push rg0 + pop rg1 + pop rg0 + sub rg0, rg1, rg0 + push rg0 + lli 4, rg0 + push rg0 + lli 3, rg0 + push rg0 + pop rg1 + pop rg0 + sub rg0, rg1, rg0 + push rg0 + lli 3, rg0 + push rg0 + lli 2, rg0 + push rg0 + pop rg1 + pop rg0 + sub rg0, rg1, rg0 + push rg0 + lli 2, rg0 + push rg0 + lli 1, rg0 + push rg0 + pop rg1 + pop rg0 + sub rg0, rg1, rg0 + push rg0 + lli 60, rg0 + push rg0 + pop rg1 + pop rg0 + add rg0, rg1, rg0 + push rg0 + pop rg1 + pop rg0 + add rg0, rg1, rg0 + push rg0 + pop rg1 + pop rg0 + add rg0, rg1, rg0 + push rg0 + pop rg1 + pop rg0 + add rg0, rg1, rg0 + push rg0 + + // return value in bpr+8 + pop rg0 + stw rg0, bpr, 8 + + // epilogue + pop rg1 + pop rg0 + return diff --git a/resources/program.dsfl b/resources/program.dsfl index 491727a..1cdec80 100644 --- a/resources/program.dsfl +++ b/resources/program.dsfl @@ -1 +1,13 @@ -(add 1 (or 1 (if true then 2 else 1))) \ No newline at end of file +(add + (sub 5 4) + (add + (sub 4 3) + (add + (sub 3 2) + (add + (sub 2 1) + 60 + ) + ) + ) +) \ No newline at end of file diff --git a/resources/test.dsc b/resources/test.dsc new file mode 100644 index 0000000..f13f9a4 --- /dev/null +++ b/resources/test.dsc @@ -0,0 +1,20 @@ +include print: "./lib/io/print.dsa"; + +fn main() -> u32 { + let x: u32 = 30; + print::print_num(x); + + 200 + 5; + + let p: Point = Point { + x: 10, + y: 20, + test: [2, 3, 4] + }; +} + +struct Point { + x: u32, + y: u32, + test: [u32; 3], +} diff --git a/resources/testprint.dsa b/resources/testprint.dsa new file mode 100644 index 0000000..5369ad6 --- /dev/null +++ b/resources/testprint.dsa @@ -0,0 +1,13 @@ +include print: "./print.dsa" + +dw stack: 0x10000 + +_init: + lwi stack, bpr + mov bpr, spr + + lwi 0x100abcaa, rg0 + push rg0 + call print::print_hex_word + pop zero + hlt \ No newline at end of file diff --git a/tooling/assembler/src/assembler/mod.rs b/tooling/assembler/src/assembler/mod.rs index c9e0876..7a203f2 100644 --- a/tooling/assembler/src/assembler/mod.rs +++ b/tooling/assembler/src/assembler/mod.rs @@ -39,18 +39,18 @@ pub use self::{ #[derive(Debug)] pub struct Assembler { - src_path: PathBuf, result_tx: mpsc::Sender, AssembleError>>, result_rx: Option, AssembleError>>>, is_running: bool, + filename: String, } impl Assembler { #[must_use] - pub fn new(src_path: impl Into) -> Self { + pub fn new(filename: impl Into) -> Self { let (tx, rx) = mpsc::channel(); Self { - src_path: src_path.into(), + filename: filename.into(), result_tx: tx, result_rx: Some(rx), is_running: false, @@ -59,21 +59,20 @@ impl Assembler { } impl TraceableProcess for Assembler { + type Input = PathBuf; type Output = DseExecutable; type Error = AssembleError; - fn run(mut self: Box, ctx: ProcessAgent) -> Result { - assemble(&self.src_path, &ctx) + fn label(&self) -> String { + format!("Assembling {}", self.filename) } - fn label(&self) -> String { - format!( - "Assembling {}", - self.src_path - .file_name() - .expect("a valid path must have a file name") - .to_string_lossy() - ) + fn run( + self: Box, + input: impl Into, + ctx: ProcessAgent, + ) -> Result { + assemble(&input.into(), &ctx) } } diff --git a/tooling/assembler/src/main.rs b/tooling/assembler/src/main.rs index f747621..f94c6ec 100644 --- a/tooling/assembler/src/main.rs +++ b/tooling/assembler/src/main.rs @@ -17,8 +17,11 @@ fn main() { // Parse command line arguments let args = Args::parse(); - let assembler = Assembler::new(PathBuf::from(args.input_path)); - let result = assembler.run_sync().expect("assembler failed."); + let assembler = Assembler::new(args.input_path.file_name().unwrap().to_string_lossy()); + let result = assembler + .run_sync(&args.input_path) + .expect("assembler failed."); + let mut buffer = Cursor::new(Vec::new()); result.write(&mut buffer).unwrap(); diff --git a/tooling/bf_compiler/Cargo.toml b/tooling/bf_compiler/Cargo.toml new file mode 100644 index 0000000..ee6e37a --- /dev/null +++ b/tooling/bf_compiler/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bf_compiler" +edition.workspace = true +version.workspace = true +authors.workspace = true + +[[bin]] +name = "dsabf" +path = "src/main.rs" + +[lib] +name = "dsabf_compiler" +path = "src/lib.rs" + +[dependencies] +clap = { version = "4.6.0", features = ["derive"] } +common = { path = "../../libraries/common" } +dsa_formats = { path = "../../libraries/dsa_formats" } +dsa_tasks = { path = "../../libraries/dsa_tasks" } \ No newline at end of file diff --git a/tooling/bf_compiler/src/lib.rs b/tooling/bf_compiler/src/lib.rs new file mode 100644 index 0000000..7037b8f --- /dev/null +++ b/tooling/bf_compiler/src/lib.rs @@ -0,0 +1,162 @@ +use dsa_tasks::{ProcessAgent, TraceableProcess}; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub struct BfError(&'static str); +impl std::error::Error for BfError {} +impl Display for BfError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +#[derive(Debug)] +pub struct BfCompiler { + filename: String, +} + +impl BfCompiler { + pub fn new(filename: impl Into) -> Self { + Self { + filename: filename.into(), + } + } +} + +impl TraceableProcess for BfCompiler { + type Input = String; + type Output = String; + type Error = BfError; + + fn label(&self) -> String { + format!("Compiling {}", self.filename) + } + + fn run( + self: Box, + input: impl Into, + ctx: ProcessAgent, + ) -> Result { + ctx.log(format!("Building {}", self.filename)); + + let asm_output = parse(&input.into(), &ctx); + + ctx.log(format!("Successfully compiled {}", self.filename)); + Ok(asm_output) + } +} + +#[must_use] +#[expect(clippy::too_many_lines)] +pub fn parse(src: &str, ctx: &ProcessAgent) -> String { + let mut result = String::new(); + + ctx.log("Parsing Source"); + let tokens = lex(src); + + ctx.log("Building Header"); + + result.push_str( + " +include print: \"./print.dsa\" + +resb program: 0x1000 +dw stack: 0x10000 +dw input: 0x30000 + +_init: + ldw stack, bpr + mov bpr, spr +main: + lwi program, rga\n", + ); + + ctx.log("Building Program"); + + // let _id = 0; + let mut idstack = Vec::::new(); + + for (id, tok) in tokens.iter().enumerate() { + match tok { + BfToken::Inc => result.push_str("\taddi acc, 1\n"), + BfToken::Dec => result.push_str("\tsubi acc, 1\n"), + BfToken::IncPtr => { + result.push_str("\tstw acc, rga\n"); + result.push_str("\taddi rga, 4\n"); + result.push_str("\tldw rga, acc\n"); + } + BfToken::DecPtr => { + result.push_str("\tstw acc, rga\n"); + result.push_str("\tsubi rga, 4\n"); + result.push_str("\tldw rga, acc\n"); + } + BfToken::Out => { + result.push_str("\tpush acc\n"); + result.push_str("\tcall print::print_byte\n"); + result.push_str("\tpop zero\n"); + } + // Read a byte from input and store it at the current data pointer + // Assuming we have an input function mapped to a specific memory location or I/O port + BfToken::In => result.push_str("\tldw input acc\n"), + BfToken::Forward => { + // Start of loop [ + let loop_start = format!("loop_start_{id}"); + let loop_end = format!("loop_end_{id}"); + + // Push the current position for the matching ] + idstack.push(id as u32); + + // Load current cell value and check if zero + result.push_str(&format!("\tjez acc, {loop_end}\n")); + + result.push_str(&format!("{loop_start}:\n")); + result.push_str("\tnop\n"); + } + BfToken::Back => { + // End of loop ] + if let Some(start_id) = idstack.pop() { + let loop_start = format!("loop_start_{start_id}"); + let loop_end = format!("loop_end_{start_id}"); + + result.push_str(&format!("\tjnz acc, {loop_start}\n")); + result.push_str(&format!("{loop_end}:\n")); + result.push_str("\tnop\n"); + } else { + // Unmatched closing bracket - could add error handling here + eprintln!("Warning: Unmatched ']' at position {id}"); + } + } + } + } + + result.push_str("\thlt\n"); + + result +} + +enum BfToken { + Inc, + Dec, + IncPtr, + DecPtr, + Out, + In, + Forward, + Back, +} + +fn lex(src: &str) -> Vec { + src.chars() + .filter_map(|c| match c { + '+' => Some(BfToken::Inc), + '-' => Some(BfToken::Dec), + '>' => Some(BfToken::IncPtr), + '<' => Some(BfToken::DecPtr), + '.' => Some(BfToken::Out), + ',' => Some(BfToken::In), + '[' => Some(BfToken::Forward), + ']' => Some(BfToken::Back), + _ => None, + }) + .collect() +} diff --git a/tooling/bf_compiler/src/main.rs b/tooling/bf_compiler/src/main.rs new file mode 100644 index 0000000..d76e181 --- /dev/null +++ b/tooling/bf_compiler/src/main.rs @@ -0,0 +1,36 @@ +use clap::Parser; +use dsa_tasks::TraceableProcess; +use dsabf_compiler::BfCompiler; +use std::path::PathBuf; + +#[derive(Parser, Debug, Clone)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(short = 'i')] + pub input_path: PathBuf, + #[arg(short = 'o')] + pub output_path: PathBuf, +} + +fn main() { + // Parse command line arguments + let args = Args::parse(); + + let bf_src = std::fs::read_to_string(&args.input_path).unwrap(); + let result = BfCompiler::new( + args.input_path + .file_stem() + .unwrap() + .to_string_lossy() + .to_string(), + ) + .run_sync(bf_src) + .unwrap(); + + println!("Successfully compiled {}", args.input_path.display()); + println!("Writing result to {}", args.output_path.display()); + + std::fs::write(&args.output_path, result).unwrap(); + + println!("Success"); +} diff --git a/tooling/compiler/Cargo.toml b/tooling/compiler/Cargo.toml deleted file mode 100644 index 1a32c86..0000000 --- a/tooling/compiler/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "compiler" -edition.workspace = true -version.workspace = true -authors.workspace = true - -[dependencies] diff --git a/tooling/compiler/src/lib.rs b/tooling/compiler/src/lib.rs deleted file mode 100644 index b93cf3f..0000000 --- a/tooling/compiler/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/tooling/dscs_compiler/Cargo.toml b/tooling/dscs_compiler/Cargo.toml new file mode 100644 index 0000000..b05daf3 --- /dev/null +++ b/tooling/dscs_compiler/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dscs_compiler" +edition.workspace = true +version.workspace = true +authors.workspace = true + +[dependencies] +clap = { version = "4.6.0", features = ["derive"] } +common = { path = "../../libraries/common" } +dsa_formats = { path = "../../libraries/dsa_formats" } +dsa_tasks = { path = "../../libraries/dsa_tasks" } +assembler = { path = "../assembler" } +chrono = "0.4.45" \ No newline at end of file diff --git a/tooling/dscs_compiler/src/backend/codegen.rs b/tooling/dscs_compiler/src/backend/codegen.rs new file mode 100644 index 0000000..0fd59c2 --- /dev/null +++ b/tooling/dscs_compiler/src/backend/codegen.rs @@ -0,0 +1,883 @@ +use super::instruction::{InsBlock as IB, Instruction as I, Label}; +use super::registers::{Register, RegisterAllocator}; +use crate::backend::instruction::Comparison::{ + Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual, +}; +use crate::model::{ + AssignmentOperator, BinaryOperator, Call, CompilerError, ConstExpr, Declaration, Dependency, + Expression, Number, Program, Statement, TypeId, UnaryOperator, Variable, +}; +use chrono::{DateTime, Local}; +use dsa_tasks::ProcessAgent; +use std::collections::HashMap; +use std::sync::atomic::AtomicU32; +use std::time::SystemTime; + +pub struct CodeGenerator { + ast: Program, + imports: HashMap, + globals: HashMap, + functions: Vec, + symbols: Vec, + allocator: RegisterAllocator, +} + +impl CodeGenerator { + pub fn new(ast: Program) -> Self { + CodeGenerator { + ast, + imports: HashMap::new(), + globals: HashMap::new(), + functions: Vec::new(), + symbols: Vec::new(), + allocator: RegisterAllocator::new(), + } + } + + pub fn include(&mut self, name: impl Into, path: impl Into) { + let name = name.into(); + self.imports.insert(name.clone(), I::include(name, path)); + } + + fn is_global(&self, name: &str) -> bool { + // Check if this variable is in the globals list + self.globals.contains_key(name) + } + + pub fn generate(&mut self, ctx: &ProcessAgent) -> Result { + // always include the print library for debugging! + self.include("print", "./lib/print.dsa"); + + for block in self.ast.clone().declarations { + match block { + Declaration::Variable { + var: Variable { name, .. }, + .. + } => self.symbols.push(name), + Declaration::Function { name, .. } => self.symbols.push(name), + Declaration::Dependency(Dependency { name, .. }) => self.symbols.push(name), + Declaration::Struct { .. } => {} /* we can't do any code generation for + * a struct yet. we may need to later + * once these become class-like + * objects with implementations */ + } + } + + for block in self.ast.clone().declarations { + self.generate_block(block.clone())?; + } + + let assembly = self.generate_layout()?; + Ok(assembly + .iter() + .map(|i| i.to_string()) + .collect::>() + .join("\n")) + } + + fn generate_layout(&mut self) -> Result { + let datetime: DateTime = SystemTime::now().into(); + + let mut block = IB::new(); + + block.extend(vec![ + I::global_comment(format!( + "GENERATED BY DSC COMPILER + Generated at {}", + datetime.format("%Y-%m-%d %H:%M:%S") + )), + I::Newline, + I::global_comment("Imports"), + ]); + + block.extend(self.imports.values().cloned().collect::>()); + + block.extend(vec![ + I::Newline, + I::global_comment("Globals & Reserved Memory"), + ]); + + block.extend(self.globals.values().cloned().collect::>()); + + block.extend(vec![ + I::Newline, + I::global_comment("Entry Point"), + I::db_word("stack", 0x10000), + I::db_string("message", "Process Exited with code:"), + // init function for stack setup. + I::label("_init"), + I::ldw_label("stack", Register::Bpr), + I::mov(Register::Bpr, Register::Spr), + I::push(Register::Zero), + I::call("main"), + I::call("print::print_newline"), + I::lwi_label("message", Register::Rg0), + I::push(Register::Rg0), + I::call("print::print"), + I::pop(Register::Zero), + I::call("print::print_hex_word"), + I::pop(Register::Zero), + I::Hlt, + ]); + + block.extend(vec![ + I::Newline, + // default return block boilerplate + I::global_comment("Return"), + I::label("_ret"), + I::mov(Register::Bpr, Register::Spr), + I::pop(Register::Bpr), + I::Return, + ]); + + for function in self.functions.iter() { + block.extend(function.iter().cloned()); + } + + Ok(block) + } + + fn generate_global(&mut self, name: &str, init: Option) { + let init = init.unwrap_or(ConstExpr::Number(0)); + match init { + ConstExpr::Number(value) => { + self.globals + .insert(name.to_string(), I::db_word(name, value as u32)); + } + ConstExpr::String(str) => { + self.globals + .insert(name.to_string(), I::db_string(name, str)); + } + } + } + + fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> { + match block { + Declaration::Variable { var, init, .. } => self.generate_global(&var.name, init), + Declaration::Function { + name, + params, + body, + return_type, + } => { + let func = self.generate_function(&name, ¶ms, &body, return_type); + self.functions.push(func); + } + Declaration::Dependency(Dependency { name, path }) => { + self.include(name, path); + } + Declaration::Struct { .. } => {} /* can't do any codegen for these yet, + * they're just types. */ + }; + + Ok(()) + } + + // Example: Generate code for a function + fn generate_function( + &mut self, + name: &str, + params: &[Variable], + body: &[Statement], + return_type: TypeId, + ) -> IB { + let mut code = IB::new(); + + // Reset allocator for new function + self.allocator.reset(); + + let fmtparams = params + .iter() + .map(|p| format!("{}: {}", p.name, p.type_id)) + .collect::>() + .join(", "); + + code.extend(vec![ + I::global_comment(format!("fn {name}({fmtparams}) -> {return_type}")), + I::label(name), + I::push(Register::Bpr), + I::mov(Register::Spr, Register::Bpr), + ]); + + // Allocate parameters to registers or stack locations + for (i, param) in params.iter().enumerate() { + let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8 + // Track that this parameter is at a stack location + let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap(); + code.append(load_code); + code.push(I::ldw_reg_offset(Register::Bpr, reg, offset)); + } + + // Generate code for function body + for stmt in body { + let stmt_code = self.generate_statement(stmt, &mut code).unwrap(); + code.append(stmt_code); + } + + // automatically return at function end + if let Some(x) = code.iter().last() + && let I::Jmp { target: Label(val) } = x + && val == "_ret" + { + } else { + code.push(I::jmp("_ret")); + } + + code.insert(0, I::Newline); + + code + } + + // Example: Generate code for a statement + fn generate_statement( + &mut self, + stmt: &Statement, + func_body: &mut IB, + ) -> Result { + let mut code = IB::new(); + + match stmt { + Statement::Declaration { var, value } => { + if let Some(expr) = value { + // Evaluate expression + let (result_reg, expr_code) = + self.generate_expression(expr, true, func_body)?; + code.append(expr_code); + + // Store result in variable + let store_code = self.allocator.store_var(&var.name, &result_reg); + code.append(store_code); + + // Free temporary register + self.allocator.free_temp(result_reg); + } else { + // Just declaring variable without initialization + self.allocator.alloc_var(&var.name)?; + } + } + + Statement::Break => unimplemented!("need scope tracking first!"), + Statement::Continue => unimplemented!("need scope tracking first!"), + Statement::Defer(_func) => unimplemented!("we need scope tracking first!"), + + Statement::PtrWrite { ptr, value } => { + let (result_reg, expr_code) = self.generate_expression(value, true, func_body)?; + code.append(expr_code); + + let (ptr_reg, ptr_code) = self.generate_expression(ptr, true, func_body)?; + code.append(ptr_code); + + code.push(I::stw_reg(result_reg, ptr_reg)); + + self.allocator.free_temp(result_reg); + self.allocator.free_temp(ptr_reg); + } + + Statement::Assign { + varname, + value, + operator, + } => { + // Evaluate expression + let (result_reg, expr_code) = self.generate_expression(value, true, func_body)?; + code.append(expr_code); + + if *operator == AssignmentOperator::Assign { + // Check if this is a global variable + if self.is_global(varname) { + // Store to global label + code.push(I::stw_label(result_reg, varname.clone())) + } else { + // Store result in local variable + let store_code = self.allocator.store_var(varname, &result_reg); + code.append(store_code); + } + + // Free temporary register + self.allocator.free_temp(result_reg); + + return Ok(code); + } + + // for more complex assignment cases we need an intermediate register. + let (temp_reg, temp_code) = self.allocator.alloc_temp()?; + code.append(temp_code); + + // fetch the value of the variable + let var_reg = if self.is_global(varname) { + let instruction = I::ldw_label(varname.clone(), temp_reg); + code.push(instruction); + temp_reg + } else { + let (rg, block) = self.allocator.load_var(varname)?; + code.append(block); + rg + }; + + let assign_code = match operator { + AssignmentOperator::Assign => { + unreachable!("assignment was already checked earlier.") + } + AssignmentOperator::AddAssign => I::add(var_reg, result_reg, temp_reg), + AssignmentOperator::SubAssign => I::sub(var_reg, result_reg, temp_reg), + AssignmentOperator::MulAssign => { + return Err(CompilerError::Unimplemented( + "TODO: implement multiplication for assignment".to_string(), + )); + } + AssignmentOperator::DivAssign => { + return Err(CompilerError::Unimplemented( + "TODO: write proper div function for DSA".to_string(), + )); + } + AssignmentOperator::ModAssign => { + return Err(CompilerError::Unimplemented( + "TODO: write proper mod function for DSA".to_string(), + )); + } + AssignmentOperator::AndAssign => I::and(var_reg, result_reg, temp_reg), + AssignmentOperator::OrAssign => I::or(var_reg, result_reg, temp_reg), + AssignmentOperator::XorAssign => I::xor(var_reg, result_reg, temp_reg), + AssignmentOperator::LeftShiftAssign => { + // this is only useful if we optimise out the register allocation + // inside value. + // if let Expression::Number { value, .. } = *value { + // I::shl(var_reg, value, temp_reg) + // } + I::shl(var_reg, result_reg, 0, temp_reg) + } + AssignmentOperator::RightShiftAssign => { + // this is only useful if we optimise out the register allocation + // if let Expression::Number { value, .. } = *value { + // I::shr(var_reg, value, temp_reg) + // } + I::shr(var_reg, result_reg, 0, temp_reg) + } + }; + code.push(assign_code); + + // Check if this is a global variable + if self.is_global(varname) { + // Store to global label + code.push(I::stw_label(temp_reg, varname.clone())) + } else { + // Store result in local variable + let store_code = self.allocator.store_var(varname, &temp_reg); + code.append(store_code); + } + + self.allocator.free_temp(result_reg); + self.allocator.free_temp(temp_reg); + } + + Statement::Return(expr) => { + if let Some(e) = expr { + let (result_reg, expr_code) = self.generate_expression(e, true, func_body)?; + code.append(expr_code); + code.push(I::stw_reg_offset(result_reg, Register::Bpr, 8)); + code.push(I::jmp("_ret")); + self.allocator.free_temp(result_reg); + } + } + + Statement::If { + condition, + then_stmt, + else_stmt, + } => { + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition, true, func_body)?; + code.append(cond_code); + + // Generate unique labels + let then_label = format!("_then_{}", self.get_unique_label()); + let else_label = format!("_else_{}", self.get_unique_label()); + let end_label = format!("_end_{}", self.get_unique_label()); + + // Jump to else if condition is false + code.push(I::jez(cond_reg, else_label.clone())); + self.allocator.free_temp(cond_reg); + + // Then block + code.push(I::label(then_label)); + for s in then_stmt { + code.append(self.generate_statement(s, func_body)?); + } + + if then_stmt.is_empty() { + code.push(I::Nop); + } + + code.push(I::jmp(end_label.clone())); + + // Else block + code.push(I::label(else_label)); + for s in else_stmt { + code.append(self.generate_statement(s, func_body)?); + } + + if else_stmt.is_empty() { + code.push(I::Nop); + } + + code.push(I::label(end_label)); + } + + Statement::While { condition, body } => { + let loop_start = format!("_while_start_{}", self.get_unique_label()); + let loop_end = format!("_while_end_{}", self.get_unique_label()); + + code.push(I::label(&loop_start)); + + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition, true, func_body)?; + code.append(cond_code); + + code.push(I::jez(cond_reg, loop_end.clone())); + self.allocator.free_temp(cond_reg); + + // Loop body + for s in body { + code.append(self.generate_statement(s, func_body)?); + } + + code.push(I::jmp(loop_start)); + code.push(I::label(loop_end)); + } + + Statement::Loop(body) => { + let loop_start = format!("_loop_start_{}", self.get_unique_label()); + code.push(I::label(&loop_start)); + + for s in body { + code.append(self.generate_statement(s, func_body)?); + } + + code.push(I::jmp(loop_start)); + } + + Statement::Expression { expr } => { + let (result_reg, expr_code) = self.generate_expression(expr, false, func_body)?; + code.append(expr_code); + self.allocator.free_temp(result_reg); + } + + Statement::Block(statements) => { + for s in statements { + code.append(self.generate_statement(s, func_body)?); + } + } + } + + Ok(code) + } + + // Example: Generate code for an expression + // Returns (register containing result, assembly code) + fn generate_expression( + &mut self, + expr: &Expression, + use_result: bool, + func_body: &mut IB, + ) -> Result<(Register, IB), CompilerError> { + let mut code = IB::new(); + + match expr { + Expression::Empty => Ok((Register::Null, code)), + + Expression::Number(n) => match n { + Number::Signed(value, _) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.append(alloc_code); + + // Load immediate value + code.push(I::lwi(*value as u32, reg)); + + Ok((reg, code)) + } + Number::Unsigned(value, _) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.append(alloc_code); + + // Load immediate value + code.push(I::lwi(*value as u32, reg)); + + Ok((reg, code)) + } + }, + + Expression::CharLiteral(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.append(alloc_code); + + // Load immediate value + code.push(I::comment(format!("char literal '{value}'"))); + code.push(I::lwi(*value as u32, reg)); + + Ok((reg, code)) + } + + Expression::StringLiteral(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.append(alloc_code); + + // write string into memory + let uuid = self.get_unique_label(); + + func_body.insert(0, I::db_string(format!("str_{uuid}"), value)); + + // Load pointer to string + code.push(I::lwi_label(format!("str_{uuid}"), reg)); + + Ok((reg, code)) + } + + Expression::ArrayLiteral { elements, type_id } => todo!(), + Expression::StructLiteral { + name, + fields, + type_id, + } => todo!(), + + Expression::Variable { name, .. } => { + if self.is_global(&name.name) { + // Allocate a temporary register for the global + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.append(alloc_code); + + // Load from global label + code.push(I::ldw_label(name.name.clone(), reg)); + + Ok((reg, code)) + } else { + // Local variable - use existing allocator logic + let (reg, load_code) = self.allocator.load_var(&name.name)?; + code.append(load_code); + Ok((reg, code)) + } + } + + Expression::Binary { + op, left, right, .. + } => { + // Evaluate left operand + let (left_reg, left_code) = self.generate_expression(left, true, func_body)?; + code.append(left_code); + + // Evaluate right operand + let (right_reg, right_code) = self.generate_expression(right, true, func_body)?; + code.append(right_code); + + // Allocate result register + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.append(result_alloc); + + // Generate operation + match op { + BinaryOperator::Add => { + code.push(I::add(left_reg, right_reg, result_reg)); + } + BinaryOperator::Sub => { + code.push(I::sub(left_reg, right_reg, result_reg)); + } + BinaryOperator::Mul => { + self.include("maths", "./lib/maths/core.dsa"); + // Call multiply function + code.push(I::push(right_reg)); + code.push(I::push(left_reg)); + code.push(I::call("maths::multiply")); + code.push(I::pop(result_reg)); + code.push(I::pop(Register::Zero)); + } + BinaryOperator::Div => { + return Err(CompilerError::Unimplemented( + "TODO: write proper div function for DSA".to_string(), + )); + // self.include("maths", "./lib/maths/core.dsa"); + // // Call divide function + // code.push(format!("\tpush {}", right_reg)); + // code.push(format!("\tpush {}", left_reg)); + // code.push("\tcall maths::divide".to_string()); + // code.push(format!("\tpop {}", result_reg)); + // code.push("\tpop zero".to_string()); + } + BinaryOperator::Mod => { + return Err(CompilerError::Unimplemented( + "TODO: write proper mod function for DSA".to_string(), + )); + // self.include("maths", "./lib/maths/core.dsa"); + // // Call modulo function + // code.push(format!("\tpush {}", right_reg)); + // code.push(format!("\tpush {}", left_reg)); + // code.push("\tcall maths::modulo".to_string()); + // code.push(format!("\tpop {}", result_reg)); + // code.push("\tpop zero".to_string()); + } + BinaryOperator::BitwiseAnd => { + code.push(I::and(left_reg, right_reg, result_reg)); + } + BinaryOperator::BitwiseOr => { + code.push(I::or(left_reg, right_reg, result_reg)); + } + BinaryOperator::BitwiseXor => { + code.push(I::xor(left_reg, right_reg, result_reg)); + } + BinaryOperator::LogicalAnd => { + return Err(CompilerError::Unimplemented( + "assembler/ISA does not yet support logical and!".to_string(), + )); + } + BinaryOperator::LogicalOr => { + return Err(CompilerError::Unimplemented( + "assembler/ISA does not yet support logical or!".to_string(), + )); + } + BinaryOperator::LeftShift => { + code.push(I::shl(left_reg, right_reg, 0, result_reg)); + } + BinaryOperator::RightShift => { + code.push(I::shr(left_reg, right_reg, 0, result_reg)); + } + // Comparison operators - return 1 (true) or 0 (false) + BinaryOperator::Equal => { + code.push(I::cmp(Equal, left_reg, right_reg, result_reg)) + } + BinaryOperator::NotEqual => { + code.push(I::cmp(NotEqual, left_reg, right_reg, result_reg)) + } + BinaryOperator::LessThan => { + code.push(I::cmp(Less, left_reg, right_reg, result_reg)) + } + BinaryOperator::LessOrEqual => { + code.push(I::cmp(LessEqual, left_reg, right_reg, result_reg)) + } + BinaryOperator::GreaterThan => { + code.push(I::cmp(Greater, left_reg, right_reg, result_reg)) + } + BinaryOperator::GreaterOrEqual => { + code.push(I::cmp(GreaterEqual, left_reg, right_reg, result_reg)) + } // _ => unimplemented!(), + } + + // Free operand registers (allocator will protect variables) + self.allocator.free_temp(left_reg); + self.allocator.free_temp(right_reg); + + Ok((result_reg, code)) + } + + Expression::UnaryPostfix { op, operand, .. } => { + let (operand_reg, operand_code) = + self.generate_expression(operand, true, func_body)?; + code.append(operand_code); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.append(result_alloc); + + match op { + UnaryOperator::Increment => { + // postfix increment - return old value + code.push(I::mov(operand_reg, result_reg)); + } + UnaryOperator::Decrement => { + // postfix decrement - return old value + code.push(I::mov(operand_reg, result_reg)); + } + _ => { + return Err(CompilerError::Generic(format!("{op} is prefix only!"))); + } + } + + self.allocator.free_temp(operand_reg); + Ok((result_reg, code)) + } + + Expression::Unary { op, operand, .. } => { + let (operand_reg, operand_code) = + self.generate_expression(operand, true, func_body)?; + code.append(operand_code); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.append(result_alloc); + + match op { + UnaryOperator::Minus => { + // Negate: result = 0 - operand + code.push(I::sub(Register::Zero, operand_reg, result_reg)); + } + UnaryOperator::Plus => { + // Just move + code.push(I::mov(operand_reg, result_reg)); + } + UnaryOperator::Dereference => { + code.push(I::ldw_reg(operand_reg, result_reg)); + } + UnaryOperator::AddressOf => { + // ensure the referenced variable is on the stack and return its + // address. + let (offset, alloc_code) = self.allocator.free_register(&operand_reg)?; + code.push(alloc_code); + code.push(I::iadd_dest( + Register::Spr, + offset - self.allocator.get_stack_offset(), + result_reg, + )); + } + UnaryOperator::SizeOf => { + if let Ok(id) = operand.type_id() { + let size = id.size(); + code.push(I::lwi(size as u32, result_reg)); + } + } + UnaryOperator::Increment => { + // prefix increment + code.push(I::mov(operand_reg, result_reg)); + code.push(I::iadd_dest(operand_reg, 1, result_reg)); + } + UnaryOperator::Decrement => { + // prefix decrement + code.push(I::mov(operand_reg, result_reg)); + code.push(I::iadd_dest(operand_reg, -1, result_reg)); + } + UnaryOperator::BitwiseNot => { + code.push(I::not(operand_reg, result_reg)); + } + UnaryOperator::LogicalNot => { + return Err(CompilerError::Unimplemented( + "Assembler/ISA does not yet support logical not".to_string(), + )); + } + _ => { + return Err(CompilerError::Generic(format!("{op} is postfix only!"))); + } + } + + self.allocator.free_temp(operand_reg); + Ok((result_reg, code)) + } + + Expression::Call { + func: Call { name, args }, + .. + } => { + // first evaluate all the args we're going to need + let mut arg_regs = Vec::new(); + for arg in args.iter().rev() { + let (arg_reg, arg_code) = self.generate_expression(arg, true, func_body)?; + code.append(arg_code); + arg_regs.push(arg_reg); + } + + // Save caller-saved registers and track which ones we saved + let saved_regs = self.allocator.get_caller_saved_registers(); + for reg in &saved_regs { + // spill variables to stack + code.push(self.allocator.free_register(reg).unwrap().1); + } + + // Evaluate and push arguments in reverse order + for (i, arg_reg) in arg_regs.iter().enumerate() { + code.push(I::comment(format!("push arg {}", args.len() - 1 - i))); + code.push(I::push(*arg_reg)); + } + + if self.symbols.contains(&name.name) { + // Call local function + code.push(I::call(name.to_string())); + } else if let Some(ns) = name.namespace.clone() + && self.imports.contains_key(&ns) + { + code.push(I::call(name.to_string())); + } else { + return Err(CompilerError::Undefined(name.clone())); + } + + let result_reg: Register; + + if use_result { + let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?; + result_reg = temp_result_reg; + + code.append(result_alloc); + code.push(I::pop(result_reg)); + + // Clean up arguments + if args.len() > 1 { + for _ in 0..(args.len() - 1) { + code.push(I::pop(Register::Zero)); + } + } + } else { + result_reg = Register::Zero; + + // Clean up arguments + if args.len() > 0 { + for _ in 0..(args.len()) { + code.push(I::pop(Register::Zero)); + } + } + } + + // Free argument registers + for reg in arg_regs { + self.allocator.free_temp(reg); + } + + Ok((result_reg, code)) + } + + Expression::IndexAccess { + expr, + index, + type_id, + } => { + let (expr_reg, expr_alloc) = self.generate_expression(expr, true, func_body)?; + code.append(expr_alloc); + + let (index_reg, index_alloc) = self.generate_expression(index, true, func_body)?; + code.append(index_alloc); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.append(result_alloc); + + // add the expr pointer to the index to get the final address. + code.push(I::add(expr_reg, index_reg, result_reg)); + // load the value at the address. + code.push(I::ldw_reg(result_reg, result_reg)); + + self.allocator.free_temp(expr_reg); + self.allocator.free_temp(index_reg); + + Ok((result_reg, code)) + } + Expression::MemberAccess { + expr, + field_name, + type_id, + } => Err(CompilerError::Unimplemented( + "Structs are not yet implemented!".to_string(), + )), + + Expression::TypeCast { + expr, + target_type, + type_id, + } => { + let (expr_reg, expr_code) = self.generate_expression(expr, true, func_body)?; + + // not sure if we actually need to do anything here. + // for now we just return the previous expression. + Ok((expr_reg, expr_code)) + } + } + } + + // Helper for generating unique labels + fn get_unique_label(&mut self) -> String { + // You'd implement a counter here + static COUNTER: AtomicU32 = AtomicU32::new(0); + + let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + (val + 1).to_string() + } +} diff --git a/tooling/dscs_compiler/src/backend/instruction.rs b/tooling/dscs_compiler/src/backend/instruction.rs new file mode 100644 index 0000000..b5407fc --- /dev/null +++ b/tooling/dscs_compiler/src/backend/instruction.rs @@ -0,0 +1,858 @@ +use std::fmt; + +use crate::backend::registers::Register; + +pub struct InsBlock { + instructions: Vec, +} + +impl InsBlock { + pub fn new() -> Self { + Self { + instructions: vec![], + } + } + + pub fn insert(&mut self, index: usize, instr: Instruction) { + self.instructions.insert(index, instr); + } + + pub fn push(&mut self, instr: Instruction) { + self.instructions.push(instr); + } + + pub fn append(&mut self, mut other: Self) { + self.instructions.append(&mut other.instructions); + } + + pub fn extend(&mut self, instrs: impl IntoIterator) { + self.instructions.extend(instrs); + } + + pub fn is_empty(&self) -> bool { + self.instructions.is_empty() + } + + pub fn len(&self) -> usize { + self.instructions.len() + } + + pub fn iter(&self) -> impl Iterator { + self.instructions.iter() + } +} + +impl From> for InsBlock { + fn from(instructions: Vec) -> Self { + Self { instructions } + } +} + +impl From for InsBlock { + fn from(instr: Instruction) -> Self { + Self { + instructions: vec![instr], + } + } +} + +pub enum Comparison { + Greater, + GreaterEqual, + Equal, + NotEqual, + Less, + LessEqual, +} + +#[derive(Debug, Clone)] +pub enum Instruction { + // Labels and comments + Label(Label), + Comment { + text: String, + top_level: bool, + }, + Newline, + + // Data Directives + Db { + label: String, + data: Vec, + }, + Dh { + label: String, + data: Vec, + }, + Dw { + label: String, + data: Vec, + }, + DString { + // alias for db. + label: String, + data: String, + }, + + Resx { + label: String, + size: u32, + }, + + // Include + Include { + name: String, + path: String, + }, + + // Data movement + Mov { + src: Register, + dest: Register, + }, + Movs { + src: Register, + dest: Register, + }, + + // Memory operations + Ldb { + src: MemOperand, + dest: Register, + }, + Ldh { + src: MemOperand, + dest: Register, + }, + Ldw { + src: MemOperand, + dest: Register, + }, + Stb { + src: Register, + dest: MemOperand, + }, + Sth { + src: Register, + dest: MemOperand, + }, + Stw { + src: Register, + dest: MemOperand, + }, + + // Immediate loads + Lli { + imm: Imm, + dest: Register, + }, + Lui { + imm: Imm, + dest: Register, + }, + Lwi { + imm: Imm, + dest: Register, + }, + LwiLabel { + label: String, + dest: Register, + }, + + // Arithmetic + Add { + src1: Register, + src2: Register, + dest: Register, + }, + Sub { + src1: Register, + src2: Register, + dest: Register, + }, + + AddI { + src: Register, + imm: Imm, + dest: Option, + }, + SubI { + src: Register, + imm: Imm, + dest: Option, + }, + + // Bitwise + And { + src1: Register, + src2: Register, + dest: Register, + }, + Or { + src1: Register, + src2: Register, + dest: Register, + }, + Xor { + src1: Register, + src2: Register, + dest: Register, + }, + Not { + src: Register, + dest: Register, + }, + Nand { + src1: Register, + src2: Register, + dest: Register, + }, + Nor { + src1: Register, + src2: Register, + dest: Register, + }, + Xnor { + src1: Register, + src2: Register, + dest: Register, + }, + + // Shifts + Shl { + src1: Register, + r_shamt: Register, + i_shamt: u16, + dest: Register, + }, + Shr { + src1: Register, + r_shamt: Register, + i_shamt: u16, + dest: Register, + }, + + Ieq { + src1: Register, + src2: Register, + dest: Register, + }, + Ine { + src1: Register, + src2: Register, + dest: Register, + }, + Ilt { + src1: Register, + src2: Register, + dest: Register, + }, + Ile { + src1: Register, + src2: Register, + dest: Register, + }, + Igt { + src1: Register, + src2: Register, + dest: Register, + }, + Ige { + src1: Register, + src2: Register, + dest: Register, + }, + + // Jumps + Jmp { + target: Label, + }, + + Jnz { + src: Register, + target: Label, + }, + Jez { + src: Register, + target: Label, + }, + + Jic { + target: Label, + }, + Jnc { + target: Label, + }, + + // Stack + Push { + reg: Register, + }, + Pop { + reg: Register, + }, + + // Function calls + Call { + target: String, + }, // namespace::function + Return, + + // System + Hlt, + Nop, + Int { + code: u8, + }, +} + +pub enum DataDirective { + U8(Vec), + U16(Vec), + U32(Vec), + String(String), + Char(char), +} + +impl fmt::Display for Instruction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Label(l) => write!(f, "{}:", l), + Self::Newline => write!(f, ""), /* empty string as newlines are inserted */ + // automatically. + Self::Comment { text, top_level } => write!( + f, + "{}", + text.lines() + .map(|line| format!( + "{}// {}", + if *top_level { "" } else { " " }, + line.trim(), + )) + .collect::>() + .join("\n") + ), + + Self::Include { name, path } => write!(f, "include {name}: \"{}\"", path), + + Self::Db { label, data } => write!( + f, + "db {}: {}", + label, + data.iter() + .map(|&b| format!("{:#04X}", b)) + .collect::>() + .join(", ") + ), + Self::Dh { label, data } => write!( + f, + "dh {}: {}", + label, + data.iter() + .map(|&b| format!("{:#06X}", b)) + .collect::>() + .join(", ") + ), + Self::Dw { label, data } => write!( + f, + "dw {}: {}", + label, + data.iter() + .map(|&b| format!("{:#08X}", b)) + .collect::>() + .join(", ") + ), + Self::DString { label, data } => write!(f, "db {}: \"{}\"", label, data), + + Self::Resx { label, size } => write!(f, "resx {}: {}", label, size), + + Self::Mov { src, dest } => write!(f, " mov {}, {}", src, dest), + Self::Movs { src, dest } => write!(f, " movs {}, {}", src, dest), + + Self::Ldb { src: addr, dest } => { + let (reg, offset) = reg_and_offset(addr); + write!(f, " ldb {}, {}, {}", reg, dest, offset) + } + Self::Ldh { src: addr, dest } => { + let (reg, offset) = reg_and_offset(addr); + write!(f, " ldh {}, {}, {}", reg, dest, offset) + } + Self::Ldw { src, dest } => { + let (reg, offset) = reg_and_offset(src); + write!(f, " ldw {}, {}, {}", reg, dest, offset) + } + // Self::Ldbs { addr, dest } => { + // write!(f, " ldbs {}, {}", format_mem_operand(addr), dest) + // } + // Self::Ldhs { addr, dest } => { + // write!(f, " ldhs {}, {}", format_mem_operand(addr), dest) + // } + // Self::Ldws { addr, dest } => { + // write!(f, " ldws {}, {}", format_mem_operand(addr), dest) + // } + Self::Stb { src, dest: addr } => { + let (reg, offset) = reg_and_offset(addr); + write!(f, " stb {}, {}, {}", src, reg, offset) + } + Self::Sth { src, dest: addr } => { + let (reg, offset) = reg_and_offset(addr); + write!(f, " sth {}, {}, {}", src, reg, offset) + } + Self::Stw { src, dest: addr } => { + let (reg, offset) = reg_and_offset(addr); + write!(f, " stw {}, {}, {}", src, reg, offset) + } + + Self::Lli { imm, dest } => write!(f, " lli {}, {}", imm, dest), + Self::Lui { imm, dest } => write!(f, " lui {}, {}", imm, dest), + Self::Lwi { imm, dest } => write!(f, " lwi {}, {}", imm, dest), + Self::LwiLabel { label, dest } => write!(f, " lwi {}, {}", label, dest), + + // arithmetic + Self::Add { src1, src2, dest } => { + write!(f, " add {}, {}, {}", src1, src2, dest) + } + Self::Sub { src1, src2, dest } => { + write!(f, " sub {}, {}, {}", src1, src2, dest) + } + Self::And { src1, src2, dest } => { + write!(f, " and {}, {}, {}", src1, src2, dest) + } + Self::Or { src1, src2, dest } => { + write!(f, " or {}, {}, {}", src1, src2, dest) + } + Self::Nand { src1, src2, dest } => { + write!(f, " nand {}, {}, {}", src1, src2, dest) + } + Self::Xor { src1, src2, dest } => { + write!(f, " xor {}, {}, {}", src1, src2, dest) + } + Self::Nor { src1, src2, dest } => { + write!(f, " nor {}, {}, {}", src1, src2, dest) + } + Self::Not { src, dest } => { + write!(f, " not {} {}", src, dest) + } + Self::Xnor { src1, src2, dest } => { + write!(f, " xnor {}, {}, {}", src1, src2, dest) + } + Self::AddI { src, imm, dest } => { + if let Some(d) = dest { + write!(f, " addi {}, {}, {}", src, imm, d) + } else { + write!(f, " addi {}, {}", src, imm) + } + } + Self::SubI { src, imm, dest } => { + if let Some(d) = dest { + write!(f, " subi {}, {}, {}", src, imm, d) + } else { + write!(f, " subi {}, {}", src, imm) + } + } + + // shift instructions + Self::Shl { + src1, + r_shamt, + i_shamt, + dest, + } => { + write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest) + } + Self::Shr { + src1, + r_shamt, + i_shamt, + dest, + } => { + write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest) + } + + Self::Ieq { src1, src2, dest } => write!(f, " ieq {}, {}, {}", src1, src2, dest), + Self::Ine { src1, src2, dest } => write!(f, " ine {}, {}, {}", src1, src2, dest), + Self::Ilt { src1, src2, dest } => write!(f, " ilt {}, {}, {}", src1, src2, dest), + Self::Ile { src1, src2, dest } => write!(f, " ile {}, {}, {}", src1, src2, dest), + Self::Igt { src1, src2, dest } => write!(f, " igt {}, {}, {}", src1, src2, dest), + Self::Ige { src1, src2, dest } => write!(f, " ige {}, {}, {}", src1, src2, dest), + + // jump instructions + Self::Jmp { target } => write!(f, " jmp {}", target), + Self::Jic { target } => write!(f, " jic {}", target), + Self::Jnc { target } => write!(f, " jnc {}", target), + Self::Jez { src, target } => write!(f, " jez {}, {}", src, target), + Self::Jnz { src, target } => write!(f, " jnz {}, {}", src, target), + + // stack instructions + Self::Push { reg } => write!(f, " push {}", reg), + Self::Pop { reg } => write!(f, " pop {}", reg), + + // call & return instructions + Self::Call { target } => write!(f, " call {}", target), + Self::Return => write!(f, " ret"), + + // misc instructions + Self::Int { code } => write!(f, " int {}", code), + Self::Hlt => write!(f, " hlt"), + Self::Nop => write!(f, " nop"), + } + } +} + +impl Instruction { + // data directives + pub fn db_string(label: impl Into, data: impl Into) -> Self { + Self::DString { + label: label.into(), + data: data.into(), + } + } + + pub fn db_word(label: impl Into, data: u32) -> Self { + Self::Dw { + label: label.into(), + data: vec![data], + } + } + + pub fn db_bytes(label: impl Into, data: &[u8]) -> Self { + Self::Db { + label: label.into(), + data: data.to_vec(), + } + } + + // Movement + pub fn mov(src: R1, dest: R2) -> Self + where + R1: Into, + R2: Into, + { + Self::Mov { + src: src.into(), + dest: dest.into(), + } + } + + // Memory loads + pub fn ldw_reg(base: R, dest: Register) -> Self + where + R: Into, + { + Self::Ldw { + src: MemOperand::RegIndirect(base.into()), + dest, + } + } + + pub fn ldw_reg_offset(base: R, dest: Register, offset: i32) -> Self + where + R: Into, + { + Self::Ldw { + src: MemOperand::RegOffset(base.into(), offset), + dest, + } + } + + pub fn ldw_label(label: impl Into