aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2024-07-15 08:55:57 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-12 07:31:52 +0100
commitc6c9aa4e5f3e5a01fcb9ccabf6dbd2f6da277650 (patch)
tree55f205062606664d3f17ff762e475a7895ceefee /lib
parent587dd5bb80364688419bb43aba4afc044ac0ef48 (diff)
downloadtools.nes-c6c9aa4e5f3e5a01fcb9ccabf6dbd2f6da277650.tar.gz
tools.nes-c6c9aa4e5f3e5a01fcb9ccabf6dbd2f6da277650.zip
Add the notion of mapping
Mappings and segments are fundamental blocks on how code is distributed both on the actual ROM File and in the memory layout. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/xixanta/src/assembler.rs108
-rw-r--r--lib/xixanta/src/errors.rs1
-rw-r--r--lib/xixanta/src/instruction.rs40
-rw-r--r--lib/xixanta/src/lib.rs1
-rw-r--r--lib/xixanta/src/mapping.rs66
5 files changed, 190 insertions, 26 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 5d03186..783cccd 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -3,6 +3,7 @@ use crate::errors::ParseError;
use crate::instruction::{
AddressingMode, Encodable, Generic, Instruction, Literal, Node, PString, Scoped,
};
+use crate::mapping::{Mapping, Segment};
use crate::opcodes::{INSTRUCTIONS, OPCODES};
use std::collections::hash_map::Entry;
use std::io::{self, BufRead, Read};
@@ -15,25 +16,17 @@ pub struct Assembler {
column: usize,
offset_address: usize,
context: Context,
-
- // TODO: segments instead
- nodes: Vec<Node>,
-}
-
-impl Default for Assembler {
- fn default() -> Self {
- Assembler::new()
- }
+ mapping: Mapping,
}
impl Assembler {
- pub fn new() -> Self {
+ pub fn new(segments: Vec<Segment>) -> Self {
Self {
line: 0,
column: 0,
offset_address: 0,
context: Context::new(),
- nodes: vec![],
+ mapping: Mapping::new(segments),
}
}
@@ -41,26 +34,56 @@ impl Assembler {
self.line = 0;
self.column = 0;
self.offset_address = 0;
- self.nodes = vec![];
self.context = Context::new();
+ self.mapping.reset();
}
- pub fn to_nodes(&mut self, reader: impl Read) -> Result<&Vec<Node>> {
+ pub fn assemble_nodes(&mut self, reader: impl Read) -> Result<()> {
self.from_reader(reader)?;
self.context.reset();
self.evaluate()?;
- Ok(&self.nodes)
+ Ok(())
}
pub fn assemble(&mut self, reader: impl Read) -> Result<Vec<&dyn Encodable>> {
let mut instructions: Vec<&dyn Encodable> = vec![];
- for node in self.to_nodes(reader)? {
- match node {
- Node::Instruction(instr) => instructions.push(instr),
- Node::Literal(lit) => instructions.push(lit),
- _ => {}
+ self.assemble_nodes(reader)?;
+
+ for segment in &self.mapping.segments {
+ let mut size: usize = 0;
+
+ 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
+ ),
+ });
+ }
+ if segment.fill_value.is_none() {
+ continue;
+ }
+
+ while size < segment.size {
+ instructions.push(segment.fill_value.as_ref().unwrap());
+ size += 1;
}
}
@@ -71,7 +94,7 @@ impl Assembler {
self.from_byte_reader(reader)?;
let mut instructions: Vec<&dyn Encodable> = vec![];
- for node in &self.nodes {
+ for node in self.mapping.current() {
match node {
Node::Instruction(instr) => instructions.push(instr),
Node::Literal(lit) => instructions.push(lit),
@@ -106,7 +129,7 @@ impl Assembler {
}
pub fn evaluate(&mut self) -> Result<()> {
- for node in &mut self.nodes {
+ for node in self.mapping.current_mut() {
match node {
Node::Instruction(instr) => {
Self::update_instruction_with_context(instr, &self.context)?
@@ -706,6 +729,7 @@ impl Assembler {
match id.value.to_lowercase().as_str() {
".scope" => self.parse_scope_definition(&id, line),
".endscope" => self.parse_scope_end(&id),
+ ".segment" => self.parse_segment_definition(&id, line),
".byte" | ".db" => self.parse_literal_bytes(&id, line, false),
".word" | ".dw" => self.parse_literal_bytes(&id, line, true),
_ => {
@@ -716,6 +740,15 @@ impl Assembler {
}
}
+ 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);
@@ -726,7 +759,7 @@ impl Assembler {
));
}
self.context.push(&identifier.value);
- self.nodes.push(Node::Scoped(Scoped {
+ self.mapping.push(Node::Scoped(Scoped {
identifier,
start: true,
}));
@@ -738,7 +771,7 @@ impl Assembler {
if !self.context.pop() {
return Err(id.parser_error("missmatched '.endscope': there is no scope to end"));
}
- self.nodes.push(Node::Scoped(Scoped {
+ self.mapping.push(Node::Scoped(Scoped {
identifier: PString::new(),
start: false,
}));
@@ -791,7 +824,7 @@ impl Assembler {
// will evaluate each literal as needed (e.g. replacing
// values from variables being used in this literal).
let string = line.get(self.column..self.column + idx).unwrap_or(" ");
- self.nodes.push(Node::Literal(Literal {
+ self.mapping.push(Node::Literal(Literal {
identifier: PString {
value: string.to_owned(),
line: self.line,
@@ -861,6 +894,29 @@ impl Assembler {
}
}
+ 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_statement(&mut self, id: PString, line: &str) -> Result<()> {
if line.chars().nth(self.column).unwrap_or(' ') == ':' {
self.parse_label(id, line)
@@ -1089,7 +1145,7 @@ impl Assembler {
instr.left = node.left;
instr.right = node.right;
- self.nodes.push(Node::Instruction(instr));
+ self.mapping.push(Node::Instruction(instr));
Ok(())
}
@@ -1118,7 +1174,7 @@ impl Assembler {
}
bs[i as usize] = buf[0];
}
- self.nodes.push(Node::Instruction(Instruction {
+ self.mapping.push(Node::Instruction(Instruction {
mnemonic: PString::from(&v.mnemonic),
opcode: v.opcode,
size: v.size,
diff --git a/lib/xixanta/src/errors.rs b/lib/xixanta/src/errors.rs
index 48f7286..d9a12cd 100644
--- a/lib/xixanta/src/errors.rs
+++ b/lib/xixanta/src/errors.rs
@@ -1,5 +1,6 @@
use std::fmt;
+// TODO: global error
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub line: usize,
diff --git a/lib/xixanta/src/instruction.rs b/lib/xixanta/src/instruction.rs
index fac6d8e..227a3e1 100644
--- a/lib/xixanta/src/instruction.rs
+++ b/lib/xixanta/src/instruction.rs
@@ -268,10 +268,50 @@ impl Encodable for Literal {
}
}
+#[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 enum Node {
Generic(Generic),
Instruction(Instruction),
Scoped(Scoped),
Literal(Literal),
+ Fill(Fill),
+}
+
+impl Node {
+ pub fn is_encodeable(&self) -> bool {
+ matches!(
+ self,
+ &Node::Instruction(_) | &Node::Literal(_) | &Node::Fill(_)
+ )
+ }
}
diff --git a/lib/xixanta/src/lib.rs b/lib/xixanta/src/lib.rs
index fc8ca96..fd3419c 100644
--- a/lib/xixanta/src/lib.rs
+++ b/lib/xixanta/src/lib.rs
@@ -7,4 +7,5 @@ pub mod assembler;
mod context;
mod errors;
pub mod instruction;
+pub mod mapping;
mod opcodes;
diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs
new file mode 100644
index 0000000..56acf18
--- /dev/null
+++ b/lib/xixanta/src/mapping.rs
@@ -0,0 +1,66 @@
+use crate::instruction::{Fill, Node, PString};
+use std::collections::HashMap;
+
+use crate::errors::ParseError;
+type Result<T> = std::result::Result<T, ParseError>;
+
+#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
+pub struct Segment {
+ pub name: String,
+ pub start: u16,
+ pub size: usize,
+ pub fill_value: Option<Fill>,
+}
+
+#[derive(Debug)]
+pub struct Mapping {
+ pub segments: Vec<Segment>,
+ pub nodes: HashMap<String, Vec<Node>>,
+ current: String,
+}
+
+impl Mapping {
+ pub fn new(mut segments: Vec<Segment>) -> Self {
+ segments.sort_by(|a, b| b.start.cmp(&a.start));
+
+ let mut nodes = HashMap::new();
+ for segment in segments.iter() {
+ nodes.insert(segment.name.clone(), vec![]);
+ }
+
+ let current_segment = &segments.first().unwrap().name.clone();
+
+ Mapping {
+ segments,
+ nodes,
+ current: current_segment.to_string(),
+ }
+ }
+
+ pub fn reset(&mut self) {
+ // TODO
+ }
+
+ 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())
+ );
+ }
+
+ id.value.clone_into(&mut self.current);
+ Ok(())
+ }
+
+ pub fn current(&self) -> &Vec<Node> {
+ self.nodes.get(&self.current).unwrap()
+ }
+
+ pub fn current_mut(&mut self) -> &mut Vec<Node> {
+ self.nodes.get_mut(&self.current).unwrap()
+ }
+
+ pub fn push(&mut self, node: Node) {
+ self.nodes.get_mut(&self.current).unwrap().push(node);
+ }
+}