diff options
| -rw-r--r-- | .github/workflows/ci.yml | 18 | ||||
| -rw-r--r-- | Cargo.lock | 111 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | crates/nasm/src/main.rs | 17 | ||||
| -rw-r--r-- | lib/xixanta/Cargo.toml | 2 | ||||
| -rw-r--r-- | lib/xixanta/fuzz/fuzz_targets/fuzz_target_1.rs | 2 | ||||
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 2886 | ||||
| -rw-r--r-- | lib/xixanta/src/context.rs | 180 | ||||
| -rw-r--r-- | lib/xixanta/src/errors.rs | 98 | ||||
| -rw-r--r-- | lib/xixanta/src/instruction.rs | 451 | ||||
| -rw-r--r-- | lib/xixanta/src/lib.rs | 10 | ||||
| -rw-r--r-- | lib/xixanta/src/mapping.rs | 25 | ||||
| -rw-r--r-- | lib/xixanta/src/node.rs | 164 | ||||
| -rw-r--r-- | lib/xixanta/src/opcodes.rs | 618 | ||||
| -rw-r--r-- | lib/xixanta/src/parser.rs | 579 |
15 files changed, 2252 insertions, 2911 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 818c8bd..e5e8d79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,11 +16,27 @@ jobs: - uses: actions/checkout@v4 - name: Run tests - run: cargo test + run: cargo test --verbose - name: Run Clippy run: cargo clippy --all-targets --all-features + fuzz: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Update rust toolchain to nightly + run: | + rustup update nightly + rustup default nightly + + - name: Install cargo fuzz + run: cargo install cargo-fuzz + + - name: Build with nightly + run: cargo build --verbose + - name: Fuzzy testing run: | cd lib/xixanta @@ -4,47 +4,48 @@ version = 3 [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys", @@ -52,15 +53,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -68,9 +69,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -80,9 +81,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -92,15 +93,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "heck" @@ -109,10 +110,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "nasm" @@ -139,18 +146,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -163,9 +170,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -174,15 +181,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "windows-sys" @@ -195,9 +202,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -211,51 +218,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "xixanta" @@ -3,7 +3,7 @@ members = ["lib/*", "crates/*"] resolver = "2" [workspace.package] -rust-version = "1.73" +rust-version = "1.82" edition = "2021" license = "GPLv3+" authors = ["Miquel Sabaté Solà"] diff --git a/crates/nasm/src/main.rs b/crates/nasm/src/main.rs index 52de035..fbe2bef 100644 --- a/crates/nasm/src/main.rs +++ b/crates/nasm/src/main.rs @@ -62,10 +62,19 @@ fn main() -> Result<()> { // output.write_all("\n".as_bytes())?; // } } else { - let bundles = assembler.assemble(input)?; - for b in bundles { - for i in 0..b.size { - output.write_all(&[b.bytes[i as usize]])?; + match assembler.assemble(input) { + Ok(bundles) => { + for b in bundles { + for i in 0..b.size { + output.write_all(&[b.bytes[i as usize]])?; + } + } + } + Err(errors) => { + for err in errors { + println!("{}", err); + } + std::process::exit(1); } } } diff --git a/lib/xixanta/Cargo.toml b/lib/xixanta/Cargo.toml index 76a0660..d14bb60 100644 --- a/lib/xixanta/Cargo.toml +++ b/lib/xixanta/Cargo.toml @@ -11,4 +11,4 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lazy_static = "1.4.0" +lazy_static = "1.5.0" diff --git a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_1.rs b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_1.rs index 6a28b46..442dbbf 100644 --- a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_1.rs @@ -3,6 +3,6 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let mut parser = xixanta::parser::Parser::new(); + let mut parser = xixanta::parser::Parser::default(); let _ = parser.parse(data); }); diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 1e9854d..46d3d94 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -1,227 +1,610 @@ -use crate::context::{Context, PValue}; -use crate::errors::ParseError; -use crate::instruction::{AddressingMode, Bundle}; -use crate::mapping::{Mapping, Segment}; -use crate::opcodes::INSTRUCTIONS; -use crate::parser::{NodeType, PNode, Parser}; +use crate::context::Context; +use crate::errors::{Error, EvalError}; +use crate::mapping::Segment; +use crate::node::{NodeType, PNode, PString}; +use crate::opcodes::{AddressingMode, INSTRUCTIONS}; +use crate::parser::Parser; +use std::cmp::Ordering; use std::collections::HashMap; use std::io::Read; +use std::ops::Range; -// TODO: proper AST: WRITE_PPU_DATA from NES is a good example -// TODO: for christ's sake, automated tests! -// TODO: macros are meant to be global! -// TODO: instead of mapping.nodes having a value of vec<node>, the value should be a Context. -// TODO: proc's, labels, macros, and scopes can be merged dramatically. -// TODO: allow pointer arithmetic (e.g. 'adc #List::ptr + 1'). -// TODO: more to_owned() stuff, more rustacean way of doing things, more ... -// TODO: warning on empty segments +/// A Bundle represents a set of bytes that can be encoded as binary data. +#[derive(Debug, Default, Clone)] +pub struct Bundle { + /// The bytes which make up any encodable element for the application. The + /// capacity is of three bytes maximum, but the actual size is encoded in + /// the `size` property. + pub bytes: [u8; 3], -type Result<T> = std::result::Result<T, ParseError>; + /// The amount of bytes which have actually been set on this bundle. + pub size: u8, -#[derive(Debug, Clone, PartialEq)] + /// The address where the given bytes are to be placed on the resulting + /// binary file. + pub address: usize, + + /// If this bundle encodes an instruction, the amount of cycles it takes for + /// the CPU to actually execute it. + pub cycles: u8, + + /// Whether the cost in cycles is affected when crossing a page boundary. + pub affected_on_page: bool, +} + +#[derive(Clone, PartialEq)] pub enum LiteralMode { Hexadecimal, Binary, Plain, } +// TODO: is it really necessary to be this fully fledged? +#[derive(PartialEq)] +pub enum Stage { + Init, + Parsing, + Context, + Unrolling, + Bundling, +} + +#[derive(Clone, Debug)] +pub struct Macro { + nodes: Range<usize>, + args: Vec<PString>, +} + pub struct Assembler { - line: usize, - column: usize, context: Context, literal_mode: Option<LiteralMode>, - only_context: bool, - force_decimal: bool, - mapping: Mapping, - offsets: HashMap<String, usize>, + stage: Stage, + macros: HashMap<String, Macro>, } -// Control statements which end up affecting which context we are in. -const TOUCH_CONTEXT: [&str; 7] = [ - ".scope", - ".endscope", - ".proc", - ".endproc", - ".macro", - ".endmacro", - ".segment", -]; - impl Assembler { pub fn new(segments: Vec<Segment>) -> Self { - assert!(segments.len() > 0); + assert!(!segments.is_empty()); + // TODO let mut offsets = HashMap::new(); - for segment in &segments { - offsets.insert(segment.name.clone(), 0); + for segment in segments { + offsets.insert(segment.name, 0); } + // TODO Self { - line: 0, - column: 0, - literal_mode: None, - only_context: false, - force_decimal: false, context: Context::new(), - mapping: Mapping::new(segments), - offsets, + literal_mode: None, + stage: Stage::Init, + macros: HashMap::new(), } } - pub fn reset(&mut self) { - self.line = 0; - self.column = 0; - self.context = Context::new(); - self.mapping.reset(); - - self.offsets = HashMap::new(); - for segment in &self.mapping.segments { - self.offsets.insert(segment.name.clone(), 0); + pub fn assemble(&mut self, reader: impl Read) -> Result<Vec<Bundle>, Vec<Error>> { + // First of all, parse the input so we get a list of nodes we can work + // with. + self.stage = Stage::Parsing; + let mut parser = Parser::default(); + if let Err(errors) = parser.parse(reader) { + return Err(errors.iter().map(|e| Error::Parse(e.clone())).collect()); } - } - pub fn assemble(&mut self, reader: impl Read) -> Result<Vec<Bundle>> { - let mut res = vec![]; + // Build the context by iterating over the parsed nodes and checking + // where scopes start/end, evaluating values for variables, labels, etc. + self.stage = Stage::Context; + self.eval_context(&parser.nodes)?; - let mut parser = Parser::new(); - parser.parse(reader)?; + // TODO: unroll macros, fill out labels, etc. + self.stage = Stage::Unrolling; - // println!("{:#?}", parser.nodes); + // Finally convert the relevant nodes into binary bundles which can be + // used by the caller. + self.stage = Stage::Bundling; + self.bundle(&parser.nodes) + } - // NOTE: first step: unroll macros, update context, set variables. + pub fn eval_context(&mut self, nodes: &[PNode]) -> Result<(), Vec<Error>> { + let mut errors = Vec::new(); + let mut current_macro = None; - self.only_context = true; - for node in parser.nodes.clone() { + for (idx, node) in nodes.iter().enumerate() { + // TODO: initilize labels on each scope. match node.node_type { NodeType::Assignment => { - self.evaluate_assignment(node)?; - - println!("{:#?}", self.context); + // TODO: in fact, we cannot have assignments in many places. + if current_macro.is_some() { + errors.push(Error::Eval(EvalError { + message: "cannot have assignments inside of macro definitions" + .to_string(), + line: node.value.line, + })); + continue; + } + match self.evaluate_node(node.left.as_ref().unwrap()) { + Ok(value) => { + if let Err(err) = self.context.set_variable(&node.value, &value) { + errors.push(Error::Context(err)); + } + } + Err(e) => errors.push(Error::Eval(e)), + } } NodeType::Control => { - self.evaluate_control(node)?; + // TODO: prevent nesting of control statements depending on + // a definition (e.g. .macro's cannot be nested inside of + // another control statement, but .if yes). + let id = node.value.value.as_str(); + + if id == ".macro" { + // TODO: macros are only on the global scope. + // + // TODO: boy this is ugly. In fact, this stupid shit if + // current_macro might not be relevant anymore. + current_macro = Some(&node.left.as_ref().unwrap().value); + // TODO: watch out for weird shit on the name of arguments. + self.macros + .entry(node.left.as_ref().unwrap().value.value.clone()) + .or_insert(Macro { + nodes: Range { + start: idx + 1, + end: idx + 1, + }, + args: node + .args + .clone() + .unwrap_or_default() + .into_iter() + .map(|a| a.value) + .collect::<Vec<_>>(), + }); + } else if id == ".endmacro" { + // TODO: if m.nodes.start < idx - 1 => empty macro + + if let Some(name) = current_macro { + self.macros + .entry(name.value.clone()) + .and_modify(|m| m.nodes.end = idx - 1); + } + current_macro = None; + } + if let Err(err) = self.context.change_context(node) { + // TODO: forbid if inside_macro + errors.push(Error::Context(err)); + } } _ => {} } } - self.only_context = false; - - // Check for unclosed scope definition. - if !self.context.is_global() { - return Err(self.parser_error( - format!( - "definition for '{}' has not been closed", - self.context.name() - ) - .as_str(), - )); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) } + } - // NOTE: second step: let's rock. + pub fn bundle(&mut self, nodes: &Vec<PNode>) -> Result<Vec<Bundle>, Vec<Error>> { + let mut bundles = Vec::new(); + let mut errors = Vec::new(); + let mut inside_macro = false; - for node in parser.nodes { + for node in nodes { match node.node_type { NodeType::Instruction => { - res.push(self.evaluate_node(node)?); + if !inside_macro { + match self.evaluate_node(node) { + Ok(bundle) => bundles.push(bundle), + Err(e) => errors.push(Error::Eval(e)), + } + } } NodeType::Control => { - self.evaluate_control(node)?; + let id = node.value.value.as_str(); + + // TODO: skip macros altogether. + if id == ".macro" { + if inside_macro { + errors.push(Error::Eval(EvalError { + message: "nesting macros is forbidden".to_string(), + line: node.value.line, + })); + continue; + } + inside_macro = true; + } else if id == ".endmacro" { + inside_macro = false; + } else if let Err(err) = self.context.change_context(node) { + // TODO: forbid if inside_macro + errors.push(Error::Context(err)); + } + } + NodeType::Value | NodeType::Call => { + if let Err(e) = self.bundle_call(node, nodes, &mut bundles) { + errors.push(Error::Eval(e)); + } } + _ => {} } } - // NOTE: third step: update addresses of referenced labels. - // TODO - - // println!("{:#?}", res); - Ok(res) + if errors.is_empty() { + Ok(bundles) + } else { + Err(errors) + } } - // pub fn disassemble(&mut self, reader: impl Read) -> Result<Vec<&dyn Encodable>> { - // self.from_byte_reader(reader)?; - - // let mut instructions: Vec<&dyn Encodable> = vec![]; - // for node in self.mapping.current() { - // println!("{:#?}", node); - // match node { - // Node::Instruction(instr) => instructions.push(instr), - // Node::Literal(lit) => instructions.push(lit), - // _ => {} - // } - // } - - // Ok(instructions) - // } - - fn evaluate_assignment(&mut self, node: Box<PNode>) -> Result<()> { - if self - .context - .current_mut() - .unwrap() - .contains_key(&node.value.value) - { - return Err(ParseError { - line: self.line, + fn bundle_call( + &mut self, + node: &PNode, + nodes: &[PNode], + bundles: &mut Vec<Bundle>, + ) -> Result<(), EvalError> { + // Get the macro object for the given identifier. + let mcr = self + .macros + .get(&node.value.value) + .ok_or(EvalError { + line: node.value.line, + message: format!( + "could not find a macro with the name '{}'", + node.value.value + ), + })? + .clone(); + + // Detect missmatches between the number of arguments provided and the + // ones defined by the macro. + let args = node.args.as_ref(); + let nargs = match args { + Some(v) => v.len(), + None => 0, + }; + if mcr.args.len() != nargs { + return Err(EvalError { + line: node.value.line, message: format!( - "variable '{}' is being re-assigned: it was previously defined in line {}", - node.value.value, node.value.line, + "wrong number of arguments for '{}': {} required but {} given", + node.value.value, + mcr.args.len(), + nargs ), - parse: false, }); } - if let Some(value_node) = node.left { - self.force_decimal = true; - println!("{:#?}", value_node); - let val = self.evaluate_node(value_node.clone())?; - println!("{:#?}", val); - self.force_decimal = false; - - self.context.current_mut().unwrap().insert( - node.value.value.to_owned(), - PValue { - node: *value_node, - value: val, - label: false, - }, - ); + // If there are arguments defined by the macro, set their values now. + if nargs > 0 { + let mut margs = mcr.args.iter(); + + for (idx, arg) in args.unwrap().iter().enumerate() { + let bundle = self.evaluate_node(arg)?; + self.context + .set_variable(margs.nth(idx).unwrap(), &bundle)?; + } + } + + // And now replicate the nodes as contained inside of the macro + // definition. + for node in nodes + .get(mcr.nodes.start..=mcr.nodes.end) + .unwrap_or_default() + { + bundles.push(self.evaluate_node(node)?); } Ok(()) } - fn evaluate_node(&mut self, node: Box<PNode>) -> Result<Bundle> { + fn evaluate_node(&mut self, node: &PNode) -> Result<Bundle, EvalError> { match node.node_type { - NodeType::Control => self.evaluate_control(node), - NodeType::Literal => self.evaluate_literal(node), - NodeType::Instruction => self.evaluate_instruction(node), + NodeType::Instruction => Ok(self.evaluate_instruction(node)?), + NodeType::Literal => Ok(self.evaluate_literal(node)?), + NodeType::Control => Ok(self.evaluate_control(node)?), NodeType::Value => match self.literal_mode { - Some(LiteralMode::Hexadecimal) => self.evaluate_hexadecimal(node), - Some(LiteralMode::Binary) => self.evaluate_binary(node), - Some(LiteralMode::Plain) => self.evaluate_decimal(node), + Some(LiteralMode::Hexadecimal) => Ok(self.evaluate_hexadecimal(node)?), + Some(LiteralMode::Binary) => Ok(self.evaluate_binary(node)?), + Some(LiteralMode::Plain) => Ok(self.evaluate_decimal(node)?), None => { - if self.force_decimal { - self.evaluate_decimal(node) + if self.stage == Stage::Context { + // If we are just evaluating the context (e.g. parsing a + // variable), we'll assume that non-prefixed literals are + // just decimal values. + Ok(self.evaluate_decimal(node)?) + } else if node.value.is_valid_identifier(true).is_err() { + // If this is not a valid identifier, just error out. + Err(EvalError { + message: "no prefix was given to operand".to_string(), + line: node.value.line, + }) } else { - Err(self.parser_error("no prefix was given to operand")) + // This is actually a valid identifier! Try to fetch the + // variable. + match self.evaluate_variable(&node.value) { + Ok(v) => { + self.literal_mode = Some(LiteralMode::Hexadecimal); + Ok(v) + } + Err(err) => Err(EvalError { + message: format!( + "no prefix was given to operand and {} either", + err.message + ), + line: node.value.line, + }), + } } } }, - // TODO - _ => Ok(Bundle::new()), + _ => Err(EvalError { + message: format!("unexpected '{}' expression type", node.node_type), + line: node.value.line, + }), } } - fn evaluate_instruction(&mut self, node: Box<PNode>) -> Result<Bundle> { - let mnemonic = node.value.value.to_lowercase(); + fn evaluate_hexadecimal(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + let mut chars = node.value.value.chars(); + let mut bytes = [0, 0, 0]; + let size: u8; + + match node.value.value.len() { + 1 => { + bytes[0] = self.char_to_hex(chars.next(), node)?; + size = 1; + } + 2 => { + bytes[0] = self.char_to_hex(chars.next(), node)? * 16; + bytes[0] += self.char_to_hex(chars.next(), node)?; + size = 1; + } + 3 => { + bytes[1] = self.char_to_hex(chars.next(), node)?; + bytes[0] = self.char_to_hex(chars.next(), node)? * 16; + bytes[0] += self.char_to_hex(chars.next(), node)?; + size = 2; + } + 4 => { + bytes[1] = self.char_to_hex(chars.next(), node)? * 16; + bytes[1] += self.char_to_hex(chars.next(), node)?; + bytes[0] = self.char_to_hex(chars.next(), node)? * 16; + bytes[0] += self.char_to_hex(chars.next(), node)?; + size = 2; + } + _ => { + if self.evaluate_variable(&node.value).is_ok() { + return Err(EvalError { + message: format!( + "you cannot use variables like '{}' in hexadecimal literals", + node.value.value + ), + line: node.value.line, + }); + } + return Err(EvalError { + message: "expecting a number of 1 to 4 hexadecimal digits".to_string(), + line: node.value.line, + }); + } + } + + Ok(Bundle { + bytes, + size, + address: 0, + cycles: 0, + affected_on_page: false, + }) + } + + fn evaluate_binary(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + let string = node.value.value.as_str(); + let mut value = 0; + let mut shift = 0; + + for c in string.chars().rev() { + if c == '1' { + let val = 1 << shift; + value += val; + } else if c != '0' { + if self.evaluate_variable(&node.value).is_ok() { + return Err(EvalError { + message: format!( + "you cannot use variables like '{}' in binary literals", + string + ), + line: node.value.line, + }); + } + return Err(EvalError { + message: format!("bad binary format for '{}'", string), + line: node.value.line, + }); + } + + shift += 1; + } + + match shift.cmp(&8) { + Ordering::Less => Err(EvalError { + message: "missing binary digits to get a full byte".to_string(), + line: node.value.line, + }), + Ordering::Greater => Err(EvalError { + message: "too many binary digits for a single byte".to_string(), + line: node.value.line, + }), + Ordering::Equal => Ok(Bundle { + bytes: [value as u8, 0, 0], + size: 1, + address: 0, + cycles: 0, + affected_on_page: false, + }), + } + } + + fn evaluate_decimal(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + let string = node.value.value.as_str(); + if string.is_empty() { + return Err(EvalError { + message: "empty decimal literal".to_string(), + line: node.value.line, + }); + } + + let mut value = 0; + let mut shift = 1; + + for c in string.chars().rev() { + if shift > 100 { + return Err(EvalError { + message: "decimal value is too big".to_string(), + line: node.value.line, + }); + } + if c != '0' { + match c.to_digit(10) { + Some(digit) => { + value += digit * shift; + } + None => { + if self.stage == Stage::Context { + return Err(EvalError { + message: format!( + "variables must come from a constant expression, \ + you cannot use other variables such as '{}' \ + in variable definitions", + string + ), + line: node.value.line, + }); + } + match self.evaluate_variable(&node.value) { + Ok(v) => return Ok(v), + Err(err) => { + return Err(EvalError { + message: format!( + "'{}' is not a decimal value and {} either", + c, err.message + ), + line: node.value.line, + }) + } + } + } + } + } + + shift *= 10; + } + if value > 255 { + return Err(EvalError { + message: "decimal value is too big".to_string(), + line: node.value.line, + }); + } + + Ok(Bundle { + bytes: [value as u8, 0, 0], + size: 1, + address: 0, + cycles: 0, + affected_on_page: false, + }) + } - let (mode, mut bundle) = if node.left.is_some() { - self.get_addressing_mode_and_bytes(node)? + fn evaluate_literal(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + // The value of the literal is guaranteed to not be empty by the parser. + // If that's not the case, then it's a bug. + let val = node.value.value.as_str(); + assert!(!val.is_empty(), "the value for the literal was empty!"); + + // Pick up the left node, which is the node to be further evaluated, and + // determine the literal mode to be used. + let left = node.left.as_ref().unwrap(); + + let lm; + if val.starts_with('$') { + lm = Some(LiteralMode::Hexadecimal); + if left.node_type == NodeType::Literal { + return Err(EvalError { + message: "literal cannot embed another literal".to_string(), + line: node.value.line, + }); + } + } else if val.starts_with('%') { + lm = Some(LiteralMode::Binary); + if left.node_type == NodeType::Literal { + return Err(EvalError { + message: "literal cannot embed another literal".to_string(), + line: node.value.line, + }); + } } else { - (AddressingMode::Implied, Bundle::new()) + lm = Some(LiteralMode::Plain); + } + + // And evaluate the left node. + self.literal_mode = lm.clone(); + let expr = self.evaluate_node(left)?; + self.literal_mode = lm; + + Ok(expr) + } + + fn char_to_hex(&mut self, oc: Option<char>, source: &PNode) -> Result<u8, EvalError> { + match oc { + Some(c) => match c.to_digit(16) { + Some(c) => Ok(c as u8), + None => { + if (c.is_alphabetic() || c == '_') + && self.evaluate_variable(&source.value).is_ok() + { + return Err(EvalError { + message: format!( + "you cannot use variables like '{}' in hexadecimal literals", + source.value.value + ), + line: source.value.line, + }); + } + Err(EvalError { + message: "could not convert digit to hexadecimal".to_string(), + line: source.value.line, + }) + } + }, + None => Err(EvalError { + message: "digit out of bounds".to_string(), + line: source.value.line, + }), + } + } + + fn evaluate_control(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + println!("NODE: {:#?}", node); + Ok(Bundle::default()) + } + + fn evaluate_variable(&mut self, id: &PString) -> Result<Bundle, EvalError> { + match self.context.get_variable(id) { + Ok(value) => Ok(value), + Err(e) => Err(EvalError { + message: e.message, + line: id.line, + }), + } + } + + fn evaluate_instruction(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + let (mode, mut bundle) = match &node.left { + Some(_) => self.get_addressing_mode_and_bytes(node)?, + None => (AddressingMode::Implied, Bundle::default()), }; + let mnemonic = node.value.value.to_lowercase(); match INSTRUCTIONS.get(&mnemonic) { Some(entries) => match entries.get(&mode) { Some(values) => { @@ -233,17 +616,20 @@ impl Assembler { bundle.bytes[0] = values.opcode.to_le_bytes()[0]; } None => { - return Err(self.parser_error( - format!( + return Err(EvalError { + message: format!( "cannot use {} addressing mode for the instruction '{}'", mode, mnemonic - ) - .as_str(), - )) + ), + line: node.value.line, + }) } }, None => { - return Err(self.parser_error(format!("unknown instruction {}", mnemonic).as_str())) + return Err(EvalError { + message: format!("unknown instruction {}", mnemonic), + line: node.value.line, + }); } } Ok(bundle) @@ -251,78 +637,101 @@ impl Assembler { fn get_addressing_mode_and_bytes( &mut self, - node: Box<PNode>, - ) -> Result<(AddressingMode, Bundle)> { - if node.clone().left.unwrap().node_type == NodeType::Indirection { + node: &PNode, + ) -> Result<(AddressingMode, Bundle), EvalError> { + let left = &node.left; + + if left.as_ref().unwrap().node_type == NodeType::Indirection { self.get_from_indirect(node) } else if node.right.is_some() { self.get_from_indexed(node) } else { - self.get_from_left(node) + self.get_from_left(left.as_ref().unwrap()) } } - fn get_from_indirect(&mut self, node: Box<PNode>) -> Result<(AddressingMode, Bundle)> { - let left = node.left.unwrap(); + fn get_from_indirect(&mut self, node: &PNode) -> Result<(AddressingMode, Bundle), EvalError> { + let left = node.left.as_ref().unwrap(); - match node.right { + match node.right.as_ref() { Some(right) => { if right.value.value.trim().to_lowercase() == "y" { if left.right.is_some() { - return Err(self.parser_error( - "it has to be either X addressing or Y addressing, not all at once", - )); + return Err(EvalError { + message: + "it has to be either X addressing or Y addressing, not all at once" + .to_string(), + line: node.value.line, + }); } - let val = self.evaluate_node(left.left.unwrap())?; + let val = self.evaluate_node(left.left.as_ref().unwrap())?; if val.size != 1 { - return Err(self.parser_error( - "address can only be one byte long on indirect Y addressing", - )); + return Err(EvalError { + message: "address can only be one byte long on indirect Y addressing" + .to_string(), + line: node.value.line, + }); } return Ok((AddressingMode::IndirectY, val)); } - return Err( - self.parser_error("only the Y index is allowed on indirect Y addressing") - ); + Err(EvalError { + message: "only the Y index is allowed on indirect Y addressing".to_string(), + line: node.value.line, + }) } - None => match left.right { + None => match left.right.as_ref() { Some(right) => { if right.value.value.trim().to_lowercase() == "x" { - let val = self.evaluate_node(left.left.unwrap())?; + let val = self.evaluate_node(left.left.as_ref().unwrap())?; if val.size != 1 { - return Err(self.parser_error( - "address can only be one byte long on indirect X addressing", - )); + return Err(EvalError { + message: + "address can only be one byte long on indirect X addressing" + .to_string(), + line: node.value.line, + }); } return Ok((AddressingMode::IndirectX, val)); } - return Err( - self.parser_error("only the X index is allowed on indirect X addressing") - ); + Err(EvalError { + message: "only the X index is allowed on indirect X addressing".to_string(), + line: node.value.line, + }) } None => { - let val = self.evaluate_node(left.left.unwrap())?; + let val = self.evaluate_node(left.left.as_ref().unwrap())?; if val.size != 2 { - return Err(self.parser_error("expecting a full 16-bit address")); + return Err(EvalError { + message: "expecting a full 16-bit address".to_string(), + line: node.value.line, + }); } - return Ok((AddressingMode::Indirect, val)); + Ok((AddressingMode::Indirect, val)) } }, } } - fn get_from_indexed(&mut self, node: Box<PNode>) -> Result<(AddressingMode, Bundle)> { - self.literal_mode = None; // TODO: needed? - let val = self.evaluate_node(node.left.unwrap())?; + fn get_from_indexed(&mut self, node: &PNode) -> Result<(AddressingMode, Bundle), EvalError> { + // Evaluate the left arm of the instruction. + let left = node.left.as_ref().unwrap(); + let val = self.evaluate_node(left)?; + // Ensure that the literal mode for the left arm ensures an address + // instead of some bogus number. if let Some(lm) = &self.literal_mode { if *lm != LiteralMode::Hexadecimal { - return Err(self.parser_error("indexed addressing only works with addresses")); + return Err(EvalError { + message: "indexed addressing only works with addresses".to_string(), + line: node.value.line, + }); } } - match node.right.unwrap().value.value.to_lowercase().trim() { + // Check the right arm to know the index being used. + let right = node.right.as_ref().unwrap(); + match right.value.value.to_lowercase().trim() { "x" => { if val.size == 1 { Ok((AddressingMode::ZeropageIndexedX, val)) @@ -337,20 +746,19 @@ impl Assembler { Ok((AddressingMode::IndexedY, val)) } } - _ => Err(self.parser_error("can only use X and Y as indices")), + _ => Err(EvalError { + message: "can only use X and Y as indices".to_string(), + line: node.value.line, + }), } } - fn get_from_left(&mut self, node: Box<PNode>) -> Result<(AddressingMode, Bundle)> { - let left = node.left.unwrap(); - - if left.value.value.to_lowercase().trim() == "a" { - return Ok((AddressingMode::Implied, Bundle::new())); + fn get_from_left(&mut self, left_arm: &PNode) -> Result<(AddressingMode, Bundle), EvalError> { + if left_arm.value.value.to_lowercase().trim() == "a" { + return Ok((AddressingMode::Implied, Bundle::default())); } - self.literal_mode = None; // TODO: needed? - let val = self.evaluate_node(left)?; - + let val = self.evaluate_node(left_arm)?; match self.literal_mode { Some(LiteralMode::Hexadecimal) => { if val.size == 1 { @@ -361,1692 +769,631 @@ impl Assembler { } Some(LiteralMode::Plain) => { if val.size > 1 { - Err(self.parser_error("immediate is too big")) + Err(EvalError { + message: "immediate is too big".to_string(), + line: left_arm.value.line, + }) } else { Ok((AddressingMode::Immediate, val)) } } - _ => { - Err(self - .parser_error("left arm of instruction is neither an address nor an immediate")) - } - } - } - - fn evaluate_control(&mut self, node: Box<PNode>) -> Result<Bundle> { - let id = node.value.value.to_lowercase(); - let id_str = id.as_str(); - - // If we are just dealing with context resolution/assignment and the - // current control statement does not matter on that regard, just skip - // it. - // if self.only_context && !TOUCH_CONTEXT.contains(&id_str) { - // return Ok(Bundle::new()); - // } - - match id_str { - ".hibyte" => self.evaluate_hilo_byte(node.args.unwrap_or(vec![]), true), - ".lobyte" => self.evaluate_hilo_byte(node.args.unwrap_or(vec![]), false), - ".scope" => self.evaluate_scope_definition(node), - ".endscope" => self.evaluate_scope_end(), - // ".segment" => self.parse_segment_definition(&id, line), - // ".byte" | ".db" => self.parse_literal_bytes(&id, line, false), - // ".word" | ".dw" | ".addr" => self.parse_literal_bytes(&id, line, true), - // ".proc" => self.parse_proc_definition(&id, line), - // ".endproc" => self.parse_proc_end(&id), - // ".macro" => self.parse_macro_definition(&id, line), - // ".endmacro" => self.parse_macro_end(&id), - _ => Err(self.parser_error(format!("unknown control statement '{}'", id).as_str())), + _ => Err(EvalError { + message: "left arm of instruction is neither an address nor an immediate" + .to_string(), + line: left_arm.value.line, + }), } } +} - fn evaluate_literal(&mut self, node: Box<PNode>) -> Result<Bundle> { - let mut prev = None; - self.literal_mode = None; - - let ret = match node.value.value.chars().nth(0) { - Some(prefix) => { - if prefix == '$' { - prev = Some(LiteralMode::Hexadecimal); - } else if prefix == '%' { - prev = Some(LiteralMode::Binary); - } else { - prev = Some(LiteralMode::Plain); - } - self.literal_mode = prev.clone(); - self.evaluate_node(node.left.unwrap()) - } - None => Err(self.parser_error("no prefix was given to operand")), - }; - - self.literal_mode = prev; +#[cfg(test)] +mod tests { + use super::*; + use crate::mapping::EMPTY; - ret - } + fn assert_instruction(line: &str, hex: &[u8]) { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm.assemble(line.as_bytes()).unwrap(); - fn evaluate_hexadecimal(&mut self, node: Box<PNode>) -> Result<Bundle> { - let mut chars = node.value.value.chars(); - let mut bytes = [0, 0, 0]; - let size: u8; + assert_eq!(res.len(), 1); - match node.value.value.len() { - 1 => { - bytes[0] = self.char_to_hex(chars.next())?; - size = 1; - } - 2 => { - bytes[0] = self.char_to_hex(chars.next())? * 16; - bytes[0] += self.char_to_hex(chars.next())?; - size = 1; - } - 3 => { - bytes[1] = self.char_to_hex(chars.next())?; - bytes[0] = self.char_to_hex(chars.next())? * 16; - bytes[0] += self.char_to_hex(chars.next())?; - size = 2; - } - 4 => { - bytes[1] = self.char_to_hex(chars.next())? * 16; - bytes[1] += self.char_to_hex(chars.next())?; - bytes[0] = self.char_to_hex(chars.next())? * 16; - bytes[0] += self.char_to_hex(chars.next())?; - size = 2; - } - _ => return Err(self.parser_error("expecting a number of 1 to 4 hexadecimal digits")), + for i in 0..res[0].size { + assert_eq!(hex[i as usize], res[0].bytes[i as usize]); } - - Ok(Bundle { - bytes, - size, - address: 0, - cycles: 0, - affected_on_page: false, - }) } - fn char_to_hex(&mut self, oc: Option<char>) -> Result<u8> { - match oc { - Some(c) => match c.to_digit(16) { - Some(c) => Ok(c as u8), - None => Err(self.parser_error("could not convert digit to hexadecimal")), - }, - None => Err(self.parser_error("digit out of bounds")), - } + fn assert_error(line: &str, id: &str, line_num: usize, message: &str) { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm.assemble(line.as_bytes()); + let msg = format!("{} error (line {}): {}.", id, line_num, message); + assert_eq!(res.unwrap_err().first().unwrap().to_string().as_str(), msg); } - fn evaluate_binary(&mut self, node: Box<PNode>) -> Result<Bundle> { - let string = node.value.value.as_str(); - let mut value = 0; - let mut shift = 0; - - for c in string.chars().rev() { - if c == '1' { - let val = 1 << shift; - value += val; - } else if c != '0' { - return Err( - self.parser_error(format!("bad binary format for '{}'", string).as_str()) - ); - } - - shift += 1; - } - - if shift < 8 { - Err(self.parser_error("missing binary digits to get a full byte")) - } else if shift > 8 { - Err(self.parser_error("too many binary digits for a single byte")) - } else { - Ok(Bundle { - bytes: [value as u8, 0, 0], - size: 1, - address: 0, - cycles: 0, - affected_on_page: false, - }) - } + fn assert_eval_error(line: &str, message: &str) { + assert_error(line, "Evaluation", 1, message); } - fn evaluate_decimal(&mut self, node: Box<PNode>) -> Result<Bundle> { - let string = node.value.value.as_str(); - if string.is_empty() { - return Err(self.parser_error("empty decimal literal")); - } - - match self.do_evaluate_decimal(string) { - Ok(val) => Ok(val), - Err(e) => { - if e.parse { - Err(e) - } else { - self.fetch_variable(string) - } - } - } + fn assert_context_error(line: &str, message: &str, line_num: usize) { + assert_error(line, "Context", line_num, message); } - fn fetch_variable(&mut self, mut string: &str) -> Result<Bundle> { - // Get the context that might be being referenced. - let ctxt = match string.find("::") { - Some(_) => { - let tctxt = string.rsplit_once("::").unwrap_or(("", "")); - if tctxt.0.is_empty() { - self.context.current() - } else { - string = tctxt.1; - self.context.find(tctxt.0) - } - } - None => self.context.current(), - }; + // Empty - // println!("{:#?}", self.context); - // println!("{:#?}", ctxt); - - match ctxt { - Some(hash) => { - match hash.get(string) { - Some(var) => { - // TODO - // If this is just a memory address (e.g. - // label), then just return it as is. - // if var.label { - // return Ok((node.clone(), false)); - // } - Ok(var.value.clone()) - } - None => { - Err(self.parser_error(format!("unknown variable '{}'", string).as_str())) - } - } - } - None => Err(self.parser_error(format!("unknown scope '{}'", "Global").as_str())), + #[test] + fn empty_line() { + for line in vec!["", " ", ";; Comment", " ;; Comment"].into_iter() { + let mut assembler = Assembler::new(EMPTY.to_vec()); + let bundles = assembler.assemble(line.as_bytes()).unwrap(); + assert!(bundles.is_empty()); } } - fn do_evaluate_decimal(&mut self, string: &str) -> Result<Bundle> { - let mut value = 0; - let mut shift = 1; - - if string.is_empty() { - return Err(self.parser_error("empty decimal literal")); - } - - for c in string.chars().rev() { - if shift > 100 { - return Err(self.parser_error("decimal value is too big")); - } - if c != '0' { - match c.to_digit(10) { - Some(digit) => { - value += digit * shift; - } - None => { - return Err(ParseError { - line: self.line, - message: format!("'{}' is not a decimal value", c), - parse: false, - }); - } - } - } - - shift *= 10; - } - if value > 255 { - return Err(self.parser_error("decimal value is too big")); - } + // Literal modes - Ok(Bundle { - bytes: [value as u8, 0, 0], - size: 1, - address: 0, - cycles: 0, - affected_on_page: false, - }) + #[test] + fn parse_binary() { + assert_eval_error("adc #%0001", "missing binary digits to get a full byte"); + assert_eval_error("adc #%0001000", "missing binary digits to get a full byte"); + assert_eval_error( + "adc #%000100001", + "too many binary digits for a single byte", + ); + assert_error( + r#" +Variable = 42 +adc %Variable +"#, + "Evaluation", + 3, + "you cannot use variables like 'Variable' in binary literals", + ); + assert_instruction("adc #%10100010", &[0x69, 0xA2]); } - fn evaluate_hilo_byte(&mut self, args: Vec<Box<PNode>>, hi: bool) -> Result<Bundle> { - if args.len() != 1 { - return Err(self.parser_error("wrong number of arguments: expecting exactly one")); - } + #[test] + fn parse_hexadecimal() { + assert_eval_error( + "adc #$12345", + "expecting a number of 1 to 4 hexadecimal digits", + ); + assert_eval_error("adc $AW", "could not convert digit to hexadecimal"); + assert_error( + r#" +Variable = 42 +adc $Variable +"#, + "Evaluation", + 3, + "you cannot use variables like 'Variable' in hexadecimal literals", + ); + assert_error( + r#" +Four = 4 +adc $Four +"#, + "Evaluation", + 3, + "you cannot use variables like 'Four' in hexadecimal literals", + ); + assert_instruction("adc $AA", &[0x65, 0xAA]); + assert_instruction("adc $10", &[0x65, 0x10]); + assert_instruction("adc $10AB", &[0x6D, 0xAB, 0x10]); + } - let val = self.evaluate_node(args.first().unwrap().clone())?; - if val.size < 1 { - let s = if hi { ".hibyte" } else { ".lobyte" }; - return Err(self.parser_error(format!("empty value for {}", s).as_str())); - } + #[test] + fn parse_decimal() { + assert_eval_error("adc #256", "decimal value is too big"); + assert_eval_error("adc #2000", "decimal value is too big"); + assert_eval_error( + "adc #2A", + "'A' is not a decimal value and could not find variable '2A' in the global scope either", + ); + assert_instruction("adc #1", &[0x69, 0x01]); + } - let b = if hi { - if val.size == 1 { - val.bytes[0] - } else { - val.bytes[1] - } - } else { - val.bytes[0] - }; + // Variables - Ok(Bundle { - bytes: [b, 0, 0], - size: 1, - address: 0, - cycles: 0, - affected_on_page: false, - }) - } + #[test] + fn scoped_variable() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +.scope One ; This is a comment + adc #Variable - fn evaluate_scope_definition(&mut self, node: Box<PNode>) -> Result<Bundle> { - println!("{:#?}", node); - match node.left { - Some(identifier) => { - self.context.push(&identifier.value.value); - // TODO: mapping? + Variable = $20 +.endscope - Ok(Bundle::new()) - } - None => return Err(self.parser_error("scope definition with no identifier")), - } - } +.scope Another + Variable = $40 +.endscope - fn evaluate_scope_end(&mut self) -> Result<Bundle> { - if !self.context.pop() { - return Err(self.parser_error("missmatched '.endscope': there is no scope to end")); - } +Variable = $30 +adc #Variable - // TODO: mapping? +adc #One::Variable +adc #Another::Variable +"# + .as_bytes(), + ) + .unwrap(); - Ok(Bundle::new()) - } + assert_eq!(res.len(), 4); + let instrs: Vec<[u8; 2]> = vec![[0x69, 0x20], [0x69, 0x30], [0x69, 0x20], [0x69, 0x40]]; - // pub fn assemble(&mut self, reader: impl Read) -> Result<Vec<&dyn Encodable>> { - // let mut instructions: Vec<&dyn Encodable> = vec![]; - - // self.assemble_nodes(reader)?; - - // let mut idx: usize = 0; - // for segment in &self.mapping.segments { - // let mut size: usize = 0; - - // while idx < segment.start.into() { - // match &segment.fill { - // Some(fill) => instructions.push(fill), - // None => instructions.push(&Fill { value: 0x00 }), - // } - // idx += 1; - // } - - // for node in &self.mapping.nodes[&segment.name] { - // match node { - // Node::Instruction(instr) => { - // instructions.push(instr); - // size += usize::from(instr.size()); - // } - // Node::Literal(lit) => { - // instructions.push(lit); - // size += usize::from(lit.size()); - // } - // _ => {} - // } - // } - - // if size > segment.size { - // return Err(ParseError { - // line: 0, - // message: format!( - // "segment '{}' expected a size of '{}' bytes but '{}' bytes were produced instead", - // segment.name, size, segment.size - // ), - // }); - // } - // idx += size; - // if segment.fill.is_none() { - // continue; - // } - - // while size < segment.size { - // instructions.push(segment.fill.as_ref().unwrap()); - // size += 1; - // idx += 1; - // } - // } - - // Ok(instructions) - // } - - // pub fn evaluate(&mut self) -> Result<()> { - // for segment in &self.mapping.segments { - // for node in self.mapping.nodes.get_mut(&segment.name).unwrap() { - // match node { - // Node::Instruction(instr) => { - // Self::update_instruction_with_context(instr, &self.context)?; - // instr.address = segment.start; - - // self.offsets - // .entry(segment.name.clone()) - // .and_modify(|value| { - // instr.address += *value as u16; - // *value += usize::from(instr.size()) - // }) - // .or_insert(instr.size().into()); - // } - // Node::Scoped(scope) => { - // if scope.start { - // self.context.push(&scope.identifier.value); - // } else { - // _ = self.context.pop(); - // } - // } - // Node::Literal(literal) => { - // Self::update_literal_with_context(literal, &self.context)?; - // self.offsets - // .entry(segment.name.clone()) - // .and_modify(|value| *value += usize::from(literal.size())) - // .or_insert(literal.size().into()); - // } - // Node::Label(label) => { - // let address = - // usize::from(segment.start) + self.offsets.get(&segment.name).unwrap(); - - // self.context - // .current_mut() - // .unwrap() - // .entry(label.value.clone()) - // .and_modify(|e| e.value = address); - // } - // _ => {} - // } - // } - // } - - // Ok(()) - // } - - // // TODO: oh boy... - // pub fn resolve_labels(&mut self) -> Result<()> { - // for segment in &self.mapping.segments { - // for node in self.mapping.nodes.get_mut(&segment.name).unwrap() { - // match node { - // Node::Instruction(instr) => { - // if !instr.resolved { - // match &instr.left { - // Some(pstring) => { - // match self.context.current().unwrap().get(&pstring.value) { - // Some(entry) => { - // if instr.mode == AddressingMode::Absolute { - // let bytes = entry.value.to_le_bytes(); - // instr.bytes = [bytes[0], bytes[1]]; - // } else { - // let diff: isize = entry.value as isize - // - (instr.address as isize + 2); - // if diff < -128 || diff > 127 { - // return Err(instr.mnemonic.parser_error( - // format!("relative addressing out of range") - // .as_str(), - // )); - // } - // let bytes = diff.to_le_bytes(); - // instr.bytes = [bytes[0], 0]; - // } - // } - // None => { - // return Err(instr.mnemonic.parser_error( - // format!("label '{}' not found", pstring.value) - // .as_str(), - // )) - // } - // } - // } - // None => { - // return Err(instr.mnemonic.parser_error( - // format!("there is no label for the given jump instruction") - // .as_str(), - // )) - // } - // } - // } - // } - // Node::Literal(literal) => { - // if !literal.resolved { - // match self - // .context - // .current() - // .unwrap() - // .get(&literal.identifier.value) - // { - // Some(entry) => { - // let bytes = entry.value.to_le_bytes(); - // literal.bytes = [bytes[0], bytes[1]]; - // } - // None => { - // return Err(literal.identifier.parser_error( - // format!( - // "'{}' is neither a known variable or label at this scope", - // literal.identifier.value - // ) - // .as_str(), - // )) - // } - // } - // } - // } - // _ => {} - // } - // } - // } - - // Ok(()) - // } - - // fn update_instruction_with_context(instr: &mut Instruction, context: &Context) -> Result<()> { - // // To keep things simple, we remove out the `implied` case and we parse - // // further with a known `Some` value for the base algorithm implemented - // // in `update_instruction_and_bytes`. - // if instr.left.is_some() { - // Self::update_addressing_and_bytes(instr, context)?; - // } else { - // instr.mode = AddressingMode::Implied; - // } - - // // Now that we have the addressing mode and the bytes, we can fill out - // // the rest of it by fetching the values on `INSTRUCTIONS`. - // match INSTRUCTIONS.get(&instr.mnemonic.value.to_lowercase()) { - // Some(entries) => match entries.get(&instr.mode) { - // Some(values) => { - // instr.cycles = values.cycles; - // instr.opcode = values.opcode; - // instr.size = values.size; - // instr.affected_on_page = values.affected_on_page; - // } - // None => { - // return Err(instr.mnemonic.parser_error( - // format!( - // "bad addressing mode '{}' for the instruction '{}'", - // &instr.mode, &instr.mnemonic.value - // ) - // .as_str(), - // )); - // } - // }, - // None => { - // return Err(instr.mnemonic.parser_error( - // format!("unknown instruction '{}'", &instr.mnemonic.value).as_str(), - // )); - // } - // } - - // Ok(()) - // } - - // fn update_addressing_and_bytes(instr: &mut Instruction, context: &Context) -> Result<()> { - // // `unwrap()` is guaranteed to work by the caller. - // let left = instr.left.as_ref().unwrap(); - - // // We will first try to check if there's any variable involved on the - // // left arm and replace the string if so. This will greatly simplify - // // things down the line. That being said, there is a special reserved - // // case, which is the implied addressing by using "a". In this case, we - // // want to ensure that we assume an implied addressing and not a - // // variable named "a". - // if left.value.to_lowercase() == "a" { - // instr.mode = AddressingMode::Implied; - // } else { - // let (nleft, resolved) = Self::replace_variable(left, context)?; - // // TODO - // instr.resolved = resolved; - // if !resolved { - // if instr.mnemonic.value == "jmp" { - // instr.mode = AddressingMode::Absolute; - // } else { - // instr.mode = AddressingMode::RelativeOrZeropage; - // } - // } - - // if nleft.value.starts_with('$') { - // // This is an address. At this point we should assume that the - // // left node contains the address itself, and that the right one - // // will contain whether there is indexing. - - // let string = nleft.value.chars().as_str(); - // instr.bytes = Self::parse_hex_from(string, &nleft, true, false, true)?; - - // match &instr.right { - // Some(xy) => match xy.value.to_lowercase().as_str() { - // "x" => { - // if string.len() == 3 { - // instr.mode = AddressingMode::ZeropageIndexedX; - // } else { - // instr.mode = AddressingMode::IndexedX; - // } - // } - // "y" => { - // if string.len() == 3 { - // instr.mode = AddressingMode::ZeropageIndexedY; - // } else { - // instr.mode = AddressingMode::IndexedY; - // } - // } - // _ => return Err(xy.parser_error("index is neither X nor Y")), - // }, - // None => { - // if string.len() == 3 { - // instr.mode = AddressingMode::RelativeOrZeropage; - // } else { - // instr.mode = AddressingMode::Absolute; - // } - // } - // } - // } else if nleft.value.starts_with('#') { - // // Immediate addressing in any case: hexadecimal, binary or - // // decimal. Hence, just figure out the character being used and - // // call the right function for it. - - // let mut chars = nleft.value.chars(); - // chars.next(); - // let string = chars.as_str(); - - // instr.bytes = Self::parse_numeric(string, &nleft, false)?; - // instr.mode = AddressingMode::Immediate; - // } else if nleft.value.starts_with('(') { - // // Indirect addressing. In this case the left arm can be further - // // subdivided. That is, indirect X-indexing is represented like - // // so: `instr ($NN, x)`. Hence, first of all we have to figure - // // out whether there is a subdivision. - - // let (left1, oleft2) = Self::split_left_arm(&nleft)?; - // match oleft2 { - // Some(left2) => { - // // There is subdivision. Thus, we have to assume - // // indirect X-indexing, which means that the right arm - // // should be None and that the right side of the left - // // node must match the X register. Other than that, the - // // address being referenced must be zero page. - // if instr.right.is_some() { - // return Err(instr.right.as_ref().unwrap().parser_error( - // "bad indirect mode, expecting an indirect X-indexed addressing mode" - // )); - // } - // if left2.value.to_lowercase() != "x" { - // return Err(left2.parser_error( - // "the index in indirect X-indexed addressing must be X", - // )); - // } - // match Self::parse_hex_from(&left1.value, &left1, false, false, true) { - // Ok(bytes) => instr.bytes = bytes, - // Err(e) => { - // let msg = String::from( - // "when parsing an instruction with indirect X-indexed addressing: ", - // ) + &e.message; - // return Err(left1.parser_error(msg.as_str())); - // } - // } - // instr.mode = AddressingMode::IndirectX; - // } - // None => { - // // There is no subdivision on the left arm. Hence, if - // // there is something on the right arm then we must - // // assume indirect Y-index addressing, and if not then - // // it's indirect addressing with no indices involvved. - // if instr.right.is_some() { - // if instr.right.as_ref().unwrap().value.to_lowercase() != "y" { - // return Err(instr.right.as_ref().unwrap().parser_error( - // "the index in indirect Y-indexed addressing must be Y", - // )); - // } - // match Self::parse_hex_from(&left1.value, &left1, false, false, true) { - // Ok(bytes) => instr.bytes = bytes, - // Err(e) => { - // let msg = String::from( - // "when parsing an instruction with indirect Y-indexed addressing: ", - // ) + &e.message; - // return Err(left1.parser_error(msg.as_str())); - // } - // } - // instr.mode = AddressingMode::IndirectY; - // } else { - // instr.bytes = - // Self::parse_hex_from(&left1.value, &left1, true, true, true)?; - // instr.mode = AddressingMode::Indirect; - // } - // } - // } - // } else { - // // At this point all of the syntax cases have been exhausted: - // // the programmer messed up. From this point on we try to figure - // // out how they messed up. - - // if nleft.value.starts_with('=') { - // return Err(instr.mnemonic.parser_error( - // format!( - // "cannot use '{}' in an assignment because it's a word reserved for an instruction mnemonic", - // instr.mnemonic.value - // ).as_str(), - // )); - // } - // // TODO: - // // instr.mode = AddressingMode::Absolute; - // // return Err(instr.mnemonic.parser_error( - // // format!( - // // "unknown addressing mode for instruction '{}'", - // // instr.mnemonic.value - // // ) - // // .as_str(), - // // )); - // } - // } - - // Ok(()) - // } - - // fn update_literal_with_context(literal: &mut Literal, context: &Context) -> Result<()> { - // // If it has already been set, skip it. - // // TODO: add a proper `is_set` thingie to it instead of this hack. - // if literal.bytes[0] != 0 || literal.bytes[1] != 0 { - // return Ok(()); - // } - - // // Evaluate any possible variable being used inside of this literal. - // let (evaled, resolved) = Self::replace_variable(&literal.identifier, context)?; - - // // It may happen that the literal is just a label that is to be resolved - // // in the future. If so, let's leave early. - // literal.resolved = resolved; - // if !resolved { - // return Ok(()); - // } - - // // Parse the numeric value after a possible variable has been replaced. - // let two_bytes_allowed = literal.size == 2; - // let res = Self::parse_numeric( - // evaled.value.as_str(), - // &literal.identifier, - // two_bytes_allowed, - // ); - - // // And finally assign the computed bytes. - // match res { - // Ok(bytes) => { - // literal.bytes = bytes; - // Ok(()) - // } - // Err(e) => { - // let msg = String::from("when parsing a data literal: ") + &e.message; - // Err(literal.identifier.parser_error(msg.as_str())) - // } - // } - // } - - // fn parse_numeric(string: &str, node: &PString, two_bytes_allowed: bool) -> Result<[u8; 2]> { - // if string.starts_with('$') { - // Ok(Self::parse_hex_from( - // string, - // node, - // two_bytes_allowed, - // false, - // true, - // )?) - // } else if string.starts_with('%') { - // Ok([Self::parse_binary_from(string, node)?, 0]) - // } else { - // Ok([Self::parse_decimal_from(string, node)?, 0]) - // } - // } - - // fn split_left_arm(node: &PString) -> Result<(PString, Option<PString>)> { - // let mut chars = node.value.chars(); - // chars.next(); - // let string = chars.as_str(); - - // match string.find(|c: char| c == ',') { - // Some(idx) => { - // let left1 = string.get(..idx).unwrap_or("").trim(); - // let left2 = string.get(idx + 1..).unwrap_or("").trim(); - - // Ok(( - // PString { - // value: left1.to_string(), - // line: node.line, - // range: Range { - // start: node.range.start + 1, - // end: node.range.start + 1 + left1.len(), - // }, - // }, - // Some(PString { - // value: left2.to_string(), - // line: node.line, - // range: Range { - // start: node.range.start + 1 + idx, - // end: node.range.start + 1 + idx + left2.len(), - // }, - // }), - // )) - // } - // None => Ok(( - // PString { - // value: string.to_string(), - // line: node.line, - // range: Range { - // start: node.range.start + 1, - // end: node.range.end, - // }, - // }, - // None, - // )), - // } - // } - - // fn parse_binary_from(string: &str, node: &PString) -> Result<u8> { - // let mut value = 0; - // let mut shift = 0; - - // for c in string.get(1..).unwrap_or("").chars().rev() { - // if c == '1' { - // let val = 1 << shift; - // value += val; - // } else if c != '0' { - // return Err( - // node.parser_error(format!("bad binary format for '{}'", string).as_str()) - // ); - // } - - // shift += 1; - // } - - // if shift < 8 { - // Err(node.parser_error("missing binary digits to get a full byte")) - // } else if shift > 8 { - // Err(node.parser_error("too many binary digits for a single byte")) - // } else { - // Ok(value) - // } - // } - - // // TODO: returns if resolved - // fn replace_variable(node: &PString, context: &Context) -> Result<(PString, bool)> { - // match node - // .value - // .find(|c: char| c.is_alphabetic() || c == '_' || c == '@') - // { - // Some(idx) => { - // // Before doing any replacement, let's check the character - // // before the one that was found. In this case, if it was a - // // proper ASCII digit, then it cannot be a variable but it's - // // part of a numeric literal (e.g. '1A'): then just let the - // // different numeric parsing functions do their job. - // if idx > 0 { - // let prev = node.value.chars().nth(idx - 1).unwrap_or(' '); - // if prev.is_ascii_digit() { - // return Ok((node.clone(), true)); - // } - // } - - // // The variable might still be before an inner comma (e.g. - // // sta ($20, x)). We will assume that variables can happen - // // only before that. - // let end = node.value.find(',').unwrap_or(node.value.len()); - // let mut string = node.value.get(idx..end).unwrap_or(""); - // let tail = node.value.get(end..).unwrap_or(""); - - // // Get the context that might be being referenced. - // let ctxt = match string.find("::") { - // Some(_) => { - // let tctxt = string.rsplit_once("::").unwrap_or(("", "")); - // if tctxt.0.is_empty() { - // context.current() - // } else { - // string = tctxt.1; - // context.find(tctxt.0) - // } - // } - // None => context.current(), - // }; - - // match ctxt { - // Some(hash) => { - // // If there was a comma before the "variable" (i.e. idx > - // // end and hence string == ""), or this is just the regular - // // X or Y index, just return early. - // match string.to_lowercase().as_str() { - // "x" | "y" | "" => return Ok((node.clone(), true)), - // _ => {} - // } - - // // It's not any of the indices, let's look for a match on - // // the current scope. - // match hash.get(string) { - // Some(var) => { - // // If this is just a memory address (e.g. - // // label), then just return it as is. - // if var.label { - // return Ok((node.clone(), false)); - // } - - // let value = String::from(node.value.get(..idx).unwrap_or("")) - // + var.node.value.as_str(); - // Ok(( - // PString { - // value: value.clone() + tail, - // line: node.line, - // range: Range { - // start: node.range.start, - // end: node.range.start + value.len(), - // }, - // }, - // true, - // )) - // } - // None => { - // // If a variable could not be found, check that - // // this is not a purely hexadecimal number (e.g. - // // 'AA'). If that's the case, then just return - // // its value. - // if Self::parse_hex_from(string, node, true, false, false).is_ok() { - // return Ok((node.clone(), true)); - // } - - // // We've tried hard to not assume the programmer - // // messing up, but there's no other way around - // // it: it's an "unknown variable" error. - // return Err(node.parser_error( - // format!("unknown variable '{}'", string).as_str(), - // )); - // } - // } - // } - // None => { - // Err(node.parser_error(format!("unknown scope '{}'", "Global").as_str())) - // } - // } - // } - // None => Ok((node.clone(), true)), - // } - // } - - // fn parse_macro_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_identifier(id, line)?; - // if identifier.is_reserved() { - // return Err(identifier.parser_error( - // format!( - // "cannot use reserved name '{}' for proc name", - // identifier.value - // ) - // .as_str(), - // )); - // } - - // self.mapping.current_macro = Some(identifier.value.clone()); - // self.mapping.macros.entry(identifier.value).or_default(); - // Ok(()) - // } - - // fn parse_macro_end(&mut self, id: &PString) -> Result<()> { - // match self.mapping.current_macro { - // Some(_) => self.mapping.current_macro = None, - // None => { - // return Err(id.parser_error( - // format!("bad `.endmacro`: we are not inside of a macro definition").as_str(), - // )) - // } - // } - - // Ok(()) - // } - - // fn parse_proc_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_identifier(id, line)?; - // if identifier.is_reserved() { - // return Err(identifier.parser_error( - // format!( - // "cannot use reserved name '{}' for proc name", - // identifier.value - // ) - // .as_str(), - // )); - // } - - // // Insert the given identifier into the context. - // if let Some(entry) = self.context.current_mut() { - // match entry.entry(identifier.value.clone()) { - // Entry::Occupied(e) => { - // return Err(ParseError { - // line: self.line, - // message: format!( - // "proc '{}' already exists for this context: it was previously defined in line {}", - // id.value, e.get().node.line), - // }) - // } - // Entry::Vacant(e) => e.insert(PValue { - // node: PString { - // value: identifier.value.clone(), - // line: self.line, - // range: Range { - // start: id.range.start, - // end: id.range.end, - // }, - // }, - // value: 0, - // label: true, - // }), - // }; - // } - - // // And add the node so it's picked up later. - // self.mapping.push(Node::Label(Label { - // value: identifier.value.to_string(), - // })); - - // // TODO: lol - // self.context.push_stack(&identifier.value); - - // self.mapping.push(Node::Scoped(Scoped { - // identifier: identifier.clone(), - // start: true, - // })); - - // Ok(()) - // } - - // fn parse_proc_end(&mut self, id: &PString) -> Result<()> { - // if !self.context.pop() { - // return Err(id.parser_error("missmatched '.endproc': there is no proc to end")); - // } - // self.mapping.push(Node::Scoped(Scoped { - // identifier: PString::new(), - // start: false, - // })); - - // Ok(()) - // } - - // fn parse_segment_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_possibly_quoted_identifier(id, line)?; - // self.mapping.switch(&identifier)?; - - // Ok(()) - // } - - // fn parse_scope_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_identifier(id, line)?; - // if identifier.is_reserved() { - // return Err(identifier.parser_error( - // format!("cannot use reserved name '{}'", identifier.value).as_str(), - // )); - // } - // self.context.push(&identifier.value); - // self.mapping.push(Node::Scoped(Scoped { - // identifier, - // start: true, - // })); - - // Ok(()) - // } - - // fn parse_scope_end(&mut self, id: &PString) -> Result<()> { - // if !self.context.pop() { - // return Err(id.parser_error("missmatched '.endscope': there is no scope to end")); - // } - // self.mapping.push(Node::Scoped(Scoped { - // identifier: PString::new(), - // start: false, - // })); - - // Ok(()) - // } - - // fn parse_literal_bytes( - // &mut self, - // node: &PString, - // line: &str, - // two_bytes_allowed: bool, - // ) -> Result<()> { - // loop { - // self.skip_whitespace(line); - - // match line.chars().nth(self.column) { - // Some(byte) => { - // let needle = if byte == '\'' { - // self.column += 1; - // self.skip_whitespace(line); - // '\'' - // } else if byte == '"' { - // self.column += 1; - // self.skip_whitespace(line); - // '"' - // } else { - // ',' - // }; - - // // Find the index of the needle. If it cannot be found, try - // // to find the first whitespace (e.g. to ditch out inline - // // comments or other artifacts). If neither of these are - // // found, it will simply return the end of the string. - // // - // // TODO: instead of ditching out what's right of the first - // // whitespace, try to error out on weird scenarios. - // let needle_idx = line - // .get(self.column..) - // .unwrap_or("") - // .find(|c: char| c == needle); - // let idx = match needle_idx { - // Some(v) => v, - // None => line - // .get(self.column..) - // .unwrap_or("") - // .find(|c: char| c.is_whitespace()) - // .unwrap_or(line.len() - self.column), - // }; - - // // If this is the last character, the needle was a quote and - // // the last char is not the needle, then it means that the - // // quote was left open. Complain about this as well. - // if idx == line.len() - self.column { - // if line.chars().nth(idx).unwrap_or(' ') != needle - // && (needle == '"' || needle == '\'') - // { - // return Err(node.parser_error("non-terminated quote for byte literal")); - // } - // } - - // // Now we have our string. Before pushing it, though, there - // // is a special case for alphabetic literals that need to be - // // translated. - // let string = line.get(self.column..self.column + idx).unwrap_or(" "); - // let mut bytes: [u8; 2] = [0, 0]; - // if string.len() == 1 && string.chars().nth(0).unwrap().is_ascii_alphabetic() { - // let v = Vec::from(string); - // bytes[0] = v[0]; - // } - - // // NOTE: for now we push an incomplete literal. We need the - // // first pass to fill the context and then a second pass - // // will evaluate each literal as needed (e.g. replacing - // // values from variables being used in this literal). - // self.mapping.push(Node::Literal(Literal { - // identifier: PString { - // value: string.to_owned(), - // line: self.line, - // range: Range { - // start: self.column, - // end: self.column + idx, - // }, - // }, - // size: if two_bytes_allowed { 2 } else { 1 }, - // bytes, - // resolved: true, - // })); - - // self.column += idx; - // for c in line.get(self.column..).unwrap_or(" ").chars() { - // if c == ',' { - // break; - // } - // if c == ';' { - // return Ok(()); - // } - // self.column += 1; - // } - // self.column += 1; - // self.skip_whitespace(line); - // } - // None => break, - // }; - // } - - // Ok(()) - // } - - // fn fetch_identifier(&mut self, id: &PString, line: &str) -> Result<PString> { - // let idx = line - // .get(self.column..) - // .unwrap_or(" ") - // .find(|c: char| c.is_whitespace()); - - // match idx { - // Some(offset) => { - // let end = self.column + offset; - // let rest = line.get(end..).unwrap_or("").trim(); - // if !rest.is_empty() { - // if rest.chars().nth(0).unwrap_or(' ') != ';' { - // return Err(id.parser_error( - // "there should not be any further content besides the identifier", - // )); - // } - // } - // Ok(PString { - // value: line.get(self.column..end).unwrap_or(" ").trim().to_string(), - // line: self.line, - // range: Range { - // start: self.column, - // end, - // }, - // }) - // } - // None => Ok(PString { - // value: line.get(self.column..).unwrap_or(" ").trim().to_string(), - // line: self.line, - // range: Range { - // start: self.column, - // end: line.len(), - // }, - // }), - // } - // } - - // fn fetch_possibly_quoted_identifier(&mut self, id: &PString, line: &str) -> Result<PString> { - // let mut identifier = self.fetch_identifier(id, line)?; - - // if identifier.value.starts_with('\'') || identifier.value.starts_with('`') { - // return Err(id.parser_error("use double quotes for the segment identifier instead")); - // } else if identifier.value.starts_with('"') { - // identifier.value = match identifier - // .value - // .get(1..(identifier.range.end - identifier.range.start - 1)) - // { - // Some(v) => v.to_string(), - // None => return Err(id.parser_error("could not fetch quoted identifier")), - // }; - // if identifier.value.contains('"') { - // return Err(id.parser_error("do not use double quotes inside of the identifier")); - // } - // identifier.range.start += 1; - // identifier.range.end -= 1; - // } - - // Ok(identifier) - // } - - // fn parse_label(&mut self, id: PString, _line: &str) -> Result<()> { - // let name = &id.value.as_str()[..id.value.len() - 1].to_string(); - - // // Forbid weird scenarios. - // if name.contains("::") { - // return Err(id.parser_error( - // format!( - // "the label '{}' is scoped: do not declare variables this way", - // id.value - // ) - // .as_str(), - // )); - // } - - // // Insert the given label into the context. - // if let Some(entry) = self.context.current_mut() { - // match entry.entry(name.clone()) { - // Entry::Occupied(e) => { - // return Err(ParseError { - // line: self.line, - // message: format!( - // "label '{}' already exists for this context: it was previously defined in line {}", - // id.value, e.get().node.line), - // }) - // } - // Entry::Vacant(e) => e.insert(PValue { - // node: PString { - // value: name.clone(), - // line: self.line, - // range: Range { - // start: id.range.start, - // end: id.range.end, - // }, - // }, - // value: 0, - // label: true, - // }), - // }; - // } - - // // And add the node so it's picked up later. - // self.mapping.push(Node::Label(Label { - // value: name.to_string(), - // })); - // Ok(()) - // } - - fn parser_error(&self, msg: &str) -> ParseError { - ParseError { - message: String::from(msg), - line: self.line, - parse: true, + for i in 0..4 { + assert_eq!(res[i].size, 2); + assert_eq!(res[i].bytes[0], instrs[i][0]); + assert_eq!(res[i].bytes[1], instrs[i][1]); } } - // fn from_byte_reader<R: Read>(&mut self, mut reader: R) -> Result<()> { - // loop { - // let mut buf = [0; 1]; - // let n = reader.read(&mut buf)?; - // if n == 0 { - // break; - // } - - // match OPCODES.get(&buf[0]) { - // Some(v) => { - // let mut bs = [0; 2]; - // for i in 0..v.size - 1 { - // let nn = reader.read(&mut buf)?; - // if nn == 0 { - // break; - // } - // bs[i as usize] = buf[0]; - // } - // self.mapping.push(Node::Instruction(Instruction { - // mnemonic: PString::from(&v.mnemonic), - // opcode: v.opcode, - // size: v.size, - // bytes: bs, - // left: None, - // right: None, - // mode: v.mode.to_owned(), - // cycles: v.cycles, - // affected_on_page: v.affected_on_page, - // address: 0, // TODO - // resolved: true, - // })) - // } - - // None => { - // return Err( - // self.parser_error(format!("unknown byte '0x{:02X}'", buf[0]).as_str()) - // ) - // } - // } - // } - - // Ok(()) - // } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mapping::EMPTY; - - fn instruction_test(line: &str, hex: &[u8], skip_disassemble: bool) { + #[test] + fn bare_variables() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm.assemble(line.as_bytes()).unwrap(); + let res = asm + .assemble( + r#" +Variable = 4 +adc Variable +"# + .as_bytes(), + ) + .unwrap(); assert_eq!(res.len(), 1); - for i in 0..res[0].size { - assert_eq!(hex[i as usize], res[0].bytes[i as usize]); - } + let instr = res.first().unwrap(); + assert_eq!(instr.size, 2); + assert_eq!(instr.bytes[0], 0x65); + assert_eq!(instr.bytes[1], 0x04); + } - if skip_disassemble { - return; - } - // TODO + #[test] + fn bad_variable_but_valid_identifier_in_instruction() { + assert_eval_error( + "adc Variable", + "no prefix was given to operand and could not find variable 'Variable' in the global scope either", + ); + assert_eval_error( + "adc Scoped::Variable", + "no prefix was given to operand and did not find scope 'Scoped' either", + ); } - fn instruction_err(line: &str, message: &str) { - let mut asm = Assembler::new(EMPTY.to_vec()); - let err = asm.assemble(line.as_bytes()); + #[test] + fn redefined_variable() { + assert_context_error( + r#" +.scope One + Variable = 1 +.endscope - assert!(err.is_err()); - if let Err(e) = err { - assert_eq!(e.message, message); - } +Variable = 1 +Yet = 3 +Yet = 4 +"#, + "'Yet' already defined in the global scope: you cannot re-assign variables", + 8, + ); + } + + #[test] + fn unknown_variables() { + assert_eval_error( + "lda #Variable", + "'e' is not a decimal value and could not find variable \ + 'Variable' in the global scope either", + ); + assert_eval_error( + "lda #Scope::Variable", + "'e' is not a decimal value and did not find scope 'Scope' either", + ); + assert_error( + r#" +.scope Scope +.endscope +lda #Scope::Variable +"#, + "Evaluation", + 4, + "'e' is not a decimal value and could not find variable 'Variable' in 'Scope' either", + ); } + // Regular instructions + #[test] fn bad_addressing() { - instruction_err("unknown #$20", "unknown instruction 'unknown'"); - instruction_err( + assert_eval_error( + "unknown #$20", + "could not find a macro with the name 'unknown'", + ); + assert_eval_error( "adc ($2002, x)", "address can only be one byte long on indirect X addressing", ); - instruction_err( - "adc ($20, x), y", - "it has to be either X addressing or Y addressing, not all at once", - ); - instruction_err( + assert_eval_error( "adc ($2002), y", "address can only be one byte long on indirect Y addressing", ); - instruction_err( + assert_eval_error( "adc ($20, y)", "only the X index is allowed on indirect X addressing", ); - instruction_err( + assert_eval_error( "adc ($20), x", "only the Y index is allowed on indirect Y addressing", ); - instruction_err("jmp ($20)", "expecting a full 16-bit address"); - instruction_err("adc $20, z", "can only use X and Y as indices"); - instruction_err( + assert_eval_error("jmp ($20)", "expecting a full 16-bit address"); + assert_eval_error("adc $20, z", "can only use X and Y as indices"); + assert_eval_error( "adc ($2000)", "cannot use indirect addressing mode for the instruction 'adc'", ); - instruction_err("lda 12", "no prefix was given to operand") - } - - #[test] - fn parse_binary() { - instruction_err("adc #%", "missing binary digits to get a full byte"); - instruction_err("adc #%0001", "missing binary digits to get a full byte"); - instruction_err("adc #%0001000", "missing binary digits to get a full byte"); - instruction_err( - "adc #%000100001", - "too many binary digits for a single byte", - ); - instruction_test("adc #%10100010", &[0x69, 0xA2], true); - } - - #[test] - fn parse_hexadecimal() { - instruction_err("adc $", "expecting a number of 1 to 4 hexadecimal digits"); - // TODO: see comment on literal_mode being a stack. - instruction_err("adc #$", "expecting a number of 1 to 4 hexadecimal digits"); - instruction_err("adc $AW", "could not convert digit to hexadecimal"); - instruction_test("adc $AA", &[0x65, 0xAA], false); - instruction_test("adc $10", &[0x65, 0x10], false); - instruction_test("adc $10AB", &[0x6D, 0xAB, 0x10], false); + assert_eval_error("lda 12", "no prefix was given to operand") } #[test] - fn parse_decimal() { - instruction_err("adc #", "empty decimal literal"); - instruction_err("adc #256", "decimal value is too big"); - instruction_err("adc #2000", "decimal value is too big"); - instruction_err("adc #2A", "unknown variable '2A'"); // TODO: not sure about this - instruction_test("adc #1", &[0x69, 0x01], true); - } - - // Individual instructions. - - #[test] fn adc() { - instruction_test("adc #20", &[0x69, 0x14], true); - instruction_test("adc #$20", &[0x69, 0x20], false); - instruction_test("adc $2002", &[0x6D, 0x02, 0x20], false); - instruction_test("adc $20", &[0x65, 0x20], false); - instruction_test("adc $20, x", &[0x75, 0x20], false); - instruction_test("adc $2002, x", &[0x7D, 0x02, 0x20], false); - instruction_test("adc $2002, y", &[0x79, 0x02, 0x20], false); - instruction_test("adc ($20, x)", &[0x61, 0x20], false); - instruction_test("adc ($20), y", &[0x71, 0x20], false); + assert_instruction("adc #20", &[0x69, 0x14]); + assert_instruction("adc #$20", &[0x69, 0x20]); + assert_instruction("adc $2002", &[0x6D, 0x02, 0x20]); + assert_instruction("adc $20", &[0x65, 0x20]); + assert_instruction("adc $20, x", &[0x75, 0x20]); + assert_instruction("adc $2002, x", &[0x7D, 0x02, 0x20]); + assert_instruction("adc $2002, y", &[0x79, 0x02, 0x20]); + assert_instruction("adc ($20, x)", &[0x61, 0x20]); + assert_instruction("adc ($20), y", &[0x71, 0x20]); } #[test] fn sbc() { - instruction_test("sbc #$20", &[0xE9, 0x20], false); - instruction_test("sbc $2002", &[0xED, 0x02, 0x20], false); - instruction_test("sbc $20", &[0xE5, 0x20], false); - instruction_test("sbc $20, x", &[0xF5, 0x20], false); - instruction_test("sbc $2002, x", &[0xFD, 0x02, 0x20], false); - instruction_test("sbc $2002, y", &[0xF9, 0x02, 0x20], false); - instruction_test("sbc ($20, x)", &[0xE1, 0x20], false); - instruction_test("sbc ($20), y", &[0xF1, 0x20], false); + assert_instruction("sbc #$20", &[0xE9, 0x20]); + assert_instruction("sbc $2002", &[0xED, 0x02, 0x20]); + assert_instruction("sbc $20", &[0xE5, 0x20]); + assert_instruction("sbc $20, x", &[0xF5, 0x20]); + assert_instruction("sbc $2002, x", &[0xFD, 0x02, 0x20]); + assert_instruction("sbc $2002, y", &[0xF9, 0x02, 0x20]); + assert_instruction("sbc ($20, x)", &[0xE1, 0x20]); + assert_instruction("sbc ($20), y", &[0xF1, 0x20]); } #[test] fn shift() { // asl - instruction_test("asl", &[0x0A], false); - instruction_test("asl a", &[0x0A], true); - instruction_test("asl $20", &[0x06, 0x20], false); - instruction_test("asl $20, x", &[0x16, 0x20], false); - instruction_test("asl $2002", &[0x0E, 0x02, 0x20], false); - instruction_test("asl $2002, x", &[0x1E, 0x02, 0x20], false); + assert_instruction("asl", &[0x0A]); + assert_instruction("asl a", &[0x0A]); + assert_instruction("asl $20", &[0x06, 0x20]); + assert_instruction("asl $20, x", &[0x16, 0x20]); + assert_instruction("asl $2002", &[0x0E, 0x02, 0x20]); + assert_instruction("asl $2002, x", &[0x1E, 0x02, 0x20]); // lsr - instruction_test("lsr", &[0x4A], false); - instruction_test("lsr a", &[0x4A], true); - instruction_test("lsr $20", &[0x46, 0x20], false); - instruction_test("lsr $20, x", &[0x56, 0x20], false); - instruction_test("lsr $2002", &[0x4E, 0x02, 0x20], false); - instruction_test("lsr $2002, x", &[0x5E, 0x02, 0x20], false); + assert_instruction("lsr", &[0x4A]); + assert_instruction("lsr a", &[0x4A]); + assert_instruction("lsr $20", &[0x46, 0x20]); + assert_instruction("lsr $20, x", &[0x56, 0x20]); + assert_instruction("lsr $2002", &[0x4E, 0x02, 0x20]); + assert_instruction("lsr $2002, x", &[0x5E, 0x02, 0x20]); } #[test] fn rotate() { // rol - instruction_test("rol", &[0x2A], false); - instruction_test("rol a", &[0x2A], true); - instruction_test("rol $20", &[0x26, 0x20], false); - instruction_test("rol $20, x", &[0x36, 0x20], false); - instruction_test("rol $2002", &[0x2E, 0x02, 0x20], false); - instruction_test("rol $2002, x", &[0x3E, 0x02, 0x20], false); + assert_instruction("rol", &[0x2A]); + assert_instruction("rol a", &[0x2A]); + assert_instruction("rol $20", &[0x26, 0x20]); + assert_instruction("rol $20, x", &[0x36, 0x20]); + assert_instruction("rol $2002", &[0x2E, 0x02, 0x20]); + assert_instruction("rol $2002, x", &[0x3E, 0x02, 0x20]); // ror - instruction_test("ror", &[0x6A], false); - instruction_test("ror a", &[0x6A], true); - instruction_test("ror $20", &[0x66, 0x20], false); - instruction_test("ror $20, x", &[0x76, 0x20], false); - instruction_test("ror $2002", &[0x6E, 0x02, 0x20], false); - instruction_test("ror $2002, x", &[0x7E, 0x02, 0x20], false); + assert_instruction("ror", &[0x6A]); + assert_instruction("ror a", &[0x6A]); + assert_instruction("ror $20", &[0x66, 0x20]); + assert_instruction("ror $20, x", &[0x76, 0x20]); + assert_instruction("ror $2002", &[0x6E, 0x02, 0x20]); + assert_instruction("ror $2002, x", &[0x7E, 0x02, 0x20]); } #[test] fn and() { - instruction_test("and #$20", &[0x29, 0x20], false); - instruction_test("and $2002", &[0x2D, 0x02, 0x20], false); - instruction_test("and $20", &[0x25, 0x20], false); - instruction_test("and $20, x", &[0x35, 0x20], false); - instruction_test("and $2002, x", &[0x3D, 0x02, 0x20], false); - instruction_test("and $2002, y", &[0x39, 0x02, 0x20], false); - instruction_test("and ($20, x)", &[0x21, 0x20], false); - instruction_test("and ($20), y", &[0x31, 0x20], false); + assert_instruction("and #$20", &[0x29, 0x20]); + assert_instruction("and $2002", &[0x2D, 0x02, 0x20]); + assert_instruction("and $20", &[0x25, 0x20]); + assert_instruction("and $20, x", &[0x35, 0x20]); + assert_instruction("and $2002, x", &[0x3D, 0x02, 0x20]); + assert_instruction("and $2002, y", &[0x39, 0x02, 0x20]); + assert_instruction("and ($20, x)", &[0x21, 0x20]); + assert_instruction("and ($20), y", &[0x31, 0x20]); } #[test] fn or() { // eor - instruction_test("eor #$20", &[0x49, 0x20], false); - instruction_test("eor $20", &[0x45, 0x20], false); - instruction_test("eor $20, x", &[0x55, 0x20], false); - instruction_test("eor $2002", &[0x4D, 0x02, 0x20], false); - instruction_test("eor $2002, x", &[0x5D, 0x02, 0x20], false); - instruction_test("eor $2002, y", &[0x59, 0x02, 0x20], false); - instruction_test("eor ($20, x)", &[0x41, 0x20], false); - instruction_test("eor ($20), y", &[0x51, 0x20], false); + assert_instruction("eor #$20", &[0x49, 0x20]); + assert_instruction("eor $20", &[0x45, 0x20]); + assert_instruction("eor $20, x", &[0x55, 0x20]); + assert_instruction("eor $2002", &[0x4D, 0x02, 0x20]); + assert_instruction("eor $2002, x", &[0x5D, 0x02, 0x20]); + assert_instruction("eor $2002, y", &[0x59, 0x02, 0x20]); + assert_instruction("eor ($20, x)", &[0x41, 0x20]); + assert_instruction("eor ($20), y", &[0x51, 0x20]); // ora - instruction_test("ora #$20", &[0x09, 0x20], false); - instruction_test("ora $20", &[0x05, 0x20], false); - instruction_test("ora $20, x", &[0x15, 0x20], false); - instruction_test("ora $2002", &[0x0D, 0x02, 0x20], false); - instruction_test("ora $2002, x", &[0x1D, 0x02, 0x20], false); - instruction_test("ora $2002, y", &[0x19, 0x02, 0x20], false); - instruction_test("ora ($20, x)", &[0x01, 0x20], false); - instruction_test("ora ($20), y", &[0x11, 0x20], false); + assert_instruction("ora #$20", &[0x09, 0x20]); + assert_instruction("ora $20", &[0x05, 0x20]); + assert_instruction("ora $20, x", &[0x15, 0x20]); + assert_instruction("ora $2002", &[0x0D, 0x02, 0x20]); + assert_instruction("ora $2002, x", &[0x1D, 0x02, 0x20]); + assert_instruction("ora $2002, y", &[0x19, 0x02, 0x20]); + assert_instruction("ora ($20, x)", &[0x01, 0x20]); + assert_instruction("ora ($20), y", &[0x11, 0x20]); } #[test] fn load() { // lda - instruction_test("lda #$20", &[0xA9, 0x20], false); - instruction_test("lda $20", &[0xA5, 0x20], false); - instruction_test("lda $20, x", &[0xB5, 0x20], false); - instruction_test("lda $2002", &[0xAD, 0x02, 0x20], false); - instruction_test("lda $2002, x", &[0xBD, 0x02, 0x20], false); - instruction_test("lda $2002, y", &[0xB9, 0x02, 0x20], false); - instruction_test("lda ($20, x)", &[0xA1, 0x20], false); - instruction_test("lda ($20), y", &[0xB1, 0x20], false); + assert_instruction("lda #$20", &[0xA9, 0x20]); + assert_instruction("lda $20", &[0xA5, 0x20]); + assert_instruction("lda $20, x", &[0xB5, 0x20]); + assert_instruction("lda $2002", &[0xAD, 0x02, 0x20]); + assert_instruction("lda $2002, x", &[0xBD, 0x02, 0x20]); + assert_instruction("lda $2002, y", &[0xB9, 0x02, 0x20]); + assert_instruction("lda ($20, x)", &[0xA1, 0x20]); + assert_instruction("lda ($20), y", &[0xB1, 0x20]); // ldx - instruction_test("ldx #$20", &[0xA2, 0x20], false); - instruction_test("ldx $20", &[0xA6, 0x20], false); - instruction_test("ldx $20, y", &[0xB6, 0x20], false); - instruction_test("ldx $2002", &[0xAE, 0x02, 0x20], false); - instruction_test("ldx $2002, y", &[0xBE, 0x02, 0x20], false); + assert_instruction("ldx #$20", &[0xA2, 0x20]); + assert_instruction("ldx $20", &[0xA6, 0x20]); + assert_instruction("ldx $20, y", &[0xB6, 0x20]); + assert_instruction("ldx $2002", &[0xAE, 0x02, 0x20]); + assert_instruction("ldx $2002, y", &[0xBE, 0x02, 0x20]); // ldy - instruction_test("ldy #$20", &[0xA0, 0x20], false); - instruction_test("ldy $20", &[0xA4, 0x20], false); - instruction_test("ldy $20, x", &[0xB4, 0x20], false); - instruction_test("ldy $2002", &[0xAC, 0x02, 0x20], false); - instruction_test("ldy $2002, x", &[0xBC, 0x02, 0x20], false); + assert_instruction("ldy #$20", &[0xA0, 0x20]); + assert_instruction("ldy $20", &[0xA4, 0x20]); + assert_instruction("ldy $20, x", &[0xB4, 0x20]); + assert_instruction("ldy $2002", &[0xAC, 0x02, 0x20]); + assert_instruction("ldy $2002, x", &[0xBC, 0x02, 0x20]); } #[test] fn jump() { - instruction_test("jsr $2002", &[0x20, 0x02, 0x20], false); + assert_instruction("jsr $2002", &[0x20, 0x02, 0x20]); - instruction_test("jmp $2002", &[0x4C, 0x02, 0x20], false); - instruction_test("jmp ($2002)", &[0x6C, 0x02, 0x20], false); + assert_instruction("jmp $2002", &[0x4C, 0x02, 0x20]); + assert_instruction("jmp ($2002)", &[0x6C, 0x02, 0x20]); } #[test] fn inc_dec_instructions() { // inc - instruction_test("inc $10", &[0xE6, 0x10], false); - instruction_test("inc $1000", &[0xEE, 0x00, 0x10], false); - instruction_test("inc $10, x", &[0xF6, 0x10], false); - instruction_test("inc $1000, x", &[0xFE, 0x00, 0x10], false); + assert_instruction("inc $10", &[0xE6, 0x10]); + assert_instruction("inc $1000", &[0xEE, 0x00, 0x10]); + assert_instruction("inc $10, x", &[0xF6, 0x10]); + assert_instruction("inc $1000, x", &[0xFE, 0x00, 0x10]); - instruction_test("inx", &[0xE8], false); + assert_instruction("inx", &[0xE8]); - instruction_test("iny", &[0xC8], false); + assert_instruction("iny", &[0xC8]); // dec - instruction_test("dec $10", &[0xC6, 0x10], false); - instruction_test("dec $1000", &[0xCE, 0x00, 0x10], false); - instruction_test("dec $10, x", &[0xD6, 0x10], false); - instruction_test("dec $1000, x", &[0xDE, 0x00, 0x10], false); + assert_instruction("dec $10", &[0xC6, 0x10]); + assert_instruction("dec $1000", &[0xCE, 0x00, 0x10]); + assert_instruction("dec $10, x", &[0xD6, 0x10]); + assert_instruction("dec $1000, x", &[0xDE, 0x00, 0x10]); - instruction_test("dex", &[0xCA], false); + assert_instruction("dex", &[0xCA]); - instruction_test("dey", &[0x88], false); + assert_instruction("dey", &[0x88]); } #[test] fn transfer_instructions() { - instruction_test("tax", &[0xAA], false); - instruction_test("tay", &[0xA8], false); - instruction_test("tsx", &[0xBA], false); - instruction_test("txa", &[0x8A], false); - instruction_test("txs", &[0x9A], false); - instruction_test("tya", &[0x98], false); + assert_instruction("tax", &[0xAA]); + assert_instruction("tay", &[0xA8]); + assert_instruction("tsx", &[0xBA]); + assert_instruction("txa", &[0x8A]); + assert_instruction("txs", &[0x9A]); + assert_instruction("tya", &[0x98]); } #[test] fn return_instructions() { - instruction_test("rti", &[0x40], false); - instruction_test("rts", &[0x60], false); + assert_instruction("rti", &[0x40]); + assert_instruction("rts", &[0x60]); } #[test] fn set_clear_instructions() { - instruction_test("clc", &[0x18], false); - instruction_test("cld", &[0xD8], false); - instruction_test("cli", &[0x58], false); - instruction_test("clv", &[0xB8], false); - - instruction_test("sec", &[0x38], false); - instruction_test("sed", &[0xF8], false); - instruction_test("sei", &[0x78], false); + assert_instruction("clc", &[0x18]); + assert_instruction("cld", &[0xD8]); + assert_instruction("cli", &[0x58]); + assert_instruction("clv", &[0xB8]); + + assert_instruction("sec", &[0x38]); + assert_instruction("sed", &[0xF8]); + assert_instruction("sei", &[0x78]); } #[test] fn push_pull_instructions() { - instruction_test("pha", &[0x48], false); - instruction_test("php", &[0x08], false); - instruction_test("pla", &[0x68], false); - instruction_test("plp", &[0x28], false); + assert_instruction("pha", &[0x48]); + assert_instruction("php", &[0x08]); + assert_instruction("pla", &[0x68]); + assert_instruction("plp", &[0x28]); } #[test] fn nop_brk() { - instruction_test("nop", &[0xEA], false); - instruction_test("brk", &[0x00], false); + assert_instruction("nop", &[0xEA]); + assert_instruction("brk", &[0x00]); } #[test] fn cmp() { // cmp - instruction_test("cmp #$20", &[0xC9, 0x20], false); - instruction_test("cmp $2002", &[0xCD, 0x02, 0x20], false); - instruction_test("cmp $20", &[0xC5, 0x20], false); - instruction_test("cmp $20, x", &[0xD5, 0x20], false); - instruction_test("cmp $2002, x", &[0xDD, 0x02, 0x20], false); - instruction_test("cmp $2002, y", &[0xD9, 0x02, 0x20], false); - instruction_test("cmp ($20, x)", &[0xC1, 0x20], false); - instruction_test("cmp ($20), y", &[0xD1, 0x20], false); + assert_instruction("cmp #$20", &[0xC9, 0x20]); + assert_instruction("cmp $2002", &[0xCD, 0x02, 0x20]); + assert_instruction("cmp $20", &[0xC5, 0x20]); + assert_instruction("cmp $20, x", &[0xD5, 0x20]); + assert_instruction("cmp $2002, x", &[0xDD, 0x02, 0x20]); + assert_instruction("cmp $2002, y", &[0xD9, 0x02, 0x20]); + assert_instruction("cmp ($20, x)", &[0xC1, 0x20]); + assert_instruction("cmp ($20), y", &[0xD1, 0x20]); // cpx - instruction_test("cpx #$20", &[0xE0, 0x20], false); - instruction_test("cpx $2002", &[0xEC, 0x02, 0x20], false); - instruction_test("cpx $20", &[0xE4, 0x20], false); + assert_instruction("cpx #$20", &[0xE0, 0x20]); + assert_instruction("cpx $2002", &[0xEC, 0x02, 0x20]); + assert_instruction("cpx $20", &[0xE4, 0x20]); // cpy - instruction_test("cpy #$20", &[0xC0, 0x20], false); - instruction_test("cpy $2002", &[0xCC, 0x02, 0x20], false); - instruction_test("cpy $20", &[0xC4, 0x20], false); + assert_instruction("cpy #$20", &[0xC0, 0x20]); + assert_instruction("cpy $2002", &[0xCC, 0x02, 0x20]); + assert_instruction("cpy $20", &[0xC4, 0x20]); } #[test] fn store_instructions() { //sta - instruction_test("sta $20", &[0x85, 0x20], false); - instruction_test("sta $20, x", &[0x95, 0x20], false); - instruction_test("sta $2002", &[0x8D, 0x02, 0x20], false); - instruction_test("sta $2002, x", &[0x9D, 0x02, 0x20], false); - instruction_test("sta $2002, y", &[0x99, 0x02, 0x20], false); - instruction_test("sta ($20, x)", &[0x81, 0x20], false); - instruction_test("sta ($20), y", &[0x91, 0x20], false); + assert_instruction("sta $20", &[0x85, 0x20]); + assert_instruction("sta $20, x", &[0x95, 0x20]); + assert_instruction("sta $2002", &[0x8D, 0x02, 0x20]); + assert_instruction("sta $2002, x", &[0x9D, 0x02, 0x20]); + assert_instruction("sta $2002, y", &[0x99, 0x02, 0x20]); + assert_instruction("sta ($20, x)", &[0x81, 0x20]); + assert_instruction("sta ($20), y", &[0x91, 0x20]); // stx - instruction_test("stx $20", &[0x86, 0x20], false); - instruction_test("stx $20, y", &[0x96, 0x20], false); - instruction_test("stx $2002", &[0x8E, 0x02, 0x20], false); + assert_instruction("stx $20", &[0x86, 0x20]); + assert_instruction("stx $20, y", &[0x96, 0x20]); + assert_instruction("stx $2002", &[0x8E, 0x02, 0x20]); // sty - instruction_test("sty $20", &[0x84, 0x20], false); - instruction_test("sty $20, x", &[0x94, 0x20], false); - instruction_test("sty $2002", &[0x8C, 0x02, 0x20], false); + assert_instruction("sty $20", &[0x84, 0x20]); + assert_instruction("sty $20, x", &[0x94, 0x20]); + assert_instruction("sty $2002", &[0x8C, 0x02, 0x20]); } #[test] fn bit() { - instruction_test("bit $10", &[0x24, 0x10], false); - instruction_test("bit $1001", &[0x2C, 0x01, 0x10], false); + assert_instruction("bit $10", &[0x24, 0x10]); + assert_instruction("bit $1001", &[0x2C, 0x01, 0x10]); } - // Variables & scopes. + // Labels & branching + // TODO + + // Control statements + // TODO: .byte, .word, variables in between (e.g. `.byte Variable::Value`, `lda #.hibyte(Variable)`) + + // Macros #[test] - fn using_variables() { - // TODO - // todo!() + fn macro_no_arguments() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 + +.macro MACRO + lda #2 +.endmacro + +lda #1 +MACRO +"# + .as_bytes(), + ) + .unwrap(); + + assert_eq!(res.len(), 3); + let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]]; + + for i in 0..3 { + assert_eq!(res[i].size, 2); + assert_eq!(res[i].bytes[0], instrs[i][0]); + assert_eq!(res[i].bytes[1], instrs[i][1]); + } } #[test] - fn scoped_variable() { + fn macro_not_enough_arguments() { let mut asm = Assembler::new(EMPTY.to_vec()); let res = asm .assemble( r#" -.scope One ; This is a comment - adc #Variable +lda #42 - Variable = $20 -.endscope +.macro MACRO(Var) + lda #Var +.endmacro -.scope Another - Variable = $40 -.endscope +lda #1 +MACRO +"# + .as_bytes(), + ) + .unwrap_err(); -Variable = $30 -adc #Variable + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 9): wrong number of arguments for 'MACRO': 1 required but 0 given." + ); + } -adc #One::Variable -adc #Another::Variable + #[test] + fn macro_too_many_arguments() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 + +.macro MACRO(Var) + lda #Var +.endmacro + +lda #1 +MACRO(1, 2) +"# + .as_bytes(), + ) + .unwrap_err(); + + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 9): wrong number of arguments for 'MACRO': 1 required but 2 given." + ); + } + + #[test] + fn macro_with_one_argument() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 + +.macro MACRO(Var) + lda #Var +.endmacro + +lda #1 +MACRO(2) "# .as_bytes(), ) .unwrap(); - assert_eq!(res.len(), 4); - let instrs: Vec<[u8; 2]> = vec![[0x69, 0x20], [0x69, 0x30], [0x69, 0x20], [0x69, 0x40]]; + assert_eq!(res.len(), 3); + let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]]; - for i in 0..4 { + for i in 0..3 { assert_eq!(res[i].size, 2); assert_eq!(res[i].bytes[0], instrs[i][0]); assert_eq!(res[i].bytes[1], instrs[i][1]); @@ -2054,120 +1401,55 @@ adc #Another::Variable } #[test] - fn redefined_variable() { - let mut parser = Assembler::new(EMPTY.to_vec()); - let res = parser.assemble( - r#" -.scope One - Variable = 1 -.endscope + fn macro_unknown_arguments() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 -Variable = 1 -Yet = 3 -Yet = 4 +.macro MACRO(Var) + lda #Va +.endmacro + +lda #1 +MACRO(1) "# - .as_bytes(), - ); + .as_bytes(), + ) + .unwrap_err(); - assert!(res.is_err()); - if let Err(e) = res { - assert_eq!( - e.message, - "variable 'Yet' is being re-assigned: it was previously defined in line 7" - ); - } + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 5): 'a' is not a decimal value and \ + could not find variable 'Va' in the global scope either." + ); } #[test] - fn bad_assignment() { - instruction_err("Variable =", "incomplete assignment"); - instruction_err("Variable = ; comment", "incomplete assignment"); + fn macro_shadow_argument() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +Var = 3 +lda #42 + +.macro MACRO(Var) + lda #Va +.endmacro + +lda #1 +MACRO(1) +"# + .as_bytes(), + ) + .unwrap_err(); + + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 5): 'Var' already defined in the global scope: \ + you cannot re-assign variables." + ); } } - -// // Literals - -// #[test] -// fn byte_literals_errors() { -// // TODO -// // instruction_err( -// // ".byte $0102", -// // "when parsing a data literal: only one byte of data is allowed here", -// // ); -// // instruction_err(".byte '$01", "non-terminated quote for byte literal"); -// // instruction_err(".byte '$01, $02", "non-terminated quote for byte literal"); -// } - -// #[test] -// fn byte_literals() { -// let mut asm = Assembler::new(EMPTY.to_vec()); - -// let mut res = asm.assemble(".byte $01".as_bytes()).unwrap(); -// assert_eq!(res.len(), 1); -// assert_hex(res[0], &[0x01]); - -// asm.reset(); -// res = asm.assemble(".db $01, $02".as_bytes()).unwrap(); -// assert_eq!(res.len(), 2); -// assert_hex(res[0], &[0x01]); -// assert_hex(res[1], &[0x02]); - -// asm.reset(); -// res = asm -// .assemble(".byte $01, 2, '%00000011', \"$04\"".as_bytes()) -// .unwrap(); -// assert_eq!(res.len(), 4); -// assert_hex(res[0], &[0x01]); -// assert_hex(res[1], &[0x02]); -// assert_hex(res[2], &[0x03]); -// assert_hex(res[3], &[0x04]); -// } - -// #[test] -// fn word_literals() { -// let mut asm = Assembler::new(EMPTY.to_vec()); - -// let mut res = asm.assemble(".word $01".as_bytes()).unwrap(); -// assert_eq!(res.len(), 1); -// assert_hex(res[0], &[0x01, 0x00]); - -// asm.reset(); -// res = asm.assemble(".dw $0102, $02".as_bytes()).unwrap(); -// assert_eq!(res.len(), 2); -// assert_hex(res[0], &[0x02, 0x01]); -// assert_hex(res[1], &[0x02, 0x00]); - -// asm.reset(); -// res = asm -// .assemble(".word $0102, $0204, '$0308', \"$0410\"".as_bytes()) -// .unwrap(); -// assert_eq!(res.len(), 4); -// assert_hex(res[0], &[0x02, 0x01]); -// assert_hex(res[1], &[0x04, 0x02]); -// assert_hex(res[2], &[0x08, 0x03]); -// assert_hex(res[3], &[0x10, 0x04]); -// } - -// #[test] -// fn variables_in_literals() { -// let mut asm = Assembler::new(EMPTY.to_vec()); -// let res = asm -// .assemble( -// r#" -// .scope One -// Variable = $01 -// .endscope - -// Variable = $02 -// .byte One::Variable, Variable, $03 -// "# -// .as_bytes(), -// ) -// .unwrap(); - -// assert_eq!(res.len(), 3); -// assert_hex(res[0], &[0x01]); -// assert_hex(res[1], &[0x02]); -// assert_hex(res[2], &[0x03]); -// } -// } diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/context.rs index 2ac6da8..8e77094 100644 --- a/lib/xixanta/src/context.rs +++ b/lib/xixanta/src/context.rs @@ -1,29 +1,22 @@ -use crate::instruction::Bundle; -use crate::parser::PNode; +use crate::assembler::Bundle; +use crate::errors::{ContextError, ContextErrorReason}; +use crate::node::{PNode, PString}; +use crate::opcodes::CONTROL_FUNCTIONS; use std::collections::HashMap; +// The name of the global context as used internally.. const GLOBAL_CONTEXT: &str = "Global"; -#[derive(Debug)] -pub struct PValue { - pub node: PNode, - pub value: Bundle, - pub label: bool, -} - +/// Context holds information about the different scopes being defined, the +/// current scope, and has a map of all the variables defined for each scope. #[derive(Debug)] pub struct Context { stack: Vec<String>, - map: HashMap<String, HashMap<String, PValue>>, -} - -impl Default for Context { - fn default() -> Self { - Context::new() - } + map: HashMap<String, HashMap<String, Bundle>>, } impl Context { + /// Returns a new empty context. pub fn new() -> Self { Context { stack: vec![], @@ -31,60 +24,149 @@ impl Context { } } - pub fn find(&self, name: &str) -> Option<&HashMap<String, PValue>> { - self.map.get(name) - } + /// Returns the value of the variable represented by the given `id`. Note + /// that this `id` can be scoped or not, and this function will try to pick + /// the variable from the right scope. + pub fn get_variable(&self, id: &PString) -> Result<Bundle, ContextError> { + // First of all, figure out the name of the scope and the real name of + // the variable. If this was not scoped at all (None case when trying to + // rsplit by the "::" operator), then we assume it's a global variable. + let (scope_name, var_name) = match id.value.rsplit_once("::") { + Some((scope, name)) => (scope, name), + None => (self.name(), id.value.as_str()), + }; - pub fn current(&self) -> Option<&HashMap<String, PValue>> { - match self.stack.last() { - Some(name) => self.map.get(name), - None => self.map.get(GLOBAL_CONTEXT), + // And with that, the only thing left is to find the scope and the + // variable in it. + match self.map.get(scope_name) { + Some(scope) => match scope.get(var_name) { + Some(var) => Ok(var.clone()), + None => Err(ContextError { + message: format!( + "could not find variable '{}' in {}", + var_name, + self.to_human_with(scope_name) + ), + line: id.line, + reason: ContextErrorReason::UnknownVariable, + }), + }, + None => Err(ContextError { + message: format!("did not find scope '{}'", scope_name), + line: id.line, + reason: ContextErrorReason::BadScope, + }), } } - pub fn current_mut(&mut self) -> Option<&mut HashMap<String, PValue>> { - match self.stack.last() { - Some(name) => self.map.get_mut(name), - None => self.map.get_mut(GLOBAL_CONTEXT), + /// Sets a value for a new variable defined in the assignment `node`. + pub fn set_variable(&mut self, id: &PString, bundle: &Bundle) -> Result<(), ContextError> { + let scope_name = self.name().to_string(); + let scope = self.map.get_mut(&scope_name).unwrap(); + + match scope.get_mut(&id.value) { + Some(_) => { + return Err(ContextError { + message: format!( + "'{}' already defined in {}: you cannot re-assign variables", + id.value, + self.to_human() + ), + line: id.line, + reason: ContextErrorReason::Redefinition, + }) + } + None => { + self.map.insert( + scope_name, + HashMap::from([(id.value.clone(), bundle.to_owned())]), + ); + } } - } - pub fn is_global(&self) -> bool { - self.stack.is_empty() + Ok(()) } - pub fn name(&self) -> &str { - match self.stack.last() { - Some(name) => name, - None => GLOBAL_CONTEXT, + /// Change the current context given a `node`. + pub fn change_context(&mut self, node: &PNode) -> Result<(), ContextError> { + // The parser already guarantees that the control node is + // from a function that we already know, so calling `unwrap` + // is not dangerous. + let control = CONTROL_FUNCTIONS + .get(&node.value.value.to_lowercase()) + .unwrap(); + + // If the control function does not touch the context, leave early. + if !control.touches_context { + return Ok(()); + } + + // And push/pop the context depending on the control being used. + match node.value.value.as_str() { + ".macro" | ".proc" | ".scope" => self.context_push(&node.left.clone().unwrap()), + ".endmacro" | ".endproc" | ".endscope" => self.context_pop(&node.value)?, + _ => {} } + + Ok(()) } - pub fn push(&mut self, identifier: &String) { + // Pushes a new context given a `node`, which holds the identifier of the + // new scope. + fn context_push(&mut self, id: &PNode) { let name = match self.stack.last() { - Some(n) => n.to_owned() + &String::from("::") + identifier, - None => identifier.to_string(), + Some(n) => format!("{}::{}", n, id.value.value), + None => id.value.value.clone(), }; + // Actually push the name to the stack and initialize it on the variable + // map. self.stack.push(name.clone()); self.map.entry(name).or_default(); } - // pub fn push_stack(&mut self, identifier: &String) { - // let name = match self.stack.last() { - // Some(n) => n.to_owned() + &String::from("::") + identifier, - // None => identifier.to_string(), - // }; - - // self.stack.push(name.clone()); - // } - - pub fn pop(&mut self) -> bool { + // Pops out the latest context that was pushed. + fn context_pop(&mut self, id: &PString) -> Result<(), ContextError> { if self.stack.is_empty() { - return false; + return Err(ContextError { + message: format!("missplaced '{}' statement", id.value), + reason: ContextErrorReason::BadScope, + line: id.line, + }); } self.stack.truncate(self.stack.len() - 1); - true + Ok(()) + } + + // Returns the name of the current context. + fn name(&self) -> &str { + match self.stack.last() { + Some(name) => name, + None => GLOBAL_CONTEXT, + } + } + + // Returns a human-readable string representing the current context. + fn to_human(&self) -> String { + match self.stack.last() { + Some(n) => format!("'{}'", n), + None => "the global scope".to_string(), + } + } + + // Returns a human-readable string representing the given context. + fn to_human_with(&self, name: &str) -> String { + if name == GLOBAL_CONTEXT { + "the global scope".to_string() + } else { + format!("'{}'", name) + } + } +} + +impl Default for Context { + fn default() -> Self { + Self::new() } } diff --git a/lib/xixanta/src/errors.rs b/lib/xixanta/src/errors.rs index 6c99f57..d5444df 100644 --- a/lib/xixanta/src/errors.rs +++ b/lib/xixanta/src/errors.rs @@ -1,23 +1,35 @@ use std::fmt; -// TODO: global error -// TODO: more errors, the `parse` thing is a hack! +#[derive(Debug, Clone, PartialEq)] +pub enum Error { + Parse(ParseError), + Context(ContextError), + Eval(EvalError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Parse(parse_error) => write!(f, "{}", parse_error), + Error::Context(context_error) => write!(f, "{}", context_error), + Error::Eval(eval_error) => write!(f, "{}", eval_error), + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct ParseError { pub line: usize, pub message: String, - pub parse: bool, } impl std::error::Error for ParseError {} impl From<std::io::Error> for ParseError { fn from(err: std::io::Error) -> Self { - // TODO ParseError { line: 0, message: err.to_string(), - parse: true, } } } @@ -27,3 +39,79 @@ impl fmt::Display for ParseError { write!(f, "parser (line {}): {}.", self.line + 1, self.message) } } + +#[derive(Debug, Clone, PartialEq)] +pub enum ContextErrorReason { + Redefinition, + UnknownVariable, + BadScope, + Other, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ContextError { + pub line: usize, + pub reason: ContextErrorReason, + pub message: String, +} + +impl std::error::Error for ContextError {} + +// TODO: needed? +impl From<std::io::Error> for ContextError { + fn from(err: std::io::Error) -> Self { + ContextError { + line: 0, + reason: ContextErrorReason::Other, + message: err.to_string(), + } + } +} + +impl fmt::Display for ContextError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Context error (line {}): {}.", + self.line + 1, + self.message + ) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct EvalError { + pub line: usize, + pub message: String, +} + +impl std::error::Error for EvalError {} + +impl From<std::io::Error> for EvalError { + fn from(err: std::io::Error) -> Self { + EvalError { + line: 0, + message: err.to_string(), + } + } +} + +impl From<ContextError> for EvalError { + fn from(err: ContextError) -> Self { + EvalError { + line: err.line, + message: err.message, + } + } +} + +impl fmt::Display for EvalError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Evaluation error (line {}): {}.", + self.line + 1, + self.message + ) + } +} diff --git a/lib/xixanta/src/instruction.rs b/lib/xixanta/src/instruction.rs deleted file mode 100644 index ced452a..0000000 --- a/lib/xixanta/src/instruction.rs +++ /dev/null @@ -1,451 +0,0 @@ -use crate::errors::ParseError; -use std::fmt; -use std::ops::Range; - -/// PString is a String with position information. -#[derive(Debug, Clone, PartialEq)] -pub struct PString { - pub value: String, - pub line: usize, - pub range: Range<usize>, -} - -impl PString { - pub fn new() -> Self { - PString { - value: String::from(""), - line: 0, - range: Range { start: 0, end: 0 }, - } - } - - pub fn from(value: &str) -> Self { - PString { - value: String::from(value), - line: 0, - range: Range { start: 0, end: 0 }, - } - } - - pub fn parser_error(&self, message: &str) -> ParseError { - // TODO: we can go further :) - ParseError { - line: self.line, - message: String::from(message), - parse: true, - } - } - - pub fn is_valid(&self) -> bool { - !(self.value.is_empty() || self.range.is_empty()) - } - - pub fn is_valid_identifier(&self) -> Result<(), String> { - if self.value.trim().is_empty() { - return Err(format!("empty identifier")); - } - - // You cannot assign into a name which is reserved. - if matches!(self.value.to_lowercase().as_str(), "x" | "y" | "a") { - return Err(format!("cannot use reserved name '{}'", self.value)); - } - - // You cannot assign into scoped names: declare them into their - // respective scopes instead. - if self.value.contains("::") { - return Err(format!( - "the name '{}' is scoped: do not declare things this way", - self.value - )); - } - - // Let's gather info from the variable name which is relevant to later - // checks. - let mut alpha_seen = false; - let mut valid_hex = match self.value.len() { - 1 | 2 | 3 | 4 => true, - _ => false, - }; - for c in self.value.to_lowercase().chars() { - if c == '_' { - valid_hex = false; - } else { - if c.is_alphabetic() { - alpha_seen = true; - if c > 'f' && c <= 'z' { - valid_hex = false; - } - } - } - } - - // We need at least one alphabetic character. Otherwise it might be - // confusing with numbers. - if !alpha_seen { - return Err(format!( - "name '{}' requires at least one alphabetic character", - self.value - )); - } - - // To avoid problems down the line, you cannot assign into names which - // are proper hexadecimal values. - if valid_hex { - return Err(format!( - "cannot use names which are valid hexadecimal values such as '{}'", - self.value - )); - } - - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Bundle { - pub bytes: [u8; 3], - pub size: u8, - pub address: usize, - pub cycles: u8, - pub affected_on_page: bool, -} - -impl Bundle { - pub fn new() -> Self { - Self { - bytes: [0, 0, 0], - size: 0, - address: 0, - cycles: 0, - affected_on_page: false, - } - } -} - -#[derive(Eq, Hash, PartialEq, Debug, Clone)] -pub enum AddressingMode { - Unknown, // TODO: is this really used? - Implied, - Immediate, - Absolute, - RelativeOrZeropage, - IndexedX, - IndexedY, - ZeropageIndexedX, - ZeropageIndexedY, - Indirect, - IndirectX, - IndirectY, -} - -impl fmt::Display for AddressingMode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - AddressingMode::Implied => write!(f, "implied"), - AddressingMode::Immediate => write!(f, "immediate"), - AddressingMode::Absolute => write!(f, "absolute"), - AddressingMode::RelativeOrZeropage => write!(f, "relative or zeropage"), - AddressingMode::IndexedX => write!(f, "indexed by x"), - AddressingMode::IndexedY => write!(f, "indexed by y"), - AddressingMode::ZeropageIndexedX => write!(f, "zeropage indexed by x"), - AddressingMode::ZeropageIndexedY => write!(f, "zeropage indexed by y"), - AddressingMode::Indirect => write!(f, "indirect"), - AddressingMode::IndirectX => write!(f, "indirect indexed by x"), - AddressingMode::IndirectY => write!(f, "indirect indexed by y"), - _ => write!(f, "unknown"), - } - } -} - -/// Encodable is a trait to be implemented by those structs that might need to -/// be encoded into the outside world. That is, structures that make sense to -/// output into files or other output streams. -pub trait Encodable { - /// Returns a fixed array of bytes which belong to an encodable object. Note - /// that the capacity is fixed, but the actual size must be checked with the - /// `size` trait function, otherwise elements beyond that size might contain - /// junk. - fn to_bytes(&self) -> [u8; 3]; - - /// Returns the actual size of the data returned by `to_bytes`. - fn size(&self) -> u8; - - /// Returns a vector which contains the exact byte data for the given - /// object. In contrast with `to_bytes`, the caller does not need to check - /// for `size`: the returned vector is tailored to the exact amount of - /// bytes for the object. - fn to_hex(&self) -> Vec<String>; - - /// Returns a string representation which makes sense to a human (e.g. - /// instead of providing the byte encoded opcode for an instruction, show - /// the mnemonic). - fn to_human(&self) -> String; - - /// Returns a string representation with higher verbosity than `to_human`. - fn to_verbose(&self) -> String; -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Instruction { - pub mnemonic: PString, - pub opcode: u8, - pub bytes: [u8; 2], - pub size: u8, - pub left: Option<PString>, - pub right: Option<PString>, - pub mode: AddressingMode, - pub cycles: u8, // NOTE: relative addressing makes this runtime-dependant (if branch is taken, then +1 cycle to the base cycle here). - pub affected_on_page: bool, // TODO: needed? - pub address: u16, - pub resolved: bool, -} - -impl Instruction { - pub fn unknown() -> Instruction { - Instruction { - mnemonic: PString::new(), - opcode: 0, - bytes: [0, 0], - size: 0, - left: None, - right: None, - mode: AddressingMode::Unknown, - cycles: 0, - affected_on_page: false, - address: 0, - resolved: true, - } - } - - pub fn from(mnemonic: &str) -> Instruction { - Instruction { - mnemonic: PString::from(mnemonic), - opcode: 0, - bytes: [0, 0], - size: 0, - left: None, - right: None, - mode: AddressingMode::Unknown, - cycles: 0, - affected_on_page: false, - address: 0, - resolved: true, - } - } -} - -impl Encodable for Instruction { - fn size(&self) -> u8 { - self.size - } - - fn to_hex(&self) -> Vec<String> { - let mut ret = vec![]; - - ret.push(format!("{:02X}", self.opcode)); - if self.size > 1 { - ret.push(format!("{:02X}", self.bytes[0])); - } - if self.size == 3 { - ret.push(format!("{:02X}", self.bytes[1])); - } - - ret - } - - fn to_bytes(&self) -> [u8; 3] { - [self.opcode.to_le_bytes()[0], self.bytes[0], self.bytes[1]] - } - - fn to_human(&self) -> String { - match self.mode { - AddressingMode::Implied => self.mnemonic.value.clone(), - AddressingMode::Immediate => format!("{} #${:02X}", self.mnemonic.value, self.bytes[0]), - AddressingMode::Absolute => format!( - "{} ${:02X}{:02X}", - self.mnemonic.value, self.bytes[1], self.bytes[0] - ), - AddressingMode::RelativeOrZeropage => { - format!("{} ${:02X}", self.mnemonic.value, self.bytes[0]) - } - AddressingMode::IndexedX => format!( - "{} ${:02X}{:02X}, x", - self.mnemonic.value, self.bytes[1], self.bytes[0] - ), - AddressingMode::IndexedY => format!( - "{} ${:02X}{:02X}, y", - self.mnemonic.value, self.bytes[1], self.bytes[0] - ), - AddressingMode::ZeropageIndexedX => { - format!("{} ${:02X}, x", self.mnemonic.value, self.bytes[0]) - } - AddressingMode::ZeropageIndexedY => { - format!("{} ${:02X}, y", self.mnemonic.value, self.bytes[0]) - } - AddressingMode::Indirect => format!( - "{} (${:02X}{:02X})", - self.mnemonic.value, self.bytes[1], self.bytes[0] - ), - AddressingMode::IndirectX => { - format!("{} (${:02X}, x)", self.mnemonic.value, self.bytes[0]) - } - AddressingMode::IndirectY => { - format!("{} (${:02X}), y", self.mnemonic.value, self.bytes[0]) - } - AddressingMode::Unknown => String::from("unknown instruction"), - } - } - - fn to_verbose(&self) -> String { - format!("{:#?}", self) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Generic { - pub identifier: PString, - pub left: Option<Box<Node>>, - pub right: Option<Box<Node>>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Scoped { - pub identifier: PString, - pub start: bool, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Literal { - pub identifier: PString, - pub bytes: [u8; 2], - pub size: u8, - pub resolved: bool, -} - -impl Encodable for Literal { - fn size(&self) -> u8 { - self.size - } - - fn to_hex(&self) -> Vec<String> { - let mut ret = vec![]; - - ret.push(format!("{:02X}", self.bytes[0])); - if self.size == 2 { - ret.push(format!("{:02X}", self.bytes[1])); - } else if self.size != 1 { - panic!("size for literal should be either 1 or 2"); - } - - ret - } - - fn to_bytes(&self) -> [u8; 3] { - [self.bytes[0], self.bytes[1], 0] - } - - fn to_human(&self) -> String { - match self.size { - 1 => format!(".byte ${:02X}", self.bytes[0]), - 2 => format!(".byte ${:02X}{:02X}", self.bytes[1], self.bytes[0]), - _ => String::from("unknown literal"), - } - } - - fn to_verbose(&self) -> String { - format!("{:#?}", self) - } -} - -#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] -pub struct Fill { - pub value: u8, -} - -impl Encodable for Fill { - fn size(&self) -> u8 { - 1 - } - - fn to_hex(&self) -> Vec<String> { - let mut ret = vec![]; - ret.push(format!("{:02X}", self.value)); - - ret - } - - fn to_bytes(&self) -> [u8; 3] { - [self.value, 0, 0] - } - - fn to_human(&self) -> String { - format!("${:02X}", self.value) - } - - fn to_verbose(&self) -> String { - format!("{:#?}", self) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Label { - pub value: String, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Node { - Generic(Generic), - Instruction(Instruction), - Scoped(Scoped), - Literal(Literal), - Fill(Fill), - Label(Label), -} - -impl Node { - pub fn is_encodeable(&self) -> bool { - matches!( - self, - &Node::Instruction(_) | &Node::Literal(_) | &Node::Fill(_) - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn is_err(line: &str, message: &str) { - let pstring = PString { - value: line.to_string(), - line: 0, - range: Range::default(), - }; - let ret = pstring.is_valid_identifier(); - - assert!(ret.is_err()); - if let Err(e) = ret { - assert_eq!(e, message); - } - } - - #[test] - fn bad_variable_names() { - is_err("a", "cannot use reserved name 'a'"); - is_err("X", "cannot use reserved name 'X'"); - - is_err( - "AA", - "cannot use names which are valid hexadecimal values such as 'AA'", - ); - - is_err("11", "name '11' requires at least one alphabetic character"); - - is_err("__", "name '__' requires at least one alphabetic character"); - - is_err( - "Scope::Variable", - "the name 'Scope::Variable' is scoped: do not declare things this way", - ); - } -} diff --git a/lib/xixanta/src/lib.rs b/lib/xixanta/src/lib.rs index 5e81eba..0b68ac9 100644 --- a/lib/xixanta/src/lib.rs +++ b/lib/xixanta/src/lib.rs @@ -1,12 +1,10 @@ #[macro_use] extern crate lazy_static; -// TODO: be more mindful on what's exported outside. - pub mod assembler; -mod context; -mod errors; -pub mod instruction; +pub mod context; +pub mod errors; pub mod mapping; -mod opcodes; +pub mod node; +pub mod opcodes; pub mod parser; diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs index 231883f..9d313a5 100644 --- a/lib/xixanta/src/mapping.rs +++ b/lib/xixanta/src/mapping.rs @@ -1,9 +1,3 @@ -use crate::instruction::{Fill, Node, PString}; -use std::collections::HashMap; - -use crate::errors::ParseError; -type Result<T> = std::result::Result<T, ParseError>; - lazy_static! { pub static ref EMPTY: Vec<Segment> = vec![Segment { name: String::from("CODE"), @@ -16,25 +10,25 @@ lazy_static! { name: String::from("HEADER"), start: 0x0000, size: 0x0010, - fill: Some(Fill { value: 0x00 }), + fill: Some(0x00), }, Segment { name: String::from("VECTORS"), start: 0xFFFA, size: 0x0006, - fill: Some(Fill { value: 0x00 }), + fill: Some(0x00), }, Segment { name: String::from("CODE"), start: 0x8000, size: 0x7FFA, - fill: Some(Fill { value: 0x00 }), + fill: Some(0x00), }, Segment { name: String::from("CHARS"), start: 0x0000, size: 0x2000, - fill: Some(Fill { value: 0x00 }), + fill: Some(0x00), } ]; } @@ -44,9 +38,10 @@ pub struct Segment { pub name: String, pub start: u16, pub size: usize, - pub fill: Option<Fill>, + pub fill: Option<usize>, } +/* TODO #[derive(Debug)] pub struct Mapping { pub segments: Vec<Segment>, @@ -90,9 +85,10 @@ impl Mapping { pub fn switch(&mut self, id: &PString) -> Result<()> { if !self.nodes.contains_key(&id.value) { - return Err( - id.parser_error(format!("segment '{}' has not been defined", id.value).as_str()) - ); + // TODO + // return Err( + // id.parser_error(format!("segment '{}' has not been defined", id.value).as_str()) + // ); } id.value.clone_into(&mut self.current); @@ -114,3 +110,4 @@ impl Mapping { } } } +*/ diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs new file mode 100644 index 0000000..18a5402 --- /dev/null +++ b/lib/xixanta/src/node.rs @@ -0,0 +1,164 @@ +use std::fmt; +use std::ops::Range; + +/// A Positioned String. That is, a String which also has information on the +/// line number and the column range. +#[derive(Debug, Default, Clone, PartialEq)] +pub struct PString { + /// The actual value. + pub value: String, + + /// Line number where it has been found. + pub line: usize, + + /// The column range where it has been found. + pub range: Range<usize>, +} + +impl PString { + /// Returns true if the string has either an empty value or an empty range. + pub fn is_empty(&self) -> bool { + self.value.is_empty() || self.range.is_empty() + } + + /// Returns an empty tuple if the string contains a valid identifier, or a + /// String containing the error otherwise. If `allow_scoped` is set to true, + /// then identifiers with the "::" operator in it will be considered valid. + pub fn is_valid_identifier(&self, allow_scoped: bool) -> Result<(), String> { + if self.value.trim().is_empty() { + return Err(String::from("empty identifier")); + } + + // You cannot assign into a name which is reserved. + if matches!(self.value.to_lowercase().as_str(), "x" | "y" | "a") { + return Err(format!("cannot use reserved name '{}'", self.value)); + } + + // You cannot assign into scoped names: declare them into their + // respective scopes instead. + if !allow_scoped && self.value.contains("::") { + return Err(format!( + "the name '{}' is scoped: do not declare things this way", + self.value + )); + } + + // Let's gather info from the variable name which is relevant to later + // checks. + let mut alpha_seen = false; + let mut valid_hex = matches!(self.value.len(), 1..=4); + for c in self.value.to_lowercase().chars() { + if c == '_' { + valid_hex = false; + } else if c.is_alphabetic() { + alpha_seen = true; + if c > 'f' && c <= 'z' { + valid_hex = false; + } + } + } + + // We need at least one alphabetic character. Otherwise it might be + // confusing with numbers. + if !alpha_seen { + return Err(format!( + "name '{}' requires at least one alphabetic character", + self.value + )); + } + + // To avoid problems down the line, you cannot assign into names which + // are proper hexadecimal values. + if valid_hex { + return Err(format!( + "cannot use names which are valid hexadecimal values such as '{}'", + self.value + )); + } + + Ok(()) + } +} + +/// The PNode type. +#[derive(Debug, Clone, PartialEq)] +pub enum NodeType { + /// A general/abstract value. This can either be a variable, or a macro call + /// with no arguments. This is, thus, to be determined by the assembler + /// after a proper context has been set. + Value, + + /// An instruction. The value of the PNode has the instruction mnemonic. The + /// left node is the "left arm", which is whatever is left of the comma from + /// expressions like "lda Left, x", or an Indirection on indirect addressing + /// mode (e.g. "lda (Indirect, x)"). The righ node contains the "right arm", + /// which basically contains the index on an indexed addressing mode. + Instruction, + + /// An Indirection node, which might have two arms (e.g. "lda (Left, + /// Right)"), or just a left one (e.g. "lda (Left), y") + Indirection, + + /// An assignment holds the name of the variable on the `value` and the + /// actual expression that initializes it on the `left`. The rest is None. + Assignment, + + /// A control statement (e.g. ".proc foo"). The `value` string contains the + /// name of the function, the `left` an optional identifier (e.g. the "foo" + /// on ".proc foo"), and the `args` contain any possible arguments that have + /// been passed to this control statement. + Control, + + /// A literal expression, that is, something that starts with '#', '%' or + /// '$'. The `left` node contains the inner expression. + Literal, + + /// A label statement, which only sets the `value`, the name of the label. + Label, + + /// A macro call. Note that a Value might also encode this, but when a Call + /// has been detected, then there is no doubt on it. + Call, +} + +impl fmt::Display for NodeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + NodeType::Value => write!(f, "value"), + NodeType::Instruction => write!(f, "instruction"), + NodeType::Indirection => write!(f, "indirection"), + NodeType::Assignment => write!(f, "assignment"), + NodeType::Control => write!(f, "control function"), + NodeType::Literal => write!(f, "literal"), + NodeType::Label => write!(f, "label"), + NodeType::Call => write!(f, "call"), + } + } +} + +/// A Position Node. This is a node on a binary tree which holds a PString as a +/// value. The node type determines the actual representation of the value and +/// both childs (see the `NodeType` enum). Moreover, out of convenience, a node +/// also holds an optional list of arguments, which simplifies arrangements such +/// as the `Call` or the `Control` node types. +#[derive(Debug, Clone)] +pub struct PNode { + /// The type of the PNode. + pub node_type: NodeType, + + /// A Positioned String acting as the value. Check with the `NodeType` + /// documentation to check when it makes sense to use it. + pub value: PString, + + /// The left child of the node. Check with the `NodeType` documentation to + /// check when it makes sense to use it. + pub left: Option<Box<PNode>>, + + /// The right child of the node. Check with the `NodeType` documentation to + /// check when it makes sense to use it. + pub right: Option<Box<PNode>>, + + /// Convenience list used by some node types in order to express a list of + /// optional arguments. + pub args: Option<Vec<PNode>>, +} diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs index e849126..9198e3a 100644 --- a/lib/xixanta/src/opcodes.rs +++ b/lib/xixanta/src/opcodes.rs @@ -1,28 +1,63 @@ -use crate::instruction::AddressingMode; use std::collections::HashMap; +use std::fmt; + +/// Represents the addressing mode being used by an instruction. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum AddressingMode { + Implied, + Immediate, + Absolute, + RelativeOrZeropage, + IndexedX, + IndexedY, + ZeropageIndexedX, + ZeropageIndexedY, + Indirect, + IndirectX, + IndirectY, +} -#[derive(Debug)] -pub struct ShortEntry { - pub cycles: u8, - pub opcode: u8, - pub size: u8, - pub affected_on_page: bool, +impl fmt::Display for AddressingMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AddressingMode::Implied => write!(f, "implied"), + AddressingMode::Immediate => write!(f, "immediate"), + AddressingMode::Absolute => write!(f, "absolute"), + AddressingMode::RelativeOrZeropage => write!(f, "relative or zeropage"), + AddressingMode::IndexedX => write!(f, "indexed by x"), + AddressingMode::IndexedY => write!(f, "indexed by y"), + AddressingMode::ZeropageIndexedX => write!(f, "zeropage indexed by x"), + AddressingMode::ZeropageIndexedY => write!(f, "zeropage indexed by y"), + AddressingMode::Indirect => write!(f, "indirect"), + AddressingMode::IndirectX => write!(f, "indirect indexed by x"), + AddressingMode::IndirectY => write!(f, "indirect indexed by y"), + } + } } #[derive(Debug)] -pub struct Entry { - pub mode: AddressingMode, - pub mnemonic: String, +pub struct ShortEntry { pub cycles: u8, pub opcode: u8, pub size: u8, pub affected_on_page: bool, } +// #[derive(Debug)] +// pub struct Entry { +// pub mode: AddressingMode, +// pub mnemonic: String, +// pub cycles: u8, +// pub opcode: u8, +// pub size: u8, +// pub affected_on_page: bool, +// } + #[derive(Debug)] pub struct Control { pub has_identifier: bool, pub required_args: Option<usize>, + pub touches_context: bool, } lazy_static! { @@ -407,292 +442,291 @@ lazy_static! { instrs }; - pub static ref OPCODES: HashMap<u8, Entry> = { - let mut opcodes = HashMap::new(); - - // adc - opcodes.insert(0x69, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("adc"), cycles: 2, size: 2, opcode: 0x69, affected_on_page: false }); - opcodes.insert(0x65, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("adc"), cycles: 3, size: 2, opcode: 0x65, affected_on_page: false }); - opcodes.insert(0x75, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("adc"), cycles: 4, size: 2, opcode: 0x75, affected_on_page: false }); - opcodes.insert(0x7D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("adc"), cycles: 4, size: 3, opcode: 0x7D, affected_on_page: true }); - opcodes.insert(0x79, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("adc"), cycles: 4, size: 3, opcode: 0x79, affected_on_page: true }); - opcodes.insert(0x61, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("adc"), cycles: 6, size: 2, opcode: 0x61, affected_on_page: false }); - opcodes.insert(0x71, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("adc"), cycles: 5, size: 2, opcode: 0x71, affected_on_page: true }); - opcodes.insert(0x6D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("adc"), cycles: 4, size: 3, opcode: 0x6D, affected_on_page: false }); - - // and - opcodes.insert(0x29, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("and"), cycles: 2, size: 2, opcode: 0x29, affected_on_page: false }); - opcodes.insert(0x25, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("and"), cycles: 3, size: 2, opcode: 0x25, affected_on_page: false }); - opcodes.insert(0x35, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("and"), cycles: 4, size: 2, opcode: 0x35, affected_on_page: false }); - opcodes.insert(0x2D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("and"), cycles: 4, size: 3, opcode: 0x2D, affected_on_page: false }); - opcodes.insert(0x3D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("and"), cycles: 4, size: 3, opcode: 0x3D, affected_on_page: true }); - opcodes.insert(0x39, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("and"), cycles: 4, size: 3, opcode: 0x39, affected_on_page: true }); - opcodes.insert(0x21, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("and"), cycles: 6, size: 2, opcode: 0x21, affected_on_page: false }); - opcodes.insert(0x31, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("and"), cycles: 5, size: 2, opcode: 0x31, affected_on_page: true }); - - // asl - opcodes.insert(0x0A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("asl"), cycles: 2, size: 1, opcode: 0x0A, affected_on_page: false }); - opcodes.insert(0x06, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("asl"), cycles: 5, size: 2, opcode: 0x06, affected_on_page: false }); - opcodes.insert(0x16, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("asl"), cycles: 6, size: 2, opcode: 0x16, affected_on_page: false }); - opcodes.insert(0x0E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("asl"), cycles: 6, size: 3, opcode: 0x0E, affected_on_page: false }); - opcodes.insert(0x1E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("asl"), cycles: 7, size: 3, opcode: 0x1E, affected_on_page: false }); - - // bcc - opcodes.insert(0x90, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bcc"), cycles: 2, size: 2, opcode: 0x90, affected_on_page: false }); - - // bcs - opcodes.insert(0xB0, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bcs"), cycles: 2, size: 2, opcode: 0xB0, affected_on_page: false }); - - // beq - opcodes.insert(0xF0, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("beq"), cycles: 2, size: 2, opcode: 0xF0, affected_on_page: true }); - - // bit - opcodes.insert(0x24, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bit"), cycles: 3, size: 2, opcode: 0x24, affected_on_page: false }); - opcodes.insert(0x2C, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("bit"), cycles: 4, size: 3, opcode: 0x2C, affected_on_page: false }); - - // bmi - opcodes.insert(0x30, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bmi"), cycles: 2, size: 2, opcode: 0x30, affected_on_page: false }); - - // bne - opcodes.insert(0xD0, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bne"), cycles: 2, size: 2, opcode: 0xD0, affected_on_page: false }); - - // bpl - opcodes.insert(0x10, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bpl"), cycles: 2, size: 2, opcode: 0x10, affected_on_page: false }); - - // brk - opcodes.insert(0x00, Entry { mode: AddressingMode::Implied, mnemonic: String::from("brk"), cycles: 7, size: 1, opcode: 0x00, affected_on_page: false }); - - // bvc - opcodes.insert(0x50, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bvc"), cycles: 2, size: 2, opcode: 0x50, affected_on_page: false }); - - // bvs - opcodes.insert(0x70, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bvs"), cycles: 2, size: 2, opcode: 0x70, affected_on_page: false }); - - // clc - opcodes.insert(0x18, Entry { mode: AddressingMode::Implied, mnemonic: String::from("clc"), cycles: 2, size: 1, opcode: 0x18, affected_on_page: false }); - - // cld - opcodes.insert(0xD8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("cld"), cycles: 2, size: 1, opcode: 0xD8, affected_on_page: false }); - - // cli - opcodes.insert(0x58, Entry { mode: AddressingMode::Implied, mnemonic: String::from("cli"), cycles: 2, size: 1, opcode: 0x58, affected_on_page: false }); - - // clv - opcodes.insert(0xB8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("clv"), cycles: 2, size: 1, opcode: 0xB8, affected_on_page: false }); - - // cmp - opcodes.insert(0xC9, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("cmp"), cycles: 2, size: 2, opcode: 0xC9, affected_on_page: false }); - opcodes.insert(0xC5, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("cmp"), cycles: 3, size: 2, opcode: 0xC5, affected_on_page: false }); - opcodes.insert(0xD5, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("cmp"), cycles: 4, size: 2, opcode: 0xD5, affected_on_page: false }); - opcodes.insert(0xCD, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("cmp"), cycles: 4, size: 3, opcode: 0xCD, affected_on_page: false }); - opcodes.insert(0xDD, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("cmp"), cycles: 4, size: 3, opcode: 0xDD, affected_on_page: true }); - opcodes.insert(0xD9, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("cmp"), cycles: 4, size: 3, opcode: 0xD9, affected_on_page: true }); - opcodes.insert(0xC1, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("cmp"), cycles: 6, size: 2, opcode: 0xC1, affected_on_page: false }); - opcodes.insert(0xD1, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("cmp"), cycles: 5, size: 2, opcode: 0xD1, affected_on_page: true }); - - // cpx - opcodes.insert(0xE0, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("cpx"), cycles: 2, size: 2, opcode: 0xE0, affected_on_page: false }); - opcodes.insert(0xE4, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("cpx"), cycles: 3, size: 2, opcode: 0xE4, affected_on_page: false }); - opcodes.insert(0xEC, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("cpx"), cycles: 4, size: 3, opcode: 0xEC, affected_on_page: false }); - - // cpy - opcodes.insert(0xC0, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("cpy"), cycles: 2, size: 2, opcode: 0xC0, affected_on_page: false }); - opcodes.insert(0xC4, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("cpy"), cycles: 3, size: 2, opcode: 0xC4, affected_on_page: false }); - opcodes.insert(0xCC, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("cpy"), cycles: 4, size: 3, opcode: 0xCC, affected_on_page: false }); - - // dec - opcodes.insert(0xC6, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("dec"), cycles: 5, size: 2, opcode: 0xC6, affected_on_page: false }); - opcodes.insert(0xD6, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("dec"), cycles: 6, size: 2, opcode: 0xD6, affected_on_page: false }); - opcodes.insert(0xCE, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("dec"), cycles: 6, size: 3, opcode: 0xCE, affected_on_page: false }); - opcodes.insert(0xDE, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("dec"), cycles: 7, size: 3, opcode: 0xDE, affected_on_page: false }); - - // dex - opcodes.insert(0xCA, Entry { mode: AddressingMode::Implied, mnemonic: String::from("dex"), cycles: 2, size: 1, opcode: 0xCA, affected_on_page: false }); - - // dey - opcodes.insert(0x88, Entry { mode: AddressingMode::Implied, mnemonic: String::from("dey"), cycles: 2, size: 1, opcode: 0x88, affected_on_page: false }); - - // eor - opcodes.insert(0x49, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("eor"), cycles: 2, size: 2, opcode: 0x49, affected_on_page: false }); - opcodes.insert(0x45, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("eor"), cycles: 3, size: 2, opcode: 0x45, affected_on_page: false }); - opcodes.insert(0x55, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("eor"), cycles: 4, size: 2, opcode: 0x55, affected_on_page: false }); - opcodes.insert(0x4D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("eor"), cycles: 4, size: 3, opcode: 0x4D, affected_on_page: false }); - opcodes.insert(0x5D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("eor"), cycles: 4, size: 3, opcode: 0x5D, affected_on_page: true }); - opcodes.insert(0x59, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("eor"), cycles: 4, size: 3, opcode: 0x59, affected_on_page: true }); - opcodes.insert(0x41, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("eor"), cycles: 6, size: 2, opcode: 0x41, affected_on_page: false }); - opcodes.insert(0x51, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("eor"), cycles: 5, size: 2, opcode: 0x51, affected_on_page: true }); - - // inc - opcodes.insert(0xE6, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("inc"), cycles: 5, size: 2, opcode: 0xE6, affected_on_page: false }); - opcodes.insert(0xF6, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("inc"), cycles: 6, size: 2, opcode: 0xF6, affected_on_page: false }); - opcodes.insert(0xEE, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("inc"), cycles: 6, size: 3, opcode: 0xEE, affected_on_page: false }); - opcodes.insert(0xFE, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("inc"), cycles: 7, size: 3, opcode: 0xFE, affected_on_page: false }); - - // inx - opcodes.insert(0xE8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("inx"), cycles: 2, size: 1, opcode: 0xE8, affected_on_page: false }); - - // iny - opcodes.insert(0xC8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("iny"), cycles: 2, size: 1, opcode: 0xC8, affected_on_page: false }); - - // jmp - opcodes.insert(0x4C, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("jmp"), cycles: 3, size: 3, opcode: 0x4C, affected_on_page: false }); - opcodes.insert(0x6C, Entry { mode: AddressingMode::Indirect, mnemonic: String::from("jmp"), cycles: 5, size: 3, opcode: 0x6C, affected_on_page: false }); - - // jsr - opcodes.insert(0x20, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("jsr"), cycles: 6, size: 3, opcode: 0x20, affected_on_page: false }); - - // lda - opcodes.insert(0xA9, Entry{ mode: AddressingMode::Immediate, mnemonic: String::from("lda"), cycles: 2, size: 2, opcode: 0xA9, affected_on_page: false }); - opcodes.insert(0xA5, Entry{ mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("lda"), cycles: 3, size: 2, opcode: 0xA5, affected_on_page: false }); - opcodes.insert(0xB5, Entry{ mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("lda"), cycles: 4, size: 2, opcode: 0xB5, affected_on_page: false }); - opcodes.insert(0xAD, Entry{ mode: AddressingMode::Absolute, mnemonic: String::from("lda"), cycles: 4, size: 3, opcode: 0xAD, affected_on_page: false }); - opcodes.insert(0xBD, Entry{ mode: AddressingMode::IndexedX, mnemonic: String::from("lda"), cycles: 4, size: 3, opcode: 0xBD, affected_on_page: true }); - opcodes.insert(0xB9, Entry{ mode: AddressingMode::IndexedY, mnemonic: String::from("lda"), cycles: 4, size: 3, opcode: 0xB9, affected_on_page: true }); - opcodes.insert(0xA1, Entry{ mode: AddressingMode::IndirectX, mnemonic: String::from("lda"), cycles: 6, size: 2, opcode: 0xA1, affected_on_page: false }); - opcodes.insert(0xB1, Entry{ mode: AddressingMode::IndirectY, mnemonic: String::from("lda"), cycles: 5, size: 2, opcode: 0xB1, affected_on_page: true }); - - // ldx - opcodes.insert(0xA2, Entry{ mode: AddressingMode::Immediate, mnemonic: String::from("ldx"), cycles: 2, size: 2, opcode: 0xA2, affected_on_page: false }); - opcodes.insert(0xA6, Entry{ mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ldx"), cycles: 3, size: 2, opcode: 0xA6, affected_on_page: false }); - opcodes.insert(0xB6, Entry{ mode: AddressingMode::ZeropageIndexedY, mnemonic: String::from("ldx"), cycles: 4, size: 2, opcode: 0xB6, affected_on_page: false }); - opcodes.insert(0xAE, Entry{ mode: AddressingMode::Absolute, mnemonic: String::from("ldx"), cycles: 4, size: 3, opcode: 0xAE, affected_on_page: false }); - opcodes.insert(0xBE, Entry{ mode: AddressingMode::IndexedY, mnemonic: String::from("ldx"), cycles: 4, size: 3, opcode: 0xBE, affected_on_page: true }); - - // ldy - opcodes.insert(0xA0, Entry{ mode: AddressingMode::Immediate, mnemonic: String::from("ldy"), cycles: 2, size: 2, opcode: 0xA0, affected_on_page: false }); - opcodes.insert(0xA4, Entry{ mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ldy"), cycles: 3, size: 2, opcode: 0xA4, affected_on_page: false }); - opcodes.insert(0xB4, Entry{ mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("ldy"), cycles: 4, size: 2, opcode: 0xB4, affected_on_page: false }); - opcodes.insert(0xAC, Entry{ mode: AddressingMode::Absolute, mnemonic: String::from("ldy"), cycles: 4, size: 3, opcode: 0xAC, affected_on_page: false }); - opcodes.insert(0xBC, Entry{ mode: AddressingMode::IndexedX, mnemonic: String::from("ldy"), cycles: 4, size: 3, opcode: 0xBC, affected_on_page: true }); - - // lsr - opcodes.insert(0x4A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("lsr"), cycles: 2, size: 1, opcode: 0x4A, affected_on_page: false }); - opcodes.insert(0x46, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("lsr"), cycles: 5, size: 2, opcode: 0x46, affected_on_page: false }); - opcodes.insert(0x56, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("lsr"), cycles: 6, size: 2, opcode: 0x56, affected_on_page: false }); - opcodes.insert(0x4E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("lsr"), cycles: 6, size: 3, opcode: 0x4E, affected_on_page: false }); - opcodes.insert(0x5E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("lsr"), cycles: 7, size: 3, opcode: 0x5E, affected_on_page: false }); - - // nop - opcodes.insert(0xEA, Entry { mode: AddressingMode::Implied, mnemonic: String::from("nop"), cycles: 2, size: 1, opcode: 0xEA, affected_on_page: false }); - - // ora - opcodes.insert(0x09, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("ora"), cycles: 2, size: 2, opcode: 0x09, affected_on_page: false }); - opcodes.insert(0x05, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ora"), cycles: 3, size: 2, opcode: 0x05, affected_on_page: false }); - opcodes.insert(0x15, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("ora"), cycles: 4, size: 2, opcode: 0x15, affected_on_page: false }); - opcodes.insert(0x0D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("ora"), cycles: 4, size: 3, opcode: 0x0D, affected_on_page: false }); - opcodes.insert(0x1D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("ora"), cycles: 4, size: 3, opcode: 0x1D, affected_on_page: true }); - opcodes.insert(0x19, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("ora"), cycles: 4, size: 3, opcode: 0x19, affected_on_page: true }); - opcodes.insert(0x01, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("ora"), cycles: 6, size: 2, opcode: 0x01, affected_on_page: false }); - opcodes.insert(0x11, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("ora"), cycles: 5, size: 2, opcode: 0x11, affected_on_page: true }); - - // pha - opcodes.insert(0x48, Entry { mode: AddressingMode::Implied, mnemonic: String::from("pha"), cycles: 3, size: 1, opcode: 0x48, affected_on_page: false }); - - // php - opcodes.insert(0x08, Entry { mode: AddressingMode::Implied, mnemonic: String::from("php"), cycles: 3, size: 1, opcode: 0x08, affected_on_page: false }); - - // pla - opcodes.insert(0x68, Entry { mode: AddressingMode::Implied, mnemonic: String::from("pla"), cycles: 4, size: 1, opcode: 0x68, affected_on_page: false }); - - // plp - opcodes.insert(0x28, Entry { mode: AddressingMode::Implied, mnemonic: String::from("plp"), cycles: 4, size: 1, opcode: 0x28, affected_on_page: false }); - - // rol - opcodes.insert(0x2A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("rol"), cycles: 2, size: 1, opcode: 0x2A, affected_on_page: false }); - opcodes.insert(0x26, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("rol"), cycles: 5, size: 2, opcode: 0x26, affected_on_page: false }); - opcodes.insert(0x36, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("rol"), cycles: 6, size: 2, opcode: 0x36, affected_on_page: false }); - opcodes.insert(0x2E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("rol"), cycles: 6, size: 3, opcode: 0x2E, affected_on_page: false }); - opcodes.insert(0x3E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("rol"), cycles: 7, size: 3, opcode: 0x3E, affected_on_page: false }); - - // ror - opcodes.insert(0x6A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("ror"), cycles: 2, size: 1, opcode: 0x6A, affected_on_page: false }); - opcodes.insert(0x66, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ror"), cycles: 5, size: 2, opcode: 0x66, affected_on_page: false }); - opcodes.insert(0x76, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("ror"), cycles: 6, size: 2, opcode: 0x76, affected_on_page: false }); - opcodes.insert(0x6E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("ror"), cycles: 6, size: 3, opcode: 0x6E, affected_on_page: false }); - opcodes.insert(0x7E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("ror"), cycles: 7, size: 3, opcode: 0x7E, affected_on_page: false }); - - // rti - opcodes.insert(0x40, Entry { mode: AddressingMode::Implied, mnemonic: String::from("rti"), cycles: 6, size: 1, opcode: 0x40, affected_on_page: false }); - - // rts - opcodes.insert(0x60, Entry { mode: AddressingMode::Implied, mnemonic: String::from("rts"), cycles: 6, size: 1, opcode: 0x60, affected_on_page: false }); - - // sbc - opcodes.insert(0xE9, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("sbc"), cycles: 2, size: 2, opcode: 0xE9, affected_on_page: false }); - opcodes.insert(0xE5, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("sbc"), cycles: 3, size: 2, opcode: 0xE5, affected_on_page: false }); - opcodes.insert(0xF5, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("sbc"), cycles: 4, size: 2, opcode: 0xF5, affected_on_page: false }); - opcodes.insert(0xED, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("sbc"), cycles: 4, size: 3, opcode: 0xED, affected_on_page: false }); - opcodes.insert(0xFD, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("sbc"), cycles: 4, size: 3, opcode: 0xFD, affected_on_page: true }); - opcodes.insert(0xF9, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("sbc"), cycles: 4, size: 3, opcode: 0xF9, affected_on_page: true }); - opcodes.insert(0xE1, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("sbc"), cycles: 6, size: 2, opcode: 0xE1, affected_on_page: false }); - opcodes.insert(0xF1, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("sbc"), cycles: 5, size: 2, opcode: 0xF1, affected_on_page: true }); - - // sec - opcodes.insert(0x38, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("sec"), cycles: 2, size: 1, opcode: 0x38, affected_on_page: false }); - - // sed - opcodes.insert(0xF8, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("sed"), cycles: 2, size: 1, opcode: 0xF8, affected_on_page: false }); - - // sei - opcodes.insert(0x78, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("sei"), cycles: 2, size: 1, opcode: 0x78, affected_on_page: false }); - - // sta - opcodes.insert(0x85, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("sta"), cycles: 3, size: 2, opcode: 0x85, affected_on_page: false }); - opcodes.insert(0x95, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("sta"), cycles: 4, size: 2, opcode: 0x95, affected_on_page: false }); - opcodes.insert(0x8D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("sta"), cycles: 4, size: 3, opcode: 0x8D, affected_on_page: false }); - opcodes.insert(0x9D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("sta"), cycles: 5, size: 3, opcode: 0x9D, affected_on_page: false }); - opcodes.insert(0x99, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("sta"), cycles: 5, size: 3, opcode: 0x99, affected_on_page: false }); - opcodes.insert(0x81, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("sta"), cycles: 6, size: 2, opcode: 0x81, affected_on_page: false }); - opcodes.insert(0x91, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("sta"), cycles: 6, size: 2, opcode: 0x91, affected_on_page: false }); - - // stx - opcodes.insert(0x86, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("stx"), cycles: 3, size: 2, opcode: 0x86, affected_on_page: false }); - opcodes.insert(0x96, Entry { mode: AddressingMode::ZeropageIndexedY, mnemonic: String::from("stx"), cycles: 4, size: 2, opcode: 0x96, affected_on_page: false }); - opcodes.insert(0x8E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("stx"), cycles: 4, size: 3, opcode: 0x8E, affected_on_page: false }); - - // sty - opcodes.insert(0x84, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("sty"), cycles: 3, size: 2, opcode: 0x84, affected_on_page: false }); - opcodes.insert(0x94, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("sty"), cycles: 4, size: 2, opcode: 0x94, affected_on_page: false }); - opcodes.insert(0x8C, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("sty"), cycles: 4, size: 3, opcode: 0x8C, affected_on_page: false }); - - // tax - opcodes.insert(0xAA, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("tax"), cycles: 2, size: 1, opcode: 0xAA, affected_on_page: false }); - - // tay - opcodes.insert(0xA8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("tay"), cycles: 2, size: 1, opcode: 0xA8, affected_on_page: false }); - - // tsx - opcodes.insert(0xBA, Entry { mode: AddressingMode::Implied, mnemonic: String::from("tsx"), cycles: 2, size: 1, opcode: 0xBA, affected_on_page: false }); - - // txa - opcodes.insert(0x8A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("txa"), cycles: 2, size: 1, opcode: 0x8A, affected_on_page: false }); - - // txs - opcodes.insert(0x9A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("txs"), cycles: 2, size: 1, opcode: 0x9A, affected_on_page: false }); - - // tya - opcodes.insert(0x98, Entry { mode: AddressingMode::Implied, mnemonic: String::from("tya"), cycles: 2, size: 1, opcode: 0x98, affected_on_page: false }); - - opcodes - }; + // pub static ref OPCODES: HashMap<u8, Entry> = { + // let mut opcodes = HashMap::new(); + + // // adc + // opcodes.insert(0x69, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("adc"), cycles: 2, size: 2, opcode: 0x69, affected_on_page: false }); + // opcodes.insert(0x65, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("adc"), cycles: 3, size: 2, opcode: 0x65, affected_on_page: false }); + // opcodes.insert(0x75, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("adc"), cycles: 4, size: 2, opcode: 0x75, affected_on_page: false }); + // opcodes.insert(0x7D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("adc"), cycles: 4, size: 3, opcode: 0x7D, affected_on_page: true }); + // opcodes.insert(0x79, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("adc"), cycles: 4, size: 3, opcode: 0x79, affected_on_page: true }); + // opcodes.insert(0x61, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("adc"), cycles: 6, size: 2, opcode: 0x61, affected_on_page: false }); + // opcodes.insert(0x71, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("adc"), cycles: 5, size: 2, opcode: 0x71, affected_on_page: true }); + // opcodes.insert(0x6D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("adc"), cycles: 4, size: 3, opcode: 0x6D, affected_on_page: false }); + + // // and + // opcodes.insert(0x29, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("and"), cycles: 2, size: 2, opcode: 0x29, affected_on_page: false }); + // opcodes.insert(0x25, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("and"), cycles: 3, size: 2, opcode: 0x25, affected_on_page: false }); + // opcodes.insert(0x35, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("and"), cycles: 4, size: 2, opcode: 0x35, affected_on_page: false }); + // opcodes.insert(0x2D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("and"), cycles: 4, size: 3, opcode: 0x2D, affected_on_page: false }); + // opcodes.insert(0x3D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("and"), cycles: 4, size: 3, opcode: 0x3D, affected_on_page: true }); + // opcodes.insert(0x39, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("and"), cycles: 4, size: 3, opcode: 0x39, affected_on_page: true }); + // opcodes.insert(0x21, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("and"), cycles: 6, size: 2, opcode: 0x21, affected_on_page: false }); + // opcodes.insert(0x31, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("and"), cycles: 5, size: 2, opcode: 0x31, affected_on_page: true }); + + // // asl + // opcodes.insert(0x0A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("asl"), cycles: 2, size: 1, opcode: 0x0A, affected_on_page: false }); + // opcodes.insert(0x06, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("asl"), cycles: 5, size: 2, opcode: 0x06, affected_on_page: false }); + // opcodes.insert(0x16, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("asl"), cycles: 6, size: 2, opcode: 0x16, affected_on_page: false }); + // opcodes.insert(0x0E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("asl"), cycles: 6, size: 3, opcode: 0x0E, affected_on_page: false }); + // opcodes.insert(0x1E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("asl"), cycles: 7, size: 3, opcode: 0x1E, affected_on_page: false }); + + // // bcc + // opcodes.insert(0x90, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bcc"), cycles: 2, size: 2, opcode: 0x90, affected_on_page: false }); + + // // bcs + // opcodes.insert(0xB0, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bcs"), cycles: 2, size: 2, opcode: 0xB0, affected_on_page: false }); + + // // beq + // opcodes.insert(0xF0, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("beq"), cycles: 2, size: 2, opcode: 0xF0, affected_on_page: true }); + + // // bit + // opcodes.insert(0x24, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bit"), cycles: 3, size: 2, opcode: 0x24, affected_on_page: false }); + // opcodes.insert(0x2C, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("bit"), cycles: 4, size: 3, opcode: 0x2C, affected_on_page: false }); + + // // bmi + // opcodes.insert(0x30, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bmi"), cycles: 2, size: 2, opcode: 0x30, affected_on_page: false }); + + // // bne + // opcodes.insert(0xD0, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bne"), cycles: 2, size: 2, opcode: 0xD0, affected_on_page: false }); + + // // bpl + // opcodes.insert(0x10, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bpl"), cycles: 2, size: 2, opcode: 0x10, affected_on_page: false }); + + // // brk + // opcodes.insert(0x00, Entry { mode: AddressingMode::Implied, mnemonic: String::from("brk"), cycles: 7, size: 1, opcode: 0x00, affected_on_page: false }); + + // // bvc + // opcodes.insert(0x50, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bvc"), cycles: 2, size: 2, opcode: 0x50, affected_on_page: false }); + + // // bvs + // opcodes.insert(0x70, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("bvs"), cycles: 2, size: 2, opcode: 0x70, affected_on_page: false }); + + // // clc + // opcodes.insert(0x18, Entry { mode: AddressingMode::Implied, mnemonic: String::from("clc"), cycles: 2, size: 1, opcode: 0x18, affected_on_page: false }); + + // // cld + // opcodes.insert(0xD8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("cld"), cycles: 2, size: 1, opcode: 0xD8, affected_on_page: false }); + + // // cli + // opcodes.insert(0x58, Entry { mode: AddressingMode::Implied, mnemonic: String::from("cli"), cycles: 2, size: 1, opcode: 0x58, affected_on_page: false }); + + // // clv + // opcodes.insert(0xB8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("clv"), cycles: 2, size: 1, opcode: 0xB8, affected_on_page: false }); + + // // cmp + // opcodes.insert(0xC9, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("cmp"), cycles: 2, size: 2, opcode: 0xC9, affected_on_page: false }); + // opcodes.insert(0xC5, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("cmp"), cycles: 3, size: 2, opcode: 0xC5, affected_on_page: false }); + // opcodes.insert(0xD5, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("cmp"), cycles: 4, size: 2, opcode: 0xD5, affected_on_page: false }); + // opcodes.insert(0xCD, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("cmp"), cycles: 4, size: 3, opcode: 0xCD, affected_on_page: false }); + // opcodes.insert(0xDD, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("cmp"), cycles: 4, size: 3, opcode: 0xDD, affected_on_page: true }); + // opcodes.insert(0xD9, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("cmp"), cycles: 4, size: 3, opcode: 0xD9, affected_on_page: true }); + // opcodes.insert(0xC1, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("cmp"), cycles: 6, size: 2, opcode: 0xC1, affected_on_page: false }); + // opcodes.insert(0xD1, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("cmp"), cycles: 5, size: 2, opcode: 0xD1, affected_on_page: true }); + + // // cpx + // opcodes.insert(0xE0, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("cpx"), cycles: 2, size: 2, opcode: 0xE0, affected_on_page: false }); + // opcodes.insert(0xE4, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("cpx"), cycles: 3, size: 2, opcode: 0xE4, affected_on_page: false }); + // opcodes.insert(0xEC, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("cpx"), cycles: 4, size: 3, opcode: 0xEC, affected_on_page: false }); + + // // cpy + // opcodes.insert(0xC0, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("cpy"), cycles: 2, size: 2, opcode: 0xC0, affected_on_page: false }); + // opcodes.insert(0xC4, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("cpy"), cycles: 3, size: 2, opcode: 0xC4, affected_on_page: false }); + // opcodes.insert(0xCC, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("cpy"), cycles: 4, size: 3, opcode: 0xCC, affected_on_page: false }); + + // // dec + // opcodes.insert(0xC6, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("dec"), cycles: 5, size: 2, opcode: 0xC6, affected_on_page: false }); + // opcodes.insert(0xD6, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("dec"), cycles: 6, size: 2, opcode: 0xD6, affected_on_page: false }); + // opcodes.insert(0xCE, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("dec"), cycles: 6, size: 3, opcode: 0xCE, affected_on_page: false }); + // opcodes.insert(0xDE, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("dec"), cycles: 7, size: 3, opcode: 0xDE, affected_on_page: false }); + + // // dex + // opcodes.insert(0xCA, Entry { mode: AddressingMode::Implied, mnemonic: String::from("dex"), cycles: 2, size: 1, opcode: 0xCA, affected_on_page: false }); + + // // dey + // opcodes.insert(0x88, Entry { mode: AddressingMode::Implied, mnemonic: String::from("dey"), cycles: 2, size: 1, opcode: 0x88, affected_on_page: false }); + + // // eor + // opcodes.insert(0x49, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("eor"), cycles: 2, size: 2, opcode: 0x49, affected_on_page: false }); + // opcodes.insert(0x45, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("eor"), cycles: 3, size: 2, opcode: 0x45, affected_on_page: false }); + // opcodes.insert(0x55, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("eor"), cycles: 4, size: 2, opcode: 0x55, affected_on_page: false }); + // opcodes.insert(0x4D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("eor"), cycles: 4, size: 3, opcode: 0x4D, affected_on_page: false }); + // opcodes.insert(0x5D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("eor"), cycles: 4, size: 3, opcode: 0x5D, affected_on_page: true }); + // opcodes.insert(0x59, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("eor"), cycles: 4, size: 3, opcode: 0x59, affected_on_page: true }); + // opcodes.insert(0x41, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("eor"), cycles: 6, size: 2, opcode: 0x41, affected_on_page: false }); + // opcodes.insert(0x51, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("eor"), cycles: 5, size: 2, opcode: 0x51, affected_on_page: true }); + + // // inc + // opcodes.insert(0xE6, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("inc"), cycles: 5, size: 2, opcode: 0xE6, affected_on_page: false }); + // opcodes.insert(0xF6, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("inc"), cycles: 6, size: 2, opcode: 0xF6, affected_on_page: false }); + // opcodes.insert(0xEE, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("inc"), cycles: 6, size: 3, opcode: 0xEE, affected_on_page: false }); + // opcodes.insert(0xFE, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("inc"), cycles: 7, size: 3, opcode: 0xFE, affected_on_page: false }); + + // // inx + // opcodes.insert(0xE8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("inx"), cycles: 2, size: 1, opcode: 0xE8, affected_on_page: false }); + + // // iny + // opcodes.insert(0xC8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("iny"), cycles: 2, size: 1, opcode: 0xC8, affected_on_page: false }); + + // // jmp + // opcodes.insert(0x4C, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("jmp"), cycles: 3, size: 3, opcode: 0x4C, affected_on_page: false }); + // opcodes.insert(0x6C, Entry { mode: AddressingMode::Indirect, mnemonic: String::from("jmp"), cycles: 5, size: 3, opcode: 0x6C, affected_on_page: false }); + + // // jsr + // opcodes.insert(0x20, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("jsr"), cycles: 6, size: 3, opcode: 0x20, affected_on_page: false }); + + // // lda + // opcodes.insert(0xA9, Entry{ mode: AddressingMode::Immediate, mnemonic: String::from("lda"), cycles: 2, size: 2, opcode: 0xA9, affected_on_page: false }); + // opcodes.insert(0xA5, Entry{ mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("lda"), cycles: 3, size: 2, opcode: 0xA5, affected_on_page: false }); + // opcodes.insert(0xB5, Entry{ mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("lda"), cycles: 4, size: 2, opcode: 0xB5, affected_on_page: false }); + // opcodes.insert(0xAD, Entry{ mode: AddressingMode::Absolute, mnemonic: String::from("lda"), cycles: 4, size: 3, opcode: 0xAD, affected_on_page: false }); + // opcodes.insert(0xBD, Entry{ mode: AddressingMode::IndexedX, mnemonic: String::from("lda"), cycles: 4, size: 3, opcode: 0xBD, affected_on_page: true }); + // opcodes.insert(0xB9, Entry{ mode: AddressingMode::IndexedY, mnemonic: String::from("lda"), cycles: 4, size: 3, opcode: 0xB9, affected_on_page: true }); + // opcodes.insert(0xA1, Entry{ mode: AddressingMode::IndirectX, mnemonic: String::from("lda"), cycles: 6, size: 2, opcode: 0xA1, affected_on_page: false }); + // opcodes.insert(0xB1, Entry{ mode: AddressingMode::IndirectY, mnemonic: String::from("lda"), cycles: 5, size: 2, opcode: 0xB1, affected_on_page: true }); + + // // ldx + // opcodes.insert(0xA2, Entry{ mode: AddressingMode::Immediate, mnemonic: String::from("ldx"), cycles: 2, size: 2, opcode: 0xA2, affected_on_page: false }); + // opcodes.insert(0xA6, Entry{ mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ldx"), cycles: 3, size: 2, opcode: 0xA6, affected_on_page: false }); + // opcodes.insert(0xB6, Entry{ mode: AddressingMode::ZeropageIndexedY, mnemonic: String::from("ldx"), cycles: 4, size: 2, opcode: 0xB6, affected_on_page: false }); + // opcodes.insert(0xAE, Entry{ mode: AddressingMode::Absolute, mnemonic: String::from("ldx"), cycles: 4, size: 3, opcode: 0xAE, affected_on_page: false }); + // opcodes.insert(0xBE, Entry{ mode: AddressingMode::IndexedY, mnemonic: String::from("ldx"), cycles: 4, size: 3, opcode: 0xBE, affected_on_page: true }); + + // // ldy + // opcodes.insert(0xA0, Entry{ mode: AddressingMode::Immediate, mnemonic: String::from("ldy"), cycles: 2, size: 2, opcode: 0xA0, affected_on_page: false }); + // opcodes.insert(0xA4, Entry{ mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ldy"), cycles: 3, size: 2, opcode: 0xA4, affected_on_page: false }); + // opcodes.insert(0xB4, Entry{ mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("ldy"), cycles: 4, size: 2, opcode: 0xB4, affected_on_page: false }); + // opcodes.insert(0xAC, Entry{ mode: AddressingMode::Absolute, mnemonic: String::from("ldy"), cycles: 4, size: 3, opcode: 0xAC, affected_on_page: false }); + // opcodes.insert(0xBC, Entry{ mode: AddressingMode::IndexedX, mnemonic: String::from("ldy"), cycles: 4, size: 3, opcode: 0xBC, affected_on_page: true }); + + // // lsr + // opcodes.insert(0x4A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("lsr"), cycles: 2, size: 1, opcode: 0x4A, affected_on_page: false }); + // opcodes.insert(0x46, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("lsr"), cycles: 5, size: 2, opcode: 0x46, affected_on_page: false }); + // opcodes.insert(0x56, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("lsr"), cycles: 6, size: 2, opcode: 0x56, affected_on_page: false }); + // opcodes.insert(0x4E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("lsr"), cycles: 6, size: 3, opcode: 0x4E, affected_on_page: false }); + // opcodes.insert(0x5E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("lsr"), cycles: 7, size: 3, opcode: 0x5E, affected_on_page: false }); + + // // nop + // opcodes.insert(0xEA, Entry { mode: AddressingMode::Implied, mnemonic: String::from("nop"), cycles: 2, size: 1, opcode: 0xEA, affected_on_page: false }); + + // // ora + // opcodes.insert(0x09, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("ora"), cycles: 2, size: 2, opcode: 0x09, affected_on_page: false }); + // opcodes.insert(0x05, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ora"), cycles: 3, size: 2, opcode: 0x05, affected_on_page: false }); + // opcodes.insert(0x15, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("ora"), cycles: 4, size: 2, opcode: 0x15, affected_on_page: false }); + // opcodes.insert(0x0D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("ora"), cycles: 4, size: 3, opcode: 0x0D, affected_on_page: false }); + // opcodes.insert(0x1D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("ora"), cycles: 4, size: 3, opcode: 0x1D, affected_on_page: true }); + // opcodes.insert(0x19, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("ora"), cycles: 4, size: 3, opcode: 0x19, affected_on_page: true }); + // opcodes.insert(0x01, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("ora"), cycles: 6, size: 2, opcode: 0x01, affected_on_page: false }); + // opcodes.insert(0x11, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("ora"), cycles: 5, size: 2, opcode: 0x11, affected_on_page: true }); + + // // pha + // opcodes.insert(0x48, Entry { mode: AddressingMode::Implied, mnemonic: String::from("pha"), cycles: 3, size: 1, opcode: 0x48, affected_on_page: false }); + + // // php + // opcodes.insert(0x08, Entry { mode: AddressingMode::Implied, mnemonic: String::from("php"), cycles: 3, size: 1, opcode: 0x08, affected_on_page: false }); + + // // pla + // opcodes.insert(0x68, Entry { mode: AddressingMode::Implied, mnemonic: String::from("pla"), cycles: 4, size: 1, opcode: 0x68, affected_on_page: false }); + + // // plp + // opcodes.insert(0x28, Entry { mode: AddressingMode::Implied, mnemonic: String::from("plp"), cycles: 4, size: 1, opcode: 0x28, affected_on_page: false }); + + // // rol + // opcodes.insert(0x2A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("rol"), cycles: 2, size: 1, opcode: 0x2A, affected_on_page: false }); + // opcodes.insert(0x26, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("rol"), cycles: 5, size: 2, opcode: 0x26, affected_on_page: false }); + // opcodes.insert(0x36, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("rol"), cycles: 6, size: 2, opcode: 0x36, affected_on_page: false }); + // opcodes.insert(0x2E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("rol"), cycles: 6, size: 3, opcode: 0x2E, affected_on_page: false }); + // opcodes.insert(0x3E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("rol"), cycles: 7, size: 3, opcode: 0x3E, affected_on_page: false }); + + // // ror + // opcodes.insert(0x6A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("ror"), cycles: 2, size: 1, opcode: 0x6A, affected_on_page: false }); + // opcodes.insert(0x66, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("ror"), cycles: 5, size: 2, opcode: 0x66, affected_on_page: false }); + // opcodes.insert(0x76, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("ror"), cycles: 6, size: 2, opcode: 0x76, affected_on_page: false }); + // opcodes.insert(0x6E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("ror"), cycles: 6, size: 3, opcode: 0x6E, affected_on_page: false }); + // opcodes.insert(0x7E, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("ror"), cycles: 7, size: 3, opcode: 0x7E, affected_on_page: false }); + + // // rti + // opcodes.insert(0x40, Entry { mode: AddressingMode::Implied, mnemonic: String::from("rti"), cycles: 6, size: 1, opcode: 0x40, affected_on_page: false }); + + // // rts + // opcodes.insert(0x60, Entry { mode: AddressingMode::Implied, mnemonic: String::from("rts"), cycles: 6, size: 1, opcode: 0x60, affected_on_page: false }); + + // // sbc + // opcodes.insert(0xE9, Entry { mode: AddressingMode::Immediate, mnemonic: String::from("sbc"), cycles: 2, size: 2, opcode: 0xE9, affected_on_page: false }); + // opcodes.insert(0xE5, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("sbc"), cycles: 3, size: 2, opcode: 0xE5, affected_on_page: false }); + // opcodes.insert(0xF5, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("sbc"), cycles: 4, size: 2, opcode: 0xF5, affected_on_page: false }); + // opcodes.insert(0xED, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("sbc"), cycles: 4, size: 3, opcode: 0xED, affected_on_page: false }); + // opcodes.insert(0xFD, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("sbc"), cycles: 4, size: 3, opcode: 0xFD, affected_on_page: true }); + // opcodes.insert(0xF9, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("sbc"), cycles: 4, size: 3, opcode: 0xF9, affected_on_page: true }); + // opcodes.insert(0xE1, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("sbc"), cycles: 6, size: 2, opcode: 0xE1, affected_on_page: false }); + // opcodes.insert(0xF1, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("sbc"), cycles: 5, size: 2, opcode: 0xF1, affected_on_page: true }); + + // // sec + // opcodes.insert(0x38, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("sec"), cycles: 2, size: 1, opcode: 0x38, affected_on_page: false }); + + // // sed + // opcodes.insert(0xF8, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("sed"), cycles: 2, size: 1, opcode: 0xF8, affected_on_page: false }); + + // // sei + // opcodes.insert(0x78, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("sei"), cycles: 2, size: 1, opcode: 0x78, affected_on_page: false }); + + // // sta + // opcodes.insert(0x85, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("sta"), cycles: 3, size: 2, opcode: 0x85, affected_on_page: false }); + // opcodes.insert(0x95, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("sta"), cycles: 4, size: 2, opcode: 0x95, affected_on_page: false }); + // opcodes.insert(0x8D, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("sta"), cycles: 4, size: 3, opcode: 0x8D, affected_on_page: false }); + // opcodes.insert(0x9D, Entry { mode: AddressingMode::IndexedX, mnemonic: String::from("sta"), cycles: 5, size: 3, opcode: 0x9D, affected_on_page: false }); + // opcodes.insert(0x99, Entry { mode: AddressingMode::IndexedY, mnemonic: String::from("sta"), cycles: 5, size: 3, opcode: 0x99, affected_on_page: false }); + // opcodes.insert(0x81, Entry { mode: AddressingMode::IndirectX, mnemonic: String::from("sta"), cycles: 6, size: 2, opcode: 0x81, affected_on_page: false }); + // opcodes.insert(0x91, Entry { mode: AddressingMode::IndirectY, mnemonic: String::from("sta"), cycles: 6, size: 2, opcode: 0x91, affected_on_page: false }); + + // // stx + // opcodes.insert(0x86, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("stx"), cycles: 3, size: 2, opcode: 0x86, affected_on_page: false }); + // opcodes.insert(0x96, Entry { mode: AddressingMode::ZeropageIndexedY, mnemonic: String::from("stx"), cycles: 4, size: 2, opcode: 0x96, affected_on_page: false }); + // opcodes.insert(0x8E, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("stx"), cycles: 4, size: 3, opcode: 0x8E, affected_on_page: false }); + + // // sty + // opcodes.insert(0x84, Entry { mode: AddressingMode::RelativeOrZeropage, mnemonic: String::from("sty"), cycles: 3, size: 2, opcode: 0x84, affected_on_page: false }); + // opcodes.insert(0x94, Entry { mode: AddressingMode::ZeropageIndexedX, mnemonic: String::from("sty"), cycles: 4, size: 2, opcode: 0x94, affected_on_page: false }); + // opcodes.insert(0x8C, Entry { mode: AddressingMode::Absolute, mnemonic: String::from("sty"), cycles: 4, size: 3, opcode: 0x8C, affected_on_page: false }); + + // // tax + // opcodes.insert(0xAA, Entry{ mode: AddressingMode::Implied, mnemonic: String::from("tax"), cycles: 2, size: 1, opcode: 0xAA, affected_on_page: false }); + + // // tay + // opcodes.insert(0xA8, Entry { mode: AddressingMode::Implied, mnemonic: String::from("tay"), cycles: 2, size: 1, opcode: 0xA8, affected_on_page: false }); + + // // tsx + // opcodes.insert(0xBA, Entry { mode: AddressingMode::Implied, mnemonic: String::from("tsx"), cycles: 2, size: 1, opcode: 0xBA, affected_on_page: false }); + + // // txa + // opcodes.insert(0x8A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("txa"), cycles: 2, size: 1, opcode: 0x8A, affected_on_page: false }); + + // // txs + // opcodes.insert(0x9A, Entry { mode: AddressingMode::Implied, mnemonic: String::from("txs"), cycles: 2, size: 1, opcode: 0x9A, affected_on_page: false }); + + // // tya + // opcodes.insert(0x98, Entry { mode: AddressingMode::Implied, mnemonic: String::from("tya"), cycles: 2, size: 1, opcode: 0x98, affected_on_page: false }); + + // opcodes + // }; pub static ref CONTROL_FUNCTIONS: HashMap<String, Control> = { let mut functions = HashMap::new(); - functions.insert(String::from(".hibyte"), Control { has_identifier: false, required_args: Some(1) }); - functions.insert(String::from(".lobyte"), Control { has_identifier: false, required_args: Some(1) }); - functions.insert(String::from(".macro"), Control { has_identifier: true, required_args: None }); - functions.insert(String::from(".proc"), Control { has_identifier: true, required_args: Some(0) }); - functions.insert(String::from(".scope"), Control { has_identifier: true, required_args: Some(0) }); - functions.insert(String::from(".end"), Control { has_identifier: false, required_args: Some(0) }); - functions.insert(String::from(".endscope"), Control { has_identifier: false, required_args: Some(0) }); - functions.insert(String::from(".endproc"), Control { has_identifier: false, required_args: Some(0) }); - functions.insert(String::from(".endmacro"), Control { has_identifier: false, required_args: Some(0) }); - functions.insert(String::from(".segment"), Control { has_identifier: false, required_args: Some(1) }); - functions.insert(String::from(".byte"), Control { has_identifier: false, required_args: None }); - functions.insert(String::from(".db"), Control { has_identifier: false, required_args: None }); - functions.insert(String::from(".word"), Control { has_identifier: false, required_args: None }); - functions.insert(String::from(".dw"), Control { has_identifier: false, required_args: None }); + functions.insert(String::from(".hibyte"), Control { has_identifier: false, required_args: Some(1), touches_context: false }); + functions.insert(String::from(".lobyte"), Control { has_identifier: false, required_args: Some(1), touches_context: false }); + functions.insert(String::from(".macro"), Control { has_identifier: true, required_args: None, touches_context: true }); + functions.insert(String::from(".proc"), Control { has_identifier: true, required_args: Some(0), touches_context: true }); + functions.insert(String::from(".scope"), Control { has_identifier: true, required_args: Some(0), touches_context: true }); + functions.insert(String::from(".endscope"), Control { has_identifier: false, required_args: Some(0), touches_context: true }); + functions.insert(String::from(".endproc"), Control { has_identifier: false, required_args: Some(0), touches_context: true }); + functions.insert(String::from(".endmacro"), Control { has_identifier: false, required_args: Some(0), touches_context: true }); + functions.insert(String::from(".segment"), Control { has_identifier: false, required_args: Some(1), touches_context: true }); + functions.insert(String::from(".byte"), Control { has_identifier: false, required_args: None, touches_context: false }); + functions.insert(String::from(".db"), Control { has_identifier: false, required_args: None, touches_context: false }); + functions.insert(String::from(".word"), Control { has_identifier: false, required_args: None, touches_context: false }); + functions.insert(String::from(".dw"), Control { has_identifier: false, required_args: None, touches_context: false }); functions }; diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs index d532841..7e93aca 100644 --- a/lib/xixanta/src/parser.rs +++ b/lib/xixanta/src/parser.rs @@ -1,92 +1,61 @@ use crate::errors::ParseError; -use crate::instruction::PString; +use crate::node::{NodeType, PNode, PString}; use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS}; +use std::cmp::Ordering; use std::io::{self, BufRead, Read}; use std::ops::Range; -type Result<T> = std::result::Result<T, ParseError>; - -#[derive(Debug, Clone, PartialEq)] -pub enum NodeType { - Value, - Instruction, - Indirection, - Assignment, - Control, - Literal, - Identifier, - Label, - Call, - Empty, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct PNode { - pub node_type: NodeType, - pub value: PString, - pub left: Option<Box<PNode>>, - pub right: Option<Box<PNode>>, - pub args: Option<Vec<Box<PNode>>>, -} - -impl PNode { - pub fn empty() -> PNode { - Self { - node_type: NodeType::Empty, - value: PString::new(), - left: None, - right: None, - args: None, - } - } -} - -#[derive(Debug, Clone, PartialEq)] +/// The Parser struct holds basic data for the current parsing session. +#[derive(Default)] pub struct Parser { + // The current line number. line: usize, + + // The current column number. column: usize, + + // The offset of the string being evaluated as a "line". Note that this can + // vary wildly because on recursive expression parsing the line might be + // slightly different. This property allows us to have a proper value for + // each iteration. offset: usize, - pub nodes: Vec<Box<PNode>>, - pub errors: Vec<ParseError>, + + /// The nodes that have been evaluated for the current parsing session. You + /// can count on this vector to be filled after calling + /// `parser::Parser::parse`. + pub nodes: Vec<PNode>, } impl Parser { - pub fn new() -> Self { - Self { - line: 0, - column: 0, - offset: 0, - nodes: Vec::new(), - errors: Vec::new(), - } - } + /// Parse the input from the given `reader`. You can then access the results + /// from the `nodes` field. Otherwise, a vector of ParseError's might be + /// returned. + pub fn parse(&mut self, reader: impl Read) -> Result<(), Vec<ParseError>> { + let mut errors = Vec::new(); - pub fn reset(&mut self) { - self.line = 0; - self.column = 0; - self.offset = 0; - self.nodes = Vec::new(); - self.errors = Vec::new(); - } - - pub fn parse(&mut self, reader: impl Read) -> Result<()> { for line in io::BufReader::new(reader).lines() { - if let Err(err) = self.parse_line(line?.as_str()) { - self.errors.push(err); + match line { + Ok(l) => { + if let Err(err) = self.parse_line(l.as_str()) { + errors.push(err); + } + } + Err(_) => errors.push(self.parser_error("could not get line")), } self.line += 1; } - println!("NODES: {:#?}", self.nodes); - - match self.errors.last() { - Some(err) => Err(err.clone()), - None => Ok(()), + if errors.is_empty() { + Ok(()) + } else { + Err(errors) } } - fn parse_line(&mut self, line: &str) -> Result<()> { + // Parse a single `line` and push the parsed nodes into `self.nodes`. + fn parse_line(&mut self, line: &str) -> Result<(), ParseError> { self.column = 0; + self.offset = 0; // Skip until the first non-whitespace character. If that's not // possible, then it's an empty line and we can return early. @@ -97,7 +66,7 @@ impl Parser { // Let's pin point the last character we need to care for parsing. This // can be either the start position of an inline comment (i.e. ';'), or // the real line end. - let end = if let Some(comment) = line.find(|c: char| c == ';') { + let end = if let Some(comment) = line.find(';') { comment } else { line.len() @@ -119,13 +88,13 @@ impl Parser { self.offset = 0; let (mut id, mut nt) = self.parse_identifier(l)?; if nt == NodeType::Label { - self.nodes.push(Box::new(PNode { + self.nodes.push(PNode { node_type: nt, value: id, left: None, right: None, args: None, - })); + }); self.skip_whitespace(l); @@ -149,15 +118,39 @@ impl Parser { self.parse_statement(l, id) } - fn parse_identifier(&mut self, line: &str) -> Result<(PString, NodeType)> { + // Given a `line` parses an identifier if possible. This identifier is not + // necessary an "identifier" per se, but rather a first identifier-like + // string which can be used to determine which kind of expression we are + // dealing with. Returns a PString representing this identifier on success, + // plus a hint on whether the identifier belongs to a label or not. + fn parse_identifier(&mut self, line: &str) -> Result<(PString, NodeType), ParseError> { let start = self.column; let base_offset = self.offset; // For the general case we just need to iterate until a whitespace - // character or an inline comment is found. Then our PString object - // is merely whatever is on the column..self.column range. - for c in line.get(self.offset..).unwrap_or("").chars() { + // character or an inline comment is found. Then our PString object is + // merely whatever is on the column..self.column range. Note that we + // need this iteration to be peekable so we can look ahead. This is + // interesting for detecting identifiers which are scoped (e.g. + // "Scope::Identifier"). + let mut chars = line + .get(self.offset..) + .unwrap_or_default() + .chars() + .peekable(); + while let Some(c) = chars.next() { if c.is_whitespace() || c == ':' || c == '(' || c == ')' || c == '=' { + // Look ahead to determine whether this is a regular ':' from a + // label, of a scoping operator '::'. + if let Some(next) = chars.peek() { + if c == ':' && *next == ':' { + chars.next(); + self.next(); + self.next(); + continue; + } + } + let val = String::from(line.get(base_offset..self.offset).unwrap_or("").trim()); let nt = if c == ':' { NodeType::Label @@ -165,8 +158,6 @@ impl Parser { NodeType::Value }; - // TODO - // self.next(); let end = if c == ':' { self.next(); self.column - 1 @@ -178,18 +169,10 @@ impl Parser { PString { value: val, line: self.line, - range: Range { - start, - end, // TODO - // end: self.column - 1, - }, + range: Range { start, end }, }, nt, )); - } else if !c.is_alphanumeric() && c != '_' { - // TODO: on the contrary, if alphanumeric or _, just follow - // through. Otherwise always break. TODO NOT REALLY - // return Err(self.parser_error("bad character for possible identifier")); } self.next(); @@ -197,7 +180,7 @@ impl Parser { // The line is merely the identifier (e.g. instruction with implied // addressing). - let id = String::from(line.get(base_offset..).unwrap_or("").trim()); + let id = String::from(line.get(base_offset..).unwrap_or_default().trim()); Ok(( PString { value: id, @@ -211,7 +194,9 @@ impl Parser { )) } - fn parse_statement(&mut self, line: &str, id: PString) -> Result<()> { + // Parse the top-level statement as found on the given `line` which has a + // leading `id` positioned-string which may be an identifier. + fn parse_statement(&mut self, line: &str, id: PString) -> Result<(), ParseError> { // There are only two top-level statements: instructions and // assignments. Other kinds of expressions can also be used in the // middle of assignments or instructions, and so they have to be handled @@ -231,102 +216,148 @@ impl Parser { } } - fn parse_instruction(&mut self, line: &str, id: PString) -> Result<()> { + // Parse the given `line` as an instruction being identified by `id`. + fn parse_instruction(&mut self, line: &str, id: PString) -> Result<(), ParseError> { let mut paren = 0; + // After the initial instruction identifier (e.g. `lda`), there might be + // an undefined white space. Let's skip it now. self.skip_whitespace(line); - if line.contains("=") { + // After skipping the identifier, are we actually on a weird assignment + // scenario? (e.g. `lda = #42`). If so, then complain on the programmer + // using a reserved instruction mnemonic as a variable name. + if line.chars().nth(self.offset).unwrap_or_default() == '=' { return Err(self.parser_error( - format!("cannot used reserved name for the mnemonic '{}'", id.value).as_str(), + format!( + "cannot use the reserved mnemonic '{}' as a variable name", + id.value + ) + .as_str(), )); } + // If the line at the current point starts with an open paren, then we + // assume that the indirect addressing mode is being used. If this is + // the case, then the left arm is actually what's inside of the + // parenthesis. Otherwise we have to grab until the very end. let indirect = line.chars().nth(self.offset).unwrap_or(',') == '('; let l = if indirect { + // Skip the '(' character and skip whitespaces. self.next(); self.skip_whitespace(line); + + // Now let's find the matching paren for the one that opened the + // indirect addressing mode, and that's the end of our left arm for + // this instruction. paren = self.find_matching_paren(line, self.offset)?; line.get(self.offset..paren).unwrap_or_default() } else { line.get(self.offset..).unwrap_or_default() }; + // Is there a left arm at all? If so, then parse it now but considering + // the trimmed `l` variable, which is a bit special when using indirect + // addressing mode. self.offset = 0; let mut left = if l.is_empty() { None } else { - Some(self.parse_left_arm(l)?) + Some(Box::new(self.parse_left_arm(l)?)) }; + // Skip whitespace until the possible right arm. Notice that both + // `paren` on indirect addressing mode, and `parse_left_arm` have set + // the "cursor" just after any possible comma. Hence, if there's + // anything left, then it's the right arm which might have leading + // spaces. self.skip_whitespace(l); - // The parsing of the left arm should have advanced the offset right - // into the right arm. If there is nothing there, then we have no right - // arm. Otherwise we have to parse the expression. - // TODO + // At this point, if there is nothing there, then we have no right arm. + // Otherwise we have to parse the expression. let mut right_str = l.get(self.offset..).unwrap_or_default(); let mut right = if right_str.is_empty() { None } else { self.offset = 0; - Some(self.parse_expression(right_str)?) + Some(Box::new(self.parse_expression(right_str)?)) }; + // If we were in indirect addressing mode, then there's some juggling we + // have to do for the parsed expressions. This is the most complex part + // from this function, as it will mutate the `left` and the `right` + // nodes in subtle ways. But this is better than having things in a + // different function because the rest of the code is pretty much the + // same. if indirect { + // In indirect addressing mode there's *always* a left arm. if left.is_none() { return Err(self.parser_error("empty indirect addressing")); } + // Now, here's the trick: if there's something left after the + // parenthesis, then we have the right arm there. Note that the + // Indirection node cannot have a right arm at the same time. If + // this is the case, we are going to freak out now. + right_str = line.get(paren..).unwrap_or_default(); + if !(right_str.is_empty() || right_str == ")") && right.is_some() { + return Err(self.parser_error("bad indirect addressing")); + } + + // The left arm from an indirect addressing mode is actually the + // indirection itself. left = Some(Box::new(PNode { node_type: NodeType::Indirection, - value: PString::new(), + value: PString::default(), left, - right: right.clone(), + right, args: None, })); - right_str = line.get(paren..).unwrap_or_default(); - if !(right_str.is_empty() || right_str == ")") && right.is_some() { - return Err(self.parser_error("bad indirect addressing")); - } - + // Do we have anything as a right arm? right = if right_str.is_empty() || right_str == ")" { None } else { self.offset = 0; - // TODO: ") ," + // Skip any possible leading space on ") ,". self.next(); self.skip_whitespace(right_str); - // TODO: ", " + // Skip any possible leading space after the comma. self.next(); self.skip_whitespace(right_str); - Some(self.parse_expression(right_str)?) + // And finally parse the right arm for the global instruction. + Some(Box::new(self.parse_expression(right_str)?)) }; } - self.nodes.push(Box::new(PNode { + // We can push the resulting parsed expressions. + self.nodes.push(PNode { node_type: NodeType::Instruction, value: id, left, right, args: None, - })); + }); Ok(()) } - fn parse_assignment(&mut self, line: &str, id: PString) -> Result<()> { - if let Err(msg) = id.is_valid_identifier() { + // Parse the given `line` as an assignment statement which declares a + // variable at `id`. + fn parse_assignment(&mut self, line: &str, id: PString) -> Result<(), ParseError> { + // Notice that `parse_identifier` pretty much swallows any kind of + // identifier without doing any sanity checks. Now it's the time to do + // so. + if let Err(msg) = id.is_valid_identifier(false) { return Err(self.parser_error(&msg)); } // Skip whitespaces and make sure that we have a '=' sign. self.skip_whitespace(line); - if line.chars().nth(self.offset).unwrap_or(' ') != '=' { + if line.chars().nth(self.offset).unwrap_or_default() != '=' { return Err(self.parser_error(format!("unknown instruction '{}'", id.value).as_str())); } @@ -340,20 +371,24 @@ impl Parser { return Err(self.parser_error("incomplete assignment")); }; self.offset = 0; - let left = Some(self.parse_expression(rest)?); + let left = self.parse_expression(rest)?; - self.nodes.push(Box::new(PNode { + // And push the node. + self.nodes.push(PNode { node_type: NodeType::Assignment, - value: id.clone(), - left, + value: id, + left: Some(Box::new(left)), right: None, args: None, - })); + }); Ok(()) } - fn parse_arguments(&mut self, line: &str) -> Result<Vec<Box<PNode>>> { + // Parse any possible arguments for the given `line`. The offset is supposed + // to be at a point where arguments might appear, either between parens or + // not. + fn parse_arguments(&mut self, line: &str) -> Result<Vec<PNode>, ParseError> { // Skip any possible whitespace before the optional opening paren. self.skip_whitespace(line); @@ -362,63 +397,74 @@ impl Parser { // the end of the cleaned line. let paren = line.chars().nth(self.offset).unwrap_or_default() == '('; let end = if paren { + // We have a parenthesis. Skip it. self.next(); self.skip_whitespace(line); + + // The end is actually the matching paren for the current opening + // one. self.find_matching_paren(line, self.offset)? } else { line.len() }; let mut args = Vec::new(); + let trimmed_str = line.get(..end).unwrap_or_default().trim_end(); + // Having an infinite loop with `break`s inside is admittedly not the + // cleanest thing ever, but it does its job. loop { - // TODO: trimmed_str out? - let trimmed_str = line.get(..end).unwrap_or_default().trim_end(); - println!("TRIMME: {:#?}", trimmed_str.get(self.offset..)); - - let (arg_end, comma) = self.find_left_end(trimmed_str, false)?; + // This looks scarier than it actually is. It first finds the end of + // the argument. Then it calculates a diff on trimming the end or + // not. This diff will be used to re-adjust the column after the + // argument is parsed, so we skip any final spaces. + let (arg_end, comma) = self.find_left_end(trimmed_str)?; let arg_untrimmed = line.get(self.offset..arg_end).unwrap_or_default(); let arg = arg_untrimmed.trim_end(); let diff = arg_untrimmed.len() - arg.len(); - // .trim_end(); - println!( - "ARG_END: {:#?} -- ARG: {:#?} - DIFF: {}", - arg_end, arg, diff - ); + + // Do we actually have an argument. If not then this is the end of + // our loop. if arg.is_empty() { break; } - // if !comma { - // s = arg.to_owned() + " "; - // arg = s.as_str(); - // } + // Parse the argument, which is trimmed down from the line and hence + // the offset needs to be reset. self.offset = 0; args.push(self.parse_expression(arg)?); + // After the parsing is done for the current argument, move both + // `self.offset` and `self.column` right after the end of the + // current argument. self.offset = arg_end; self.column += diff; - println!("{:#?}", line.get(self.offset..end)); - self.skip_whitespace(line); // TODO + + // Was the argument ended by a comma? If so there are more arguments + // to be parsed. Otherwise we can break the loop if it wasn't + // catched for whatever reason by the previous check. if comma { + // Skip the comma character and any leading white spaces for the + // next argument. self.next(); self.skip_whitespace(line); + } else { + break; } } - println!("ARGS: {:#?}", args); - Ok(args) } - fn parse_left_arm(&mut self, line: &str) -> Result<Box<PNode>> { + // Parse the left arm from an instruction and leave `offset` and `column` + // past the end of it. + fn parse_left_arm(&mut self, line: &str) -> Result<PNode, ParseError> { let start_column = self.column; // We track the start value of the offset and we will keep track of the // movement of it on `end`. This allows us to preserve the value on // inner calls that might modify the offset value. - // TODO - let (end, comma) = self.find_left_end(line, false)?; + let (end, comma) = self.find_left_end(line)?; // Set the offset to 0 since we are constraining the string to be // parsed. @@ -429,20 +475,26 @@ impl Parser { // computed end. let expr = self.parse_expression(str); - // Set the offset to the end of the line that is shared with the caller. - // let diff_column = (end - start) - (self.column - start_column); + // Set the `offset` and `column` to the end of the line that is shared + // with the caller. self.offset = end; self.column = start_column + end; + + // If there was a comma, then the caller expects this function to move + // both `offset` and `column` past it. if comma { self.next(); } expr } - // TODO: revisit inside_paren - fn find_left_end(&self, line: &str, inside_paren: bool) -> Result<(usize, bool)> { + // Find the end position for a "left arm"-like expression. That is, there + // might be opening/closing parenthesis which need to be balanced. On + // success it returns the index from within the given `line`, and a boolean + // which is set to true/false on whether a comma was found. + fn find_left_end(&self, line: &str) -> Result<(usize, bool), ParseError> { let mut idx = self.offset; - let mut parens = if inside_paren { 1 } else { 0 }; + let mut parens = 0; let mut comma = false; for c in line.get(self.offset..).unwrap_or_default().chars() { @@ -470,7 +522,10 @@ impl Parser { Ok((idx, comma)) } - fn find_matching_paren(&self, line: &str, init: usize) -> Result<usize> { + // Finds the matching parenthesis which closes the parenthesis that was just + // opened. The `init` index point to the next character after the opening + // paren from the given `line`. + fn find_matching_paren(&self, line: &str, init: usize) -> Result<usize, ParseError> { let mut idx = init; let mut parens = 1; @@ -481,10 +536,10 @@ impl Parser { parens -= 1; } - if parens == 0 { - return Ok(idx); - } else if parens < 0 { - return Err(self.parser_error("too many closing parenthesis")); + match parens.cmp(&0) { + Ordering::Equal => return Ok(idx), + Ordering::Less => return Err(self.parser_error("too many closing parenthesis")), + Ordering::Greater => {} } idx += 1; @@ -501,7 +556,7 @@ impl Parser { // `line` (e.g. the line might not be a full line but rather a limited range // and the offset has been set accordingly). Returns a new node for the // expression at hand. - fn parse_expression(&mut self, line: &str) -> Result<Box<PNode>> { + fn parse_expression(&mut self, line: &str) -> Result<PNode, ParseError> { let (id, nt) = self.parse_identifier(line)?; if nt == NodeType::Label { @@ -515,11 +570,15 @@ impl Parser { // part of it has already been parsed and evaluated as the given `id`. // Indeces such as `self.column` and `self.offset` are assumed to be correct // at this point. Returns a new node for the expression at hand. - fn parse_expression_with_identifier(&mut self, id: PString, line: &str) -> Result<Box<PNode>> { + fn parse_expression_with_identifier( + &mut self, + id: PString, + line: &str, + ) -> Result<PNode, ParseError> { // Reaching this condition is usually a bad sign, but there is so many // ways in which it could go wrong, that an `assert!` wouldn't be fair // either. Hence, just error out. - if !id.is_valid() { + if id.is_empty() { return Err(self.parser_error("invalid identifier")); } @@ -538,31 +597,31 @@ impl Parser { .is_empty() { let args = self.parse_arguments(line)?; - return Ok(Box::new(PNode { + return Ok(PNode { node_type: NodeType::Call, value: id, left: None, right: None, args: if args.is_empty() { None } else { Some(args) }, - })); + }); } // Blindly return the identifier as a PNode. This might be either a // value as-is, or a macro call which we can't make sense at the // moment. Eitherway, let the assembler decide. - Ok(Box::new(PNode { + Ok(PNode { node_type: NodeType::Value, value: id, left: None, right: None, args: None, - })) + }) } } // Returns a NodeType::Control node with whatever could be parsed // considering the given `id` and rest of the `line`. - fn parse_control(&mut self, id: PString, line: &str) -> Result<Box<PNode>> { + fn parse_control(&mut self, id: PString, line: &str) -> Result<PNode, ParseError> { let mut left = None; let required; @@ -601,18 +660,18 @@ impl Parser { } } - Ok(Box::new(PNode { + Ok(PNode { node_type: NodeType::Control, value: id, left, right: None, args: if args.is_empty() { None } else { Some(args) }, - })) + }) } // Returns a NodeType::Literal node with whatever could be parsed // considering the given `id` and rest of the `line`. - fn parse_literal(&mut self, id: PString, line: &str) -> Result<Box<PNode>> { + fn parse_literal(&mut self, id: PString, line: &str) -> Result<PNode, ParseError> { // Force the column to point to the literal character just in case // of expressions like '#.hibyte'. Then skip whitespaces for super // ugly statements such as '# 20'. This is ugly but we should permit @@ -628,13 +687,13 @@ impl Parser { self.offset = 0; let left = self.parse_expression(inner)?; - Ok(Box::new(PNode { + Ok(PNode { node_type: NodeType::Literal, value: id, - left: Some(left), + left: Some(Box::new(left)), right: None, args: None, - })) + }) } // Returns a new ParseError by using the current line. @@ -642,7 +701,6 @@ impl Parser { ParseError { message: String::from(msg), line: self.line, - parse: true, } } @@ -684,7 +742,7 @@ mod tests { assert!(parser.nodes.len() == 1); } - fn assert_node(node: &Box<PNode>, nt: NodeType, line: &str, value: &str) { + fn assert_node(node: &PNode, nt: NodeType, line: &str, value: &str) { assert_eq!(node.node_type, nt); assert_eq!( node.value.value.as_str(), @@ -697,23 +755,23 @@ mod tests { #[test] fn empty_line() { - let mut parser = Parser::new(); - assert!(!parser.parse("".as_bytes()).is_err()); + let mut parser = Parser::default(); + assert!(parser.parse("".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 0); } #[test] fn spaced_line() { - let mut parser = Parser::new(); - assert!(!parser.parse(" ".as_bytes()).is_err()); + let mut parser = Parser::default(); + assert!(parser.parse(" ".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 0); } #[test] fn just_a_comment_line() { for line in vec![";; This is a comment", " ;; Comment"].into_iter() { - let mut parser = Parser::new(); - assert!(!parser.parse(line.as_bytes()).is_err()); + let mut parser = Parser::default(); + assert!(parser.parse(line.as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 0); } } @@ -722,14 +780,14 @@ mod tests { #[test] fn anonymous_label() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(":".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert!(parser.nodes.first().unwrap().value.value.is_empty()); assert_eq!(parser.nodes.first().unwrap().value.range.start, 0); assert_eq!(parser.nodes.first().unwrap().value.range.end, 0); - parser = Parser::new(); + parser = Parser::default(); assert!(parser.parse(" :".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert!(parser.nodes.first().unwrap().value.value.is_empty()); @@ -739,14 +797,14 @@ mod tests { #[test] fn named_label() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse("label:".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert_eq!(parser.nodes.first().unwrap().value.value, "label"); assert_eq!(parser.nodes.first().unwrap().value.range.start, 0); assert_eq!(parser.nodes.first().unwrap().value.range.end, 5); - parser = Parser::new(); + parser = Parser::default(); assert!(parser.parse(" label:".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert_eq!(parser.nodes.first().unwrap().value.value, "label"); @@ -758,7 +816,7 @@ mod tests { fn label_with_instruction() { let line = "label: dex"; - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 2); @@ -781,7 +839,7 @@ mod tests { #[test] fn parse_pound_literal() { for line in vec!["#20", " #20 ", " #20 ; Comment", " label: # 20"].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -799,7 +857,7 @@ mod tests { #[test] fn parse_compound_literal() { let line = "#$20"; - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -821,7 +879,7 @@ mod tests { #[test] fn parse_variable_in_literal() { let line = "#Variable"; - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -835,6 +893,16 @@ mod tests { assert_eq!(line.get(inner.value.range).unwrap(), "Variable"); } + #[test] + fn parse_bad_literals() { + for line in vec!["#", "#%", "$"].into_iter() { + let mut parser = Parser::default(); + let err = parser.parse(line.as_bytes()).unwrap_err(); + + assert_eq!(err.first().unwrap().message, "invalid identifier"); + } + } + // Regular instructions. #[test] @@ -848,7 +916,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -862,7 +930,7 @@ mod tests { #[test] fn instruction_with_implied_explicit() { for line in vec!["inc a", " inc a", " inc a "].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); @@ -877,7 +945,7 @@ mod tests { #[test] fn instruction_with_zeropage() { for line in vec!["inc $20", " inc $20", " inc $20 "].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); @@ -892,7 +960,7 @@ mod tests { #[test] fn instruction_with_immediate() { for line in vec!["adc #$20", " adc #$20 ", " adc #$20 "].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); @@ -907,7 +975,7 @@ mod tests { #[test] fn instruction_with_absolute() { for line in vec!["inc $2002", " inc $2002", " inc $2002 "].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); @@ -934,7 +1002,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -961,7 +1029,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -986,7 +1054,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1002,16 +1070,16 @@ mod tests { #[test] fn bad_indirect_addressing_x() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); - let err = parser.parse("lda (Variable, x), y".as_bytes()); - assert_eq!(err.unwrap_err().message, "bad indirect addressing"); + let err = parser.parse("lda (Variable, x), y".as_bytes()).unwrap_err(); + assert_eq!(err.first().unwrap().message, "bad indirect addressing"); } #[test] fn indirect_addressing_y() { for line in vec!["lda ($20), y"].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1030,7 +1098,7 @@ mod tests { #[test] fn variable_in_instruction() { let line = "lda Variable, x"; - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1049,7 +1117,7 @@ mod tests { #[test] fn variable_literal_in_instruction() { let line = "lda #Variable, x"; - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1065,41 +1133,90 @@ mod tests { assert_node(&node.right.clone().unwrap(), NodeType::Value, line, "x"); } + #[test] + fn scoped_variable_literal_in_instruction() { + for var in vec!["Scope::Variable", "Scope::Inner::Variable"].into_iter() { + let line = format!("lda #{}", var); + let mut parser = Parser::default(); + assert!(parser.parse(line.as_bytes()).is_ok()); + + let node = parser.nodes.last().unwrap(); + assert_node(node, NodeType::Instruction, line.as_str(), "lda"); + assert!(node.right.is_none()); + assert!(node.args.is_none()); + + assert_node( + &node.left.clone().unwrap(), + NodeType::Literal, + line.as_str(), + format!("#{}", var).as_str(), + ); + } + } + + #[test] + fn bad_variable_scoping() { + let mut parser = Parser::default(); + + let err = parser.parse("adc #One:Variable".as_bytes()).unwrap_err(); + assert_eq!( + err.first().unwrap().message, + "not expecting a label defined here" + ); + } + + #[test] + fn reserved_mnemonic_name() { + let mut parser = Parser::default(); + + let err = parser.parse("lda = $10".as_bytes()).unwrap_err(); + assert_eq!( + err.first().unwrap().message, + "cannot use the reserved mnemonic 'lda' as a variable name" + ); + } + // Assignments #[test] fn bad_assignments() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); - let mut err = parser.parse("abc = $10".as_bytes()); + let mut err = parser.parse("abc = $10".as_bytes()).unwrap_err(); assert_eq!( - err.unwrap_err().message, + err.first().unwrap().message, "cannot use names which are valid hexadecimal values such as 'abc'" ); - parser = Parser::new(); - err = parser.parse("var =".as_bytes()); - assert_eq!(err.unwrap_err().message, "incomplete assignment"); + parser = Parser::default(); + err = parser.parse("var =".as_bytes()).unwrap_err(); + assert_eq!(err.first().unwrap().message, "incomplete assignment"); - parser = Parser::new(); - err = parser.parse("var = ".as_bytes()); - assert_eq!(err.unwrap_err().message, "incomplete assignment"); + parser = Parser::default(); + err = parser.parse("var = ".as_bytes()).unwrap_err(); + assert_eq!(err.first().unwrap().message, "incomplete assignment"); - parser = Parser::new(); - err = parser.parse("var = ; Comment".as_bytes()); - assert_eq!(err.unwrap_err().message, "incomplete assignment"); + parser = Parser::default(); + err = parser.parse("var = ; Comment".as_bytes()).unwrap_err(); + assert_eq!(err.first().unwrap().message, "incomplete assignment"); } // Control statements. #[test] fn parse_control_no_args() { - for line in vec![".end", " .end", " label: .end ; Comment"].into_iter() { - let mut parser = Parser::new(); + for line in vec![ + ".endmacro", + " .endmacro", + " label: .endmacro ; Comment", + ] + .into_iter() + { + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); - assert_node(node, NodeType::Control, line, ".end"); + assert_node(node, NodeType::Control, line, ".endmacro"); assert!(node.left.is_none()); assert!(node.right.is_none()); assert!(node.args.is_none()); @@ -1117,7 +1234,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1142,7 +1259,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1167,7 +1284,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1191,7 +1308,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1218,7 +1335,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1238,9 +1355,11 @@ mod tests { #[test] fn parse_control_bad_number_args() { for line in vec![".hibyte", ".hibyte($20, $22)"].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); + let err = parser.parse(line.as_bytes()).unwrap_err(); + assert_eq!( - parser.parse(line.as_bytes()).unwrap_err().message, + err.first().unwrap().message, "wrong number of arguments for function '.hibyte'" ); } @@ -1249,7 +1368,7 @@ mod tests { #[test] fn parse_control_in_instructions() { for line in vec!["lda #.hibyte($2010)", " label: lda #.hibyte $2010 "].into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1281,7 +1400,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1320,7 +1439,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1360,7 +1479,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1386,17 +1505,13 @@ mod tests { #[test] fn parse_unknown_control() { - let mut parser = Parser::new(); - assert_eq!( - parser.parse(".".as_bytes()).unwrap_err().message, - "unknown function '.'" - ); + let mut parser = Parser::default(); + let mut err = parser.parse(".".as_bytes()).unwrap_err(); + assert_eq!(err.first().unwrap().message, "unknown function '.'"); - parser = Parser::new(); - assert_eq!( - parser.parse(".whatever".as_bytes()).unwrap_err().message, - "unknown function '.whatever'" - ); + parser = Parser::default(); + err = parser.parse(".whatever".as_bytes()).unwrap_err(); + assert_eq!(err.first().unwrap().message, "unknown function '.whatever'"); } // Macro calls. @@ -1410,7 +1525,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1432,7 +1547,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1453,7 +1568,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); @@ -1477,7 +1592,7 @@ mod tests { ] .into_iter() { - let mut parser = Parser::new(); + let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); |
