From e49ecd139a85c6eb1fa56f54686539a188f9d3fd Mon Sep 17 00:00:00 2001 From: "Paul W." Date: Wed, 11 Sep 2024 21:05:27 -0400 Subject: [PATCH] Add Odin templating and Z80Disasm --- odin-templating/README.md | 61 +++++++ odin-templating/html_builder.odin | 237 +++++++++++++++++++++++++++ odin-templating/main.odin | 46 ++++++ z80_disasm/.gitignore | 8 + z80_disasm/README.md | 49 ++++++ z80_disasm/src/Test.cs | 22 +++ z80_disasm/src/Z80Disasm.cs | 255 ++++++++++++++++++++++++++++++ z80_disasm/z80_disasm.csproj | 9 ++ 8 files changed, 687 insertions(+) create mode 100644 odin-templating/README.md create mode 100644 odin-templating/html_builder.odin create mode 100644 odin-templating/main.odin create mode 100644 z80_disasm/.gitignore create mode 100644 z80_disasm/README.md create mode 100644 z80_disasm/src/Test.cs create mode 100644 z80_disasm/src/Z80Disasm.cs create mode 100644 z80_disasm/z80_disasm.csproj diff --git a/odin-templating/README.md b/odin-templating/README.md new file mode 100644 index 0000000..d0b881f --- /dev/null +++ b/odin-templating/README.md @@ -0,0 +1,61 @@ +# HTML Templates in Odin + +```odin + page := html() + {head() + {title("HTML Title")} + } + {body() + {div() + class("container") + {div() + class("article") + {h1("Hello, world!")} + {h6("Goodbye, world!")} + { + {div() + attr("aria-data", "idk!") + text("Yo, what's up!") + } + {div() + {a("don't be eeeviiiilll", "http://google.com")} + {a("This link has a class called 'button'") + class("button")} + } + } + } + + {img("background.png", "green texture") + class("img") + } + {div() + id("lol") + class("hello") + } + {text("Random text")} + {text("")} + } + } +``` + + + + HTML Title + + +
+
+

Hello, world!

+
Goodbye, world!
+
Yo, what's up!
+
+ don't be eeeviiiilll + This link has a class called 'button' +
+
+ green texture +
+ Random text +
+ + \ No newline at end of file diff --git a/odin-templating/html_builder.odin b/odin-templating/html_builder.odin new file mode 100644 index 0000000..1e603db --- /dev/null +++ b/odin-templating/html_builder.odin @@ -0,0 +1,237 @@ +package html_builder + +import "core:fmt" +import "core:slice" +import "core:strings" + +Element :: struct { + tag_name: string, + id: string, + children: [dynamic]Node, + attributes: map[string]string, + class_list: [dynamic]string, +} + +Node :: union { + Element, + string, +} + +empty_tags: map[string]bool = { + "img" = true, + "br" = true, + "hr" = true, +} + +indent :: proc(sb: ^strings.Builder, depth: int) { + for i in 0 ..< depth { + fmt.sbprintf(sb, "\t") + } +} + +element_to_string :: proc(sb: ^strings.Builder, e: Element, depth: int = 0) -> string { + indent(sb, depth) + + fmt.sbprintf(sb, "<%s", e.tag_name) + + for name, attr in e.attributes { + fmt.sbprintf(sb, " %s=\"%s\"", name, attr) + } + + fmt.sbprintf(sb, ">") + + children_len := len(e.children) + + if children_len == 1 { + switch ch in e.children[0] { + case string: + fmt.sbprintf(sb, "%s", ch) + case Element: + fmt.sbprintf(sb, "\n") + element_to_string(sb, ch, depth + 1) + fmt.sbprintf(sb, "\n") + indent(sb, depth) + } + } + if children_len > 1 { + for child, idx in e.children { + fmt.sbprintf(sb, "\n") + switch ch in child { + case string: + indent(sb, depth + 1) + fmt.sbprintf(sb, "%s", ch) + case Element: + element_to_string(sb, ch, depth + 1) + } + } + fmt.sbprintf(sb, "\n") + indent(sb, depth) + } + if !empty_tags[e.tag_name] do fmt.sbprintf(sb, "", e.tag_name) + + return strings.to_string(sb^) +} + +parent_element: ^Element +elements_by_id: map[string]^Element +elements_by_classes: map[string][dynamic]^Element + +_element_scope_start :: proc(tag_name: string, id: string = "") -> (previous_parent: ^Element) { + + children := &parent_element.children + new_idx := len(children^) + new_element := Element { + tag_name = tag_name, + children = make([dynamic]Node), + } + append(children, new_element) + + previous_parent = parent_element + parent_element = &children[new_idx].(Element) + + + return previous_parent +} + +_element_scope_end :: proc(previous_parent: ^Element) { + parent_element = previous_parent +} + +// @(deferred_out = free_html) +html :: proc() -> ^Element { + parent_element = new(Element) + parent_element.tag_name = "html" + parent_element.children = make([dynamic]Node) + + return parent_element +} + +text :: proc(txt: string) { + append(&parent_element.children, txt) +} + +attr :: proc(name: string, value: string) { + if name == "class" { + class(value) + return + } + map_insert(&parent_element.attributes, name, value) +} +id :: proc(id: string) { + if id != "" { + parent_element.id = id + parent_element.attributes["id"] = id + elements_by_id[id] = parent_element + } +} + +class :: proc(class_names: string) { + classes: [dynamic]string + for class in strings.split(class_names, " ") { + _, found := slice.linear_search(parent_element.class_list[:], class) + + if elements_by_classes[class] == nil do elements_by_classes[class] = make([dynamic]^Element) + if !found { + append(&classes, class) + append(&elements_by_classes[class], parent_element) + } + } + append(&parent_element.class_list, ..classes[:]) + map_insert( + &parent_element.attributes, + "class", + strings.join(parent_element.class_list[:], " "), + ) +} + +free_html :: proc(el: ^Element) { + defer parent_element = nil + handle_children :: proc(children: [dynamic]Node) { + for e in children { + _e, ok := e.(Element) + if ok do handle_children(_e.children) + } + delete(children) + } + + handle_children(el.children) + free(el) +} + +@(deferred_out = _element_scope_end) +body :: proc(id: string = "") -> (previous_parent: ^Element) { + return _element_scope_start("body", id) +} + +@(deferred_out = _element_scope_end) +head :: proc(id: string = "") -> (previous_parent: ^Element) { + return _element_scope_start("head", id) +} + +@(deferred_out = _element_scope_end) +title :: proc(txt: string = "") -> (previous_parent: ^Element) { + el := _element_scope_start("title") + text(txt) + return el +} + +@(deferred_out = _element_scope_end) +h1 :: proc(txt: string = "") -> (previous_parent: ^Element) { + el := _element_scope_start("h1") + text(txt) + return el +} +@(deferred_out = _element_scope_end) +h2 :: proc(txt: string = "") -> (previous_parent: ^Element) { + el := _element_scope_start("h2") + text(txt) + return el +} +@(deferred_out = _element_scope_end) +h3 :: proc(txt: string = "") -> (previous_parent: ^Element) { + el := _element_scope_start("h3") + text(txt) + return el +} +@(deferred_out = _element_scope_end) +h4 :: proc(txt: string = "") -> (previous_parent: ^Element) { + el := _element_scope_start("h4") + text(txt) + return el +} +@(deferred_out = _element_scope_end) +h5 :: proc(txt: string = "") -> (previous_parent: ^Element) { + el := _element_scope_start("h5") + text(txt) + return el +} +@(deferred_out = _element_scope_end) +h6 :: proc(txt: string = "") -> (previous_parent: ^Element) { + el := _element_scope_start("h6") + text(txt) + return el +} + +@(deferred_out = _element_scope_end) +div :: proc(id: string = "") -> (previous_parent: ^Element) { + previous_parent = _element_scope_start("div", id) + return previous_parent +} + +@(deferred_out = _element_scope_end) +img :: proc(src: string = "", alt: string = "") -> (previous_parent: ^Element) { + previous_parent = _element_scope_start("img") + parent_element.children = nil + parent_element.attributes["src"] = src + if alt != "" do parent_element.attributes["alt"] = alt + return previous_parent +} + +@(deferred_out = _element_scope_end) +a :: proc(txt: string, href: string = "") -> (previous_parent: ^Element) { + previous_parent = _element_scope_start("a") + append(&parent_element.children, txt) + parent_element.attributes["href"] = href + return previous_parent +} + diff --git a/odin-templating/main.odin b/odin-templating/main.odin new file mode 100644 index 0000000..e1639ab --- /dev/null +++ b/odin-templating/main.odin @@ -0,0 +1,46 @@ +package html_builder + +import "core:fmt" +import "core:strings" + +main :: proc() { + page := html() + {head() + {title("HTML Title")} + } + {body() + {div() + class("container") + {div() + class("article") + {h1("Hello, world!")} + {h6("Goodbye, world!")} + { + {div() + attr("aria-data", "idk!") + text("Yo, what's up!") + } + {div() + {a("don't be eeeviiiilll", "http://google.com")} + {a("This link has a class called 'button'") + class("button")} + } + } + } + + {img("background.png", "green texture") + class("img") + } + {div() + id("lol") + class("hello") + } + {text("Random text")} + } + } + + defer free_html(page) + sb: strings.Builder + fmt.println(element_to_string(&sb, page^)) +} + diff --git a/z80_disasm/.gitignore b/z80_disasm/.gitignore new file mode 100644 index 0000000..3b6ba8e --- /dev/null +++ b/z80_disasm/.gitignore @@ -0,0 +1,8 @@ +bin +obj +.DS_Store +cache +.cache +.dll +.exe +.vs diff --git a/z80_disasm/README.md b/z80_disasm/README.md new file mode 100644 index 0000000..614157e --- /dev/null +++ b/z80_disasm/README.md @@ -0,0 +1,49 @@ +# A Z80 Disassembler + +The purpose of this project is to disassemble Z80 machine code as usable string. + +## TODO + +- [x] Basic Disassembly +- [ ] Extended Instructions Support +- [ ] IX Instructions Support +- [ ] IY Instructions Support +- [ ] Unit Testing + +## About the Z80 Instruction Set + +The Z80 CPU has 158 different instruction types including the 78 8080A instructions. + +### Instruction Types +They are categorized into: +- Load and Exchange +- Block Transfer and Search +- Arithmetic and Logical +- Rotate and Shift +- Bit Manipulation +- Jump, Call, and Return +- Input/Output +- CPU Control + +### Addressing Modes +- Immediate +- Immediate Extended +- Modified Page Zero +- Relative +- Extended +- Indexed Addressing +- Register +- Implied +- Register Indirect +- Bit + +or a combination of any two modes. + +Registers + +Accumulators | Flags +------ | ------ +A | F +B | C +D | E +H | L \ No newline at end of file diff --git a/z80_disasm/src/Test.cs b/z80_disasm/src/Test.cs new file mode 100644 index 0000000..6f61fed --- /dev/null +++ b/z80_disasm/src/Test.cs @@ -0,0 +1,22 @@ +using Z80; +class Test { + + // not sure what i was thinking when i wrote this + static void Main(string[] args) + { + Disasm disasm = new Disasm(); + byte[] x = new byte[3000]; + for (uint i = 0; i < 3000; i++) + { + x[i] = (byte) (i % 256); + } + disasm.Bytes = x; + var watch = System.Diagnostics.Stopwatch.StartNew(); + System.Console.WriteLine(disasm.Bytes.Length); + System.Console.WriteLine(disasm.Disassemble()); + watch.Stop(); + var elapsedMs = watch.ElapsedMilliseconds; + System.Console.WriteLine("Time elapsed: " + elapsedMs + "ms."); + + } +} \ No newline at end of file diff --git a/z80_disasm/src/Z80Disasm.cs b/z80_disasm/src/Z80Disasm.cs new file mode 100644 index 0000000..267b92b --- /dev/null +++ b/z80_disasm/src/Z80Disasm.cs @@ -0,0 +1,255 @@ +namespace Z80 +{ + public class Disasm + { + public byte[] Bytes + { + get; set; + } + private uint labelCounter = 0; + private uint programCounter = 0; + private byte currentOp = 0; + private string currentInstrName; + private string currentPredicate; + + + public string Disassemble() + { + if (Bytes == null) + { + throw new System.NullReferenceException(); + } + + string ret = ""; + for (int i = 0; i < Bytes.Length; i++) + { + ret += DisassembleInstruction() + "\n"; + } + return ret; + } + + public string DisassembleInstruction() + { + int err; + if (programCounter + 1 > Bytes.Length) + return "EOS"; + err = DisasmInstructionName(); + if (programCounter + 1 > Bytes.Length) + return "EOS"; + if (err == -2) + DisasmBitInstructionNames(); + + DisasmPredicate(); + + return currentInstrName + " " + currentPredicate; + } + + private int DisasmInstructionName() + { + currentOp = Bytes[programCounter++]; + currentInstrName = ""; + + switch (currentOp) + { + case 0: + currentInstrName = "NOP"; + break; + case 0xCB: + currentInstrName = "BIT INSTRUCTIONS"; + return -2; + case 0xDD: + currentInstrName = "IX INSTRUCTIONS"; + return -3; + case 0xED: + currentInstrName = "EXTENDED INSTRUCTIONS"; + return -4; + default: + break; + } + + /* + The load instruction + */ + if (currentOp == 0xF9) + { + currentInstrName = "LD"; + } + + switch (currentOp & 0xF0) + { + case 0x40: + case 0x50: + case 0x60: + case 0x70: + if (currentOp != 0x76) + currentInstrName = "LD"; + else + currentInstrName = "HALT"; + break; + default: + break; + } + + switch ((currentOp) & 0x0F) + { + case 0x01: + case 0x02: + case 0x06: + case 0x0A: + case 0x0E: + if (currentOp < 0x80) + currentInstrName = "LD"; + break; + default: + break; + } + + if (currentOp >= 0x80 && currentOp <= 0x8F || currentOp == 0xC6) + currentInstrName = "ADD"; + if (currentOp >= 0x90 && currentOp <= 0x9F || currentOp == 0xD6) + currentInstrName = "SUB"; + if (currentOp >= 0xA0 && currentOp <= 0xAF || currentOp == 0xE6) + currentInstrName = "AND"; + if (currentOp >= 0xB0 && currentOp <= 0xBF || currentOp == 0xF6) + currentInstrName = "OR"; + + if (currentOp >= 0x88 && currentOp <= 0x8F || currentOp == 0xCE) + currentInstrName = "ADC"; + if (currentOp >= 0x98 && currentOp <= 0x9F || currentOp == 0xDE) + currentInstrName = "SBC"; + if (currentOp >= 0xA8 && currentOp <= 0xAF || currentOp == 0xEE) + currentInstrName = "XOR"; + if (currentOp >= 0xB8 && currentOp <= 0xBF || currentOp == 0xFE) + currentInstrName = "CP"; + return 0; + } + + private int DisasmBitInstructionNames() + { + byte instr = Bytes[programCounter++]; + if ((instr & 0x30) != 0) + { + if (instr <= 0x07) + currentInstrName = "RLC"; + else if (instr <= 0x0F) + currentInstrName = "RRC"; + else if (instr <= 0x17) + currentInstrName = "RL"; + else if (instr <= 0x1F) + currentInstrName = "RR"; + else if (instr <= 0x27) + currentInstrName = "SLA"; + else if (instr <= 0x2F) + currentInstrName = "SRA"; + else if (instr <= 0x37) + currentInstrName = "SSL"; + else if (instr <= 0x3F) + currentInstrName = "SRL"; + } + switch (instr & 0xF0) + { + case 0x40: + case 0x50: + case 0x60: + case 0x70: + currentInstrName = "BIT"; + break; + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + currentInstrName = "RES"; + break; + case 0xC0: + case 0xD0: + case 0xE0: + case 0xF0: + currentInstrName = "SET"; + break; + default: + break; + } + return 0; + } + + public int DisasmPredicate() + { + currentPredicate = ""; + if (currentOp >= 0x40 && currentOp <= 0x47) + { + currentPredicate = "b, "; + } + if (currentOp >= 0x50 && currentOp <= 0x57) + { + currentPredicate = "d, "; + } + if (currentOp >= 0x60 && currentOp <= 0x67) + { + currentPredicate = "h, "; + } + if (currentOp >= 0x70 && currentOp <= 0x77) + { + currentPredicate = "(hl), "; + } + if (currentOp >= 0x48 && currentOp <= 0x4F) + { + currentPredicate = "c, "; + } + if (currentOp >= 0x58 && currentOp <= 0x5F) + { + currentPredicate = "e, "; + } + if (currentOp >= 0x68 && currentOp <= 0x6F) + { + currentPredicate = "l, "; + } + if (currentOp >= 0x78 && currentOp <= 0x7F) + { + currentPredicate = "a, "; + } + + if (currentOp >= 0x40 && currentOp <= 0xBF) + { + switch (currentOp & 0x0F) + { + case 0x7: + case 0xF: + currentPredicate += "a"; + break; + case 0x0: + case 0x8: + currentPredicate += "b"; + break; + case 0x1: + case 0x9: + currentPredicate += "c"; + break; + case 0x2: + case 0xA: + currentPredicate += "d"; + break; + case 0x3: + case 0xB: + currentPredicate += "e"; + break; + case 0x4: + case 0xC: + currentPredicate += "h"; + break; + case 0x5: + case 0xD: + currentPredicate += "l"; + break; + case 0x6: + case 0xE: + currentPredicate += "(hl)"; + break; + default: + break; + } + } + + return 0; + } + } +} diff --git a/z80_disasm/z80_disasm.csproj b/z80_disasm/z80_disasm.csproj new file mode 100644 index 0000000..23bfa20 --- /dev/null +++ b/z80_disasm/z80_disasm.csproj @@ -0,0 +1,9 @@ + + + + Exe + net8.0 + z80_disasm + + +