aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta')
-rw-r--r--lib/xixanta/src/assembler.rs204
-rw-r--r--lib/xixanta/src/context.rs115
-rw-r--r--lib/xixanta/src/errors.rs1
-rw-r--r--lib/xixanta/src/node.rs39
4 files changed, 329 insertions, 30 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 0f164d7..316343f 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -32,6 +32,8 @@ pub struct Bundle {
/// 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,
}
@@ -68,8 +70,8 @@ pub enum Stage {
Init,
Parsing,
Context,
- Unrolling,
Bundling,
+ Crunching,
}
#[derive(Clone, Debug)]
@@ -84,6 +86,7 @@ pub struct PendingNode {
context: String,
bundle_index: usize,
node: PNode,
+ labels_seen: usize,
}
pub struct Assembler {
@@ -95,13 +98,13 @@ pub struct Assembler {
segments: Vec<Segment>,
current_segment: usize,
pending: Vec<PendingNode>,
+ labels_seen: usize,
}
impl Assembler {
pub fn new(segments: Vec<Segment>) -> Self {
assert!(!segments.is_empty());
- // TODO
Self {
context: Context::new(),
literal_mode: None,
@@ -111,6 +114,7 @@ impl Assembler {
segments,
current_segment: 0,
pending: vec![],
+ labels_seen: 0,
}
}
@@ -128,15 +132,12 @@ impl Assembler {
self.stage = Stage::Context;
self.eval_context(&parser.nodes)?;
- // TODO: unroll macros, fill out labels, etc.
- self.stage = Stage::Unrolling;
-
// Finally convert the relevant nodes into binary bundles which can be
// used by the caller.
self.stage = Stage::Bundling;
self.bundle(&parser.nodes)?;
- // TODO
+ self.stage = Stage::Crunching;
self.crunch_and_resolve_pending()
}
@@ -147,11 +148,13 @@ impl Assembler {
for (idx, node) in nodes.iter().enumerate() {
match &node.node_type {
NodeType::Label => {
- if let Err(err) =
- self.context
- .set_variable(&node.value, &Bundle::default(), false)
- {
- errors.push(Error::Context(err));
+ if !node.value.is_empty() {
+ if let Err(err) =
+ self.context
+ .set_variable(&node.value, &Bundle::default(), false)
+ {
+ errors.push(Error::Context(err));
+ }
}
}
NodeType::Assignment => {
@@ -248,9 +251,12 @@ impl Assembler {
resolved: true,
};
- if let Err(err) = self.context.set_variable(&node.value, &bundle, true) {
- errors.push(Error::Context(err));
+ if !node.value.is_empty() {
+ if let Err(err) = self.context.set_variable(&node.value, &bundle, true) {
+ errors.push(Error::Context(err));
+ }
}
+ self.context.add_label(&bundle);
}
NodeType::Instruction => {
if self.can_bundle {
@@ -301,6 +307,7 @@ impl Assembler {
let mut errors = vec![];
for pn in self.pending.clone() {
+ self.labels_seen = pn.labels_seen;
self.context.force_context_switch(&pn.context);
match self.evaluate_node(&pn.node) {
@@ -414,6 +421,7 @@ impl Assembler {
context: self.context.name().to_string(),
bundle_index: current.bundles.len(),
node: node.to_owned(),
+ labels_seen: self.context.labels_seen(),
});
}
current.bundles.push(bundle);
@@ -436,9 +444,9 @@ impl Assembler {
// variable), we'll assume that non-prefixed literals are
// just decimal values.
Ok(self.evaluate_decimal(node)?)
+ } else if node.value.is_anonymous_relative_reference() {
+ Ok(self.evaluate_anonymous_relative_reference(node)?)
} else if node.value.is_valid_identifier(true).is_err() {
- // TODO: relative labels
- println!("NODE: {:#?}", node);
// If this is not a valid identifier, just error out.
Err(EvalError {
message: "no prefix was given to operand".to_string(),
@@ -470,6 +478,34 @@ impl Assembler {
}
}
+ fn evaluate_anonymous_relative_reference(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
+ self.literal_mode = Some(LiteralMode::Plain);
+
+ match &self.stage {
+ Stage::Bundling => Ok(Bundle {
+ bytes: [0, 0, 0],
+ size: 2,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ resolved: false,
+ }),
+ Stage::Crunching => {
+ match self
+ .context
+ .get_relative_label(node.value.to_isize(), self.labels_seen)
+ {
+ Ok(bundle) => Ok(bundle),
+ Err(e) => Err(EvalError {
+ line: node.value.line,
+ message: e.message,
+ }),
+ }
+ }
+ _ => panic!("unexpected evaluation of relative reference"),
+ }
+ }
+
fn evaluate_hexadecimal(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
let mut chars = node.value.value.chars();
let mut bytes = [0, 0, 0];
@@ -835,6 +871,8 @@ impl Assembler {
}
fn evaluate_instruction(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
+ self.literal_mode = None;
+
let (mode, mut bundle) = match &node.left {
Some(_) => self.get_addressing_mode_and_bytes(node)?,
None => (AddressingMode::Implied, Bundle::new(true)),
@@ -1008,7 +1046,9 @@ impl Assembler {
}
}
Some(LiteralMode::Plain) => {
- if val.size > 1 {
+ if base.is_branch() {
+ Ok((AddressingMode::RelativeOrZeropage, val))
+ } else if val.size > 1 {
match base.value.value.as_str() {
"jmp" | "jsr" => Ok((AddressingMode::Absolute, val)),
_ => Err(EvalError {
@@ -1036,11 +1076,6 @@ impl Assembler {
let next = (bundle.address + 2) as u16;
let target = u16::from_le_bytes([bundle.bytes[1], bundle.bytes[2]]);
- // println!(
- // "NEXT: {:#?} -- TARGET: {:#?} -- BUNDLE: {:#?}",
- // next, target, bundle
- // );
-
let byte = if target < next {
let diff = target as i16 - next as i16;
if diff < -128 {
@@ -1062,6 +1097,7 @@ impl Assembler {
};
bundle.bytes[1] = byte;
+ bundle.size = 2;
Ok(())
}
@@ -1599,7 +1635,131 @@ nop
assert_eq!(res[2].bytes[2], 0x00);
}
- // TODO: anonymous jumps & branches
+ #[test]
+ fn anonymous_relative_jumps() {
+ let mut asm = Assembler::new(EMPTY.to_vec());
+ let res = asm
+ .assemble(
+ r#"
+nop
+:
+ nop
+@hello:
+ jmp :--
+ jmp :+
+ jmp @hello
+ jmp :+++
+@end:
+ nop
+:
+ nop
+: nop
+"#
+ .as_bytes(),
+ )
+ .unwrap();
+
+ assert_eq!(res.len(), 9);
+
+ // First two nop's
+ assert_eq!(res[0].size, 1);
+ assert_eq!(res[0].bytes[0], 0xEA);
+ assert_eq!(res[1].size, 1);
+ assert_eq!(res[1].bytes[0], 0xEA);
+
+ // jmp :--
+ 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);
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // Three last nop's.
+ assert_eq!(res[6].size, 1);
+ assert_eq!(res[6].bytes[0], 0xEA);
+ assert_eq!(res[7].size, 1);
+ assert_eq!(res[7].bytes[0], 0xEA);
+ assert_eq!(res[8].size, 1);
+ assert_eq!(res[8].bytes[0], 0xEA);
+ }
+
+ #[test]
+ fn anonymous_relative_branches() {
+ let mut asm = Assembler::new(EMPTY.to_vec());
+ let res = asm
+ .assemble(
+ r#"
+nop
+:
+ nop
+@hello:
+ beq :--
+ beq :+
+ beq @hello
+ beq :+++
+@end:
+ nop
+:
+ nop
+: nop
+"#
+ .as_bytes(),
+ )
+ .unwrap();
+
+ assert_eq!(res.len(), 9);
+
+ // First two nop's
+ assert_eq!(res[0].size, 1);
+ assert_eq!(res[0].bytes[0], 0xEA);
+ assert_eq!(res[1].size, 1);
+ assert_eq!(res[1].bytes[0], 0xEA);
+
+ // beq :--
+ assert_eq!(res[2].size, 2);
+ assert_eq!(res[2].bytes[0], 0xF0);
+ assert_eq!(res[2].bytes[1], 0xFD);
+
+ // beq :+
+ assert_eq!(res[3].size, 2);
+ assert_eq!(res[3].bytes[0], 0xF0);
+ assert_eq!(res[3].bytes[1], 0x04);
+
+ // beq @hello
+ assert_eq!(res[4].size, 2);
+ assert_eq!(res[4].bytes[0], 0xF0);
+ assert_eq!(res[4].bytes[1], 0xFA);
+
+ // beq :+++
+ assert_eq!(res[5].size, 2);
+ assert_eq!(res[5].bytes[0], 0xF0);
+ assert_eq!(res[5].bytes[1], 0x02);
+
+ // Three last nop's.
+ assert_eq!(res[6].size, 1);
+ assert_eq!(res[6].bytes[0], 0xEA);
+ assert_eq!(res[7].size, 1);
+ assert_eq!(res[7].bytes[0], 0xEA);
+ assert_eq!(res[8].size, 1);
+ assert_eq!(res[8].bytes[0], 0xEA);
+ }
#[test]
fn conditional_branch_to_labels() {
diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/context.rs
index 6933085..683d750 100644
--- a/lib/xixanta/src/context.rs
+++ b/lib/xixanta/src/context.rs
@@ -4,15 +4,35 @@ 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";
/// Context holds information about the different scopes being defined, the
/// current scope, and has a map of all the variables defined for each scope.
#[derive(Debug)]
pub struct Context {
+ /// Tracks the contexts that we have entered at any given point. The last
+ /// element is the actual context, and it will be empty if we are in the
+ /// global context.
stack: Vec<String>,
+
+ /// Map of variables 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>>,
+
+ /// 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>>,
+}
+
+impl Default for Context {
+ fn default() -> Self {
+ Self::new()
+ }
}
impl Context {
@@ -21,6 +41,7 @@ impl Context {
Context {
stack: vec![],
map: HashMap::from([(String::from(GLOBAL_CONTEXT), HashMap::new())]),
+ labels: HashMap::from([(String::from(GLOBAL_CONTEXT), vec![])]),
}
}
@@ -94,6 +115,18 @@ impl Context {
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) {
+ let scope_name = self.name().to_string();
+ let scope = self.labels.get_mut(&scope_name).unwrap();
+
+ scope.push(bundle.clone());
+ }
+
/// Change the current context given a `node`. Returns a tuple which states:
/// 0. Whether the context has changed.
/// 1. Whether a caller can bundle nodes safely.
@@ -131,16 +164,87 @@ impl Context {
}
}
+ /// Change the current context to the given one identified by `name`,
+ /// disregarding any check. This is to be used when switching a context to
+ /// set a very specific value for that context. You should call
+ /// `force_context_pop` immediately.
pub fn force_context_switch(&mut self, name: &String) {
self.stack.push(name.to_owned());
}
+ /// Remove the last context being used if any. In contrast with
+ /// `context_pop`, this one does not error out, but does nothing in case we
+ /// are in the global context. This is to be used in conjunction with
+ /// `force_context_switch`.
pub fn force_context_pop(&mut self) {
if !self.stack.is_empty() {
self.stack.truncate(self.stack.len() - 1);
}
}
+ /// Returns the amount of labels that have been submitted so far for the
+ /// current scope.
+ pub fn labels_seen(&self) -> usize {
+ let scope_name = self.name().to_string();
+
+ match self.labels.get(&scope_name) {
+ Some(labels) => labels.len(),
+ None => 0,
+ }
+ }
+
+ /// Returns the bundle which is representative for the label being
+ /// referenced in a relative way. The relation is given on the `rel`
+ /// parameter, with a value between -4 or +4 where negative values represent
+ /// previous labels and positive values next ones (e.g. -2 means "2 labels
+ /// before"). This is in relation to `labels_seen`, which states how many
+ /// labels have been seen by the caller at this point.
+ pub fn get_relative_label(
+ &self,
+ rel: isize,
+ labels_seen: usize,
+ ) -> Result<Bundle, ContextError> {
+ // Bound check: the given 'rel' parameter has a proper value.
+ assert!(
+ rel < 5 && rel > -5 && rel != 0,
+ "bad parameter for relative label"
+ );
+
+ // Bound check: you cannot reference a past label that doesn't exist.
+ // This is the programmer's to blame, not on us, so don't assert.
+ if labels_seen == 0 && rel < 0 {
+ return Err(ContextError {
+ line: 0,
+ message: "cannot reference an unknown previous label".to_string(),
+ reason: ContextErrorReason::Label,
+ });
+ }
+
+ // Get the labels as referenced in the current context, and also the
+ // index that we will be using.
+ let scope_name = self.name().to_string();
+ let labels = self.labels.get(&scope_name).unwrap();
+ let idx = if rel > 0 {
+ labels_seen as isize + rel - 1
+ } else {
+ labels_seen as isize + rel
+ };
+
+ // Bound check: is the programmer referencing an "out of bounds" label?
+ // If so then it's a mistake on their part.
+ if idx < 0 || idx >= labels.len() as isize {
+ return Err(ContextError {
+ line: 0,
+ message: "cannot reference bogus label (out of bounds)".to_string(),
+ reason: ContextErrorReason::Label,
+ });
+ }
+
+ // Everything should be fine from here on, simply return the bundle that
+ // was being referenced.
+ Ok(labels[idx as usize].clone())
+ }
+
// Pushes a new context given a `node`, which holds the identifier of the
// new scope.
fn context_push(&mut self, id: &PNode) {
@@ -152,7 +256,8 @@ impl Context {
// Actually push the name to the stack and initialize it on the variable
// map.
self.stack.push(name.clone());
- self.map.entry(name).or_default();
+ self.map.entry(name.clone()).or_default();
+ self.labels.entry(name).or_default();
}
// Pops out the latest context that was pushed.
@@ -194,9 +299,3 @@ impl Context {
}
}
}
-
-impl Default for Context {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/lib/xixanta/src/errors.rs b/lib/xixanta/src/errors.rs
index d5444df..6abf280 100644
--- a/lib/xixanta/src/errors.rs
+++ b/lib/xixanta/src/errors.rs
@@ -45,6 +45,7 @@ pub enum ContextErrorReason {
Redefinition,
UnknownVariable,
BadScope,
+ Label,
Other,
}
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index c787394..993d9c1 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -80,6 +80,45 @@ impl PString {
Ok(())
}
+
+ /// Returns true if this is an anonymous relative reference (i.e. the ':+'
+ /// in something like "beq :+").
+ pub fn is_anonymous_relative_reference(&self) -> bool {
+ let mut it = self.value.chars();
+
+ // An anonymous relative reference must start with ':'. If that's not
+ // the case, return early.
+ if it.next().unwrap_or(' ') != ':' {
+ return false;
+ }
+
+ // Get the first character of the reference. The rest of the string must
+ // be the same as this character.
+ let init = it.next().unwrap_or(' ');
+ if init != '+' && init != '-' {
+ return false;
+ }
+
+ // The rest of the string should match the initial 'c' character (i.e.
+ // either '+' or '-', but never mixed in).
+ it.all(|current| init == current)
+ }
+
+ // Returns the isize value that can be computed assuming that this is an
+ // anonymous relative reference. References to a previous label will have a
+ // negative value, while references to next labels have a positive one.
+ pub fn to_isize(&self) -> isize {
+ let c = self.value.chars().nth(1).unwrap_or(' ');
+
+ // As stated from the documentation, this *has to be* a valid reference.
+ assert!(c == '+' || c == '-', "bad relative reference");
+
+ let res = self.value.chars().filter(|x| *x == c).count() as isize;
+ if c == '-' {
+ return -res;
+ }
+ res
+ }
}
/// The type of control function being used. Use this enum in order to detect