Skip to content

Commit

Permalink
Macros look like they work
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 18, 2024
1 parent ca58683 commit 9b247ca
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["lib/*", "crates/*"]
resolver = "2"

[workspace.package]
rust-version = "1.73"
rust-version = "1.82"
edition = "2021"
license = "GPLv3+"
authors = ["Miquel Sabaté Solà"]
Expand Down
2 changes: 1 addition & 1 deletion lib/xixanta/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
lazy_static = "1.4.0"
lazy_static = "1.5.0"
271 changes: 249 additions & 22 deletions lib/xixanta/src/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ pub enum Stage {
Bundling,
}

#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Macro {
nodes: Range<usize>,
args: Vec<String>,
args: Vec<PString>,
}

pub struct Assembler {
Expand Down Expand Up @@ -142,16 +142,25 @@ impl Assembler {
// TODO: boy this is ugly. In fact, this stupid shit if
// current_macro might not be relevant anymore.
current_macro = Some(&node.left.as_ref().unwrap().value);
// TODO: watch out for weird shit on the name of arguments.
self.macros
.entry(node.left.as_ref().unwrap().value.value.clone())
.or_insert(Macro {
nodes: Range {
start: idx + 1,
end: idx + 1,
},
args: vec![], // TODO
args: node
.args
.clone()
.unwrap_or_default()
.into_iter()
.map(|a| a.value)
.collect::<Vec<_>>(),
});
} else if id == ".endmacro" {
// TODO: if m.nodes.start < idx - 1 => empty macro

if let Some(name) = current_macro {
self.macros
.entry(name.value.clone())
Expand Down Expand Up @@ -210,23 +219,12 @@ impl Assembler {
errors.push(Error::Context(err));
}
}
NodeType::Value | NodeType::Call => match self.macros.get(&node.value.value) {
Some(m) => {
for node in nodes.get(m.nodes.start..=m.nodes.end).unwrap_or_default() {
match self.evaluate_node(node) {
Ok(bundle) => bundles.push(bundle),
Err(e) => errors.push(Error::Eval(e)),
}
}
NodeType::Value | NodeType::Call => {
if let Err(e) = self.bundle_call(node, nodes, &mut bundles) {
errors.push(Error::Eval(e));
}
None => errors.push(Error::Eval(EvalError {
line: node.value.line,
message: format!(
"could not find a macro with the name {}",
node.value.value
),
})),
},
}

_ => {}
}
}
Expand All @@ -238,10 +236,72 @@ impl Assembler {
}
}

fn bundle_call(
&mut self,
node: &PNode,
nodes: &Vec<PNode>,
bundles: &mut Vec<Bundle>,
) -> Result<(), EvalError> {
// Get the macro object for the given identifier.
let mcr = self
.macros
.get(&node.value.value)
.ok_or(EvalError {
line: node.value.line,
message: format!(
"could not find a macro with the name '{}'",
node.value.value
),
})?
.clone();

// Detect missmatches between the number of arguments provided and the
// ones defined by the macro.
let args = node.args.as_ref();
let nargs = match args {
Some(v) => v.len(),
None => 0,
};
if mcr.args.len() != nargs {
return Err(EvalError {
line: node.value.line,
message: format!(
"wrong number of arguments for '{}': {} required but {} given",
node.value.value,
mcr.args.len(),
nargs
),
});
}

// If there are arguments defined by the macro, set their values now.
if nargs > 0 {
let mut margs = (&mcr.args).into_iter();

for (idx, arg) in args.unwrap().into_iter().enumerate() {
let bundle = self.evaluate_node(arg)?;
self.context
.set_variable(margs.nth(idx).unwrap(), &bundle)?;
}
}

// And now replicate the nodes as contained inside of the macro
// definition.
for node in nodes
.get(mcr.nodes.start..=mcr.nodes.end)
.unwrap_or_default()
{
bundles.push(self.evaluate_node(node)?);
}

Ok(())
}

fn evaluate_node(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
match node.node_type {
NodeType::Instruction => Ok(self.evaluate_instruction(node)?),
NodeType::Literal => Ok(self.evaluate_literal(node)?),
NodeType::Control => Ok(self.evaluate_control(node)?),
NodeType::Value => match self.literal_mode {
Some(LiteralMode::Hexadecimal) => Ok(self.evaluate_hexadecimal(node)?),
Some(LiteralMode::Binary) => Ok(self.evaluate_binary(node)?),
Expand Down Expand Up @@ -523,6 +583,11 @@ impl Assembler {
}
}

fn evaluate_control(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
println!("NODE: {:#?}", node);
Ok(Bundle::default())
}

fn evaluate_variable(&mut self, id: &PString) -> Result<Bundle, EvalError> {
match self.context.get_variable(id) {
Ok(value) => Ok(value),
Expand Down Expand Up @@ -941,8 +1006,10 @@ lda #Scope::Variable

#[test]
fn bad_addressing() {
// TODO
// assert_eval_error("unknown #$20", "unknown instruction 'unknown'");
assert_eval_error(
"unknown #$20",
"could not find a macro with the name 'unknown'",
);
assert_eval_error(
"adc ($2002, x)",
"address can only be one byte long on indirect X addressing",
Expand Down Expand Up @@ -1224,5 +1291,165 @@ lda #Scope::Variable
// TODO: .byte, .word, variables in between (e.g. `.byte Variable::Value`, `lda #.hibyte(Variable)`)

// Macros
// TODO

#[test]
fn macro_no_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
let res = asm
.assemble(
r#"
lda #42
.macro MACRO
lda #2
.endmacro
lda #1
MACRO
"#
.as_bytes(),
)
.unwrap();

assert_eq!(res.len(), 3);
let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]];

for i in 0..3 {
assert_eq!(res[i].size, 2);
assert_eq!(res[i].bytes[0], instrs[i][0]);
assert_eq!(res[i].bytes[1], instrs[i][1]);
}
}

#[test]
fn macro_not_enough_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
let res = asm
.assemble(
r#"
lda #42
.macro MACRO(Var)
lda #Var
.endmacro
lda #1
MACRO
"#
.as_bytes(),
)
.unwrap_err();

assert_eq!(
res.first().unwrap().to_string(),
"Evaluation error (line 9): wrong number of arguments for 'MACRO': 1 required but 0 given."
);
}

#[test]
fn macro_too_many_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
let res = asm
.assemble(
r#"
lda #42
.macro MACRO(Var)
lda #Var
.endmacro
lda #1
MACRO(1, 2)
"#
.as_bytes(),
)
.unwrap_err();

assert_eq!(
res.first().unwrap().to_string(),
"Evaluation error (line 9): wrong number of arguments for 'MACRO': 1 required but 2 given."
);
}

#[test]
fn macro_with_one_argument() {
let mut asm = Assembler::new(EMPTY.to_vec());
let res = asm
.assemble(
r#"
lda #42
.macro MACRO(Var)
lda #Var
.endmacro
lda #1
MACRO(2)
"#
.as_bytes(),
)
.unwrap();

assert_eq!(res.len(), 3);
let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]];

for i in 0..3 {
assert_eq!(res[i].size, 2);
assert_eq!(res[i].bytes[0], instrs[i][0]);
assert_eq!(res[i].bytes[1], instrs[i][1]);
}
}

#[test]
fn macro_unknown_arguments() {
let mut asm = Assembler::new(EMPTY.to_vec());
let res = asm
.assemble(
r#"
lda #42
.macro MACRO(Var)
lda #Va
.endmacro
lda #1
MACRO(1)
"#
.as_bytes(),
)
.unwrap_err();

assert_eq!(
res.first().unwrap().to_string(),
"Evaluation error (line 5): 'a' is not a decimal value and \
could not find variable 'Va' in the global scope either."
);
}

#[test]
fn macro_shadow_argument() {
let mut asm = Assembler::new(EMPTY.to_vec());
let res = asm
.assemble(
r#"
Var = 3
lda #42
.macro MACRO(Var)
lda #Va
.endmacro
lda #1
MACRO(1)
"#
.as_bytes(),
)
.unwrap_err();

assert_eq!(
res.first().unwrap().to_string(),
"Evaluation error (line 5): 'Var' already defined in the global scope: \
you cannot re-assign variables."
);
}
}
6 changes: 5 additions & 1 deletion lib/xixanta/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::assembler::Bundle;
use crate::errors::ContextError;
use crate::errors::{ContextError, ContextErrorReason};
use crate::node::{PNode, PString};
use crate::opcodes::CONTROL_FUNCTIONS;
use std::collections::HashMap;
Expand Down Expand Up @@ -48,11 +48,13 @@ impl Context {
self.to_human_with(scope_name)
),
line: id.line,
reason: ContextErrorReason::UnknownVariable,
}),
},
None => Err(ContextError {
message: format!("did not find scope '{}'", scope_name),
line: id.line,
reason: ContextErrorReason::BadScope,
}),
}
}
Expand All @@ -71,6 +73,7 @@ impl Context {
self.to_human()
),
line: id.line,
reason: ContextErrorReason::Redefinition,
})
}
None => {
Expand Down Expand Up @@ -127,6 +130,7 @@ impl Context {
if self.stack.is_empty() {
return Err(ContextError {
message: format!("missplaced '{}' statement", id.value),
reason: ContextErrorReason::BadScope,
line: id.line,
});
}
Expand Down
Loading

0 comments on commit 9b247ca

Please sign in to comment.