aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2024-10-09 15:41:30 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-12 07:46:17 +0100
commit16114b2ca358dbbef09cb5a8d3a843a0e11a3b88 (patch)
tree49103ed4c0e906128df23c20656d88e4c4d1645e
parentabd9a7679e5da78a51ed5eb488ae081a2bcb2b6c (diff)
downloadtools.nes-16114b2ca358dbbef09cb5a8d3a843a0e11a3b88.tar.gz
tools.nes-16114b2ca358dbbef09cb5a8d3a843a0e11a3b88.zip
Adapt the assembler to the changes on the parser
As of 184c39579227 ("Re-work the parser from scratch") the parser has a proper AST, and the assembler has to traverse it accordingly. Moreover, the assembler has been stripped from a lot of unneeded memory allocations and it's overall more keen on using mere references when possible. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
-rw-r--r--.github/workflows/ci.yml18
-rw-r--r--Cargo.lock111
-rw-r--r--Cargo.toml2
-rw-r--r--crates/nasm/src/main.rs17
-rw-r--r--lib/xixanta/Cargo.toml2
-rw-r--r--lib/xixanta/fuzz/fuzz_targets/fuzz_target_1.rs2
-rw-r--r--lib/xixanta/src/assembler.rs2886
-rw-r--r--lib/xixanta/src/context.rs180
-rw-r--r--lib/xixanta/src/errors.rs98
-rw-r--r--lib/xixanta/src/instruction.rs451
-rw-r--r--lib/xixanta/src/lib.rs10
-rw-r--r--lib/xixanta/src/mapping.rs25
-rw-r--r--lib/xixanta/src/node.rs164
-rw-r--r--lib/xixanta/src/opcodes.rs618
-rw-r--r--lib/xixanta/src/parser.rs579
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
diff --git a/Cargo.lock b/Cargo.lock
index ee00bdd..72365c6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 183a9f1..1ea211b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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();