aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-09-02 16:40:38 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-09-02 16:40:38 +0200
commit8e7993a63e32981757c4d65fe6519439dde97d8e (patch)
tree082979ffe3d3b1880c02d3e56f2fd4a47b93094e /lib
parentfb2d20e715dd04d27c4873c4989202133783f161 (diff)
downloadtools.nes-8e7993a63e32.tar.gz
tools.nes-8e7993a63e32.zip
assembler: Add support for asan:reserve,ignore
This is the initial support for both directives for the address sanitizer. Note that asan:weak has been moved into asan:ignore, which is not exactly the same but for now it should suffice. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs3
-rw-r--r--lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs2
-rw-r--r--lib/xixanta/src/assembler.rs225
-rw-r--r--lib/xixanta/src/node.rs4
-rw-r--r--lib/xixanta/src/object.rs11
-rw-r--r--lib/xixanta/src/parser.rs242
6 files changed, 391 insertions, 96 deletions
diff --git a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs
index ff389a1..72077d9 100644
--- a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs
+++ b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs
@@ -4,5 +4,6 @@ use libfuzzer_sys::fuzz_target;
use xixanta::assembler::assemble;
fuzz_target!(|data: &[u8]| {
- let _ = assemble(data, "empty", &[], xixanta::SourceInfo::default());
+ let _ = assemble(data, "empty", &[], &xixanta::SourceInfo::default(), false);
+ let _ = assemble(data, "empty", &[], &xixanta::SourceInfo::default(), true);
});
diff --git a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs
index da8a350..a68b9e2 100644
--- a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs
+++ b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs
@@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let mut parser = xixanta::parser::Parser::default();
- let _ = parser.parse(data, xixanta::SourceInfo::default());
+ let _ = parser.parse(data, &xixanta::SourceInfo::default());
});
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 5ab0e3d..37f9004 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -1,5 +1,5 @@
use crate::mapping::{get_mapping_configuration, Mapping};
-use crate::node::{ControlType, EchoKind, NodeType, OperationType, PNode, PString};
+use crate::node::{CommentType, ControlType, EchoKind, NodeType, OperationType, PNode, PString};
use crate::object::{Bundle, Context, Object, ObjectType};
use crate::opcodes::{AddressingMode, INSTRUCTIONS};
use crate::parser::Parser;
@@ -10,6 +10,7 @@ use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, Read};
use std::ops::Neg;
+use std::ops::Range;
/// The mode in which a literal is expressed.
#[derive(Clone, PartialEq)]
@@ -55,6 +56,40 @@ struct PendingNode {
labels_seen: usize,
}
+/// Memory range that can be identified by a name.
+#[derive(Clone, Debug)]
+pub struct MemoryRange {
+ // The range in memory itself.
+ pub range: Range<usize>,
+
+ // Name that can access this memory range.
+ pub name: String,
+}
+
+impl MemoryRange {
+ /// Display the range in hexadecimal format and by taking into consideration
+ /// on whether it's really a range or a single value.
+ pub fn range_to_human(&self) -> String {
+ if self.range.start + 1 == self.range.end {
+ if self.range.start <= 0xFF {
+ return format!("${:02X}", self.range.start);
+ }
+ return format!("${:04X}", self.range.start);
+ }
+
+ if self.range.start <= 0xFF {
+ return format!("${:02X}-${:02X}", self.range.start, self.range.end - 1);
+ }
+ format!("${:04X}-${:04X}", self.range.start, self.range.end - 1)
+ }
+}
+
+impl std::fmt::Display for MemoryRange {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "'{}' ({})", self.name, self.range_to_human())
+ }
+}
+
struct Assembler<'a> {
context: Context,
literal_mode: Option<LiteralMode>,
@@ -87,6 +122,32 @@ struct Assembler<'a> {
// Whether the assembler has detected accesses to Working RAM or not
// (0x6000-0x7FFF).
accessing_working_ram: bool,
+
+ // Whether the Address Sanitizer is enabled or not.
+ asan_enabled: bool,
+
+ // Whether the next assignment should be ignored or not by the address
+ // sanitizer.
+ asan_next_ignore: bool,
+
+ // The amount of bytes to reserve for the next variable assignment.
+ asan_next_reserve: u8,
+}
+
+/// The result to be given at the end of `assembler::assemble` and
+/// `assembler::assemble_with_mapping` functions. It includes everything a
+/// caller might want to know about the resulting memory layout.
+#[derive(Debug, Default)]
+pub struct MemoryResult {
+ /// Memory ranges used by the source.
+ pub memory_ranges: Vec<MemoryRange>,
+
+ /// Total Internal RAM being used by the source in bytes.
+ pub total_internal_ram: usize,
+
+ /// Total Working RAM being used by the source in bytes. Ignore this field
+ /// if `accessing_working_ram` has been set to false in `Assembler`.
+ pub total_working_ram: usize,
}
/// All the information that a caller needs after calling either
@@ -112,6 +173,10 @@ pub struct AssemblerResult {
/// Whether the resulting ROM needs Working RAM to be available in order to
/// work. Ignore this field if `errors` is not empty.
pub accessing_working_ram: bool,
+
+ /// The resaulting memory analysis from the address sanitizer. Ignore this
+ /// if you did not assemble the source with the address sanitizer enabled.
+ pub memory: MemoryResult,
}
/// Read the contents from the `reader` as a source file and produce a list of
@@ -124,7 +189,8 @@ pub fn assemble(
reader: impl Read,
mapping: &str,
defines: &[(String, u8)],
- source: SourceInfo,
+ source: &SourceInfo,
+ asan: bool,
) -> AssemblerResult {
let config = match get_mapping_configuration(mapping) {
Ok(config) => config,
@@ -135,16 +201,17 @@ pub fn assemble(
global: true,
line: 0,
message: e,
- source,
+ source: source.clone(),
}],
warnings: vec![],
mappings: vec![],
accessing_working_ram: false,
+ memory: MemoryResult::default(),
};
}
};
- assemble_with_mapping(reader, config, defines, source)
+ assemble_with_mapping(reader, config, defines, source, asan)
}
/// Read the contents from the `reader` as a source file and produce a list of
@@ -157,9 +224,12 @@ pub fn assemble_with_mapping(
reader: impl Read,
mapping: Vec<Mapping>,
defines: &[(String, u8)],
- source: SourceInfo,
+ source: &SourceInfo,
+ asan: bool,
) -> AssemblerResult {
let mut asm = Assembler::new(mapping);
+ let mut memory = MemoryResult::default();
+ asm.asan_enabled = asan;
// First of all, parse the input so we get a list of nodes we can work
// with.
@@ -171,6 +241,7 @@ pub fn assemble_with_mapping(
warnings: asm.warnings,
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
+ memory,
};
}
@@ -187,6 +258,7 @@ pub fn assemble_with_mapping(
warnings: vec![],
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
+ memory,
};
}
}
@@ -200,6 +272,7 @@ pub fn assemble_with_mapping(
warnings: asm.warnings,
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
+ memory,
};
}
@@ -214,6 +287,7 @@ pub fn assemble_with_mapping(
warnings: asm.warnings,
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
+ memory,
};
}
@@ -226,9 +300,23 @@ pub fn assemble_with_mapping(
warnings: asm.warnings,
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
+ memory,
};
}
+ if asm.asan_enabled {
+ if let Err(errors) = asm.asan(&mut memory) {
+ return AssemblerResult {
+ bundles: vec![],
+ errors,
+ warnings: asm.warnings,
+ mappings: asm.mappings,
+ accessing_working_ram: asm.accessing_working_ram,
+ memory,
+ };
+ }
+ }
+
// All set, fill the vector of bundles to be returned.
match asm.fill() {
Ok(bundles) => AssemblerResult {
@@ -237,6 +325,7 @@ pub fn assemble_with_mapping(
warnings: asm.warnings,
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
+ memory,
},
Err(errors) => AssemblerResult {
bundles: vec![],
@@ -244,6 +333,7 @@ pub fn assemble_with_mapping(
warnings: asm.warnings,
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
+ memory,
},
}
}
@@ -266,6 +356,9 @@ impl<'a> Assembler<'a> {
warnings: vec![],
sources: vec![],
accessing_working_ram: false,
+ asan_enabled: false,
+ asan_next_ignore: false,
+ asan_next_reserve: 1,
}
}
@@ -292,6 +385,8 @@ impl<'a> Assembler<'a> {
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Value,
+ asan_ignore: false,
+ asan_reserve: 1,
};
if let Err(err) = self.context.set_variable(&var_name, &var_value, false) {
@@ -338,6 +433,12 @@ impl<'a> Assembler<'a> {
let mut errors = Vec::new();
for node in nodes {
+ // Remove asan:ignore if the current node is not to be an assignment.
+ if self.asan_next_ignore && !matches!(&node.node_type, NodeType::Assignment) {
+ self.asan_next_ignore = false;
+ self.asan_next_reserve = 1;
+ }
+
match &node.node_type {
// At this stage we only define the label into the current
// context so it's known. The actual value cannot be computed
@@ -362,6 +463,12 @@ impl<'a> Assembler<'a> {
errors.push(err);
}
}
+ NodeType::Comment(CommentType::AsanReserve(size)) => {
+ self.asan_next_reserve = *size;
+ }
+ NodeType::Comment(CommentType::AsanIgnore) => {
+ self.asan_next_ignore = true;
+ }
NodeType::Assignment => {
if self.macros_seen > 0 || self.repeats_seen > 0 {
errors.push(Error {
@@ -374,6 +481,7 @@ impl<'a> Assembler<'a> {
continue;
}
self.literal_mode = None;
+
match self.evaluate_node(node.left.as_ref().unwrap()) {
Ok(value) => {
if let Err(err) = self.context.set_variable(
@@ -384,6 +492,8 @@ impl<'a> Assembler<'a> {
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Value,
+ asan_ignore: self.asan_next_ignore,
+ asan_reserve: self.asan_next_reserve,
},
false,
) {
@@ -394,6 +504,8 @@ impl<'a> Assembler<'a> {
source: self.source_for(node),
});
}
+ self.asan_next_ignore = false;
+ self.asan_next_reserve = 1;
}
Err(e) => errors.push(e),
}
@@ -550,6 +662,8 @@ impl<'a> Assembler<'a> {
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Address,
+ asan_ignore: false,
+ asan_reserve: 1,
};
if !node.value.is_empty() {
@@ -718,6 +832,77 @@ impl<'a> Assembler<'a> {
}
}
+ fn asan(&mut self, memory: &mut MemoryResult) -> Result<(), Vec<Error>> {
+ let mut errors = vec![];
+
+ for (_context, map) in self.context.map.iter() {
+ for (name, bundle) in map {
+ // Ignore this bundle if the user explicitely told us to do so.
+ if bundle.asan_ignore {
+ continue;
+ }
+
+ // Evaluate if the given object conflicts with an existing
+ // range.
+ if name.starts_with("zp_") || name.starts_with("m_") || name.starts_with("wr_") {
+ let val = bundle.bundle.value() as usize;
+ let range = MemoryRange {
+ range: (val..val + bundle.asan_reserve as usize),
+ name: name.clone(),
+ };
+
+ for existing in &memory.memory_ranges {
+ if (range.range.start >= existing.range.start
+ && range.range.start < existing.range.end)
+ || (range.range.end > existing.range.start
+ && range.range.end < existing.range.end)
+ {
+ errors.push(Error {
+ line: 0,
+ global: true,
+ message: format!("The variable {range} conflicts with {existing}",),
+ source: self.sources[0].clone(),
+ });
+ }
+ }
+ memory.memory_ranges.push(range);
+ }
+
+ // Increase the counters for memory usage on either RAM slot and
+ // check for bounds.
+ if name.starts_with("zp_") || name.starts_with("m_") {
+ memory.total_internal_ram += bundle.asan_reserve as usize;
+
+ if memory.total_internal_ram > 0x800 {
+ errors.push(Error {
+ line: 0,
+ global: true,
+ message: "out of internal RAM".to_string(),
+ source: self.sources[0].clone(),
+ });
+ }
+ } else if name.starts_with("wr_") {
+ memory.total_working_ram += bundle.asan_reserve as usize;
+
+ if memory.total_working_ram > 0x2000 {
+ errors.push(Error {
+ line: 0,
+ global: true,
+ message: "out of working RAM".to_string(),
+ source: self.sources[0].clone(),
+ });
+ }
+ }
+ }
+ }
+
+ if errors.is_empty() {
+ Ok(())
+ } else {
+ Err(errors)
+ }
+ }
+
fn fill(&mut self) -> Result<Vec<Bundle>, Vec<Error>> {
let mut errors = vec![];
@@ -844,6 +1029,8 @@ impl<'a> Assembler<'a> {
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Value,
+ asan_ignore: false,
+ asan_reserve: 1,
};
// Note that we overwrite the variable value from previous
@@ -1641,7 +1828,9 @@ impl<'a> Assembler<'a> {
node: None,
mapping: self.current_mapping,
segment: self.current_segment,
+ asan_ignore: false,
object_type: ObjectType::Value,
+ asan_reserve: 1,
},
true,
) {
@@ -2385,7 +2574,13 @@ mod tests {
// the assembler will freak out.
let real_line = minimal_header().to_string() + line;
- assemble_with_mapping(real_line.as_bytes(), empty(), &[], SourceInfo::default())
+ assemble_with_mapping(
+ real_line.as_bytes(),
+ empty(),
+ &[],
+ &SourceInfo::default(),
+ false,
+ )
}
// Like `just_assemble` but it only returns bundles passed the header.
@@ -4202,7 +4397,8 @@ lda #Variable
.as_bytes(),
one_two().to_vec(),
&[],
- SourceInfo::default(),
+ &SourceInfo::default(),
+ false,
);
assert_eq!(res.bundles.len(), 0x11);
@@ -4244,7 +4440,8 @@ lda #Variable
.as_bytes(),
one_two().to_vec(),
&[],
- SourceInfo::default(),
+ &SourceInfo::default(),
+ false,
);
let bundles = &res.bundles[0x11..];
@@ -4318,7 +4515,8 @@ lda #Variable
.as_bytes(),
one_two().to_vec(),
&[],
- SourceInfo::default(),
+ &SourceInfo::default(),
+ false,
);
let bundles = &res.bundles[0x12..]; // Ignoring HEADER + first two ONE
@@ -4366,7 +4564,8 @@ lda #Variable
.as_bytes(),
one_two().to_vec(),
&[],
- SourceInfo::default(),
+ &SourceInfo::default(),
+ false,
);
let bundles = &res.bundles[0x11..]; // Ignoring HEADER + first nop
@@ -4421,7 +4620,8 @@ lda #Variable
.as_bytes(),
one_two().to_vec(),
&[],
- SourceInfo::default(),
+ &SourceInfo::default(),
+ false,
);
let bundles = &res.bundles[0x10..];
@@ -4448,7 +4648,8 @@ lda #Variable
.as_bytes(),
one_two().to_vec(),
&[],
- SourceInfo::default(),
+ &SourceInfo::default(),
+ false,
);
assert_eq!(
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index 4281c4a..7cfc107 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -252,7 +252,7 @@ pub enum OperationType {
#[derive(Debug, Clone, PartialEq)]
pub enum CommentType {
AsanReserve(u8),
- AsanWeak,
+ AsanIgnore,
}
/// The PNode type.
@@ -349,7 +349,7 @@ impl fmt::Display for NodeType {
},
NodeType::Comment(ct) => match ct {
CommentType::AsanReserve(_) => write!(f, ";; asan:reserve"),
- CommentType::AsanWeak => write!(f, ";; asan:weak"),
+ CommentType::AsanIgnore => write!(f, ";; asan:ignore"),
},
}
}
diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs
index 373d06c..920a4f8 100644
--- a/lib/xixanta/src/object.rs
+++ b/lib/xixanta/src/object.rs
@@ -101,7 +101,8 @@ impl Bundle {
}
/// The type of object being referenced, which is either a value as-is, or an
-/// address that needs to be interpreted when fetching it.
+/// address that needs to be interpreted when fetching it; or a reference to a
+/// value (i.e. something the address sanitizer can ignore).
#[derive(Debug, Clone)]
pub enum ObjectType {
Address,
@@ -132,6 +133,12 @@ pub struct Object {
/// The type for this object.
pub object_type: ObjectType,
+
+ /// Whether the address sanitizer should ignore this object or not.
+ pub asan_ignore: bool,
+
+ /// Amount of bytes reserved for this object on the address sanitizer.
+ pub asan_reserve: u8,
}
impl Object {
@@ -143,6 +150,8 @@ impl Object {
mapping,
segment,
object_type,
+ asan_ignore: false,
+ asan_reserve: 1,
}
}
}
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index c40e0d1..a0d19d0 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -80,15 +80,15 @@ impl Parser {
/// Parse the input from the given `reader`. You can then access the results
/// from the `nodes` field. Otherwise, a vector of Error's might be
/// returned.
- pub fn parse(&mut self, reader: impl Read, source: SourceInfo) -> Result<(), Vec<Error>> {
+ pub fn parse(&mut self, reader: impl Read, source: &SourceInfo) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
// Push the sources for the parsing session (note that this list might
// be initialized by the caller already). The `current_source` is simply
// the one we push upon initialization.
self.sources.push(SourceInfo {
- directory: path::absolute(&source.directory).unwrap_or(source.directory),
- name: source.name,
+ directory: path::absolute(&source.directory).unwrap_or(source.directory.clone()),
+ name: source.name.clone(),
});
self.current_source = self.sources.len() - 1;
@@ -227,9 +227,9 @@ impl Parser {
source: self.current_source,
});
}
- "asan:weak" => {
+ "asan:ignore" => {
self.nodes.last_mut().unwrap().push(PNode {
- node_type: NodeType::Comment(CommentType::AsanWeak),
+ node_type: NodeType::Comment(CommentType::AsanIgnore),
value: PString {
value: cmd,
line: self.line,
@@ -948,7 +948,7 @@ impl Parser {
};
parser.parse(
file,
- SourceInfo {
+ &SourceInfo {
directory: parent.to_path_buf(),
name: path.file_name().unwrap().to_str().unwrap().to_string(),
},
@@ -1735,7 +1735,9 @@ mod tests {
use crate::node::ControlType;
fn assert_one_valid(parser: &mut Parser, line: &str) {
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
assert!(parser.nodes.len() == 1);
}
@@ -1753,7 +1755,7 @@ mod tests {
#[test]
fn empty_line() {
let mut parser = Parser::default();
- assert!(parser.parse("".as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser.parse("".as_bytes(), &SourceInfo::default()).is_ok());
assert_eq!(parser.nodes.last().unwrap().len(), 0);
}
@@ -1761,7 +1763,7 @@ mod tests {
fn spaced_line() {
let mut parser = Parser::default();
assert!(parser
- .parse(" ".as_bytes(), SourceInfo::default())
+ .parse(" ".as_bytes(), &SourceInfo::default())
.is_ok());
assert_eq!(parser.nodes.last().unwrap().len(), 0);
}
@@ -1770,7 +1772,9 @@ mod tests {
fn just_a_comment_line() {
for line in vec![";; This is a comment", " ;; Comment"].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
assert_eq!(parser.nodes.last().unwrap().len(), 0);
}
}
@@ -1780,7 +1784,7 @@ mod tests {
#[test]
fn anonymous_label() {
let mut parser = Parser::default();
- assert!(parser.parse(":".as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser.parse(":".as_bytes(), &SourceInfo::default()).is_ok());
let mut nodes = parser.nodes.last().unwrap();
assert_eq!(nodes.len(), 1);
@@ -1790,7 +1794,7 @@ mod tests {
parser = Parser::default();
assert!(parser
- .parse(" :".as_bytes(), SourceInfo::default())
+ .parse(" :".as_bytes(), &SourceInfo::default())
.is_ok());
nodes = parser.nodes.last().unwrap();
@@ -1804,7 +1808,7 @@ mod tests {
fn named_label() {
let mut parser = Parser::default();
assert!(parser
- .parse("label:".as_bytes(), SourceInfo::default())
+ .parse("label:".as_bytes(), &SourceInfo::default())
.is_ok());
let mut nodes = parser.nodes.last().unwrap();
@@ -1815,7 +1819,7 @@ mod tests {
parser = Parser::default();
assert!(parser
- .parse(" label:".as_bytes(), SourceInfo::default())
+ .parse(" label:".as_bytes(), &SourceInfo::default())
.is_ok());
nodes = parser.nodes.last().unwrap();
@@ -1830,7 +1834,9 @@ mod tests {
let line = "label: dex";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
assert_eq!(nodes.len(), 2);
@@ -1849,7 +1855,9 @@ mod tests {
let line = ".L1: dex";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
assert_eq!(nodes.len(), 2);
@@ -1876,7 +1884,9 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
@@ -1896,7 +1906,9 @@ mod tests {
fn parse_compound_literal() {
let line = "lda #$20";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
@@ -1924,7 +1936,9 @@ mod tests {
fn parse_variable_in_literal() {
let line = "lda #Variable";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
@@ -1946,7 +1960,9 @@ mod tests {
fn parse_paren_expression() {
let line = "ldx #(Variable)";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let instr = parser.nodes.last().unwrap().last().unwrap();
assert_eq!(instr.node_type, NodeType::Instruction);
@@ -1972,7 +1988,7 @@ mod tests {
for line in vec!["lda #", "lda #%", "lda $"].into_iter() {
let mut parser = Parser::default();
let err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "invalid identifier");
@@ -1981,7 +1997,7 @@ mod tests {
for line in vec!["lda $ 2", "lda #% 2", "lda # 2"].into_iter() {
let mut parser = Parser::default();
let err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
@@ -2002,7 +2018,7 @@ mod tests {
{
let mut parser = Parser::default();
let err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "bad literal syntax");
@@ -2014,7 +2030,9 @@ mod tests {
let mut parser = Parser::default();
let line = ".asciiz \"=a: b, c; d\" ; Comment";
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let stmt = parser.nodes.last().unwrap().last().unwrap();
let inner = stmt.args.as_ref().unwrap().first().unwrap();
@@ -2032,7 +2050,7 @@ mod tests {
let line = ".asciiz \"à\"";
let err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
@@ -2055,7 +2073,9 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "dex");
@@ -2141,7 +2161,9 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "inc");
@@ -2168,7 +2190,9 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2193,7 +2217,9 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2211,7 +2237,7 @@ mod tests {
let mut parser = Parser::default();
let err = parser
- .parse("lda (Variable, x), y".as_bytes(), SourceInfo::default())
+ .parse("lda (Variable, x), y".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "bad indirect addressing");
}
@@ -2220,7 +2246,9 @@ mod tests {
fn indirect_addressing_y() {
for line in vec!["lda ($20), y"].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2239,7 +2267,9 @@ mod tests {
fn variable_in_instruction() {
let line = "lda Variable, x";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2258,7 +2288,9 @@ mod tests {
fn variable_literal_in_instruction() {
let line = "lda #Variable, x";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2278,7 +2310,9 @@ mod tests {
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(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line.as_str(), "lda");
@@ -2299,7 +2333,7 @@ mod tests {
let mut parser = Parser::default();
let err = parser
- .parse("adc #One:Variable".as_bytes(), SourceInfo::default())
+ .parse("adc #One:Variable".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2312,7 +2346,7 @@ mod tests {
let mut parser = Parser::default();
let err = parser
- .parse("lda = $10".as_bytes(), SourceInfo::default())
+ .parse("lda = $10".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2325,7 +2359,9 @@ mod tests {
for label in vec![":+", ":++", ":+++ ", ":++++", ":-", ":--", ":---", ":----"].into_iter() {
let line = format!("jmp {label}");
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line.as_str(), "jmp");
@@ -2347,7 +2383,7 @@ mod tests {
let mut line = "jmp :+++++";
let mut err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2356,7 +2392,7 @@ mod tests {
line = "jmp :+-";
err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2365,7 +2401,7 @@ mod tests {
line = "jmp :-+-";
err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2374,7 +2410,7 @@ mod tests {
line = "jmp Identifier:++";
err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2389,7 +2425,7 @@ mod tests {
let mut parser = Parser::default();
let mut err = parser
- .parse("abc = $10".as_bytes(), SourceInfo::default())
+ .parse("abc = $10".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2398,7 +2434,7 @@ mod tests {
parser = Parser::default();
err = parser
- .parse(".var = 1".as_bytes(), SourceInfo::default())
+ .parse(".var = 1".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2407,19 +2443,19 @@ mod tests {
parser = Parser::default();
err = parser
- .parse("var =".as_bytes(), SourceInfo::default())
+ .parse("var =".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "incomplete assignment");
parser = Parser::default();
err = parser
- .parse("var = ".as_bytes(), SourceInfo::default())
+ .parse("var = ".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "incomplete assignment");
parser = Parser::default();
err = parser
- .parse("var = ; Comment".as_bytes(), SourceInfo::default())
+ .parse("var = ; Comment".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "incomplete assignment");
}
@@ -2430,7 +2466,9 @@ mod tests {
fn constant_expression_test() {
let line = "ldx #(4 * NUM_SPRITES)";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "ldx");
@@ -2455,7 +2493,9 @@ mod tests {
fn nested_expression_test() {
let line = "lda #$80 >> ((BCD_BITS - 1) & 3)";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let instr = parser.nodes.last().unwrap().last().unwrap();
assert_node(instr, NodeType::Instruction, line, "lda");
@@ -2493,7 +2533,9 @@ mod tests {
fn parens_to_desambiguate() {
let line = ".byte ($01 << 4) | ($01 << 2) | ($01 << 1)";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Control(ControlType::Byte), line, ".byte");
@@ -2533,7 +2575,9 @@ mod tests {
fn parens_to_desambiguate2() {
let line = ".byte ($01 << 4) | ($01 << 2) | ($01 << 1), $02";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Control(ControlType::Byte), line, ".byte");
@@ -2556,7 +2600,9 @@ mod tests {
fn unary_operator_test() {
let line = "ldx #<NUM_SPRITES";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "ldx");
@@ -2583,7 +2629,9 @@ mod tests {
fn parse_control_no_args() {
for line in vec![".byte", " .byte", " label: .byte ; Comment"].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Control(ControlType::Byte), line, ".byte");
@@ -2605,7 +2653,9 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(
@@ -2635,7 +2685,9 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Control(ControlType::Byte), line, ".byte");
@@ -2653,7 +2705,9 @@ mod tests {
fn parse_byte_with_character_literals() {
let line = ".byte 'N', 'E', 'S', $1A";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let args = parser
.nodes
@@ -2705,7 +2759,9 @@ mod tests {
let real = String::from(line) + "\n.endscope";
let mut parser = Parser::default();
- assert!(parser.parse(real.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(real.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
let node = &nodes[nodes.len() - 2];
@@ -2739,7 +2795,9 @@ mod tests {
let real = String::from(line) + "\n.endmacro";
let mut parser = Parser::default();
- assert!(parser.parse(real.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(real.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
let node = &nodes[nodes.len() - 2];
@@ -2776,7 +2834,9 @@ mod tests {
let real = String::from(line) + "\n.endmacro";
let mut parser = Parser::default();
- assert!(parser.parse(real.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(real.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
let node = &nodes[nodes.len() - 2];
@@ -2802,7 +2862,7 @@ mod tests {
fn parse_control_unclosed() {
let mut parser = Parser::default();
let err = parser
- .parse(".macro MACRO".as_bytes(), SourceInfo::default())
+ .parse(".macro MACRO".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
@@ -2815,7 +2875,7 @@ mod tests {
fn parse_control_too_many_closes() {
let mut parser = Parser::default();
let err = parser
- .parse(".endmacro".as_bytes(), SourceInfo::default())
+ .parse(".endmacro".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
@@ -2832,7 +2892,7 @@ mod tests {
.endmacro"#;
let mut parser = Parser::default();
let err = parser
- .parse(code.as_bytes(), SourceInfo::default())
+ .parse(code.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
@@ -2852,7 +2912,9 @@ nop
inc $20
.endmacro"#;
let mut parser = Parser::default();
- assert!(parser.parse(code.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(code.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
assert_eq!(nodes.len(), 2); // .macro and .endmacro
@@ -2886,7 +2948,7 @@ inc $20
for line in vec![".hibyte", ".hibyte($20, $22)"].into_iter() {
let mut parser = Parser::default();
let err = parser
- .parse(line.as_bytes(), SourceInfo::default())
+ .parse(line.as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
@@ -2900,7 +2962,9 @@ inc $20
fn parse_control_in_instructions() {
for line in vec!["lda #.hibyte($2010)", " label: lda #.hibyte $2010 "].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2937,7 +3001,9 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2981,7 +3047,9 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -3026,7 +3094,9 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Assignment, line, "lala");
@@ -3058,7 +3128,7 @@ inc $20
fn parse_repeat_control() {
let mut parser = Parser::default();
let err = parser
- .parse(".repeat\n.endrepeat".as_bytes(), SourceInfo::default())
+ .parse(".repeat\n.endrepeat".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -3069,7 +3139,7 @@ inc $20
let err = parser
.parse(
".repeat 1, 2, 3\n.endrepeat".as_bytes(),
- SourceInfo::default(),
+ &SourceInfo::default(),
)
.unwrap_err();
assert_eq!(
@@ -3082,7 +3152,9 @@ inc $20
parser = Parser::default();
let mut line = ".repeat 2\n.endrepeat";
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let mut control = parser.nodes.last().unwrap().first().unwrap();
assert_node(
control,
@@ -3108,7 +3180,9 @@ inc $20
parser = Parser::default();
line = ".repeat 2, I\n.endrepeat";
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
control = parser.nodes.last().unwrap().first().unwrap();
assert_node(
control,
@@ -3135,13 +3209,13 @@ inc $20
fn parse_unknown_control() {
let mut parser = Parser::default();
let err = parser
- .parse(".".as_bytes(), SourceInfo::default())
+ .parse(".".as_bytes(), &SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "empty identifier");
parser = Parser::default();
assert!(parser
- .parse(".whatever".as_bytes(), SourceInfo::default())
+ .parse(".whatever".as_bytes(), &SourceInfo::default())
.is_ok());
}
@@ -3157,7 +3231,9 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Call, line, "MACRO_CALL");
@@ -3179,7 +3255,9 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Call, line, "MACRO_CALL");
@@ -3200,7 +3278,9 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Call, line, "MACRO_CALL");
@@ -3224,7 +3304,9 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(line.as_bytes(), &SourceInfo::default())
+ .is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Call, line, "MACRO_CALL");
@@ -3246,7 +3328,9 @@ VAR = $00
VAR2 = $02 ;; asan:reserve $03
"#;
let mut parser = Parser::default();
- assert!(parser.parse(code.as_bytes(), SourceInfo::default()).is_ok());
+ assert!(parser
+ .parse(code.as_bytes(), &SourceInfo::default())
+ .is_ok());
let nodes = parser.nodes();
assert_eq!(nodes.len(), 4);
@@ -3280,7 +3364,7 @@ VAR2 = $02 ;; asan:reserve $03
"#;
let mut parser = Parser::default();
- let res = parser.parse(code.as_bytes(), SourceInfo::default());
+ let res = parser.parse(code.as_bytes(), &SourceInfo::default());
assert!(res.is_err());