diff --git a/.idea/vulcan-emu.iml b/.idea/vulcan-emu.iml index e63a868..00eab34 100644 --- a/.idea/vulcan-emu.iml +++ b/.idea/vulcan-emu.iml @@ -5,6 +5,7 @@ + diff --git a/Cargo.lock b/Cargo.lock index 01ec9a7..52523bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,12 +61,39 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "bumpalo" version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.8.0" @@ -294,6 +321,15 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -315,6 +351,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "fnv" version = "1.0.7" @@ -345,6 +387,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "getrandom" version = "0.2.5" @@ -530,6 +581,12 @@ dependencies = [ "libc", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "memchr" version = "2.4.1" @@ -755,6 +812,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "parking_lot" version = "0.11.2" @@ -786,6 +849,49 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + [[package]] name = "pixels" version = "0.9.0" @@ -945,6 +1051,18 @@ version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + [[package]] name = "slotmap" version = "1.0.6" @@ -1044,6 +1162,18 @@ dependencies = [ "serde", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "ultraviolet" version = "0.8.1" @@ -1065,6 +1195,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "vasm" +version = "0.1.0" +dependencies = [ + "pest", + "pest_derive", + "vcore", +] + [[package]] name = "vcore" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f519c3e..931cfa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ resolver = "2" members = [ "vcore", "vemu", + "vasm" ] \ No newline at end of file diff --git a/vasm/Cargo.toml b/vasm/Cargo.toml new file mode 100644 index 0000000..173f7db --- /dev/null +++ b/vasm/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "vasm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +vcore = { path = "../vcore" } +pest = "2.1.3" +pest_derive = "2.1.0" diff --git a/vasm/src/main.rs b/vasm/src/main.rs new file mode 100644 index 0000000..f86d3b9 --- /dev/null +++ b/vasm/src/main.rs @@ -0,0 +1,11 @@ +extern crate pest; +#[macro_use] +extern crate pest_derive; + +mod parse_error; +mod vasm_line; +mod vasm_parser; + +fn main() { + println!("Hello, world!"); +} diff --git a/vasm/src/parse_error.rs b/vasm/src/parse_error.rs new file mode 100644 index 0000000..bf914b0 --- /dev/null +++ b/vasm/src/parse_error.rs @@ -0,0 +1,10 @@ +use vcore::opcodes::InvalidOpcode; + +#[derive(Debug, PartialEq)] +pub struct ParseError<'a>(pub &'a str); + +impl<'a> From for ParseError<'a> { + fn from(_: InvalidOpcode) -> Self { + Self("Invalid opcode") + } +} diff --git a/vasm/src/vasm.pest b/vasm/src/vasm.pest new file mode 100644 index 0000000..d2be18c --- /dev/null +++ b/vasm/src/vasm.pest @@ -0,0 +1,75 @@ +WHITESPACE = _{ " " | "\t" } + +// A comment will start with a semicolon and go to the end of the line. Actually everything is parsed +// line by line, so anything that starts with a semicolon is a comment: +COMMENT = _{ ";" ~ ANY* ~ EOI } + +// Numbers are more complicated. We'll support three formats: +// - Decimal numbers like 42 +// - Hexadecimal like 0x2a +// - Binary like 0b00101010 +// - Decimal zero needs its own pattern: it's not a decimal because +// it starts with a 0, but it has to be matched after hex and bin +// because otherwise any "0x" will parse as "decimal 0 followed +// by unparseable x" +dec_number = @{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* } +neg_number = ${ "-" ~ dec_number } +hex_number = ${ "0x" ~ ASCII_HEX_DIGIT+ } +bin_number = ${ "0b" ~ ASCII_BIN_DIGIT+ } +dec_zero = @{ "0" } +number = { dec_number | hex_number | bin_number | dec_zero | neg_number } + +// A label can be any sequence of C-identifier-y characters, as long as it doesn't start with +// a digit: +label_char = { ASCII_ALPHA_LOWER | ASCII_ALPHA_UPPER | "_" } +label = @{ label_char ~ (label_char | ASCII_DIGIT | "$")* } + +// To make relative jumps easier, we'll also allow an '@' at the start of a label, and interpret +// that as meaning "relative to the first byte of this instruction:" +relative_label = ${ "@" ~ label } + +// We'll also have a special form, $+nnn (and $-nnn) which is the first byte of the an earlier or later line: +absolute_line_offset = { "$" ~ sign ~ dec_number } + +// And the relative form of that, @+nnn and @-nnn: +relative_line_offset = { "@" ~ sign ~ dec_number } + +opcode = @{ ASCII_ALPHA_LOWER+ } // TODO: replace with actual list + +// The assembler will support some directives: +// - .org to set the current address +// - .db to embed some data +// - .equ to define some constants +directive = { ".org" | ".db" | ".equ" } + +// The .equ directive isn't much use without the ability to have expressions based on +// symbols, so, a quick arithmetic expression parser: +sign = { "+" | "-" } +term_op = { "/" | "*" | "%" } +expr = { term ~ (sign ~ term)* } +term = { fact ~ (term_op ~ fact)* } +fact = { ("(" ~ expr ~ ")") | (number | relative_line_offset | relative_label | absolute_line_offset | label) } + +// Likewise, .db would get tedious quick without a string syntax, so, let's define one of those. An escape +// sequence is a backslash followed by certain other characters: +escape = @{ "\\" ~ ("t" | "r" | "n" | "0" | "\\" | "\"") } + +// And a string is a quoted sequence of escapes or other characters: +string_inner = ${ !("\"" | "\\") ~ ANY | escape } +string = ${ "\"" ~ string_inner* ~ "\"" } + +// Parsing a line +// Normally an assembly line will be a sequence of "label, opcode, argument, comment." +// However, most of these elements are optional. An opcode is only required if an +// argument exists. Comments are already handled by the COMMENT pattern + + +// Some sub-patterns for the portions of a line: +label_group = { label ~ ":" } +argument_group = { expr | string } + +// An opcode might be an actual opcode, or a directive +instruction_group = { (opcode | directive) ~ argument_group? } + +// Finally the entire pattern for an assembly line: +line = { SOI ~ label_group? ~ instruction_group? ~ EOI } \ No newline at end of file diff --git a/vasm/src/vasm_line.rs b/vasm/src/vasm_line.rs new file mode 100644 index 0000000..b118fc2 --- /dev/null +++ b/vasm/src/vasm_line.rs @@ -0,0 +1,61 @@ +use vcore::opcodes::Opcode; + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum Directive { Db, Equ, Org } + +impl From<&str> for Directive { + fn from(value: &str) -> Self { + use Directive::*; + match value { + ".db" => Db, + ".equ" => Equ, + ".org" => Org, + _ => unreachable!() + } + } +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub struct VASMLine<'a> { + label: Option<&'a str>, + opcode: Option, + directive: Option +} + +impl<'a> VASMLine<'a> { + pub fn blank() -> VASMLine<'a> { + VASMLine { + label: None, + opcode: None, + directive: None + } + } + + pub fn for_opcode(opcode: Opcode) -> VASMLine<'a> { + Self::blank().with_opcode(opcode) + } + + pub fn with_opcode(self, opcode: Opcode) -> VASMLine<'a> { + VASMLine { + label: self.label, + opcode: Some(opcode), + directive: self.directive + } + } + + pub fn with_directive(self, directive: Directive) -> VASMLine<'a> { + VASMLine { + label: self.label, + opcode: self.opcode, + directive: Some(directive) + } + } + + pub fn with_label(self, label: &'a str) -> Self { + VASMLine { + label: Some(label), + opcode: self.opcode, + directive: self.directive + } + } +} diff --git a/vasm/src/vasm_parser.rs b/vasm/src/vasm_parser.rs new file mode 100644 index 0000000..23950cd --- /dev/null +++ b/vasm/src/vasm_parser.rs @@ -0,0 +1,63 @@ +use vcore::opcodes::Opcode; + +use crate::parse_error::ParseError; +use crate::vasm_line::{Directive, VASMLine}; + +use pest::{Parser}; +use std::convert::TryFrom; + +#[derive(Parser)] +#[grammar = "vasm.pest"] +struct VASMParser; + +fn parse_vasm_line(line: &str) -> Result> { + let mut pairs = VASMParser::parse(Rule::line, line) + .map_err(|_| ParseError("Failed to parse line"))? + .next() + .unwrap() + .into_inner() + .peekable(); + + let mut vasm_line = VASMLine::blank(); + + if let Some(label_group) = pairs.next_if(|pair| pair.as_rule() == Rule::label_group) { + let label = label_group.into_inner().next().unwrap(); + vasm_line = vasm_line.with_label(label.as_str()) + } + + if let Some(instruction_group) = pairs.next_if(|pair| pair.as_rule() == Rule::instruction_group) { + let instruction_field = instruction_group.into_inner().next().unwrap(); + vasm_line = match instruction_field.as_rule() { + Rule::opcode => vasm_line.with_opcode(Opcode::try_from(instruction_field.as_str())?), + Rule::directive => vasm_line.with_directive(Directive::from(instruction_field.as_str())), + _ => unreachable!() + } + // something something argument here + } + + Ok(vasm_line) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse() { + assert_eq!(parse_vasm_line("add"), Ok(VASMLine::for_opcode(Opcode::Add))); + assert_eq!(parse_vasm_line("sub"), Ok(VASMLine::for_opcode(Opcode::Sub))); + assert_eq!(parse_vasm_line("blah"), Err(ParseError("Invalid opcode"))); + } + + #[test] + fn test_parse_labels() { + assert_eq!(parse_vasm_line("foo: add"), Ok(VASMLine::for_opcode(Opcode::Add).with_label("foo"))); + assert_eq!(parse_vasm_line("bar:"), Ok(VASMLine::blank().with_label("bar"))); + } + + #[test] + fn test_parse_directives() { + assert_eq!(parse_vasm_line("foo: .equ"), Ok(VASMLine::blank().with_label("foo").with_directive(Directive::Equ))); + assert_eq!(parse_vasm_line(".db"), Ok(VASMLine::blank().with_directive(Directive::Db))); + } +} diff --git a/vcore/src/cpu.rs b/vcore/src/cpu.rs index b71149e..fa23335 100644 --- a/vcore/src/cpu.rs +++ b/vcore/src/cpu.rs @@ -154,8 +154,8 @@ impl CPU { Opcode::Store => self.memory.poke8(x, y.to_bytes()[0]), Opcode::Storew => self.memory.poke24(x, y), Opcode::Setsdp => { - self.dp = x.into(); - self.sp = y.into() + self.dp = x; + self.sp = y } Opcode::Brz => { if y == 0 { @@ -195,7 +195,7 @@ impl CPU { self.push_data(x); self.push_data(z) } - Opcode::Jmp => return self.pop_data().into(), + Opcode::Jmp => return self.pop_data(), Opcode::Jmpr => { let x = i32::from(self.pop_data()); return self.pc + x; @@ -203,9 +203,9 @@ impl CPU { Opcode::Call => { let x = self.pop_data(); self.push_call(self.pc + instruction.length as i32); - return x.into(); + return x; } - Opcode::Ret => return self.pop_call().into(), + Opcode::Ret => return self.pop_call(), Opcode::Hlt => self.halted = true, Opcode::Load => { let x = self.pop_data(); @@ -217,7 +217,7 @@ impl CPU { } Opcode::Inton => self.int_enabled = true, Opcode::Intoff => self.int_enabled = false, - Opcode::Setiv => self.iv = self.pop_data().into(), + Opcode::Setiv => self.iv = self.pop_data(), Opcode::Sdp => { self.push_data(self.sp); self.push_data(self.dp + 3) // The +3 accounts for the word we're about to push diff --git a/vcore/src/opcodes.rs b/vcore/src/opcodes.rs index b13fbb0..f4ada51 100644 --- a/vcore/src/opcodes.rs +++ b/vcore/src/opcodes.rs @@ -163,6 +163,60 @@ impl TryFrom for Opcode { } } +impl TryFrom<&str> for Opcode { + type Error = InvalidOpcode; + + fn try_from(value: &str) -> Result { + use Opcode::*; + Ok(match value { + "nop" => Nop, + "add" => Add, + "sub" => Sub, + "mul" => Mul, + "div" => Div, + "mod" => Mod, + "rand" => Rand, + "and" => And, + "or" => Or, + "xor" => Xor, + "not" => Not, + "gt" => Gt, + "lt" => Lt, + "agt" => Agt, + "alt" => Alt, + "lshift" => Lshift, + "rshift" => Rshift, + "arshift" => Arshift, + "pop" => Pop, + "dup" => Dup, + "swap" => Swap, + "pick" => Pick, + "rot" => Rot, + "jmp" => Jmp, + "jmpr" => Jmpr, + "call" => Call, + "ret" => Ret, + "brz" => Brz, + "brnz" => Brnz, + "hlt" => Hlt, + "load" => Load, + "loadw" => Loadw, + "store" => Store, + "storew" => Storew, + "inton" => Inton, + "intoff" => Intoff, + "setiv" => Setiv, + "sdp" => Sdp, + "setsdp" => Setsdp, + "pushr" => Pushr, + "popr" => Popr, + "peekr" => Peekr, + "debug" => Debug, + _ => return Err(InvalidOpcode(255)) + }) + } +} + #[test] fn test_decode() { assert_eq!(Opcode::try_from(18), Ok(Opcode::Pop)); diff --git a/vcore/src/word.rs b/vcore/src/word.rs index d4cd2c1..5826b6e 100644 --- a/vcore/src/word.rs +++ b/vcore/src/word.rs @@ -1,7 +1,7 @@ // 128k, the amount of memory in a standard Vulcan machine pub const MEM_SIZE: u32 = 128 * 1024; -#[derive(Debug, Copy, Clone, Eq, Ord)] +#[derive(Debug, Copy, Clone, Eq)] pub struct Word(u32); impl Word { @@ -10,9 +10,9 @@ impl Word { /// # Example /// /// ``` - /// use vulcan_emu::Word; + /// use vcore::word::Word; /// - /// assert_eq!(Word::from_bytes([0x01, 0x02, 0x03]) == 0x010203); + /// assert!(Word::from_bytes([0x01, 0x02, 0x03]) == 0x030201); /// ``` pub fn from_bytes(bytes: [u8; 3]) -> Self { let [a, b, c] = bytes; @@ -24,9 +24,9 @@ impl Word { /// # Example /// /// ``` - /// use vulcan_emu::Word; + /// use vcore::word::Word; /// - /// assert_eq!(Word::from(0x010203).to_bytes(), [0x01, 0x02, 0x03]); + /// assert_eq!(Word::from(0x010203).to_bytes(), [0x03, 0x02, 0x01]); /// ``` pub fn to_bytes(self) -> [u8; 3] { let [a, b, c, _] = self.0.to_le_bytes(); diff --git a/vemu/src/display.rs b/vemu/src/display.rs index 323d736..40a9153 100644 --- a/vemu/src/display.rs +++ b/vemu/src/display.rs @@ -211,7 +211,7 @@ fn draw_paletted_low_text(machine: &P, reg: DisplayRegisters, frame let char_byte = machine.peek(reg.font + (char_idx << 3) + char_row); let color_addr = addr + (reg.width * reg.height); - let color_byte = machine.peek(Word::from(color_addr)); + let color_byte = machine.peek(color_addr); let (fg_color_idx, bg_color_idx) = (color_byte & 0xf, color_byte >> 4); let fg_color = machine.peek(reg.palette + fg_color_idx); @@ -231,10 +231,8 @@ fn draw_direct_low_gfx(machine: &P, reg: DisplayRegisters, frame: & for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { let (display_row, display_col) = (i / 640, i % 640); - if display_row >= (240 - 64 * 3) - && display_row < (240 + 64 * 3) - && display_col >= (320 - 64 * 3) - && display_col < (320 + 64 * 3) + if ((240 - 64 * 3)..(240 + 64 * 3)).contains(&display_row) + && ((320 - 64 * 3)..(320 + 64 * 3)).contains(&display_col) { let (vulcan_row, vulcan_col) = ( Word::from((display_row - (240 - 64 * 3)) / 3), @@ -253,10 +251,8 @@ fn draw_paletted_low_gfx(machine: &P, reg: DisplayRegisters, frame: for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { let (display_row, display_col) = (i / 640, i % 640); - if display_row >= (240 - 64 * 3) - && display_row < (240 + 64 * 3) - && display_col >= (320 - 64 * 3) - && display_col < (320 + 64 * 3) + if ((240 - 64 * 3)..(240 + 64 * 3)).contains(&display_row) + && ((320 - 64 * 3)..(320 + 64 * 3)).contains(&display_col) { let (vulcan_row, vulcan_col) = ( Word::from((display_row - (240 - 64 * 3)) / 3), diff --git a/vemu/src/main.rs b/vemu/src/main.rs index dc38e02..e839a9d 100644 --- a/vemu/src/main.rs +++ b/vemu/src/main.rs @@ -59,10 +59,10 @@ fn window_loop(event_loop: EventLoop<()>, window: Window, mut pixels: Pixels, mu } if window_id == window.id() => *control_flow = ControlFlow::Exit, Event::WindowEvent { - event: WindowEvent::Resized(newSize), + event: WindowEvent::Resized(new_size), window_id } if window_id == window.id() => { - pixels.resize_surface(newSize.width, newSize.height); + pixels.resize_surface(new_size.width, new_size.height); } Event::MainEventsCleared => {