diff options
Diffstat (limited to 'lib/xixanta')
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 248 | ||||
| -rw-r--r-- | lib/xixanta/src/context.rs | 51 | ||||
| -rw-r--r-- | lib/xixanta/src/mapping.rs | 14 | ||||
| -rw-r--r-- | lib/xixanta/src/node.rs | 10 | ||||
| -rw-r--r-- | lib/xixanta/src/opcodes.rs | 1 | ||||
| -rw-r--r-- | lib/xixanta/src/parser.rs | 49 |
6 files changed, 283 insertions, 90 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 0425ede..4b496dc 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -10,7 +10,8 @@ use std::io::Read; use std::ops::Range; /// A Bundle represents a set of bytes that can be encoded as binary data. -#[derive(Debug, Default, Clone)] +/// 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 @@ -30,6 +31,28 @@ pub struct Bundle { /// Whether the cost in cycles is affected when crossing a page boundary. pub affected_on_page: bool, + + 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)] @@ -55,12 +78,23 @@ pub struct Macro { args: Vec<PString>, } +#[derive(Clone, Debug)] +pub struct PendingNode { + segment: usize, + context: String, + bundle_index: usize, + node: PNode, +} + pub struct Assembler { context: Context, literal_mode: Option<LiteralMode>, stage: Stage, macros: HashMap<String, Macro>, can_bundle: bool, + segments: Vec<Segment>, + current_segment: usize, + pending: Vec<PendingNode>, } impl Assembler { @@ -68,18 +102,15 @@ impl Assembler { assert!(!segments.is_empty()); // TODO - let mut offsets = HashMap::new(); - for segment in segments { - offsets.insert(segment.name, 0); - } - - // TODO Self { context: Context::new(), literal_mode: None, stage: Stage::Init, macros: HashMap::new(), can_bundle: true, + segments, + current_segment: 0, + pending: vec![], } } @@ -103,7 +134,10 @@ impl Assembler { // Finally convert the relevant nodes into binary bundles which can be // used by the caller. self.stage = Stage::Bundling; - self.bundle(&parser.nodes) + self.bundle(&parser.nodes)?; + + // TODO + self.crunch_and_resolve_pending() } pub fn eval_context(&mut self, nodes: &[PNode]) -> Result<(), Vec<Error>> { @@ -111,8 +145,15 @@ impl Assembler { let mut current_macro = None; for (idx, node) in nodes.iter().enumerate() { - // TODO: initilize labels on each scope. match node.node_type { + NodeType::Label => { + if let Err(err) = + self.context + .set_variable(&node.value, &Bundle::default(), false) + { + errors.push(Error::Context(err)); + } + } NodeType::Assignment => { // TODO: in fact, we cannot have assignments in many places. if current_macro.is_some() { @@ -125,7 +166,8 @@ impl Assembler { } match self.evaluate_node(node.left.as_ref().unwrap()) { Ok(value) => { - if let Err(err) = self.context.set_variable(&node.value, &value) { + if let Err(err) = self.context.set_variable(&node.value, &value, false) + { errors.push(Error::Context(err)); } } @@ -186,27 +228,47 @@ impl Assembler { } } - pub fn bundle(&mut self, nodes: &Vec<PNode>) -> Result<Vec<Bundle>, Vec<Error>> { - let mut bundles = Vec::new(); + pub fn bundle(&mut self, nodes: &Vec<PNode>) -> Result<(), Vec<Error>> { let mut errors = Vec::new(); for node in nodes { match node.node_type { + NodeType::Label => { + let segment = &self.segments[self.current_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, + }; + + if let Err(err) = self.context.set_variable(&node.value, &bundle, true) { + errors.push(Error::Context(err)); + } + } NodeType::Instruction => { if self.can_bundle { + self.literal_mode = None; match self.evaluate_node(node) { - Ok(bundle) => bundles.push(bundle), + Ok(bundle) => { + if let Err(e) = self.push_bundle(bundle, node) { + errors.push(Error::Eval(e)); + } + } Err(e) => errors.push(Error::Eval(e)), } } } NodeType::Control => { - if let Err(e) = self.evaluate_control_statement(node, &mut bundles) { + if let Err(e) = self.evaluate_control_statement(node) { errors.push(Error::Eval(e)); } } NodeType::Value | NodeType::Call => { - if let Err(e) = self.bundle_call(node, nodes, &mut bundles) { + if let Err(e) = self.bundle_call(node, nodes) { errors.push(Error::Eval(e)); } } @@ -216,18 +278,43 @@ impl Assembler { } if errors.is_empty() { - Ok(bundles) + Ok(()) } else { Err(errors) } } - fn bundle_call( - &mut self, - node: &PNode, - nodes: &[PNode], - bundles: &mut Vec<Bundle>, - ) -> Result<(), EvalError> { + // TODO: maybe split? + pub fn crunch_and_resolve_pending(&mut self) -> Result<Vec<Bundle>, Vec<Error>> { + let mut errors = vec![]; + + for pn in self.pending.clone() { + self.context.force_context_switch(&pn.context); + + match self.evaluate_node(&pn.node) { + Ok(bundle) => { + let current = &mut self.segments[pn.segment]; + current.bundles[pn.bundle_index] = bundle; + } + Err(e) => errors.push(Error::Eval(e)), + } + + self.context.force_context_pop(); + } + + let mut res = vec![]; + for segment in &mut self.segments { + res.append(&mut segment.bundles); + + if let Some(_fill) = segment.fill { + // TODO + } + } + + Ok(res) + } + + fn bundle_call(&mut self, node: &PNode, nodes: &[PNode]) -> Result<(), EvalError> { // Get the macro object for the given identifier. let mcr = self .macros @@ -267,7 +354,7 @@ impl Assembler { for (idx, arg) in args.unwrap().iter().enumerate() { let bundle = self.evaluate_node(arg)?; self.context - .set_variable(margs.nth(idx).unwrap(), &bundle)?; + .set_variable(margs.nth(idx).unwrap(), &bundle, false)?; } } @@ -277,12 +364,41 @@ impl Assembler { .get(mcr.nodes.start..=mcr.nodes.end) .unwrap_or_default() { - bundles.push(self.evaluate_node(node)?); + let bundle = self.evaluate_node(node)?; + self.push_bundle(bundle, node)?; } Ok(()) } + // TODO: move + fn push_bundle(&mut self, bundle: Bundle, node: &PNode) -> Result<(), EvalError> { + let current = &mut self.segments[self.current_segment]; + current.offset += bundle.size as usize; + + if current.offset > current.size { + return Err(EvalError { + line: 0, + message: format!( + "exceeding segment size for '{}' ({} bytes)", + current.name, current.size + ), + }); + } + + if !bundle.resolved { + self.pending.push(PendingNode { + segment: self.current_segment, + context: self.context.name().to_string(), + bundle_index: current.bundles.len(), + node: node.to_owned(), + }); + } + current.bundles.push(bundle); + + Ok(()) + } + fn evaluate_node(&mut self, node: &PNode) -> Result<Bundle, EvalError> { match node.node_type { NodeType::Instruction => Ok(self.evaluate_instruction(node)?), @@ -381,6 +497,7 @@ impl Assembler { address: 0, cycles: 0, affected_on_page: false, + resolved: true, }) } @@ -427,6 +544,7 @@ impl Assembler { address: 0, cycles: 0, affected_on_page: false, + resolved: true, }), } } @@ -498,6 +616,7 @@ impl Assembler { address: 0, cycles: 0, affected_on_page: false, + resolved: true, }) } @@ -569,11 +688,7 @@ impl Assembler { } } - fn evaluate_control_statement( - &mut self, - node: &PNode, - res: &mut Vec<Bundle>, - ) -> Result<(), EvalError> { + fn evaluate_control_statement(&mut self, node: &PNode) -> Result<(), EvalError> { let changed; // This might just be a statement that changes the context (e.g. @@ -588,8 +703,8 @@ impl Assembler { // produces bundles. let function = node.value.value.as_str(); match function { - ".byte" | ".db" => self.push_evaluated_arguments(node, res, 1), - ".addr" | ".dw" => self.push_evaluated_arguments(node, res, 2), + ".byte" | ".db" => self.push_evaluated_arguments(node, 1), + ".addr" | ".word" | ".dw" => self.push_evaluated_arguments(node, 2), _ => Err(EvalError { line: node.value.line, message: format!( @@ -639,12 +754,7 @@ impl Assembler { Ok(bundle) } - fn push_evaluated_arguments( - &mut self, - node: &PNode, - res: &mut Vec<Bundle>, - nbytes: u8, - ) -> Result<(), EvalError> { + fn push_evaluated_arguments(&mut self, node: &PNode, nbytes: u8) -> Result<(), EvalError> { match &node.args { Some(args) => { for arg in args { @@ -674,7 +784,7 @@ impl Assembler { _ => panic!("bad argument when evaluating arguments"), } } - res.push(bundle); + self.push_bundle(bundle, node)?; } } None => { @@ -704,7 +814,7 @@ impl Assembler { fn evaluate_instruction(&mut self, node: &PNode) -> Result<Bundle, EvalError> { let (mode, mut bundle) = match &node.left { Some(_) => self.get_addressing_mode_and_bytes(node)?, - None => (AddressingMode::Implied, Bundle::default()), + None => (AddressingMode::Implied, Bundle::new(true)), }; let mnemonic = node.value.value.to_lowercase(); @@ -749,7 +859,7 @@ impl Assembler { } else if node.right.is_some() { self.get_from_indexed(node) } else { - self.get_from_left(left.as_ref().unwrap()) + self.get_from_left(node, left.as_ref().unwrap()) } } @@ -856,9 +966,13 @@ impl Assembler { } } - fn get_from_left(&mut self, left_arm: &PNode) -> Result<(AddressingMode, Bundle), EvalError> { + fn get_from_left( + &mut self, + base: &PNode, + left_arm: &PNode, + ) -> Result<(AddressingMode, Bundle), EvalError> { if left_arm.value.value.to_lowercase().trim() == "a" { - return Ok((AddressingMode::Implied, Bundle::default())); + return Ok((AddressingMode::Implied, Bundle::new(true))); } let val = self.evaluate_node(left_arm)?; @@ -872,10 +986,13 @@ impl Assembler { } Some(LiteralMode::Plain) => { if val.size > 1 { - Err(EvalError { - message: "immediate is too big".to_string(), - line: left_arm.value.line, - }) + match base.value.value.as_str() { + "jmp" | "jsr" => Ok((AddressingMode::Absolute, val)), + _ => Err(EvalError { + message: "immediate is too big".to_string(), + line: left_arm.value.line, + }), + } } else { Ok((AddressingMode::Immediate, val)) } @@ -1388,7 +1505,41 @@ lda #Scope::Variable } // Labels & branching - // TODO + + #[test] + fn same_segment_labels() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +nop +@hello: + jmp @hello + jmp @end +@end: + nop +"# + .as_bytes(), + ) + .unwrap(); + + assert_eq!(res.len(), 4); + + // jmp @hello + 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); + + // 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); + } + + // TODO: anonymous jumps + // TODO: labels & anonymous beq/blt/etc. // Control statements @@ -1613,4 +1764,7 @@ MACRO(1) you cannot re-assign variables." ); } + + // Segments + // TODO: segments as is, fill data, jmp's } diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/context.rs index 3fceab9..bccac12 100644 --- a/lib/xixanta/src/context.rs +++ b/lib/xixanta/src/context.rs @@ -59,28 +59,35 @@ impl Context { } } - /// Sets a value for a new variable defined in the assignment `node`. - pub fn set_variable(&mut self, id: &PString, bundle: &Bundle) -> Result<(), ContextError> { + /// 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 + pub fn set_variable( + &mut self, + id: &PString, + bundle: &Bundle, + overwrite: bool, + ) -> Result<(), ContextError> { let scope_name = self.name().to_string(); let scope = self.map.get_mut(&scope_name).unwrap(); match scope.get_mut(&id.value) { - Some(_) => { - return Err(ContextError { - message: format!( - "'{}' already defined in {}: you cannot re-assign variables", - id.value, - self.to_human() - ), - line: id.line, - reason: ContextErrorReason::Redefinition, - }) + Some(sc) => { + if !overwrite { + return Err(ContextError { + message: format!( + "'{}' already defined in {}: you cannot re-assign variables", + id.value, + self.to_human() + ), + line: id.line, + reason: ContextErrorReason::Redefinition, + }); + } + *sc = bundle.clone(); } None => { - self.map.insert( - scope_name, - HashMap::from([(id.value.clone(), bundle.to_owned())]), - ); + scope.insert(id.value.clone(), bundle.to_owned()); } } @@ -121,6 +128,16 @@ impl Context { } } + pub fn force_context_switch(&mut self, name: &String) { + self.stack.push(name.to_owned()); + } + + pub fn force_context_pop(&mut self) { + if !self.stack.is_empty() { + self.stack.truncate(self.stack.len() - 1); + } + } + // Pushes a new context given a `node`, which holds the identifier of the // new scope. fn context_push(&mut self, id: &PNode) { @@ -150,7 +167,7 @@ impl Context { } // Returns the name of the current context. - fn name(&self) -> &str { + pub fn name(&self) -> &str { match self.stack.last() { Some(name) => name, None => GLOBAL_CONTEXT, diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs index 9d313a5..238f39d 100644 --- a/lib/xixanta/src/mapping.rs +++ b/lib/xixanta/src/mapping.rs @@ -1,34 +1,46 @@ +use crate::assembler::Bundle; + 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 { name: String::from("HEADER"), start: 0x0000, size: 0x0010, + offset: 0, fill: Some(0x00), + bundles: vec![], }, Segment { name: String::from("VECTORS"), start: 0xFFFA, size: 0x0006, + offset: 0, fill: Some(0x00), + bundles: vec![], }, Segment { name: String::from("CODE"), start: 0x8000, size: 0x7FFA, + offset: 0, fill: Some(0x00), + bundles: vec![], }, Segment { name: String::from("CHARS"), start: 0x0000, size: 0x2000, + offset: 0, fill: Some(0x00), + bundles: vec![], } ]; } @@ -38,7 +50,9 @@ pub struct Segment { pub name: String, pub start: u16, pub size: usize, + pub offset: usize, pub fill: Option<usize>, + pub bundles: Vec<Bundle>, } /* TODO diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs index 18a5402..f55ca11 100644 --- a/lib/xixanta/src/node.rs +++ b/lib/xixanta/src/node.rs @@ -1,5 +1,4 @@ use std::fmt; -use std::ops::Range; /// A Positioned String. That is, a String which also has information on the /// line number and the column range. @@ -11,14 +10,17 @@ pub struct PString { /// Line number where it has been found. pub line: usize, - /// The column range where it has been found. - pub range: Range<usize>, + /// The start column where it has been found. + pub start: usize, + + /// The end column where it has been found. + pub end: usize, } impl PString { /// Returns true if the string has either an empty value or an empty range. pub fn is_empty(&self) -> bool { - self.value.is_empty() || self.range.is_empty() + self.value.is_empty() || (self.start == self.end) } /// Returns an empty tuple if the string contains a valid identifier, or a diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs index 9198e3a..0e7f379 100644 --- a/lib/xixanta/src/opcodes.rs +++ b/lib/xixanta/src/opcodes.rs @@ -727,6 +727,7 @@ lazy_static! { functions.insert(String::from(".db"), Control { has_identifier: false, required_args: None, touches_context: false }); functions.insert(String::from(".word"), Control { has_identifier: false, required_args: None, touches_context: false }); functions.insert(String::from(".dw"), Control { has_identifier: false, required_args: None, touches_context: false }); + functions.insert(String::from(".addr"), Control { has_identifier: false, required_args: None, touches_context: false }); functions }; diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs index c379518..ec15edd 100644 --- a/lib/xixanta/src/parser.rs +++ b/lib/xixanta/src/parser.rs @@ -3,7 +3,6 @@ use crate::node::{NodeType, PNode, PString}; use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS}; use std::cmp::Ordering; use std::io::{self, BufRead, Read}; -use std::ops::Range; /// The Parser struct holds basic data for the current parsing session. #[derive(Default)] @@ -248,7 +247,8 @@ impl Parser { PString { value, line: self.line, - range: Range { start, end }, + start, + end, }, nt, )); @@ -264,10 +264,8 @@ impl Parser { PString { value: id, line: self.line, - range: Range { - start, - end: self.column, - }, + start, + end: self.column, }, NodeType::Value, )) @@ -755,7 +753,7 @@ impl Parser { // of expressions like '#.hibyte'. Then skip whitespaces for super // ugly statements such as '# 20'. This is ugly but we should permit // it. A later linter can yell at a programmer for this. - self.column = id.range.start; + self.column = id.start; self.offset = 0; self.next(); self.skip_whitespace(line); @@ -825,7 +823,7 @@ mod tests { assert_eq!(node.node_type, nt); assert_eq!( node.value.value.as_str(), - line.get(node.value.range.clone()).unwrap() + line.get(node.value.start..node.value.end).unwrap() ); assert_eq!(node.value.value.as_str(), value); } @@ -863,15 +861,15 @@ mod tests { assert!(parser.parse(":".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert!(parser.nodes.first().unwrap().value.value.is_empty()); - assert_eq!(parser.nodes.first().unwrap().value.range.start, 0); - assert_eq!(parser.nodes.first().unwrap().value.range.end, 0); + assert_eq!(parser.nodes.first().unwrap().value.start, 0); + assert_eq!(parser.nodes.first().unwrap().value.end, 0); parser = Parser::default(); assert!(parser.parse(" :".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert!(parser.nodes.first().unwrap().value.value.is_empty()); - assert_eq!(parser.nodes.first().unwrap().value.range.start, 2); - assert_eq!(parser.nodes.first().unwrap().value.range.end, 2); + assert_eq!(parser.nodes.first().unwrap().value.start, 2); + assert_eq!(parser.nodes.first().unwrap().value.end, 2); } #[test] @@ -880,15 +878,15 @@ mod tests { assert!(parser.parse("label:".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert_eq!(parser.nodes.first().unwrap().value.value, "label"); - assert_eq!(parser.nodes.first().unwrap().value.range.start, 0); - assert_eq!(parser.nodes.first().unwrap().value.range.end, 5); + assert_eq!(parser.nodes.first().unwrap().value.start, 0); + assert_eq!(parser.nodes.first().unwrap().value.end, 5); parser = Parser::default(); assert!(parser.parse(" label:".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert_eq!(parser.nodes.first().unwrap().value.value, "label"); - assert_eq!(parser.nodes.first().unwrap().value.range.start, 2); - assert_eq!(parser.nodes.first().unwrap().value.range.end, 7); + assert_eq!(parser.nodes.first().unwrap().value.start, 2); + assert_eq!(parser.nodes.first().unwrap().value.end, 7); } #[test] @@ -901,8 +899,8 @@ mod tests { // Label. assert_eq!(parser.nodes.first().unwrap().value.value, "label"); - assert_eq!(parser.nodes.first().unwrap().value.range.start, 0); - assert_eq!(parser.nodes.first().unwrap().value.range.end, 5); + assert_eq!(parser.nodes.first().unwrap().value.start, 0); + assert_eq!(parser.nodes.first().unwrap().value.end, 5); // Instruction assert_node( @@ -929,7 +927,7 @@ mod tests { let left = node.left.clone().unwrap(); assert_eq!(left.node_type, NodeType::Value); assert_eq!(left.value.value, "20"); - assert_eq!(line.get(left.value.range).unwrap(), "20"); + assert_eq!(line.get(left.value.start..left.value.end).unwrap(), "20"); } } @@ -947,12 +945,16 @@ mod tests { let inner = node.left.clone().unwrap(); assert_eq!(inner.node_type, NodeType::Literal); assert_eq!(inner.value.value, "$20"); - assert_eq!(line.get(inner.value.range).unwrap(), "$20"); + assert_eq!(line.get(inner.value.start..inner.value.end).unwrap(), "$20"); let innerinner = inner.left.clone().unwrap(); assert_eq!(innerinner.node_type, NodeType::Value); assert_eq!(innerinner.value.value, "20"); - assert_eq!(line.get(innerinner.value.range).unwrap(), "20"); + assert_eq!( + line.get(innerinner.value.start..innerinner.value.end) + .unwrap(), + "20" + ); } #[test] @@ -969,7 +971,10 @@ mod tests { let inner = node.left.clone().unwrap(); assert_eq!(inner.node_type, NodeType::Value); assert_eq!(inner.value.value, "Variable"); - assert_eq!(line.get(inner.value.range).unwrap(), "Variable"); + assert_eq!( + line.get(inner.value.start..inner.value.end).unwrap(), + "Variable" + ); } #[test] |
