aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta')
-rw-r--r--lib/xixanta/src/assembler.rs568
-rw-r--r--lib/xixanta/src/lib.rs40
-rw-r--r--lib/xixanta/src/mapping.rs417
-rw-r--r--lib/xixanta/src/object.rs (renamed from lib/xixanta/src/context.rs)164
4 files changed, 864 insertions, 325 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 76bf1de..a155d3b 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -1,7 +1,7 @@
-use crate::context::Context;
use crate::errors::{Error, EvalError};
-use crate::mapping::Segment;
+use crate::mapping::Mapping;
use crate::node::{ControlType, NodeType, PNode, PString};
+pub use crate::object::{Bundle, Context, Object, ObjectType};
use crate::opcodes::{AddressingMode, INSTRUCTIONS};
use crate::parser::Parser;
use std::cmp::Ordering;
@@ -9,54 +9,6 @@ use std::collections::HashMap;
use std::io::Read;
use std::ops::Range;
-/// A Bundle represents a set of bytes that can be encoded as binary data.
-/// TODO: maybe inside of Mapping?
-#[derive(Debug, Default, Clone, Eq, Ord, PartialEq, PartialOrd)]
-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],
-
- /// The amount of bytes which have actually been set on this bundle.
- pub size: u8,
-
- /// 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,
-
- /// Whether the bytes on `bytes` contain the final value or not. This is
- /// used for internal purposes only.
- resolved: bool,
-}
-
-impl Bundle {
- pub fn new(resolved: bool) -> Self {
- Self {
- resolved,
- ..Default::default()
- }
- }
-
- pub fn fill(value: u8) -> Self {
- Self {
- bytes: [value, 0, 0],
- size: 1,
- address: 0,
- cycles: 0,
- affected_on_page: false,
- resolved: true,
- }
- }
-}
-
#[derive(Clone, PartialEq)]
pub enum LiteralMode {
Hexadecimal,
@@ -82,6 +34,7 @@ pub struct Macro {
#[derive(Clone, Debug)]
pub struct PendingNode {
+ mapping: usize,
segment: usize,
context: String,
bundle_index: usize,
@@ -95,15 +48,16 @@ pub struct Assembler {
stage: Stage,
macros: HashMap<String, Macro>,
can_bundle: bool,
- segments: Vec<Segment>,
+ mappings: Vec<Mapping>,
+ current_mapping: usize,
current_segment: usize,
pending: Vec<PendingNode>,
labels_seen: usize,
}
impl Assembler {
- pub fn new(segments: Vec<Segment>) -> Self {
- assert!(!segments.is_empty());
+ pub fn new(mappings: Vec<Mapping>) -> Self {
+ crate::mapping::assert(&mappings);
Self {
context: Context::new(),
@@ -111,7 +65,8 @@ impl Assembler {
stage: Stage::Init,
macros: HashMap::new(),
can_bundle: true,
- segments,
+ mappings,
+ current_mapping: 0,
current_segment: 0,
pending: vec![],
labels_seen: 0,
@@ -149,10 +104,15 @@ impl Assembler {
match &node.node_type {
NodeType::Label => {
if !node.value.is_empty() {
- if let Err(err) =
- self.context
- .set_variable(&node.value, &Bundle::default(), false)
- {
+ if let Err(err) = self.context.set_variable(
+ &node.value,
+ &Object::new(
+ self.current_mapping,
+ self.current_segment,
+ ObjectType::Address,
+ ),
+ false,
+ ) {
errors.push(Error::Context(err));
}
}
@@ -170,8 +130,16 @@ impl Assembler {
}
match self.evaluate_node(node.left.as_ref().unwrap()) {
Ok(value) => {
- if let Err(err) = self.context.set_variable(&node.value, &value, false)
- {
+ if let Err(err) = self.context.set_variable(
+ &node.value,
+ &Object {
+ bundle: value,
+ mapping: self.current_mapping,
+ segment: self.current_segment,
+ object_type: ObjectType::Value,
+ },
+ false,
+ ) {
errors.push(Error::Context(err));
}
}
@@ -241,39 +209,35 @@ impl Assembler {
for node in nodes {
match node.node_type {
NodeType::Label => {
- let segment = &self.segments[self.current_segment];
- // println!("SEGMENT: {:#?}", segment)
- let value = (segment.start as usize + segment.offset).to_le_bytes();
- let bundle = Bundle {
- bytes: [value[0], value[1], value[2]],
- size: 2,
- address: 0,
- cycles: 0,
- affected_on_page: false,
- resolved: true,
+ let segment =
+ &self.mappings[self.current_mapping].segments[self.current_segment];
+ let value = segment.offset.to_le_bytes();
+ let object = Object {
+ bundle: Bundle {
+ bytes: [value[0], value[1], value[2]],
+ size: 2,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ resolved: false,
+ },
+ mapping: self.current_mapping,
+ segment: self.current_segment,
+ object_type: ObjectType::Address,
};
if !node.value.is_empty() {
- if let Err(err) = self.context.set_variable(&node.value, &bundle, true) {
+ if let Err(err) = self.context.set_variable(&node.value, &object, true) {
errors.push(Error::Context(err));
}
}
- self.context.add_label(&bundle);
+ self.context.add_label(&object);
}
NodeType::Instruction => {
if self.can_bundle {
self.literal_mode = None;
match self.evaluate_node(node) {
- Ok(mut bundle) => {
- if node.is_branch() {
- // TODO: it's a bit of a pity...
- let current = &mut self.segments[self.current_segment];
- bundle.address = current.start as usize + current.offset;
-
- if let Err(e) = self.to_relative_address(node, &mut bundle) {
- errors.push(Error::Eval(e));
- }
- }
+ Ok(bundle) => {
if let Err(e) = self.push_bundle(bundle, node) {
errors.push(Error::Eval(e));
}
@@ -314,17 +278,18 @@ impl Assembler {
match self.evaluate_node(&pn.node) {
Ok(mut bundle) => {
- // TODO: oh boy
- bundle.address = self.segments[pn.segment].bundles[pn.bundle_index].address;
+ let current = &self.mappings[pn.mapping].segments[pn.segment];
+ bundle.address = current.bundles[pn.bundle_index].address;
if pn.node.is_branch() {
+ bundle.resolved = true;
if let Err(e) = self.to_relative_address(&pn.node, &mut bundle) {
errors.push(Error::Eval(e));
}
}
- let current = &mut self.segments[pn.segment];
- current.bundles[pn.bundle_index].bytes = bundle.bytes;
+ let current_mut = &mut self.mappings[pn.mapping].segments[pn.segment];
+ current_mut.bundles[pn.bundle_index].bytes = bundle.bytes;
}
Err(e) => errors.push(Error::Eval(e)),
}
@@ -332,20 +297,41 @@ impl Assembler {
self.context.force_context_pop();
}
+ // Validate the mappings that have been evaluated before spitting it
+ // out.
+ if let Err(e) = crate::mapping::validate(&self.mappings) {
+ return Err(vec![Error::Eval(e)]);
+ }
+
let mut res = vec![];
- for segment in &mut self.segments {
- if segment.bundles.is_empty() {
- errors.push(Error::Eval(EvalError {
- line: 0,
- message: format!("segment '{}' is empty", segment.name),
- global: true,
- }));
+ // TODO: trainer support.
+
+ for mapping in &mut self.mappings {
+ for segment in mapping.segments.iter_mut() {
+ // TODO: return a warning instead.
+ if segment.is_empty() {
+ return Err(vec![Error::Eval(EvalError {
+ line: 0,
+ message: format!("segment '{}' is empty", segment.name),
+ global: true,
+ })]);
+ }
+ res.append(&mut segment.bundles);
}
- res.append(&mut segment.bundles);
+ let mut diff = mapping.size as isize - mapping.offset as isize;
+ if diff < 0 {
+ errors.push(Error::Eval(EvalError{
+ line: 0,
+ message: format!(
+ "exceeding segment size for '{}'; expecting {} bytes and {} bytes have already been seen",
+ mapping.name, mapping.size, mapping.offset,
+ ),
+ global: false,
+ }));
+ }
- if let Some(fill) = segment.fill {
- let mut diff = segment.size - segment.offset;
+ if let Some(fill) = mapping.fill {
while diff > 0 {
res.push(Bundle::fill(fill));
diff -= 1;
@@ -400,9 +386,14 @@ impl Assembler {
let mut margs = mcr.args.iter();
for arg in args.unwrap().iter() {
- let bundle = self.evaluate_node(arg)?;
+ let obj = Object {
+ bundle: self.evaluate_node(arg)?,
+ mapping: self.current_mapping,
+ segment: self.current_segment,
+ object_type: ObjectType::Value,
+ };
self.context
- .set_variable(margs.next().unwrap(), &bundle, false)?;
+ .set_variable(margs.next().unwrap(), &obj, false)?;
}
}
@@ -419,33 +410,23 @@ impl Assembler {
Ok(())
}
- // TODO: move
fn push_bundle(&mut self, mut bundle: Bundle, node: &PNode) -> Result<(), EvalError> {
- let current = &mut self.segments[self.current_segment];
+ let current = &mut self.mappings[self.current_mapping];
bundle.address = current.start as usize + current.offset; // TODO: here
current.offset += bundle.size as usize;
-
- if current.offset > current.size {
- return Err(EvalError {
- line: 0,
- message: format!(
- "exceeding segment size for '{}'; expecting {} bytes and {} bytes have already been seen",
- current.name, current.size, current.offset,
- ),
- global: false,
- });
- }
+ current.segments[self.current_segment].offset += bundle.size as usize;
if !bundle.resolved {
self.pending.push(PendingNode {
+ mapping: self.current_mapping,
segment: self.current_segment,
context: self.context.name().to_string(),
- bundle_index: current.bundles.len(),
+ bundle_index: current.segments[self.current_segment].bundles.len(),
node: node.to_owned(),
labels_seen: self.context.labels_seen(),
});
}
- current.bundles.push(bundle);
+ current.segments[self.current_segment].bundles.push(bundle);
Ok(())
}
@@ -515,11 +496,12 @@ impl Assembler {
resolved: false,
}),
Stage::Crunching => {
- match self
- .context
- .get_relative_label(node.value.to_isize(), self.labels_seen)
- {
- Ok(bundle) => Ok(bundle),
+ match self.context.get_relative_label(
+ node.value.to_isize(),
+ self.labels_seen,
+ &self.mappings,
+ ) {
+ Ok(object) => Ok(object.bundle),
Err(e) => Err(EvalError {
line: node.value.line,
message: e.message,
@@ -953,11 +935,14 @@ impl Assembler {
// Find the segment being referenced and update the
// `self.current_segment` accordingly.
let mut found = false;
- for (idx, segment) in self.segments.iter().enumerate() {
- if segment.name == name {
- self.current_segment = idx;
- found = true;
- break;
+ for (mapping_idx, mapping) in self.mappings.iter().enumerate() {
+ for (segment_idx, segment) in mapping.segments.iter().enumerate() {
+ if segment.name == name {
+ self.current_mapping = mapping_idx;
+ self.current_segment = segment_idx;
+ found = true;
+ break;
+ }
}
}
if !found {
@@ -971,8 +956,8 @@ impl Assembler {
}
fn evaluate_variable(&mut self, id: &PString) -> Result<Bundle, EvalError> {
- match self.context.get_variable(id) {
- Ok(value) => Ok(value),
+ match self.context.get_variable(id, &self.mappings) {
+ Ok(value) => Ok(value.bundle),
Err(e) => Err(EvalError {
message: e.message,
line: id.line,
@@ -1212,10 +1197,6 @@ impl Assembler {
} else {
let diff = target - next;
if diff > 127 {
- println!(
- "DIFF: {:#?} -- TARGET: {:#?} -- NEXT: {:#?} -- NODE: {:#?} -- BUNDLE: {:#?}",
- diff, target, next, node, bundle
- );
return Err(EvalError {
line: node.value.line,
message: "you cannot branch to this location: it's too far away".to_string(),
@@ -1226,6 +1207,7 @@ impl Assembler {
};
bundle.bytes[1] = byte;
+ bundle.bytes[2] = 0;
bundle.size = 2;
Ok(())
@@ -1235,32 +1217,71 @@ impl Assembler {
#[cfg(test)]
mod tests {
use super::*;
- use crate::mapping::EMPTY;
+ use crate::mapping::{SectionType, Segment, EMPTY};
- fn one_two() -> Vec<Segment> {
+ fn one_two() -> Vec<Mapping> {
vec![
- Segment {
- name: String::from("ONE"),
+ Mapping {
+ name: String::from("HEADER"),
start: 0x0000,
size: 0x0010,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
+ section_type: SectionType::Header,
+ segments: vec![Segment {
+ name: String::from("HEADER"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ }],
},
- Segment {
- name: String::from("TWO"),
- start: 0x0010,
- size: 0x0020,
+ Mapping {
+ name: String::from("ROM0"),
+ start: 0x8000,
+ size: 0x8000,
offset: 0,
fill: None,
- bundles: vec![],
+ section_type: SectionType::PrgRom,
+ segments: vec![
+ Segment {
+ name: String::from("ONE"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },
+ Segment {
+ name: String::from("TWO"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },
+ ],
},
]
}
+ fn minimal_header() -> Vec<Bundle> {
+ vec![
+ Bundle::fill(0x4E), // N
+ Bundle::fill(0x45), // E
+ Bundle::fill(0x53), // S
+ Bundle::fill(0x1A), // MS-DOS \0
+ Bundle::fill(0x01), // 1 * 8KB of PRG ROM
+ Bundle::fill(0x00), // No CHR ROM
+ ]
+ }
+
fn assert_instruction(line: &str, hex: &[u8]) {
+ // Set up the empty mapper, but we have to push a minimal header
+ // (otherwise an early assertion will fail), and we need to point to the
+ // "CODE" segment (which is in the mapping indexed by 1).
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm.assemble(line.as_bytes()).unwrap();
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+
+ // Grab the result passed the initial header.
+ let res = &asm.assemble(line.as_bytes()).unwrap()[0x10..];
assert_eq!(res.len(), 1);
@@ -1269,9 +1290,13 @@ mod tests {
}
}
- fn assert_error(line: &str, id: &str, line_num: usize, message: &str) {
+ fn assert_error(line: &str, id: &str, line_num: usize, global: bool, message: &str) {
let mut asm = Assembler::new(EMPTY.to_vec());
- assert_error_with_assembler(&mut asm, line, id, line_num, message);
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+
+ assert_error_with_assembler(&mut asm, line, id, line_num, global, message);
}
fn assert_error_with_assembler(
@@ -1279,19 +1304,24 @@ mod tests {
line: &str,
id: &str,
line_num: usize,
+ global: bool,
message: &str,
) {
let res = asm.assemble(line.as_bytes());
- let msg = format!("{} error (line {}): {}.", id, line_num, message);
+ let msg = if global {
+ format!("{} error: {}.", id, message)
+ } else {
+ format!("{} error (line {}): {}.", id, line_num, message)
+ };
assert_eq!(res.unwrap_err().first().unwrap().to_string().as_str(), msg);
}
fn assert_eval_error(line: &str, message: &str) {
- assert_error(line, "Evaluation", 1, message);
+ assert_error(line, "Evaluation", 1, false, message);
}
fn assert_context_error(line: &str, message: &str, line_num: usize) {
- assert_error(line, "Context", line_num, message);
+ assert_error(line, "Context", line_num, false, message);
}
// Empty
@@ -1299,7 +1329,7 @@ mod tests {
#[test]
fn empty_line() {
for line in vec!["", " ", ";; Comment", " ;; Comment"].into_iter() {
- assert_error(line, "Evaluation", 1, "segment 'CODE' is empty");
+ assert_error(line, "Evaluation", 1, true, "segment 'CODE' is empty");
}
}
@@ -1320,6 +1350,7 @@ adc %Variable
"#,
"Evaluation",
3,
+ false,
"you cannot use variables like 'Variable' in binary literals",
);
assert_instruction("adc #%10100010", &[0x69, 0xA2]);
@@ -1339,6 +1370,7 @@ adc $Variable
"#,
"Evaluation",
3,
+ false,
"you cannot use variables like 'Variable' in hexadecimal literals",
);
assert_error(
@@ -1348,6 +1380,7 @@ adc $Four
"#,
"Evaluation",
3,
+ false,
"you cannot use variables like 'Four' in hexadecimal literals",
);
assert_instruction("adc $AA", &[0x65, 0xAA]);
@@ -1371,7 +1404,10 @@ adc $Four
#[test]
fn scoped_variable() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
.scope One ; This is a comment
@@ -1392,7 +1428,7 @@ adc #Another::Variable
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 4);
let instrs: Vec<[u8; 2]> = vec![[0x69, 0x20], [0x69, 0x30], [0x69, 0x20], [0x69, 0x40]];
@@ -1407,7 +1443,10 @@ adc #Another::Variable
#[test]
fn bare_variables() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
Variable = 4
@@ -1415,7 +1454,7 @@ adc Variable
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 1);
@@ -1473,6 +1512,7 @@ lda #Scope::Variable
"#,
"Evaluation",
4,
+ false,
"'e' is not a decimal value and could not find variable 'Variable' in 'Scope' either",
);
}
@@ -1764,7 +1804,10 @@ lda #Scope::Variable
#[test]
fn same_segment_labels() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
nop
@@ -1776,7 +1819,7 @@ nop
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 4);
@@ -1784,19 +1827,22 @@ nop
assert_eq!(res[1].size, 3);
assert_eq!(res[1].bytes[0], 0x4C);
assert_eq!(res[1].bytes[1], 0x01);
- assert_eq!(res[1].bytes[2], 0x00);
+ assert_eq!(res[1].bytes[2], 0x80);
// jmp @end
assert_eq!(res[2].size, 3);
assert_eq!(res[2].bytes[0], 0x4C);
assert_eq!(res[2].bytes[1], 0x07);
- assert_eq!(res[2].bytes[2], 0x00);
+ assert_eq!(res[2].bytes[2], 0x80);
}
#[test]
fn anonymous_relative_jumps() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
nop
@@ -1815,7 +1861,7 @@ nop
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 9);
@@ -1829,25 +1875,25 @@ nop
assert_eq!(res[2].size, 3);
assert_eq!(res[2].bytes[0], 0x4C);
assert_eq!(res[2].bytes[1], 0x01);
- assert_eq!(res[2].bytes[2], 0x00);
+ assert_eq!(res[2].bytes[2], 0x80);
// jmp :+
assert_eq!(res[3].size, 3);
assert_eq!(res[3].bytes[0], 0x4C);
assert_eq!(res[3].bytes[1], 0x0E);
- assert_eq!(res[3].bytes[2], 0x00);
+ assert_eq!(res[3].bytes[2], 0x80);
// jmp @hello
assert_eq!(res[4].size, 3);
assert_eq!(res[4].bytes[0], 0x4C);
assert_eq!(res[4].bytes[1], 0x02);
- assert_eq!(res[4].bytes[2], 0x00);
+ assert_eq!(res[4].bytes[2], 0x80);
// jmp :+++
assert_eq!(res[5].size, 3);
assert_eq!(res[5].bytes[0], 0x4C);
assert_eq!(res[5].bytes[1], 0x10);
- assert_eq!(res[5].bytes[2], 0x00);
+ assert_eq!(res[5].bytes[2], 0x80);
// Three last nop's.
assert_eq!(res[6].size, 1);
@@ -1861,7 +1907,10 @@ nop
#[test]
fn anonymous_relative_branches() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
nop
@@ -1880,7 +1929,7 @@ nop
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 9);
@@ -1922,7 +1971,10 @@ nop
#[test]
fn conditional_branch_to_labels() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
nop
@@ -1934,7 +1986,7 @@ nop
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 4);
@@ -1957,7 +2009,10 @@ nop
#[test]
fn byte_literals() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
.scope Vars
@@ -1969,7 +2024,7 @@ nop
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 3);
@@ -1992,7 +2047,10 @@ nop
#[test]
fn hi_lo_byte() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
Var = $2002
@@ -2001,7 +2059,7 @@ lda #.hibyte(Var)
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 2);
let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x02], [0xA9, 0x20]];
@@ -2018,7 +2076,10 @@ lda #.hibyte(Var)
#[test]
fn macro_no_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
lda #42
@@ -2032,7 +2093,7 @@ MACRO
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 3);
let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]];
@@ -2047,6 +2108,9 @@ MACRO
#[test]
fn macro_not_enough_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
let res = asm
.assemble(
r#"
@@ -2072,6 +2136,9 @@ MACRO
#[test]
fn macro_too_many_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
let res = asm
.assemble(
r#"
@@ -2097,7 +2164,10 @@ MACRO(1, 2)
#[test]
fn macro_with_one_argument() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
lda #42
@@ -2111,7 +2181,7 @@ MACRO(2)
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 3);
let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]];
@@ -2126,6 +2196,9 @@ MACRO(2)
#[test]
fn macro_unknown_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
let res = asm
.assemble(
r#"
@@ -2152,6 +2225,9 @@ MACRO(1)
#[test]
fn macro_shadow_argument() {
let mut asm = Assembler::new(EMPTY.to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
let res = asm
.assemble(
r#"
@@ -2179,7 +2255,10 @@ MACRO(1)
#[test]
fn macro_multiple_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
.assemble(
r#"
.macro WRITE_PPU_DATA address, value
@@ -2196,7 +2275,7 @@ WRITE_PPU_DATA $20B9, $04
"#
.as_bytes(),
)
- .unwrap();
+ .unwrap()[0x10..];
assert_eq!(res.len(), 7);
@@ -2245,31 +2324,52 @@ WRITE_PPU_DATA $20B9, $04
#[test]
fn error_on_unknown_segment() {
let mut asm = Assembler::new(one_two().to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+
let line = r#"
.segment "THREE"
.segment "TWO"
nop
"#;
- assert_error_with_assembler(&mut asm, line, "Evaluation", 2, "unknown segment 'THREE'")
+ assert_error_with_assembler(
+ &mut asm,
+ line,
+ "Evaluation",
+ 2,
+ false,
+ "unknown segment 'THREE'",
+ )
}
#[test]
fn error_on_empty_segment() {
let mut asm = Assembler::new(one_two().to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
let line = r#"
.segment "ONE"
.segment "TWO"
nop
"#;
- assert_error_with_assembler(&mut asm, line, "Evaluation", 1, "segment 'ONE' is empty")
+ assert_error_with_assembler(
+ &mut asm,
+ line,
+ "Evaluation",
+ 1,
+ true,
+ "segment 'ONE' is empty",
+ )
}
#[test]
fn jmp_and_beq_inside_segment() {
let mut asm = Assembler::new(one_two().to_vec());
- let res = asm
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ let bundles = &asm
.assemble(
r#"
.segment "ONE"
@@ -2283,7 +2383,9 @@ nop
beq :--
beq :+
jmp @hello
+ beq @hello
beq :+++
+ beq @end
@end:
nop
:
@@ -2292,11 +2394,7 @@ nop
"#
.as_bytes(),
)
- .unwrap();
-
- // Let's ignore the instructions + fill of "ONE".
- let bundles = &res[16..res.len()];
- println!("{:#?}", bundles);
+ .unwrap()[0x11..]; // Ignoring HEADER + nop from ONE
// First two nop's
assert_eq!(bundles[0].size, 1);
@@ -2312,30 +2410,106 @@ nop
// beq :+
assert_eq!(bundles[3].size, 2);
assert_eq!(bundles[3].bytes[0], 0xF0);
- assert_eq!(bundles[3].bytes[1], 0x04);
+ assert_eq!(bundles[3].bytes[1], 0x09);
- // beq @hello
- assert_eq!(bundles[4].size, 2);
+ // jmp @hello
+ assert_eq!(bundles[4].size, 3);
assert_eq!(bundles[4].bytes[0], 0x4C);
- assert_eq!(bundles[4].bytes[1], 0x02);
- assert_eq!(bundles[4].bytes[2], 0x00);
+ assert_eq!(bundles[4].bytes[1], 0x03);
+ assert_eq!(bundles[4].bytes[2], 0x80);
- // beq :+++
+ // beq @hello
assert_eq!(bundles[5].size, 2);
assert_eq!(bundles[5].bytes[0], 0xF0);
- assert_eq!(bundles[5].bytes[1], 0x02);
+ assert_eq!(bundles[5].bytes[1], 0xF7);
+
+ // beq :+++
+ assert_eq!(bundles[6].size, 2);
+ assert_eq!(bundles[6].bytes[0], 0xF0);
+ assert_eq!(bundles[6].bytes[1], 0x04);
+
+ // beq @end
+ assert_eq!(bundles[7].size, 2);
+ assert_eq!(bundles[7].bytes[0], 0xF0);
+ assert_eq!(bundles[7].bytes[1], 0x00);
// Three last nop's.
- assert_eq!(bundles[6].size, 1);
- assert_eq!(bundles[6].bytes[0], 0xEA);
- assert_eq!(bundles[7].size, 1);
- assert_eq!(bundles[7].bytes[0], 0xEA);
assert_eq!(bundles[8].size, 1);
assert_eq!(bundles[8].bytes[0], 0xEA);
+ assert_eq!(bundles[9].size, 1);
+ assert_eq!(bundles[9].bytes[0], 0xEA);
+ assert_eq!(bundles[10].size, 1);
+ assert_eq!(bundles[10].bytes[0], 0xEA);
+ }
+
+ #[test]
+ fn jmp_on_different_segments_intertwined() {
+ let mut asm = Assembler::new(one_two().to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ let bundles = &asm
+ .assemble(
+ r#"
+.segment "ONE"
+lala:
+ rts
+
+.segment "TWO"
+code:
+ jsr lala
+ jsr code
+ rts
+
+.segment "ONE"
+ jsr code
+"#
+ .as_bytes(),
+ )
+ .unwrap()[0x11..]; // Ignoring HEADER + rts from ONE
+
+ // "jsr code" from ONE (notice that it's intertwined!)
+ assert_eq!(bundles[0].size, 3);
+ assert_eq!(bundles[0].bytes[0], 0x20);
+ assert_eq!(bundles[0].bytes[1], 0x04);
+ assert_eq!(bundles[0].bytes[2], 0x80);
+
+ // "jsr lala" from TWO
+ assert_eq!(bundles[1].size, 3);
+ assert_eq!(bundles[1].bytes[0], 0x20);
+ assert_eq!(bundles[1].bytes[1], 0x00);
+ assert_eq!(bundles[1].bytes[2], 0x80);
+
+ // "jsr code" from TWO (again, notice that it's intertwined)
+ assert_eq!(bundles[2].size, 3);
+ assert_eq!(bundles[2].bytes[0], 0x20);
+ assert_eq!(bundles[2].bytes[1], 0x04);
+ assert_eq!(bundles[2].bytes[2], 0x80);
+ }
+
+ #[test]
+ fn cannot_switch_to_segment_inside_of_scope() {
+ let mut asm = Assembler::new(EMPTY.to_vec());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
+ .assemble(
+ r#"
+.scope Vars
+.segment "CODE"
+ nop
+.endscope
+"#
+ .as_bytes(),
+ )
+ .unwrap_err();
+
+ assert_eq!(
+ res.first().unwrap().to_string(),
+ "Evaluation error (line 3): cannot switch to segment 'CODE' \
+ if we are still inside of a scope ('Vars')."
+ );
}
- // TODO: jmp's and beq's inside of segment
- // TODO: jmp's between segments
- // TODO: Error on trying segment inside of another scope
- // TODO: fill data
+ // TODO: jmp/beq outside of allocated PRG ROM
}
diff --git a/lib/xixanta/src/lib.rs b/lib/xixanta/src/lib.rs
index 0b68ac9..0ad901f 100644
--- a/lib/xixanta/src/lib.rs
+++ b/lib/xixanta/src/lib.rs
@@ -2,9 +2,45 @@
extern crate lazy_static;
pub mod assembler;
-pub mod context;
pub mod errors;
-pub mod mapping;
pub mod node;
+pub mod object;
pub mod opcodes;
pub mod parser;
+
+/// Mapping defines structures for laying out how the code will be assembled
+/// both on memory and on the ROM file itself. Notice that we take a different
+/// approach than 'cc65' because that compiler has to take into account a
+/// myriad of machines that had the MOS 6502 processor. Here we have a clear
+/// target and we can be more specific. That is, the community has settled on a
+/// very specific ROM file format, and hence the "linker configuration" turns
+/// out to be simpler. The code here takes it into consideration by defining
+/// three regions on the ROM file:
+///
+/// 1. The header (i.e. `SectionType::Header`) will be allocated exactly on
+/// the first 16 bytes of the file. Moreover, the first 6 bytes are
+/// absolutely mandatory to be filled by the programmer.
+/// 2. The PRG ROM (i.e. `SectionType::PrgRom`) will be allocated next to the
+/// header (i.e. trainer support is not available on this assembler). This
+/// section is laid out in blocks of 8KB and contains the program.
+/// 3. The CHR ROM (i.e. `SectionType::ChrRom`) will be allocated just after
+/// the PRG ROM and is laid out in blocks of 4KB, containing the ROM data (if
+/// available).
+///
+/// This is the layout for the ROM file itself, but it can itself be subdivided
+/// into "mappings" (i.e. `Mapping`). A Mapping is a configuration inside of
+/// one of these three sections. For a simple section like the header having
+/// only one mapping will be enough, but for PRG ROM we might define a mapping
+/// for each bank, for example. And even in simple scenarios, it's quite usual
+/// to split this section at least with "CODE" (which contains the actual
+/// program), and "VECTORS" (6 bytes containing the addresses for the vectors
+/// for a 6502 processor). A Mapping contains quite a lot of info, but you can
+/// think of it as a way to subdivide a section, defining stuff like where it
+/// starts, its size, how to fill it if the programmer did not occupy the
+/// region fully, etc.
+///
+/// Inside of a mapping there might be multiple segments. This is in turn a way
+/// to further subdivide the memory region, and it defines contiguous space
+/// inside of a mapping. This way, regardless of where you define a segment,
+/// there are some guarantees on the order.
+pub mod mapping;
diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs
index 657a94b..0a73648 100644
--- a/lib/xixanta/src/mapping.rs
+++ b/lib/xixanta/src/mapping.rs
@@ -1,169 +1,388 @@
use crate::assembler::Bundle;
+use crate::errors::EvalError;
lazy_static! {
- pub static ref EMPTY: Vec<Segment> = vec![Segment {
- name: String::from("CODE"),
- start: 0x0000,
- size: 0xFFFF,
- offset: 0,
- fill: None,
- bundles: vec![],
- }];
- pub static ref NROM: Vec<Segment> = vec![
- Segment {
+ /// An empty mapper used for testing purposes.
+ pub static ref EMPTY: Vec<Mapping> = vec![
+ Mapping {
name: String::from("HEADER"),
start: 0x0000,
size: 0x0010,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
+ section_type: SectionType::Header,
+ segments: vec![Segment {
+ name: String::from("HEADER"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ }]
},
- Segment {
- name: String::from("VECTORS"),
- start: 0xFFFA,
- size: 0x0006,
+ Mapping {
+ name: String::from("ROM0"),
+ start: 0x8000,
+ size: 0x8000,
+ offset: 0,
+ fill: None,
+ section_type: SectionType::PrgRom,
+ segments: vec![Segment {
+ name: String::from("CODE"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },]
+ },
+ ];
+
+ // Mapper for a simple NROM setup (e.g. Super Mario Bros).
+ pub static ref NROM: Vec<Mapping> = vec![
+ Mapping {
+ name: String::from("HEADER"),
+ start: 0x0000,
+ size: 0x0010,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
+ section_type: SectionType::Header,
+ segments: vec![Segment {
+ name: String::from("HEADER"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ }]
},
- Segment {
- name: String::from("CODE"),
+ Mapping {
+ name: String::from("ROM0"),
start: 0x8000,
size: 0x7FFA,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
+ section_type: SectionType::PrgRom,
+ segments: vec![Segment {
+ name: String::from("CODE"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },]
+ },
+ Mapping {
+ name: String::from("ROMV"),
+ start: 0xFFFA,
+ size: 0x0006,
+ offset: 0,
+ fill: Some(0x00),
+ section_type: SectionType::PrgRom,
+ segments: vec![Segment {
+ name: String::from("VECTORS"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },]
},
- Segment {
- name: String::from("CHARS"),
+ Mapping {
+ name: String::from("ROM2"),
start: 0x0000,
size: 0x2000,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
- }
+ section_type: SectionType::ChrRom,
+ segments: vec![Segment {
+ name: String::from("CHARS"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },]
+ },
];
- pub static ref NROM65: Vec<Segment> = vec![
- Segment {
+
+ // The same mapper as NROM, but it adds a "STARTUP" segment into the "ROM0"
+ // mapping so to behave the same as the default "cc65" configuration.
+ pub static ref NROM65: Vec<Mapping> = vec![
+ Mapping {
name: String::from("HEADER"),
start: 0x0000,
size: 0x0010,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
+ section_type: SectionType::Header,
+ segments: vec![Segment {
+ name: String::from("HEADER"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ }]
},
- Segment {
- name: String::from("VECTORS"),
- start: 0xFFFA,
- size: 0x0006,
- offset: 0,
- fill: Some(0x00),
- bundles: vec![],
- },
- Segment {
- name: String::from("STARTUP"),
+ Mapping {
+ name: String::from("ROM0"),
start: 0x8000,
size: 0x7FFA,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
+ section_type: SectionType::PrgRom,
+ segments: vec![
+ Segment {
+ name: String::from("STARTUP"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },
+ Segment {
+ name: String::from("CODE"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },
+ ]
},
- Segment {
- name: String::from("CODE"),
- start: 0x8000,
- size: 0x7FFA,
+ Mapping {
+ name: String::from("ROMV"),
+ start: 0xFFFA,
+ size: 0x0006,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
+ section_type: SectionType::PrgRom,
+ segments: vec![Segment {
+ name: String::from("VECTORS"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },]
},
- Segment {
- name: String::from("CHARS"),
+ Mapping {
+ name: String::from("ROM2"),
start: 0x0000,
size: 0x2000,
offset: 0,
fill: Some(0x00),
- bundles: vec![],
- }
+ section_type: SectionType::ChrRom,
+ segments: vec![Segment {
+ name: String::from("CHARS"),
+ len: 0,
+ offset: 0,
+ bundles: vec![],
+ },]
+ },
];
}
+/// The type of section that a Mapping represents.
+#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
+pub enum SectionType {
+ /// The 16 initial bytes describing the header of the ROM file.
+ Header,
+
+ /// Bank to be stored in PRG ROM with a size multiple of 8KB.
+ PrgRom,
+
+ /// Bank to be stored in CHR ROM with a size multiple of 4KB.
+ ChrRom,
+}
+
+/// A segment inside of a memory mapping, used to organize the code inside of a
+/// given memory mapping. Note that a segment does not do anything else: it's
+/// just about organizing code inside of a mapping. It doesn't deal with how to
+/// fill a memory region, or where it starts in memory, or anything like that.
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct Segment {
+ /// Name of the segment.
pub name: String,
- pub start: u16,
- pub size: usize,
+
pub offset: usize,
- pub fill: Option<u8>,
+ pub len: usize,
+
+ /// Bundles that have been generated when assembling the nodes that have
+ /// been parsed by a previous step.
pub bundles: Vec<Bundle>,
}
-/* TODO
-#[derive(Debug)]
+impl Segment {
+ /// Returns the length of the segment by counting the bundles that have been
+ // pushed so far into the segment.
+ pub fn len(&self) -> usize {
+ self.bundles
+ .iter()
+ .fold(0, |acc, bundle| acc + bundle.size as usize)
+ }
+
+ /// Returns true if the given segment has no bundles in it, false otherwise.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+}
+
+/// A region in memory which has one or more segments in it, which in turn have
+/// the bundles that are to be generated in the end of an assembling operation.
+#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct Mapping {
+ /// Name of the mapping.
+ pub name: String,
+
+ /// Address where the mapping will start when loaded on the
+ /// console/emulator. This is the address where instructions like `jmp` or
+ /// labels will rely on. Hence, it's not the address of the ROM file itself,
+ /// but the effective address where it will be loaded.
+ pub start: u16,
+
+ /// Size of the mapping. Note that this depends on the `section_type` value,
+ /// which is: exactly 0x10 for a header, multiples of 0x4000 for prg_rom,
+ /// and multiples of 0x2000 for chr_rom.
+ pub size: usize,
+
+ /// The total number of bytes that have been pushed across all its segments.
+ pub offset: usize,
+
+ /// Optional value to fill the mapping if the end size is lower than the
+ /// value on `size`. Set to `None` to skip filling the memory region for
+ /// this mapping.
+ pub fill: Option<u8>,
+
+ /// Segments for the memory region.
pub segments: Vec<Segment>,
- pub nodes: HashMap<String, Vec<Node>>,
- pub current: String,
- pub macros: HashMap<String, Vec<Node>>,
- pub current_macro: Option<String>,
+
+ /// What kind of memory region is being described by this mapping.
+ pub section_type: SectionType,
}
-impl Mapping {
- pub fn new(mut segments: Vec<Segment>) -> Self {
- segments.sort_by(|a, b| a.start.cmp(&b.start));
+/// Assert that the given mappings conform to a minimum standard.
+pub fn assert(mappings: &[Mapping]) {
+ assert!(
+ !mappings.is_empty(),
+ "We need at least one segment defined, the header"
+ );
+ assert!(
+ !mappings.first().unwrap().segments.is_empty(),
+ "We need at least one segment defined, the header"
+ );
+ assert_eq!(
+ mappings.first().unwrap().section_type,
+ SectionType::Header,
+ "First mapping section must be the header"
+ );
+ assert_eq!(
+ mappings.first().unwrap().size,
+ 0x10,
+ "The header must be exactly 16 bytes long"
+ );
- let mut nodes = HashMap::new();
- for segment in segments.iter() {
- nodes.insert(segment.name.clone(), vec![]);
- }
+ let prg_rom_len = mappings
+ .iter()
+ .filter(|m| m.section_type == SectionType::PrgRom)
+ .fold(0, |acc, x| acc + x.size);
+ assert!(prg_rom_len >= 0x4000, "PRG ROM must be at least 8KB long");
+ assert!(
+ prg_rom_len % 0x4000 == 0,
+ "PRG ROM must be formed by banks of exactly 8KB"
+ );
+}
- let current_segment = &segments.first().unwrap().name.clone();
+/// Validate some sanity checks on the given `mappings`. Only call this function
+/// after all bundles have been produced.
+pub fn validate(mappings: &[Mapping]) -> Result<(), EvalError> {
+ // Guaranteed by `crate::mapping::assert` to be the header.
+ let header: &Segment = mappings.first().unwrap().segments.first().unwrap();
- Mapping {
- segments,
- nodes,
- current: current_segment.to_string(),
- macros: HashMap::new(),
- current_macro: None,
- }
+ // Header must have at least six bytes with proper information provided by
+ // the programmer.
+ if header.len() < 6 {
+ return Err(EvalError {
+ line: 0,
+ message: String::from("The header must contain at least 6 bytes"),
+ global: true,
+ });
}
- pub fn reset(&mut self) {
- self.nodes = HashMap::new();
- for segment in self.segments.iter() {
- self.nodes.insert(segment.name.clone(), vec![]);
- }
+ // Now check that the length of the evaluated data matches the criteria
+ // stated on the ROM header that was evaluated as well.
+ let (header_prg_rom_size, header_chr_rom_size) = parse_header(header)?;
+ let prg_rom_len = mappings
+ .iter()
+ .filter(|m| m.section_type == SectionType::PrgRom)
+ .fold(0, |acc, x| {
+ acc + x.segments.iter().fold(0, |a, y| a + y.len())
+ });
+ let chr_rom_len = mappings
+ .iter()
+ .filter(|m| m.section_type == SectionType::ChrRom)
+ .fold(0, |acc, x| {
+ acc + x.segments.iter().fold(0, |a, y| a + y.len())
+ });
- self.current = self.segments.first().unwrap().name.clone();
-
- self.macros = HashMap::new();
- self.current_macro = None;
+ if header_prg_rom_size < prg_rom_len {
+ return Err(EvalError {
+ line: 0,
+ message: format!("PRG ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated", header_prg_rom_size, prg_rom_len),
+ global: true,
+ });
+ }
+ if header_chr_rom_size < chr_rom_len {
+ return Err(EvalError {
+ line: 0,
+ message: format!("CHR ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated", header_chr_rom_size, chr_rom_len),
+ global: true,
+ });
}
- pub fn switch(&mut self, id: &PString) -> Result<()> {
- if !self.nodes.contains_key(&id.value) {
- // TODO
- // return Err(
- // id.parser_error(format!("segment '{}' has not been defined", id.value).as_str())
- // );
- }
+ Ok(())
+}
- id.value.clone_into(&mut self.current);
- Ok(())
- }
+// Returns a tuple with the sizes for PRG and CHR ROM as described from the
+// computed header. This also does some sanity checks on the header.
+fn parse_header(header: &Segment) -> Result<(usize, usize), EvalError> {
+ let mut header_it = header.bundles.clone().into_iter();
- pub fn current(&self) -> &Vec<Node> {
- self.nodes.get(&self.current).unwrap()
+ // Validate the magic string: 'N', 'E', 'S', $1A
+ if header_it.next().unwrap().bytes[0] != b'N' {
+ return Err(EvalError {
+ line: 0,
+ message: String::from("First byte of the header must be 'N'"),
+ global: true,
+ });
}
-
- pub fn current_mut(&mut self) -> &mut Vec<Node> {
- self.nodes.get_mut(&self.current).unwrap()
+ if header_it.next().unwrap().bytes[0] != b'E' {
+ return Err(EvalError {
+ line: 0,
+ message: String::from("Second byte of the header must be 'E'"),
+ global: true,
+ });
+ }
+ if header_it.next().unwrap().bytes[0] != b'S' {
+ return Err(EvalError {
+ line: 0,
+ message: String::from("Third byte of the header must be 'S'"),
+ global: true,
+ });
}
+ if header_it.next().unwrap().bytes[0] != 26 {
+ return Err(EvalError {
+ line: 0,
+ message: String::from(
+ "Fourth byte of the header must be the MS-DOS termination character",
+ ),
+ global: true,
+ });
+ }
+
+ Ok((
+ header_it.next().unwrap().bytes[0] as usize * 0x4000,
+ header_it.next().unwrap().bytes[0] as usize * 0x2000,
+ ))
+}
- pub fn push(&mut self, node: Node) {
- match &self.current_macro {
- Some(m) => self.macros.get_mut(m).unwrap().push(node),
- None => self.nodes.get_mut(&self.current).unwrap().push(node),
+/// Returns the offset of the segment indexed by `segment_index` inside of
+/// `mapping`. That is, it returns back at which byte the given segment is going
+/// to be placed inside of the given mapping.
+///
+/// NOTE: this function is only useful if you already know that all the segments
+/// on the given mapping have a definitive size (i.e. they will not change in
+/// the future).
+pub fn segment_offset(mapping: &Mapping, segment_index: usize) -> u16 {
+ let mut count = 0;
+
+ for (idx, segment) in mapping.segments.iter().enumerate() {
+ if idx >= segment_index {
+ return count;
}
+ count += segment.offset as u16;
}
+
+ count
}
-*/
diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/object.rs
index 000c959..70359f1 100644
--- a/lib/xixanta/src/context.rs
+++ b/lib/xixanta/src/object.rs
@@ -1,12 +1,102 @@
-use crate::assembler::Bundle;
use crate::errors::{ContextError, ContextErrorReason};
+use crate::mapping::Mapping;
use crate::node::{ControlType, NodeType, PNode, PString};
use crate::opcodes::CONTROL_FUNCTIONS;
use std::collections::HashMap;
-// The name of the global context as used internally.
+/// The name of the global context as used internally.
const GLOBAL_CONTEXT: &str = "Global";
+/// A Bundle represents a set of bytes that can be encoded as binary data.
+#[derive(Debug, Default, Clone, Eq, Ord, PartialEq, PartialOrd)]
+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],
+
+ /// The amount of bytes which have actually been set on this bundle.
+ pub size: u8,
+
+ /// 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,
+
+ /// Whether the bytes on `bytes` contain the final value or not. This is
+ /// used for internal purposes only.
+ pub resolved: bool,
+}
+
+impl Bundle {
+ /// Create a default bundle but with the given `resolved` status.
+ pub fn new(resolved: bool) -> Self {
+ Self {
+ resolved,
+ ..Default::default()
+ }
+ }
+
+ /// Create a bundle tailored for filling purposes.
+ pub fn fill(value: u8) -> Self {
+ Self {
+ bytes: [value, 0, 0],
+ size: 1,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ resolved: true,
+ }
+ }
+}
+
+/// The type of object being referenced, which is either a value as-is, or an
+/// address that needs to be interpreted when fetching it.
+#[derive(Debug, Clone)]
+pub enum ObjectType {
+ Address,
+ Value,
+}
+
+/// Bundle and metadata which is stored on the context table for a given
+/// variable or label.
+#[derive(Debug, Clone)]
+pub struct Object {
+ /// Bundle representing the actual value.
+ pub bundle: Bundle,
+
+ /// The mapping index where the object was found. Note that this index
+ /// doesn't mean much on the table, but it has to mean something by the
+ /// caller.
+ pub mapping: usize,
+
+ /// The segment index within the referenced mapping where the object was
+ /// found. Note that this index doesn't mean much on the table, but it has
+ /// to mean something by the caller.
+ pub segment: usize,
+
+ /// The type for this object.
+ pub object_type: ObjectType,
+}
+
+impl Object {
+ /// Create a default bundle with the given metadata parameters.
+ pub fn new(mapping: usize, segment: usize, object_type: ObjectType) -> Self {
+ Self {
+ bundle: Bundle::default(),
+ mapping,
+ segment,
+ object_type,
+ }
+ }
+}
+
/// 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)]
@@ -16,17 +106,17 @@ pub struct Context {
/// global context.
stack: Vec<String>,
- /// Map of variables for any given context. The key is the name of the
+ /// Map of objects for any given context. The key is the name of the
/// context, and the value is another map. This inner map has the variable
/// name as the key, and the Bundle as a value.
- map: HashMap<String, HashMap<String, Bundle>>,
+ pub map: HashMap<String, HashMap<String, Object>>,
/// Map of labels for any given context. Note that this only keeps track of
/// the amount of labels that have been defined, which might include
/// anonymous positions. This is primarily used on relative addressing where
/// the name of the label might not be provided (e.g. anonymous relative
/// reference).
- labels: HashMap<String, Vec<Bundle>>,
+ pub labels: HashMap<String, Vec<Object>>,
}
impl Default for Context {
@@ -45,10 +135,11 @@ impl Context {
}
}
- /// 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> {
+ /// Returns the value of the object 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. The value itself will be resolved if the
+ /// type is ObjectType::Address.
+ pub fn get_variable(&self, id: &PString, mappings: &[Mapping]) -> Result<Object, 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.
@@ -61,7 +152,10 @@ impl Context {
// variable in it.
match self.map.get(scope_name) {
Some(scope) => match scope.get(var_name) {
- Some(var) => Ok(var.clone()),
+ Some(var) => match var.object_type {
+ ObjectType::Value => Ok(var.clone()),
+ ObjectType::Address => Ok(self.resolve_label(mappings, var)),
+ },
None => Err(ContextError {
message: format!(
"could not find variable '{}' in {}",
@@ -80,13 +174,34 @@ impl Context {
}
}
- /// Sets a value for a variable defined in the assignment `node`. If
- /// `overwrite` is set to true, then this value will be set even if the
- /// variable already existed, otherwise it will return a ContextError
+ /// Given an `object` which is located via `mappings`, resolve the effective
+ /// address.
+ ///
+ /// NOTE: this function asserts that the given `object` is of type
+ /// ObjectType::Address, otherwise it doesn't make sense to call it.
+ pub fn resolve_label(&self, mappings: &[Mapping], object: &Object) -> Object {
+ assert!(matches!(object.object_type, ObjectType::Address));
+
+ let mut ret = object.clone();
+
+ let mapping = &mappings[ret.mapping];
+ let internal_offset = u16::from_le_bytes([ret.bundle.bytes[0], ret.bundle.bytes[1]]);
+ let segment_offset = crate::mapping::segment_offset(mapping, ret.segment);
+ let addr = (mapping.start + segment_offset + internal_offset).to_le_bytes();
+
+ ret.bundle.bytes[0] = addr[0];
+ ret.bundle.bytes[1] = addr[1];
+
+ ret
+ }
+
+ /// Sets a value for an object identified by `id`. If `overwrite` is set to
+ /// true, then this value will be set even if the id already existed,
+ /// otherwise it will return a ContextError
pub fn set_variable(
&mut self,
id: &PString,
- bundle: &Bundle,
+ object: &Object,
overwrite: bool,
) -> Result<(), ContextError> {
let scope_name = self.name().to_string();
@@ -105,28 +220,22 @@ impl Context {
reason: ContextErrorReason::Redefinition,
});
}
- *sc = bundle.clone();
+ *sc = object.clone();
}
None => {
- scope.insert(id.value.clone(), bundle.to_owned());
+ scope.insert(id.value.clone(), object.to_owned());
}
}
Ok(())
}
- /// Add a new label that has the value as given by the `bundle` parameter.
- /// The actual name of the label does not matter since that is already
- /// referenced as a "variable". This function needs to be called whenever we
- /// are sure that we have the proper address for a label and that we should
- /// track it in order for relative addressing to work.
- pub fn add_label(&mut self, bundle: &Bundle) {
+ /// Add a new label to the list of known labels.
+ pub fn add_label(&mut self, object: &Object) {
let scope_name = self.name().to_string();
let scope = self.labels.get_mut(&scope_name).unwrap();
- // println!("PUSHING: {:#?}", bundle);
-
- scope.push(bundle.clone());
+ scope.push(object.clone());
}
/// Change the current context given a `node`. Returns a tuple which states:
@@ -205,7 +314,8 @@ impl Context {
&self,
rel: isize,
labels_seen: usize,
- ) -> Result<Bundle, ContextError> {
+ mappings: &[Mapping],
+ ) -> Result<Object, ContextError> {
// Bound check: the given 'rel' parameter has a proper value.
assert!(
rel < 5 && rel > -5 && rel != 0,
@@ -244,7 +354,7 @@ impl Context {
// Everything should be fine from here on, simply return the bundle that
// was being referenced.
- Ok(labels[idx as usize].clone())
+ Ok(self.resolve_label(mappings, &labels[idx as usize]))
}
// Pushes a new context given a `node`, which holds the identifier of the