Skip to content

Commit

Permalink
Initial support for branching
Browse files Browse the repository at this point in the history
Signed-off-by: Miquel Sabaté Solà <[email protected]>
  • Loading branch information
mssola committed Oct 29, 2024
1 parent 02d4e48 commit b8b5741
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 7 deletions.
105 changes: 98 additions & 7 deletions lib/xixanta/src/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,16 @@ impl Assembler {
if self.can_bundle {
self.literal_mode = None;
match self.evaluate_node(node) {
Ok(bundle) => {
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.offset;

if let Err(e) = self.to_relative_address(node, &mut bundle) {
errors.push(Error::Eval(e));
}
}
if let Err(e) = self.push_bundle(bundle, node) {
errors.push(Error::Eval(e));
}
Expand Down Expand Up @@ -292,9 +301,18 @@ impl Assembler {
self.context.force_context_switch(&pn.context);

match self.evaluate_node(&pn.node) {
Ok(bundle) => {
Ok(mut bundle) => {
// TODO: oh boy
bundle.address = self.segments[pn.segment].bundles[pn.bundle_index].address;

if pn.node.is_branch() {
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] = bundle;
current.bundles[pn.bundle_index].bytes = bundle.bytes;
}
Err(e) => errors.push(Error::Eval(e)),
}
Expand Down Expand Up @@ -372,8 +390,9 @@ impl Assembler {
}

// TODO: move
fn push_bundle(&mut self, bundle: Bundle, node: &PNode) -> Result<(), EvalError> {
fn push_bundle(&mut self, mut bundle: Bundle, node: &PNode) -> Result<(), EvalError> {
let current = &mut self.segments[self.current_segment];
bundle.address = current.offset;
current.offset += bundle.size as usize;

if current.offset > current.size {
Expand Down Expand Up @@ -415,6 +434,8 @@ impl Assembler {
// just decimal values.
Ok(self.evaluate_decimal(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(),
Expand Down Expand Up @@ -978,7 +999,7 @@ impl Assembler {
let val = self.evaluate_node(left_arm)?;
match self.literal_mode {
Some(LiteralMode::Hexadecimal) => {
if val.size == 1 {
if base.is_branch() || val.size == 1 {
Ok((AddressingMode::RelativeOrZeropage, val))
} else {
Ok((AddressingMode::Absolute, val))
Expand All @@ -1004,6 +1025,44 @@ impl Assembler {
}),
}
}

fn to_relative_address(&self, node: &PNode, bundle: &mut Bundle) -> Result<(), EvalError> {
if !bundle.resolved {
return Ok(());
}

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 {
return Err(EvalError {
line: node.value.line,
message: "you cannot branch to this location: it's too far away".to_string(),
});
}
diff.to_le_bytes()[0]
} else {
let diff = target - next;
if diff > 127 {
return Err(EvalError {
line: node.value.line,
message: "you cannot branch to this location: it's too far away".to_string(),
});
}
diff.to_le_bytes()[0]
};

bundle.bytes[1] = byte;

Ok(())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -1538,8 +1597,40 @@ nop
assert_eq!(res[2].bytes[2], 0x00);
}

// TODO: anonymous jumps
// TODO: labels & anonymous beq/blt/etc.
// TODO: anonymous jumps & branches

#[test]
fn conditional_branch_to_labels() {
let mut asm = Assembler::new(EMPTY.to_vec());
let res = asm
.assemble(
r#"
nop
@hello:
beq @hello
beq @end
@end:
nop
"#
.as_bytes(),
)
.unwrap();

assert_eq!(res.len(), 4);

// beq @hello
assert_eq!(res[1].size, 2);
assert_eq!(res[1].bytes[0], 0xF0);
assert_eq!(res[1].bytes[1], 0xFE);

// beq @end
assert_eq!(res[2].size, 2);
assert_eq!(res[2].bytes[0], 0xF0);
assert_eq!(res[2].bytes[1], 0x00);
}

// TODO: function calls
// TODO: labels and jumps inside of proc's, macros, etc.

// Control statements

Expand Down
14 changes: 14 additions & 0 deletions lib/xixanta/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,17 @@ pub struct PNode {
/// optional arguments.
pub args: Option<Vec<PNode>>,
}

impl PNode {
pub fn is_branch(&self) -> bool {
if self.node_type != NodeType::Instruction {
return false;
}

let opcode = self.value.value.to_lowercase();
matches!(
opcode.as_str(),
"bcc" | "bcs" | "beq" | "bmi" | "bne" | "bpl" | "bvc" | "bvs"
)
}
}
2 changes: 2 additions & 0 deletions lib/xixanta/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS};
use std::cmp::Ordering;
use std::io::{self, BufRead, Read};

// TODO: allow for labels a la ".Lwhatever:"

/// The Parser struct holds basic data for the current parsing session.
#[derive(Default)]
pub struct Parser {
Expand Down

0 comments on commit b8b5741

Please sign in to comment.