aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2024-09-26 15:53:57 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-12 07:41:30 +0100
commit184c39579227c0add80a61d489c242120001d6b6 (patch)
tree48737e0e00b5fb655090a1c9a4fc216dcf278cc0 /lib
parent54e1239fcccb2383e5caa88844a7229b5441c31e (diff)
downloadtools.nes-184c39579227.tar.gz
tools.nes-184c39579227.zip
Re-work the parser from scratch
The initial implementation was incredibly naive on how complex an assembler can actually be. Because of this, it never bothered to define and produce a proper AST, and hence it was overall extremely fragile on (not so) corner cases. This commit re-writes everything from scratch for the parser, and it should really be the last fundamental change to the parser other than minor additions/features here and there. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/xixanta/src/assembler.rs3203
-rw-r--r--lib/xixanta/src/context.rs36
-rw-r--r--lib/xixanta/src/errors.rs3
-rw-r--r--lib/xixanta/src/instruction.rs131
-rw-r--r--lib/xixanta/src/lib.rs1
-rw-r--r--lib/xixanta/src/opcodes.rs27
-rw-r--r--lib/xixanta/src/parser.rs1496
7 files changed, 3275 insertions, 1622 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 1a542ac..1e9854d 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -1,33 +1,52 @@
use crate::context::{Context, PValue};
use crate::errors::ParseError;
-use crate::instruction::{
- AddressingMode, Encodable, Fill, Generic, Instruction, Label, Literal, Node, PString, Scoped,
-};
+use crate::instruction::{AddressingMode, Bundle};
use crate::mapping::{Mapping, Segment};
-use crate::opcodes::{INSTRUCTIONS, OPCODES};
-use std::collections::hash_map::Entry;
+use crate::opcodes::INSTRUCTIONS;
+use crate::parser::{NodeType, PNode, Parser};
use std::collections::HashMap;
-use std::io::{self, BufRead, Read};
-use std::ops::Range;
+use std::io::Read;
// TODO: proper AST: WRITE_PPU_DATA from NES is a good example
// TODO: for christ's sake, automated tests!
// TODO: macros are meant to be global!
// TODO: instead of mapping.nodes having a value of vec<node>, the value should be a Context.
// TODO: proc's, labels, macros, and scopes can be merged dramatically.
+// TODO: allow pointer arithmetic (e.g. 'adc #List::ptr + 1').
// TODO: more to_owned() stuff, more rustacean way of doing things, more ...
// TODO: warning on empty segments
type Result<T> = std::result::Result<T, ParseError>;
+#[derive(Debug, Clone, PartialEq)]
+pub enum LiteralMode {
+ Hexadecimal,
+ Binary,
+ Plain,
+}
+
pub struct Assembler {
line: usize,
column: usize,
context: Context,
+ literal_mode: Option<LiteralMode>,
+ only_context: bool,
+ force_decimal: bool,
mapping: Mapping,
offsets: HashMap<String, usize>,
}
+// Control statements which end up affecting which context we are in.
+const TOUCH_CONTEXT: [&str; 7] = [
+ ".scope",
+ ".endscope",
+ ".proc",
+ ".endproc",
+ ".macro",
+ ".endmacro",
+ ".segment",
+];
+
impl Assembler {
pub fn new(segments: Vec<Segment>) -> Self {
assert!(segments.len() > 0);
@@ -40,6 +59,9 @@ impl Assembler {
Self {
line: 0,
column: 0,
+ literal_mode: None,
+ only_context: false,
+ force_decimal: false,
context: Context::new(),
mapping: Mapping::new(segments),
offsets,
@@ -58,1566 +80,1587 @@ impl Assembler {
}
}
- pub fn assemble_nodes(&mut self, reader: impl Read) -> Result<()> {
- self.from_reader(reader)?;
- self.context.global();
- self.evaluate()?;
- self.resolve_labels()?;
+ pub fn assemble(&mut self, reader: impl Read) -> Result<Vec<Bundle>> {
+ let mut res = vec![];
- Ok(())
- }
+ let mut parser = Parser::new();
+ parser.parse(reader)?;
- pub fn assemble(&mut self, reader: impl Read) -> Result<Vec<&dyn Encodable>> {
- let mut instructions: Vec<&dyn Encodable> = vec![];
+ // println!("{:#?}", parser.nodes);
- self.assemble_nodes(reader)?;
+ // NOTE: first step: unroll macros, update context, set variables.
- let mut idx: usize = 0;
- for segment in &self.mapping.segments {
- let mut size: usize = 0;
+ self.only_context = true;
+ for node in parser.nodes.clone() {
+ match node.node_type {
+ NodeType::Assignment => {
+ self.evaluate_assignment(node)?;
- while idx < segment.start.into() {
- match &segment.fill {
- Some(fill) => instructions.push(fill),
- None => instructions.push(&Fill { value: 0x00 }),
+ println!("{:#?}", self.context);
}
- idx += 1;
- }
-
- for node in &self.mapping.nodes[&segment.name] {
- match node {
- Node::Instruction(instr) => {
- instructions.push(instr);
- size += usize::from(instr.size());
- }
- Node::Literal(lit) => {
- instructions.push(lit);
- size += usize::from(lit.size());
- }
- _ => {}
+ NodeType::Control => {
+ self.evaluate_control(node)?;
}
- }
-
- if size > segment.size {
- return Err(ParseError {
- line: 0,
- message: format!(
- "segment '{}' expected a size of '{}' bytes but '{}' bytes were produced instead",
- segment.name, size, segment.size
- ),
- });
- }
- idx += size;
- if segment.fill.is_none() {
- continue;
- }
-
- while size < segment.size {
- instructions.push(segment.fill.as_ref().unwrap());
- size += 1;
- idx += 1;
+ _ => {}
}
}
+ self.only_context = false;
- Ok(instructions)
- }
+ // Check for unclosed scope definition.
+ if !self.context.is_global() {
+ return Err(self.parser_error(
+ format!(
+ "definition for '{}' has not been closed",
+ self.context.name()
+ )
+ .as_str(),
+ ));
+ }
- pub fn disassemble(&mut self, reader: impl Read) -> Result<Vec<&dyn Encodable>> {
- self.from_byte_reader(reader)?;
+ // NOTE: second step: let's rock.
- let mut instructions: Vec<&dyn Encodable> = vec![];
- for node in self.mapping.current() {
- println!("{:#?}", node);
- match node {
- Node::Instruction(instr) => instructions.push(instr),
- Node::Literal(lit) => instructions.push(lit),
+ for node in parser.nodes {
+ match node.node_type {
+ NodeType::Instruction => {
+ res.push(self.evaluate_node(node)?);
+ }
+ NodeType::Control => {
+ self.evaluate_control(node)?;
+ }
_ => {}
}
}
- Ok(instructions)
- }
+ // NOTE: third step: update addresses of referenced labels.
+ // TODO
- pub fn from_reader<R: Read>(&mut self, reader: R) -> Result<()> {
- for line in io::BufReader::new(reader).lines() {
- // TODO: instead of this, accumulate errors so to give as many
- // errors as possible.
- self.parse_line(line?.as_str())?;
- self.line += 1;
- }
- Ok(())
+ // println!("{:#?}", res);
+ Ok(res)
}
- pub fn parse_line(&mut self, line: &str) -> Result<()> {
- self.column = 0;
+ // pub fn disassemble(&mut self, reader: impl Read) -> Result<Vec<&dyn Encodable>> {
+ // self.from_byte_reader(reader)?;
- if !self.skip_whitespace(line) {
- return Ok(());
- }
+ // let mut instructions: Vec<&dyn Encodable> = vec![];
+ // for node in self.mapping.current() {
+ // println!("{:#?}", node);
+ // match node {
+ // Node::Instruction(instr) => instructions.push(instr),
+ // Node::Literal(lit) => instructions.push(lit),
+ // _ => {}
+ // }
+ // }
+
+ // Ok(instructions)
+ // }
- match self.parse_identifier(line) {
- Some(identifier) => self.parse_from_identifier(identifier, line),
- None => Ok(()),
+ fn evaluate_assignment(&mut self, node: Box<PNode>) -> Result<()> {
+ if self
+ .context
+ .current_mut()
+ .unwrap()
+ .contains_key(&node.value.value)
+ {
+ return Err(ParseError {
+ line: self.line,
+ message: format!(
+ "variable '{}' is being re-assigned: it was previously defined in line {}",
+ node.value.value, node.value.line,
+ ),
+ parse: false,
+ });
}
- }
- pub fn evaluate(&mut self) -> Result<()> {
- for segment in &self.mapping.segments {
- for node in self.mapping.nodes.get_mut(&segment.name).unwrap() {
- match node {
- Node::Instruction(instr) => {
- Self::update_instruction_with_context(instr, &self.context)?;
- instr.address = segment.start;
-
- self.offsets
- .entry(segment.name.clone())
- .and_modify(|value| {
- instr.address += *value as u16;
- *value += usize::from(instr.size())
- })
- .or_insert(instr.size().into());
- }
- Node::Scoped(scope) => {
- if scope.start {
- self.context.push(&scope.identifier.value);
- } else {
- _ = self.context.pop();
- }
- }
- Node::Literal(literal) => {
- Self::update_literal_with_context(literal, &self.context)?;
- self.offsets
- .entry(segment.name.clone())
- .and_modify(|value| *value += usize::from(literal.size()))
- .or_insert(literal.size().into());
- }
- Node::Label(label) => {
- let address =
- usize::from(segment.start) + self.offsets.get(&segment.name).unwrap();
-
- self.context
- .current_mut()
- .unwrap()
- .entry(label.value.clone())
- .and_modify(|e| e.value = address);
- }
- _ => {}
- }
- }
+ if let Some(value_node) = node.left {
+ self.force_decimal = true;
+ println!("{:#?}", value_node);
+ let val = self.evaluate_node(value_node.clone())?;
+ println!("{:#?}", val);
+ self.force_decimal = false;
+
+ self.context.current_mut().unwrap().insert(
+ node.value.value.to_owned(),
+ PValue {
+ node: *value_node,
+ value: val,
+ label: false,
+ },
+ );
}
Ok(())
}
- // TODO: oh boy...
- pub fn resolve_labels(&mut self) -> Result<()> {
- for segment in &self.mapping.segments {
- for node in self.mapping.nodes.get_mut(&segment.name).unwrap() {
- match node {
- Node::Instruction(instr) => {
- if !instr.resolved {
- match &instr.left {
- Some(pstring) => {
- match self.context.current().unwrap().get(&pstring.value) {
- Some(entry) => {
- if instr.mode == AddressingMode::Absolute {
- let bytes = entry.value.to_le_bytes();
- instr.bytes = [bytes[0], bytes[1]];
- } else {
- let diff: isize = entry.value as isize
- - (instr.address as isize + 2);
- if diff < -128 || diff > 127 {
- return Err(instr.mnemonic.parser_error(
- format!("relative addressing out of range")
- .as_str(),
- ));
- }
- let bytes = diff.to_le_bytes();
- instr.bytes = [bytes[0], 0];
- }
- }
- None => {
- return Err(instr.mnemonic.parser_error(
- format!("label '{}' not found", pstring.value)
- .as_str(),
- ))
- }
- }
- }
- None => {
- return Err(instr.mnemonic.parser_error(
- format!("there is no label for the given jump instruction")
- .as_str(),
- ))
- }
- }
- }
- }
- Node::Literal(literal) => {
- if !literal.resolved {
- match self
- .context
- .current()
- .unwrap()
- .get(&literal.identifier.value)
- {
- Some(entry) => {
- let bytes = entry.value.to_le_bytes();
- literal.bytes = [bytes[0], bytes[1]];
- }
- None => {
- return Err(literal.identifier.parser_error(
- format!(
- "'{}' is neither a known variable or label at this scope",
- literal.identifier.value
- )
- .as_str(),
- ))
- }
- }
- }
+ fn evaluate_node(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ match node.node_type {
+ NodeType::Control => self.evaluate_control(node),
+ NodeType::Literal => self.evaluate_literal(node),
+ NodeType::Instruction => self.evaluate_instruction(node),
+ NodeType::Value => match self.literal_mode {
+ Some(LiteralMode::Hexadecimal) => self.evaluate_hexadecimal(node),
+ Some(LiteralMode::Binary) => self.evaluate_binary(node),
+ Some(LiteralMode::Plain) => self.evaluate_decimal(node),
+ None => {
+ if self.force_decimal {
+ self.evaluate_decimal(node)
+ } else {
+ Err(self.parser_error("no prefix was given to operand"))
}
- _ => {}
}
- }
+ },
+ // TODO
+ _ => Ok(Bundle::new()),
}
-
- Ok(())
}
- fn update_instruction_with_context(instr: &mut Instruction, context: &Context) -> Result<()> {
- // To keep things simple, we remove out the `implied` case and we parse
- // further with a known `Some` value for the base algorithm implemented
- // in `update_instruction_and_bytes`.
- if instr.left.is_some() {
- Self::update_addressing_and_bytes(instr, context)?;
+ fn evaluate_instruction(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ let mnemonic = node.value.value.to_lowercase();
+
+ let (mode, mut bundle) = if node.left.is_some() {
+ self.get_addressing_mode_and_bytes(node)?
} else {
- instr.mode = AddressingMode::Implied;
- }
+ (AddressingMode::Implied, Bundle::new())
+ };
- // Now that we have the addressing mode and the bytes, we can fill out
- // the rest of it by fetching the values on `INSTRUCTIONS`.
- match INSTRUCTIONS.get(&instr.mnemonic.value.to_lowercase()) {
- Some(entries) => match entries.get(&instr.mode) {
+ match INSTRUCTIONS.get(&mnemonic) {
+ Some(entries) => match entries.get(&mode) {
Some(values) => {
- instr.cycles = values.cycles;
- instr.opcode = values.opcode;
- instr.size = values.size;
- instr.affected_on_page = values.affected_on_page;
+ bundle.cycles = values.cycles;
+ bundle.size = values.size;
+ bundle.affected_on_page = values.affected_on_page;
+ bundle.bytes[2] = bundle.bytes[1];
+ bundle.bytes[1] = bundle.bytes[0];
+ bundle.bytes[0] = values.opcode.to_le_bytes()[0];
}
None => {
- return Err(instr.mnemonic.parser_error(
+ return Err(self.parser_error(
format!(
- "bad addressing mode '{}' for the instruction '{}'",
- &instr.mode, &instr.mnemonic.value
+ "cannot use {} addressing mode for the instruction '{}'",
+ mode, mnemonic
)
.as_str(),
- ));
+ ))
}
},
None => {
- return Err(instr.mnemonic.parser_error(
- format!("unknown instruction '{}'", &instr.mnemonic.value).as_str(),
- ));
+ return Err(self.parser_error(format!("unknown instruction {}", mnemonic).as_str()))
}
}
-
- Ok(())
+ Ok(bundle)
}
- fn update_addressing_and_bytes(instr: &mut Instruction, context: &Context) -> Result<()> {
- // `unwrap()` is guaranteed to work by the caller.
- let left = instr.left.as_ref().unwrap();
-
- // We will first try to check if there's any variable involved on the
- // left arm and replace the string if so. This will greatly simplify
- // things down the line. That being said, there is a special reserved
- // case, which is the implied addressing by using "a". In this case, we
- // want to ensure that we assume an implied addressing and not a
- // variable named "a".
- if left.value.to_lowercase() == "a" {
- instr.mode = AddressingMode::Implied;
+ fn get_addressing_mode_and_bytes(
+ &mut self,
+ node: Box<PNode>,
+ ) -> Result<(AddressingMode, Bundle)> {
+ if node.clone().left.unwrap().node_type == NodeType::Indirection {
+ self.get_from_indirect(node)
+ } else if node.right.is_some() {
+ self.get_from_indexed(node)
} else {
- let (nleft, resolved) = Self::replace_variable(left, context)?;
- // TODO
- instr.resolved = resolved;
- if !resolved {
- if instr.mnemonic.value == "jmp" {
- instr.mode = AddressingMode::Absolute;
- } else {
- instr.mode = AddressingMode::RelativeOrZeropage;
- }
- }
+ self.get_from_left(node)
+ }
+ }
- if nleft.value.starts_with('$') {
- // This is an address. At this point we should assume that the
- // left node contains the address itself, and that the right one
- // will contain whether there is indexing.
-
- let string = nleft.value.chars().as_str();
- instr.bytes = Self::parse_hex_from(string, &nleft, true, false, true)?;
-
- match &instr.right {
- Some(xy) => match xy.value.to_lowercase().as_str() {
- "x" => {
- if string.len() == 3 {
- instr.mode = AddressingMode::ZeropageIndexedX;
- } else {
- instr.mode = AddressingMode::IndexedX;
- }
- }
- "y" => {
- if string.len() == 3 {
- instr.mode = AddressingMode::ZeropageIndexedY;
- } else {
- instr.mode = AddressingMode::IndexedY;
- }
- }
- _ => return Err(xy.parser_error("index is neither X nor Y")),
- },
- None => {
- if string.len() == 3 {
- instr.mode = AddressingMode::RelativeOrZeropage;
- } else {
- instr.mode = AddressingMode::Absolute;
- }
+ fn get_from_indirect(&mut self, node: Box<PNode>) -> Result<(AddressingMode, Bundle)> {
+ let left = node.left.unwrap();
+
+ match node.right {
+ Some(right) => {
+ if right.value.value.trim().to_lowercase() == "y" {
+ if left.right.is_some() {
+ return Err(self.parser_error(
+ "it has to be either X addressing or Y addressing, not all at once",
+ ));
+ }
+
+ let val = self.evaluate_node(left.left.unwrap())?;
+ if val.size != 1 {
+ return Err(self.parser_error(
+ "address can only be one byte long on indirect Y addressing",
+ ));
}
+ return Ok((AddressingMode::IndirectY, val));
}
- } else if nleft.value.starts_with('#') {
- // Immediate addressing in any case: hexadecimal, binary or
- // decimal. Hence, just figure out the character being used and
- // call the right function for it.
-
- let mut chars = nleft.value.chars();
- chars.next();
- let string = chars.as_str();
-
- instr.bytes = Self::parse_numeric(string, &nleft, false)?;
- instr.mode = AddressingMode::Immediate;
- } else if nleft.value.starts_with('(') {
- // Indirect addressing. In this case the left arm can be further
- // subdivided. That is, indirect X-indexing is represented like
- // so: `instr ($NN, x)`. Hence, first of all we have to figure
- // out whether there is a subdivision.
-
- let (left1, oleft2) = Self::split_left_arm(&nleft)?;
- match oleft2 {
- Some(left2) => {
- // There is subdivision. Thus, we have to assume
- // indirect X-indexing, which means that the right arm
- // should be None and that the right side of the left
- // node must match the X register. Other than that, the
- // address being referenced must be zero page.
- if instr.right.is_some() {
- return Err(instr.right.as_ref().unwrap().parser_error(
- "bad indirect mode, expecting an indirect X-indexed addressing mode"
- ));
- }
- if left2.value.to_lowercase() != "x" {
- return Err(left2.parser_error(
- "the index in indirect X-indexed addressing must be X",
+ return Err(
+ self.parser_error("only the Y index is allowed on indirect Y addressing")
+ );
+ }
+ None => match left.right {
+ Some(right) => {
+ if right.value.value.trim().to_lowercase() == "x" {
+ let val = self.evaluate_node(left.left.unwrap())?;
+ if val.size != 1 {
+ return Err(self.parser_error(
+ "address can only be one byte long on indirect X addressing",
));
}
- match Self::parse_hex_from(&left1.value, &left1, false, false, true) {
- Ok(bytes) => instr.bytes = bytes,
- Err(e) => {
- let msg = String::from(
- "when parsing an instruction with indirect X-indexed addressing: ",
- ) + &e.message;
- return Err(left1.parser_error(msg.as_str()));
- }
- }
- instr.mode = AddressingMode::IndirectX;
- }
- None => {
- // There is no subdivision on the left arm. Hence, if
- // there is something on the right arm then we must
- // assume indirect Y-index addressing, and if not then
- // it's indirect addressing with no indices involvved.
- if instr.right.is_some() {
- if instr.right.as_ref().unwrap().value.to_lowercase() != "y" {
- return Err(instr.right.as_ref().unwrap().parser_error(
- "the index in indirect Y-indexed addressing must be Y",
- ));
- }
- match Self::parse_hex_from(&left1.value, &left1, false, false, true) {
- Ok(bytes) => instr.bytes = bytes,
- Err(e) => {
- let msg = String::from(
- "when parsing an instruction with indirect Y-indexed addressing: ",
- ) + &e.message;
- return Err(left1.parser_error(msg.as_str()));
- }
- }
- instr.mode = AddressingMode::IndirectY;
- } else {
- instr.bytes =
- Self::parse_hex_from(&left1.value, &left1, true, true, true)?;
- instr.mode = AddressingMode::Indirect;
- }
+ return Ok((AddressingMode::IndirectX, val));
}
+ return Err(
+ self.parser_error("only the X index is allowed on indirect X addressing")
+ );
}
- } else {
- // At this point all of the syntax cases have been exhausted:
- // the programmer messed up. From this point on we try to figure
- // out how they messed up.
-
- if nleft.value.starts_with('=') {
- return Err(instr.mnemonic.parser_error(
- format!(
- "cannot use '{}' in an assignment because it's a word reserved for an instruction mnemonic",
- instr.mnemonic.value
- ).as_str(),
- ));
+ None => {
+ let val = self.evaluate_node(left.left.unwrap())?;
+ if val.size != 2 {
+ return Err(self.parser_error("expecting a full 16-bit address"));
+ }
+ return Ok((AddressingMode::Indirect, val));
}
- // TODO:
- // instr.mode = AddressingMode::Absolute;
- // return Err(instr.mnemonic.parser_error(
- // format!(
- // "unknown addressing mode for instruction '{}'",
- // instr.mnemonic.value
- // )
- // .as_str(),
- // ));
- }
+ },
}
-
- Ok(())
}
- fn update_literal_with_context(literal: &mut Literal, context: &Context) -> Result<()> {
- // If it has already been set, skip it.
- // TODO: add a proper `is_set` thingie to it instead of this hack.
- if literal.bytes[0] != 0 || literal.bytes[1] != 0 {
- return Ok(());
- }
-
- // Evaluate any possible variable being used inside of this literal.
- let (evaled, resolved) = Self::replace_variable(&literal.identifier, context)?;
+ fn get_from_indexed(&mut self, node: Box<PNode>) -> Result<(AddressingMode, Bundle)> {
+ self.literal_mode = None; // TODO: needed?
+ let val = self.evaluate_node(node.left.unwrap())?;
- // It may happen that the literal is just a label that is to be resolved
- // in the future. If so, let's leave early.
- literal.resolved = resolved;
- if !resolved {
- return Ok(());
+ if let Some(lm) = &self.literal_mode {
+ if *lm != LiteralMode::Hexadecimal {
+ return Err(self.parser_error("indexed addressing only works with addresses"));
+ }
}
- // Parse the numeric value after a possible variable has been replaced.
- let two_bytes_allowed = literal.size == 2;
- let res = Self::parse_numeric(
- evaled.value.as_str(),
- &literal.identifier,
- two_bytes_allowed,
- );
-
- // And finally assign the computed bytes.
- match res {
- Ok(bytes) => {
- literal.bytes = bytes;
- Ok(())
+ match node.right.unwrap().value.value.to_lowercase().trim() {
+ "x" => {
+ if val.size == 1 {
+ Ok((AddressingMode::ZeropageIndexedX, val))
+ } else {
+ Ok((AddressingMode::IndexedX, val))
+ }
}
- Err(e) => {
- let msg = String::from("when parsing a data literal: ") + &e.message;
- Err(literal.identifier.parser_error(msg.as_str()))
+ "y" => {
+ if val.size == 1 {
+ Ok((AddressingMode::ZeropageIndexedY, val))
+ } else {
+ Ok((AddressingMode::IndexedY, val))
+ }
}
+ _ => Err(self.parser_error("can only use X and Y as indices")),
}
}
- fn parse_numeric(string: &str, node: &PString, two_bytes_allowed: bool) -> Result<[u8; 2]> {
- if string.starts_with('$') {
- Ok(Self::parse_hex_from(
- string,
- node,
- two_bytes_allowed,
- false,
- true,
- )?)
- } else if string.starts_with('%') {
- Ok([Self::parse_binary_from(string, node)?, 0])
- } else {
- Ok([Self::parse_decimal_from(string, node)?, 0])
- }
- }
+ fn get_from_left(&mut self, node: Box<PNode>) -> Result<(AddressingMode, Bundle)> {
+ let left = node.left.unwrap();
- fn split_left_arm(node: &PString) -> Result<(PString, Option<PString>)> {
- let mut chars = node.value.chars();
- chars.next();
- let string = chars.as_str();
-
- match string.find(|c: char| c == ',') {
- Some(idx) => {
- let left1 = string.get(..idx).unwrap_or("").trim();
- let left2 = string.get(idx + 1..).unwrap_or("").trim();
-
- Ok((
- PString {
- value: left1.to_string(),
- line: node.line,
- range: Range {
- start: node.range.start + 1,
- end: node.range.start + 1 + left1.len(),
- },
- },
- Some(PString {
- value: left2.to_string(),
- line: node.line,
- range: Range {
- start: node.range.start + 1 + idx,
- end: node.range.start + 1 + idx + left2.len(),
- },
- }),
- ))
- }
- None => Ok((
- PString {
- value: string.to_string(),
- line: node.line,
- range: Range {
- start: node.range.start + 1,
- end: node.range.end,
- },
- },
- None,
- )),
+ if left.value.value.to_lowercase().trim() == "a" {
+ return Ok((AddressingMode::Implied, Bundle::new()));
}
- }
- fn parse_binary_from(string: &str, node: &PString) -> Result<u8> {
- let mut value = 0;
- let mut shift = 0;
+ self.literal_mode = None; // TODO: needed?
+ let val = self.evaluate_node(left)?;
- for c in string.get(1..).unwrap_or("").chars().rev() {
- if c == '1' {
- let val = 1 << shift;
- value += val;
- } else if c != '0' {
- return Err(
- node.parser_error(format!("bad binary format for '{}'", string).as_str())
- );
+ match self.literal_mode {
+ Some(LiteralMode::Hexadecimal) => {
+ if val.size == 1 {
+ Ok((AddressingMode::RelativeOrZeropage, val))
+ } else {
+ Ok((AddressingMode::Absolute, val))
+ }
+ }
+ Some(LiteralMode::Plain) => {
+ if val.size > 1 {
+ Err(self.parser_error("immediate is too big"))
+ } else {
+ Ok((AddressingMode::Immediate, val))
+ }
+ }
+ _ => {
+ Err(self
+ .parser_error("left arm of instruction is neither an address nor an immediate"))
}
-
- shift += 1;
- }
-
- if shift < 8 {
- Err(node.parser_error("missing binary digits to get a full byte"))
- } else if shift > 8 {
- Err(node.parser_error("too many binary digits for a single byte"))
- } else {
- Ok(value)
}
}
- // TODO: returns if resolved
- fn replace_variable(node: &PString, context: &Context) -> Result<(PString, bool)> {
- match node
- .value
- .find(|c: char| c.is_alphabetic() || c == '_' || c == '@')
- {
- Some(idx) => {
- // Before doing any replacement, let's check the character
- // before the one that was found. In this case, if it was a
- // proper ASCII digit, then it cannot be a variable but it's
- // part of a numeric literal (e.g. '1A'): then just let the
- // different numeric parsing functions do their job.
- if idx > 0 {
- let prev = node.value.chars().nth(idx - 1).unwrap_or(' ');
- if prev.is_ascii_digit() {
- return Ok((node.clone(), true));
- }
+ fn evaluate_control(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ let id = node.value.value.to_lowercase();
+ let id_str = id.as_str();
+
+ // If we are just dealing with context resolution/assignment and the
+ // current control statement does not matter on that regard, just skip
+ // it.
+ // if self.only_context && !TOUCH_CONTEXT.contains(&id_str) {
+ // return Ok(Bundle::new());
+ // }
+
+ match id_str {
+ ".hibyte" => self.evaluate_hilo_byte(node.args.unwrap_or(vec![]), true),
+ ".lobyte" => self.evaluate_hilo_byte(node.args.unwrap_or(vec![]), false),
+ ".scope" => self.evaluate_scope_definition(node),
+ ".endscope" => self.evaluate_scope_end(),
+ // ".segment" => self.parse_segment_definition(&id, line),
+ // ".byte" | ".db" => self.parse_literal_bytes(&id, line, false),
+ // ".word" | ".dw" | ".addr" => self.parse_literal_bytes(&id, line, true),
+ // ".proc" => self.parse_proc_definition(&id, line),
+ // ".endproc" => self.parse_proc_end(&id),
+ // ".macro" => self.parse_macro_definition(&id, line),
+ // ".endmacro" => self.parse_macro_end(&id),
+ _ => Err(self.parser_error(format!("unknown control statement '{}'", id).as_str())),
+ }
+ }
+
+ fn evaluate_literal(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ let mut prev = None;
+ self.literal_mode = None;
+
+ let ret = match node.value.value.chars().nth(0) {
+ Some(prefix) => {
+ if prefix == '$' {
+ prev = Some(LiteralMode::Hexadecimal);
+ } else if prefix == '%' {
+ prev = Some(LiteralMode::Binary);
+ } else {
+ prev = Some(LiteralMode::Plain);
}
+ self.literal_mode = prev.clone();
+ self.evaluate_node(node.left.unwrap())
+ }
+ None => Err(self.parser_error("no prefix was given to operand")),
+ };
- // The variable might still be before an inner comma (e.g.
- // sta ($20, x)). We will assume that variables can happen
- // only before that.
- let end = node.value.find(',').unwrap_or(node.value.len());
- let mut string = node.value.get(idx..end).unwrap_or("");
- let tail = node.value.get(end..).unwrap_or("");
-
- // Get the context that might be being referenced.
- let ctxt = match string.find("::") {
- Some(_) => {
- let tctxt = string.rsplit_once("::").unwrap_or(("", ""));
- if tctxt.0.is_empty() {
- context.current()
- } else {
- string = tctxt.1;
- context.find(tctxt.0)
- }
- }
- None => context.current(),
- };
-
- match ctxt {
- Some(hash) => {
- // If there was a comma before the "variable" (i.e. idx >
- // end and hence string == ""), or this is just the regular
- // X or Y index, just return early.
- match string.to_lowercase().as_str() {
- "x" | "y" | "" => return Ok((node.clone(), true)),
- _ => {}
- }
+ self.literal_mode = prev;
- // It's not any of the indices, let's look for a match on
- // the current scope.
- match hash.get(string) {
- Some(var) => {
- // If this is just a memory address (e.g.
- // label), then just return it as is.
- if var.label {
- return Ok((node.clone(), false));
- }
-
- let value = String::from(node.value.get(..idx).unwrap_or(""))
- + var.node.value.as_str();
- Ok((
- PString {
- value: value.clone() + tail,
- line: node.line,
- range: Range {
- start: node.range.start,
- end: node.range.start + value.len(),
- },
- },
- true,
- ))
- }
- None => {
- // If a variable could not be found, check that
- // this is not a purely hexadecimal number (e.g.
- // 'AA'). If that's the case, then just return
- // its value.
- if Self::parse_hex_from(string, node, true, false, false).is_ok() {
- return Ok((node.clone(), true));
- }
-
- // We've tried hard to not assume the programmer
- // messing up, but there's no other way around
- // it: it's an "unknown variable" error.
- return Err(node.parser_error(
- format!("unknown variable '{}'", string).as_str(),
- ));
- }
- }
- }
- None => {
- Err(node.parser_error(format!("unknown scope '{}'", "Global").as_str()))
- }
- }
- }
- None => Ok((node.clone(), true)),
- }
+ ret
}
- // Parse an hexadecimal value from the given `string`. A `node` must also be
- // supplied so a ParseError can be pushed inside of it in case anything goes
- // wrong. Other than that, there are three boolean parameters that have to
- // be passed:
- // - `two_bytes_allowed`: the value can be 8-bit or 16-bit long.
- // - `exactly_two_bytes`: the value has to be exactly 16-bit long.
- // - `char_given`: whether the string starts with a '$' character or not.
- fn parse_hex_from(
- string: &str,
- node: &PString,
- two_bytes_allowed: bool,
- exactly_two_bytes: bool,
- char_given: bool,
- ) -> Result<[u8; 2]> {
- let mut chars = string.chars();
- let len = if char_given {
- chars.next();
- string.len() - 1
- } else {
- string.len()
- };
+ fn evaluate_hexadecimal(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ let mut chars = node.value.value.chars();
+ let mut bytes = [0, 0, 0];
+ let size: u8;
- // TODO: re-visit this "exactly_two_byes bs"
- match len {
+ match node.value.value.len() {
1 => {
- if exactly_two_bytes {
- return Err(node.parser_error("expecting a full 16-bit address"));
- }
- let i = Self::char_to_hex(node, chars.next())? * 16;
-
- Ok([i, 0])
+ bytes[0] = self.char_to_hex(chars.next())?;
+ size = 1;
}
2 => {
- if exactly_two_bytes {
- return Err(node.parser_error("expecting a full 16-bit address"));
- }
- let mut i = Self::char_to_hex(node, chars.next())? * 16;
- i += Self::char_to_hex(node, chars.next())?;
-
- Ok([i, 0])
+ bytes[0] = self.char_to_hex(chars.next())? * 16;
+ bytes[0] += self.char_to_hex(chars.next())?;
+ size = 1;
}
3 => {
- if !two_bytes_allowed {
- return Err(node.parser_error("only one byte of data is allowed here"));
- }
-
- let hi = Self::char_to_hex(node, chars.next())? * 16;
-
- let mut lo = Self::char_to_hex(node, chars.next())? * 16;
- lo += Self::char_to_hex(node, chars.next())?;
-
- Ok([lo, hi])
+ bytes[1] = self.char_to_hex(chars.next())?;
+ bytes[0] = self.char_to_hex(chars.next())? * 16;
+ bytes[0] += self.char_to_hex(chars.next())?;
+ size = 2;
}
4 => {
- if !two_bytes_allowed {
- return Err(node.parser_error("only one byte of data is allowed here"));
- }
-
- let mut hi = Self::char_to_hex(node, chars.next())? * 16;
- hi += Self::char_to_hex(node, chars.next())?;
-
- let mut lo = Self::char_to_hex(node, chars.next())? * 16;
- lo += Self::char_to_hex(node, chars.next())?;
-
- Ok([lo, hi])
- }
- _ => {
- if two_bytes_allowed {
- Err(node.parser_error("expecting a number of 1 to 4 hexadecimal digits"))
- } else if exactly_two_bytes {
- Err(node.parser_error("expecting a number of 4 hexadecimal digits"))
- } else {
- Err(node.parser_error("expecting a number of 2 hexadecimal digits"))
- }
+ bytes[1] = self.char_to_hex(chars.next())? * 16;
+ bytes[1] += self.char_to_hex(chars.next())?;
+ bytes[0] = self.char_to_hex(chars.next())? * 16;
+ bytes[0] += self.char_to_hex(chars.next())?;
+ size = 2;
}
+ _ => return Err(self.parser_error("expecting a number of 1 to 4 hexadecimal digits")),
}
+
+ Ok(Bundle {
+ bytes,
+ size,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ })
}
- fn char_to_hex(node: &PString, oc: Option<char>) -> Result<u8> {
+ fn char_to_hex(&mut self, oc: Option<char>) -> Result<u8> {
match oc {
Some(c) => match c.to_digit(16) {
Some(c) => Ok(c as u8),
- None => Err(node.parser_error("could not convert digit to hexadecimal")),
+ None => Err(self.parser_error("could not convert digit to hexadecimal")),
},
- None => Err(node.parser_error("digit out of bounds")),
+ None => Err(self.parser_error("digit out of bounds")),
}
}
- fn parse_decimal_from(string: &str, node: &PString) -> Result<u8> {
+ fn evaluate_binary(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ let string = node.value.value.as_str();
let mut value = 0;
- let mut shift = 1;
-
- if string.is_empty() {
- return Err(node.parser_error("empty decimal literal"));
- }
+ let mut shift = 0;
for c in string.chars().rev() {
- if shift > 100 {
- return Err(node.parser_error("decimal value is too big"));
- }
- if c != '0' {
- match c.to_digit(10) {
- Some(digit) => {
- value += digit * shift;
- }
- None => {
- return Err(
- node.parser_error(format!("'{}' is not a decimal value", c).as_str())
- )
- }
- }
- }
-
- shift *= 10;
- }
- if value > 255 {
- return Err(node.parser_error("decimal value is too big"));
- }
-
- Ok(value as u8)
- }
-
- // Advances `self.column` until a non-whitespace character is found. Returns
- // false if the line can be skipped entirely, true otherwise.
- fn skip_whitespace(&mut self, line: &str) -> bool {
- for c in line.get(self.column..).unwrap_or("").chars() {
- if !c.is_whitespace() {
- if c == ';' {
- return false;
- }
- return true;
- }
-
- self.column += 1;
- }
-
- true
- }
-
- // Returns a PString object which holds the information for an identifier.
- //
- // NOTE: this function assumes that `self.column` points to a non-whitespace
- // character.
- fn parse_identifier(&mut self, line: &str) -> Option<PString> {
- let column = self.column;
-
- // For the general case we just need to iterate until a whitespace
- // character or an inline comment is found. Then our PString object
- // is merely whatever is on the column..self.column range.
- for c in line.get(column..).unwrap_or("").chars() {
- if c.is_whitespace() || c == ';' {
- let range = Range {
- start: column,
- end: self.column,
- };
-
- return Some(PString {
- value: String::from(line.get(range.clone()).unwrap_or("").trim()),
- line: self.line,
- range,
- });
+ if c == '1' {
+ let val = 1 << shift;
+ value += val;
+ } else if c != '0' {
+ return Err(
+ self.parser_error(format!("bad binary format for '{}'", string).as_str())
+ );
}
- self.column += 1;
+ shift += 1;
}
- // Otherwise, we might be at a point whether there is nothing (e.g. an
- // empty line), or the line is merely the identifier (e.g. instruction
- // with implied addressing).
- let range = Range {
- start: column,
- end: line.len(),
- };
- let id = String::from(line.get(range.clone()).unwrap_or("").trim());
- if id.is_empty() {
- None
+ if shift < 8 {
+ Err(self.parser_error("missing binary digits to get a full byte"))
+ } else if shift > 8 {
+ Err(self.parser_error("too many binary digits for a single byte"))
} else {
- Some(PString {
- value: id,
- line: self.line,
- range,
+ Ok(Bundle {
+ bytes: [value as u8, 0, 0],
+ size: 1,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
})
}
}
- // Given a PString object which acts as the identifier, try to parse the
- // rest of the line depending on whether it's an instruction (e.g. `adc
- // $20`), a control statement (e.g. `.macro whatever`) or a general
- // statement (e.g. `Var = $10`).
- fn parse_from_identifier(&mut self, id: PString, line: &str) -> Result<()> {
- if id.value.starts_with('.') {
- // If the statement starts with a '.', it's guaranteed to be a
- // control statement.
- self.parse_control(id, line)
- } else {
- // Otherwise, we will parse it either as an instruction or a general
- // statement depending on whether the parsed identifier is a valid
- // instruction mnemonic or not.
- match INSTRUCTIONS.get(&id.value) {
- Some(_instr) => self.parse_instruction(id, line),
- None => self.parse_statement(id, line),
- }
+ fn evaluate_decimal(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ let string = node.value.value.as_str();
+ if string.is_empty() {
+ return Err(self.parser_error("empty decimal literal"));
}
- }
- fn parse_control(&mut self, mut id: PString, line: &str) -> Result<()> {
- self.skip_whitespace(line);
-
- // Try to handle arguments passed to the control statement.
- let mut args: Vec<String> = vec![];
- if let Some(open) = line.find(|c: char| c == '(') {
- match line.find(|c: char| c == ')') {
- Some(close) => {
- id.value = id
- .value
- .get(..open)
- .unwrap_or(id.value.as_str())
- .to_string();
- id.range.end = open;
-
- args = line
- .get(open + 1..close)
- .unwrap_or(" ")
- .split(',')
- .map(|w| w.trim().to_string())
- .collect::<Vec<_>>();
- }
- None => {
- return Err(id.parser_error(format!("open parenthesis on macro call").as_str()))
+ match self.do_evaluate_decimal(string) {
+ Ok(val) => Ok(val),
+ Err(e) => {
+ if e.parse {
+ Err(e)
+ } else {
+ self.fetch_variable(string)
}
}
}
-
- match id.value.to_lowercase().as_str() {
- ".scope" => self.parse_scope_definition(&id, line),
- ".endscope" => self.parse_scope_end(&id),
- ".segment" => self.parse_segment_definition(&id, line),
- ".byte" | ".db" => self.parse_literal_bytes(&id, line, false),
- ".word" | ".dw" | ".addr" => self.parse_literal_bytes(&id, line, true),
- ".proc" => self.parse_proc_definition(&id, line),
- ".endproc" => self.parse_proc_end(&id),
- ".macro" => self.parse_macro_definition(&id, line),
- ".endmacro" => self.parse_macro_end(&id),
- ".hibyte" => self.parse_hi_lo_byte(&id, &args, true),
- ".lobyte" => self.parse_hi_lo_byte(&id, &args, false),
- _ => {
- return Err(
- id.parser_error(format!("unknown control statement '{}'", id.value).as_str())
- )
- }
- }
}
- fn parse_hi_lo_byte(&mut self, id: &PString, args: &Vec<String>, hi: bool) -> Result<()> {
- if args.len() > 1 {
- return Err(id.parser_error(
- format!(
- "only 1 argument was expected, but {} were passed",
- args.len()
- )
- .as_str(),
- ));
- }
-
- println!("{:#?} -- {:#?} -- {}", id, args, hi);
-
- Ok(())
- }
-
- fn parse_macro_definition(&mut self, id: &PString, line: &str) -> Result<()> {
- self.skip_whitespace(line);
-
- let identifier = self.fetch_identifier(id, line)?;
- if identifier.is_reserved() {
- return Err(identifier.parser_error(
- format!(
- "cannot use reserved name '{}' for proc name",
- identifier.value
- )
- .as_str(),
- ));
- }
-
- self.mapping.current_macro = Some(identifier.value.clone());
- self.mapping.macros.entry(identifier.value).or_default();
- Ok(())
- }
-
- fn parse_macro_end(&mut self, id: &PString) -> Result<()> {
- match self.mapping.current_macro {
- Some(_) => self.mapping.current_macro = None,
- None => {
- return Err(id.parser_error(
- format!("bad `.endmacro`: we are not inside of a macro definition").as_str(),
- ))
- }
- }
-
- Ok(())
- }
-
- fn parse_proc_definition(&mut self, id: &PString, line: &str) -> Result<()> {
- self.skip_whitespace(line);
-
- let identifier = self.fetch_identifier(id, line)?;
- if identifier.is_reserved() {
- return Err(identifier.parser_error(
- format!(
- "cannot use reserved name '{}' for proc name",
- identifier.value
- )
- .as_str(),
- ));
- }
-
- // Insert the given identifier into the context.
- if let Some(entry) = self.context.current_mut() {
- match entry.entry(identifier.value.clone()) {
- Entry::Occupied(e) => {
- return Err(ParseError {
- line: self.line,
- message: format!(
- "proc '{}' already exists for this context: it was previously defined in line {}",
- id.value, e.get().node.line),
- })
+ fn fetch_variable(&mut self, mut string: &str) -> Result<Bundle> {
+ // Get the context that might be being referenced.
+ let ctxt = match string.find("::") {
+ Some(_) => {
+ let tctxt = string.rsplit_once("::").unwrap_or(("", ""));
+ if tctxt.0.is_empty() {
+ self.context.current()
+ } else {
+ string = tctxt.1;
+ self.context.find(tctxt.0)
}
- Entry::Vacant(e) => e.insert(PValue {
- node: PString {
- value: identifier.value.clone(),
- line: self.line,
- range: Range {
- start: id.range.start,
- end: id.range.end,
- },
- },
- value: 0,
- label: true,
- }),
- };
- }
-
- // And add the node so it's picked up later.
- self.mapping.push(Node::Label(Label {
- value: identifier.value.to_string(),
- }));
-
- // TODO: lol
- self.context.push_stack(&identifier.value);
-
- self.mapping.push(Node::Scoped(Scoped {
- identifier: identifier.clone(),
- start: true,
- }));
-
- Ok(())
- }
-
- fn parse_proc_end(&mut self, id: &PString) -> Result<()> {
- if !self.context.pop() {
- return Err(id.parser_error("missmatched '.endproc': there is no proc to end"));
- }
- self.mapping.push(Node::Scoped(Scoped {
- identifier: PString::new(),
- start: false,
- }));
-
- Ok(())
- }
-
- fn parse_segment_definition(&mut self, id: &PString, line: &str) -> Result<()> {
- self.skip_whitespace(line);
-
- let identifier = self.fetch_possibly_quoted_identifier(id, line)?;
- self.mapping.switch(&identifier)?;
-
- Ok(())
- }
-
- fn parse_scope_definition(&mut self, id: &PString, line: &str) -> Result<()> {
- self.skip_whitespace(line);
-
- let identifier = self.fetch_identifier(id, line)?;
- if identifier.is_reserved() {
- return Err(identifier.parser_error(
- format!("cannot use reserved name '{}'", identifier.value).as_str(),
- ));
- }
- self.context.push(&identifier.value);
- self.mapping.push(Node::Scoped(Scoped {
- identifier,
- start: true,
- }));
-
- Ok(())
- }
-
- fn parse_scope_end(&mut self, id: &PString) -> Result<()> {
- if !self.context.pop() {
- return Err(id.parser_error("missmatched '.endscope': there is no scope to end"));
- }
- self.mapping.push(Node::Scoped(Scoped {
- identifier: PString::new(),
- start: false,
- }));
-
- Ok(())
- }
-
- fn parse_literal_bytes(
- &mut self,
- node: &PString,
- line: &str,
- two_bytes_allowed: bool,
- ) -> Result<()> {
- loop {
- self.skip_whitespace(line);
-
- match line.chars().nth(self.column) {
- Some(byte) => {
- let needle = if byte == '\'' {
- self.column += 1;
- self.skip_whitespace(line);
- '\''
- } else if byte == '"' {
- self.column += 1;
- self.skip_whitespace(line);
- '"'
- } else {
- ','
- };
-
- // Find the index of the needle. If it cannot be found, try
- // to find the first whitespace (e.g. to ditch out inline
- // comments or other artifacts). If neither of these are
- // found, it will simply return the end of the string.
- //
- // TODO: instead of ditching out what's right of the first
- // whitespace, try to error out on weird scenarios.
- let needle_idx = line
- .get(self.column..)
- .unwrap_or("")
- .find(|c: char| c == needle);
- let idx = match needle_idx {
- Some(v) => v,
- None => line
- .get(self.column..)
- .unwrap_or("")
- .find(|c: char| c.is_whitespace())
- .unwrap_or(line.len() - self.column),
- };
-
- // If this is the last character, the needle was a quote and
- // the last char is not the needle, then it means that the
- // quote was left open. Complain about this as well.
- if idx == line.len() - self.column {
- if line.chars().nth(idx).unwrap_or(' ') != needle
- && (needle == '"' || needle == '\'')
- {
- return Err(node.parser_error("non-terminated quote for byte literal"));
- }
- }
-
- // Now we have our string. Before pushing it, though, there
- // is a special case for alphabetic literals that need to be
- // translated.
- let string = line.get(self.column..self.column + idx).unwrap_or(" ");
- let mut bytes: [u8; 2] = [0, 0];
- if string.len() == 1 && string.chars().nth(0).unwrap().is_ascii_alphabetic() {
- let v = Vec::from(string);
- bytes[0] = v[0];
- }
+ }
+ None => self.context.current(),
+ };
- // NOTE: for now we push an incomplete literal. We need the
- // first pass to fill the context and then a second pass
- // will evaluate each literal as needed (e.g. replacing
- // values from variables being used in this literal).
- self.mapping.push(Node::Literal(Literal {
- identifier: PString {
- value: string.to_owned(),
- line: self.line,
- range: Range {
- start: self.column,
- end: self.column + idx,
- },
- },
- size: if two_bytes_allowed { 2 } else { 1 },
- bytes,
- resolved: true,
- }));
-
- self.column += idx;
- for c in line.get(self.column..).unwrap_or(" ").chars() {
- if c == ',' {
- break;
- }
- if c == ';' {
- return Ok(());
- }
- self.column += 1;
+ // println!("{:#?}", self.context);
+ // println!("{:#?}", ctxt);
+
+ match ctxt {
+ Some(hash) => {
+ match hash.get(string) {
+ Some(var) => {
+ // TODO
+ // If this is just a memory address (e.g.
+ // label), then just return it as is.
+ // if var.label {
+ // return Ok((node.clone(), false));
+ // }
+ Ok(var.value.clone())
}
- self.column += 1;
- self.skip_whitespace(line);
- }
- None => break,
- };
- }
-
- Ok(())
- }
-
- fn fetch_identifier(&mut self, id: &PString, line: &str) -> Result<PString> {
- let idx = line
- .get(self.column..)
- .unwrap_or(" ")
- .find(|c: char| c.is_whitespace());
-
- match idx {
- Some(offset) => {
- let end = self.column + offset;
- let rest = line.get(end..).unwrap_or("").trim();
- if !rest.is_empty() {
- if rest.chars().nth(0).unwrap_or(' ') != ';' {
- return Err(id.parser_error(
- "there should not be any further content besides the identifier",
- ));
+ None => {
+ Err(self.parser_error(format!("unknown variable '{}'", string).as_str()))
}
}
- Ok(PString {
- value: line.get(self.column..end).unwrap_or(" ").trim().to_string(),
- line: self.line,
- range: Range {
- start: self.column,
- end,
- },
- })
}
- None => Ok(PString {
- value: line.get(self.column..).unwrap_or(" ").trim().to_string(),
- line: self.line,
- range: Range {
- start: self.column,
- end: line.len(),
- },
- }),
+ None => Err(self.parser_error(format!("unknown scope '{}'", "Global").as_str())),
}
}
- fn fetch_possibly_quoted_identifier(&mut self, id: &PString, line: &str) -> Result<PString> {
- let mut identifier = self.fetch_identifier(id, line)?;
-
- if identifier.value.starts_with('\'') || identifier.value.starts_with('`') {
- return Err(id.parser_error("use double quotes for the segment identifier instead"));
- } else if identifier.value.starts_with('"') {
- identifier.value = match identifier
- .value
- .get(1..(identifier.range.end - identifier.range.start - 1))
- {
- Some(v) => v.to_string(),
- None => return Err(id.parser_error("could not fetch quoted identifier")),
- };
- if identifier.value.contains('"') {
- return Err(id.parser_error("do not use double quotes inside of the identifier"));
- }
- identifier.range.start += 1;
- identifier.range.end -= 1;
- }
+ fn do_evaluate_decimal(&mut self, string: &str) -> Result<Bundle> {
+ let mut value = 0;
+ let mut shift = 1;
- Ok(identifier)
- }
+ if string.is_empty() {
+ return Err(self.parser_error("empty decimal literal"));
+ }
- fn parse_statement(&mut self, id: PString, line: &str) -> Result<()> {
- if id.value.chars().nth(self.column - 1).unwrap_or(' ') == ':' {
- self.parse_label(id, line)
- } else {
- match self.mapping.macros.get_mut(&id.value) {
- Some(nodes) => {
- for node in nodes {
- self.mapping
- .nodes
- .get_mut(&self.mapping.current)
- .unwrap()
- .push(node.clone());
+ for c in string.chars().rev() {
+ if shift > 100 {
+ return Err(self.parser_error("decimal value is too big"));
+ }
+ if c != '0' {
+ match c.to_digit(10) {
+ Some(digit) => {
+ value += digit * shift;
+ }
+ None => {
+ return Err(ParseError {
+ line: self.line,
+ message: format!("'{}' is not a decimal value", c),
+ parse: false,
+ });
}
- Ok(())
}
- None => self.parse_assignment(id, line),
}
- }
- }
-
- fn parse_label(&mut self, id: PString, _line: &str) -> Result<()> {
- let name = &id.value.as_str()[..id.value.len() - 1].to_string();
-
- // Forbid weird scenarios.
- if name.contains("::") {
- return Err(id.parser_error(
- format!(
- "the label '{}' is scoped: do not declare variables this way",
- id.value
- )
- .as_str(),
- ));
- }
-
- // Insert the given label into the context.
- if let Some(entry) = self.context.current_mut() {
- match entry.entry(name.clone()) {
- Entry::Occupied(e) => {
- return Err(ParseError {
- line: self.line,
- message: format!(
- "label '{}' already exists for this context: it was previously defined in line {}",
- id.value, e.get().node.line),
- })
- }
- Entry::Vacant(e) => e.insert(PValue {
- node: PString {
- value: name.clone(),
- line: self.line,
- range: Range {
- start: id.range.start,
- end: id.range.end,
- },
- },
- value: 0,
- label: true,
- }),
- };
- }
-
- // And add the node so it's picked up later.
- self.mapping.push(Node::Label(Label {
- value: name.to_string(),
- }));
- Ok(())
- }
- fn parse_assignment(&mut self, id: PString, line: &str) -> Result<()> {
- // You cannot assign into a name which is reserved.
- if id.is_reserved() {
- return Err(
- id.parser_error(format!("cannot use reserved name '{}'", id.value).as_str())
- );
- }
-
- // To avoid problems down the line, you cannot assign into names which
- // are proper hexadecimal values.
- if Self::parse_hex_from(&id.value, &id, true, false, false).is_ok() {
- return Err(id.parser_error(
- format!(
- "cannot use names which are valid hexadecimal values such as '{}'",
- id.value
- )
- .as_str(),
- ));
- }
-
- // You cannot assign into scoped names: declare them into their
- // respective scopes instead.
- if id.value.contains("::") {
- return Err(id.parser_error(
- format!(
- "the name '{}' is scoped: do not declare variables this way",
- id.value
- )
- .as_str(),
- ));
- }
-
- // Skip whitespaces and make sure that we have a '=' sign.
- self.skip_whitespace(line);
- if line.chars().nth(self.column).unwrap_or(' ') != '=' {
- return Err(self.parser_error(format!("unknown instruction '{}'", id.value).as_str()));
- }
-
- // Skip the '=' sign and any possible whitespaces.
- self.column += 1;
- if !self.skip_whitespace(line) {
- return Err(self.parser_error("incomplete assignment"));
- }
-
- let l = String::from(line.get(self.column..).unwrap_or("").trim());
- if l.is_empty() {
- return Err(self.parser_error("incomplete assignment"));
+ shift *= 10;
}
-
- // The `Context` struct pretty much guarantees that `current` and
- // `current_mut` will return something, so it's safe to ignore a
- // `None`.
- if let Some(entry) = self.context.current_mut() {
- match entry.entry(id.value.clone()) {
- Entry::Occupied(e) => {
- return Err(ParseError {
- line: self.line,
- message: format!(
- "variable '{}' is being re-assigned: it was previously defined in line {}",
- id.value, e.get().node.line),
- })
- }
- Entry::Vacant(e) => e.insert(PValue {
- node: PString {
- value: l,
- line: self.line,
- range: Range {
- start: id.range.start,
- end: line.len(),
- },
- },
- value: 0,
- label: false,
- }),
- };
+ if value > 255 {
+ return Err(self.parser_error("decimal value is too big"));
}
- Ok(())
+ Ok(Bundle {
+ bytes: [value as u8, 0, 0],
+ size: 1,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ })
}
- fn parse_instruction(&mut self, id: PString, line: &str) -> Result<()> {
- // Parse the instruction into a `Generic` node.
- let node = self.get_generic_instruction_node(id, line)?;
-
- // Make sure that there is no dangling content.
- for c in line.get(self.column..).unwrap_or("").chars() {
- if c == ';' {
- break;
- }
- if !c.is_whitespace() {
- return Err(self.parser_error("only one statement is allowed per line"));
- }
- self.column += 1;
+ fn evaluate_hilo_byte(&mut self, args: Vec<Box<PNode>>, hi: bool) -> Result<Bundle> {
+ if args.len() != 1 {
+ return Err(self.parser_error("wrong number of arguments: expecting exactly one"));
}
- // And push a new `Instruction` object based on the parsed node. Note
- // that this instruction will be incomplete: we first need to evaluate
- // variables first in this context to be able to fully parse this
- // object.
- self.push_incomplete_instruction_from(node)
- }
-
- // Returns a `Generic` node with the contents that can be parsed with the
- // rest of the `line` and assuming that this is an assembly instruction
- // which is identified by `id`.
- fn get_generic_instruction_node(&mut self, id: PString, line: &str) -> Result<Generic> {
- // First of all, make sure that we are at a non-whitespace character.
- self.skip_whitespace(line);
-
- // Instructions have a character which split the left and the right arms
- // of the instruction. This is the character that we will use in order
- // to stop on the first loop.
- let needle_char = match line.chars().nth(self.column) {
- Some(c) => {
- if c == '(' {
- ')'
- } else {
- ','
- }
- }
- None => ',',
- };
-
- let mut column = self.column;
- let mut left = None;
- let mut right = None;
- let mut found = false;
-
- // First of the two loops: fetch the left arm. It iterates
- // until the needle_char is found and then initializes the `left`
- // variable with the fetched contents.
- for c in line.get(column..).unwrap_or("").chars() {
- if c == ';' {
- break;
- }
- if c == needle_char {
- self.init_positioned_maybe(&mut left, line, column, self.column);
-
- for inner in line.get(self.column..).unwrap_or("").chars() {
- if inner.is_whitespace() || inner == ';' {
- break;
- }
- self.column += 1;
- }
-
- found = true;
- break;
- }
-
- self.column += 1;
+ let val = self.evaluate_node(args.first().unwrap().clone())?;
+ if val.size < 1 {
+ let s = if hi { ".hibyte" } else { ".lobyte" };
+ return Err(self.parser_error(format!("empty value for {}", s).as_str()));
}
- // If the previous loop found the needle, the we might have a right arm.
- // Otherwise, if the needle was not found but the end of the line was
- // reached, we might still need to pick up the contents that were not
- // saved in the previous loop.
- if found {
- // Ready the `column` for the right arm.
- self.skip_whitespace(line);
- column = self.column;
-
- // Second loop: fetch the right arm. This time it iterates until a
- // whitespace character or ';' is found.
- for c in line.get(column..).unwrap_or("").chars() {
- if c == ';' {
- self.init_positioned_maybe(&mut right, line, column, self.column);
- break;
- }
- self.column += 1;
+ let b = if hi {
+ if val.size == 1 {
+ val.bytes[0]
+ } else {
+ val.bytes[1]
}
-
- // Similarly to the first loop, if this second one reached the end
- // without finding a whitespace or a ';' character, make sure that this
- // content is not ignored.
- self.init_positioned_maybe(&mut right, line, column, self.column);
} else {
- self.init_positioned_maybe(&mut left, line, column, self.column);
- }
+ val.bytes[0]
+ };
- Ok(Generic {
- identifier: id,
- left,
- right,
+ Ok(Bundle {
+ bytes: [b, 0, 0],
+ size: 1,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
})
}
- // Initialize the PString object `p` with the contents of
- // `line.get(left..right)` unless it has already a `Some` value or the
- // fetched contents would result in an empty string.
- fn init_positioned_maybe(
- &mut self,
- p: &mut Option<PString>,
- line: &str,
- left: usize,
- right: usize,
- ) {
- if !p.is_none() || left == right {
- return;
- }
+ fn evaluate_scope_definition(&mut self, node: Box<PNode>) -> Result<Bundle> {
+ println!("{:#?}", node);
+ match node.left {
+ Some(identifier) => {
+ self.context.push(&identifier.value.value);
+ // TODO: mapping?
- let string = String::from(line.get(left..right).unwrap_or("").trim());
- if !string.is_empty() {
- *p = Some(PString {
- value: string,
- line: self.line,
- range: Range {
- start: left,
- end: right,
- },
- });
+ Ok(Bundle::new())
+ }
+ None => return Err(self.parser_error("scope definition with no identifier")),
}
}
- fn push_incomplete_instruction_from(&mut self, node: Generic) -> Result<()> {
- let mut instr = Instruction::from(&node.identifier.value);
- instr.left = node.left;
- instr.right = node.right;
-
- self.mapping.push(Node::Instruction(instr));
- Ok(())
- }
+ fn evaluate_scope_end(&mut self) -> Result<Bundle> {
+ if !self.context.pop() {
+ return Err(self.parser_error("missmatched '.endscope': there is no scope to end"));
+ }
+
+ // TODO: mapping?
+
+ Ok(Bundle::new())
+ }
+
+ // pub fn assemble(&mut self, reader: impl Read) -> Result<Vec<&dyn Encodable>> {
+ // let mut instructions: Vec<&dyn Encodable> = vec![];
+
+ // self.assemble_nodes(reader)?;
+
+ // let mut idx: usize = 0;
+ // for segment in &self.mapping.segments {
+ // let mut size: usize = 0;
+
+ // while idx < segment.start.into() {
+ // match &segment.fill {
+ // Some(fill) => instructions.push(fill),
+ // None => instructions.push(&Fill { value: 0x00 }),
+ // }
+ // idx += 1;
+ // }
+
+ // for node in &self.mapping.nodes[&segment.name] {
+ // match node {
+ // Node::Instruction(instr) => {
+ // instructions.push(instr);
+ // size += usize::from(instr.size());
+ // }
+ // Node::Literal(lit) => {
+ // instructions.push(lit);
+ // size += usize::from(lit.size());
+ // }
+ // _ => {}
+ // }
+ // }
+
+ // if size > segment.size {
+ // return Err(ParseError {
+ // line: 0,
+ // message: format!(
+ // "segment '{}' expected a size of '{}' bytes but '{}' bytes were produced instead",
+ // segment.name, size, segment.size
+ // ),
+ // });
+ // }
+ // idx += size;
+ // if segment.fill.is_none() {
+ // continue;
+ // }
+
+ // while size < segment.size {
+ // instructions.push(segment.fill.as_ref().unwrap());
+ // size += 1;
+ // idx += 1;
+ // }
+ // }
+
+ // Ok(instructions)
+ // }
+
+ // pub fn evaluate(&mut self) -> Result<()> {
+ // for segment in &self.mapping.segments {
+ // for node in self.mapping.nodes.get_mut(&segment.name).unwrap() {
+ // match node {
+ // Node::Instruction(instr) => {
+ // Self::update_instruction_with_context(instr, &self.context)?;
+ // instr.address = segment.start;
+
+ // self.offsets
+ // .entry(segment.name.clone())
+ // .and_modify(|value| {
+ // instr.address += *value as u16;
+ // *value += usize::from(instr.size())
+ // })
+ // .or_insert(instr.size().into());
+ // }
+ // Node::Scoped(scope) => {
+ // if scope.start {
+ // self.context.push(&scope.identifier.value);
+ // } else {
+ // _ = self.context.pop();
+ // }
+ // }
+ // Node::Literal(literal) => {
+ // Self::update_literal_with_context(literal, &self.context)?;
+ // self.offsets
+ // .entry(segment.name.clone())
+ // .and_modify(|value| *value += usize::from(literal.size()))
+ // .or_insert(literal.size().into());
+ // }
+ // Node::Label(label) => {
+ // let address =
+ // usize::from(segment.start) + self.offsets.get(&segment.name).unwrap();
+
+ // self.context
+ // .current_mut()
+ // .unwrap()
+ // .entry(label.value.clone())
+ // .and_modify(|e| e.value = address);
+ // }
+ // _ => {}
+ // }
+ // }
+ // }
+
+ // Ok(())
+ // }
+
+ // // TODO: oh boy...
+ // pub fn resolve_labels(&mut self) -> Result<()> {
+ // for segment in &self.mapping.segments {
+ // for node in self.mapping.nodes.get_mut(&segment.name).unwrap() {
+ // match node {
+ // Node::Instruction(instr) => {
+ // if !instr.resolved {
+ // match &instr.left {
+ // Some(pstring) => {
+ // match self.context.current().unwrap().get(&pstring.value) {
+ // Some(entry) => {
+ // if instr.mode == AddressingMode::Absolute {
+ // let bytes = entry.value.to_le_bytes();
+ // instr.bytes = [bytes[0], bytes[1]];
+ // } else {
+ // let diff: isize = entry.value as isize
+ // - (instr.address as isize + 2);
+ // if diff < -128 || diff > 127 {
+ // return Err(instr.mnemonic.parser_error(
+ // format!("relative addressing out of range")
+ // .as_str(),
+ // ));
+ // }
+ // let bytes = diff.to_le_bytes();
+ // instr.bytes = [bytes[0], 0];
+ // }
+ // }
+ // None => {
+ // return Err(instr.mnemonic.parser_error(
+ // format!("label '{}' not found", pstring.value)
+ // .as_str(),
+ // ))
+ // }
+ // }
+ // }
+ // None => {
+ // return Err(instr.mnemonic.parser_error(
+ // format!("there is no label for the given jump instruction")
+ // .as_str(),
+ // ))
+ // }
+ // }
+ // }
+ // }
+ // Node::Literal(literal) => {
+ // if !literal.resolved {
+ // match self
+ // .context
+ // .current()
+ // .unwrap()
+ // .get(&literal.identifier.value)
+ // {
+ // Some(entry) => {
+ // let bytes = entry.value.to_le_bytes();
+ // literal.bytes = [bytes[0], bytes[1]];
+ // }
+ // None => {
+ // return Err(literal.identifier.parser_error(
+ // format!(
+ // "'{}' is neither a known variable or label at this scope",
+ // literal.identifier.value
+ // )
+ // .as_str(),
+ // ))
+ // }
+ // }
+ // }
+ // }
+ // _ => {}
+ // }
+ // }
+ // }
+
+ // Ok(())
+ // }
+
+ // fn update_instruction_with_context(instr: &mut Instruction, context: &Context) -> Result<()> {
+ // // To keep things simple, we remove out the `implied` case and we parse
+ // // further with a known `Some` value for the base algorithm implemented
+ // // in `update_instruction_and_bytes`.
+ // if instr.left.is_some() {
+ // Self::update_addressing_and_bytes(instr, context)?;
+ // } else {
+ // instr.mode = AddressingMode::Implied;
+ // }
+
+ // // Now that we have the addressing mode and the bytes, we can fill out
+ // // the rest of it by fetching the values on `INSTRUCTIONS`.
+ // match INSTRUCTIONS.get(&instr.mnemonic.value.to_lowercase()) {
+ // Some(entries) => match entries.get(&instr.mode) {
+ // Some(values) => {
+ // instr.cycles = values.cycles;
+ // instr.opcode = values.opcode;
+ // instr.size = values.size;
+ // instr.affected_on_page = values.affected_on_page;
+ // }
+ // None => {
+ // return Err(instr.mnemonic.parser_error(
+ // format!(
+ // "bad addressing mode '{}' for the instruction '{}'",
+ // &instr.mode, &instr.mnemonic.value
+ // )
+ // .as_str(),
+ // ));
+ // }
+ // },
+ // None => {
+ // return Err(instr.mnemonic.parser_error(
+ // format!("unknown instruction '{}'", &instr.mnemonic.value).as_str(),
+ // ));
+ // }
+ // }
+
+ // Ok(())
+ // }
+
+ // fn update_addressing_and_bytes(instr: &mut Instruction, context: &Context) -> Result<()> {
+ // // `unwrap()` is guaranteed to work by the caller.
+ // let left = instr.left.as_ref().unwrap();
+
+ // // We will first try to check if there's any variable involved on the
+ // // left arm and replace the string if so. This will greatly simplify
+ // // things down the line. That being said, there is a special reserved
+ // // case, which is the implied addressing by using "a". In this case, we
+ // // want to ensure that we assume an implied addressing and not a
+ // // variable named "a".
+ // if left.value.to_lowercase() == "a" {
+ // instr.mode = AddressingMode::Implied;
+ // } else {
+ // let (nleft, resolved) = Self::replace_variable(left, context)?;
+ // // TODO
+ // instr.resolved = resolved;
+ // if !resolved {
+ // if instr.mnemonic.value == "jmp" {
+ // instr.mode = AddressingMode::Absolute;
+ // } else {
+ // instr.mode = AddressingMode::RelativeOrZeropage;
+ // }
+ // }
+
+ // if nleft.value.starts_with('$') {
+ // // This is an address. At this point we should assume that the
+ // // left node contains the address itself, and that the right one
+ // // will contain whether there is indexing.
+
+ // let string = nleft.value.chars().as_str();
+ // instr.bytes = Self::parse_hex_from(string, &nleft, true, false, true)?;
+
+ // match &instr.right {
+ // Some(xy) => match xy.value.to_lowercase().as_str() {
+ // "x" => {
+ // if string.len() == 3 {
+ // instr.mode = AddressingMode::ZeropageIndexedX;
+ // } else {
+ // instr.mode = AddressingMode::IndexedX;
+ // }
+ // }
+ // "y" => {
+ // if string.len() == 3 {
+ // instr.mode = AddressingMode::ZeropageIndexedY;
+ // } else {
+ // instr.mode = AddressingMode::IndexedY;
+ // }
+ // }
+ // _ => return Err(xy.parser_error("index is neither X nor Y")),
+ // },
+ // None => {
+ // if string.len() == 3 {
+ // instr.mode = AddressingMode::RelativeOrZeropage;
+ // } else {
+ // instr.mode = AddressingMode::Absolute;
+ // }
+ // }
+ // }
+ // } else if nleft.value.starts_with('#') {
+ // // Immediate addressing in any case: hexadecimal, binary or
+ // // decimal. Hence, just figure out the character being used and
+ // // call the right function for it.
+
+ // let mut chars = nleft.value.chars();
+ // chars.next();
+ // let string = chars.as_str();
+
+ // instr.bytes = Self::parse_numeric(string, &nleft, false)?;
+ // instr.mode = AddressingMode::Immediate;
+ // } else if nleft.value.starts_with('(') {
+ // // Indirect addressing. In this case the left arm can be further
+ // // subdivided. That is, indirect X-indexing is represented like
+ // // so: `instr ($NN, x)`. Hence, first of all we have to figure
+ // // out whether there is a subdivision.
+
+ // let (left1, oleft2) = Self::split_left_arm(&nleft)?;
+ // match oleft2 {
+ // Some(left2) => {
+ // // There is subdivision. Thus, we have to assume
+ // // indirect X-indexing, which means that the right arm
+ // // should be None and that the right side of the left
+ // // node must match the X register. Other than that, the
+ // // address being referenced must be zero page.
+ // if instr.right.is_some() {
+ // return Err(instr.right.as_ref().unwrap().parser_error(
+ // "bad indirect mode, expecting an indirect X-indexed addressing mode"
+ // ));
+ // }
+ // if left2.value.to_lowercase() != "x" {
+ // return Err(left2.parser_error(
+ // "the index in indirect X-indexed addressing must be X",
+ // ));
+ // }
+ // match Self::parse_hex_from(&left1.value, &left1, false, false, true) {
+ // Ok(bytes) => instr.bytes = bytes,
+ // Err(e) => {
+ // let msg = String::from(
+ // "when parsing an instruction with indirect X-indexed addressing: ",
+ // ) + &e.message;
+ // return Err(left1.parser_error(msg.as_str()));
+ // }
+ // }
+ // instr.mode = AddressingMode::IndirectX;
+ // }
+ // None => {
+ // // There is no subdivision on the left arm. Hence, if
+ // // there is something on the right arm then we must
+ // // assume indirect Y-index addressing, and if not then
+ // // it's indirect addressing with no indices involvved.
+ // if instr.right.is_some() {
+ // if instr.right.as_ref().unwrap().value.to_lowercase() != "y" {
+ // return Err(instr.right.as_ref().unwrap().parser_error(
+ // "the index in indirect Y-indexed addressing must be Y",
+ // ));
+ // }
+ // match Self::parse_hex_from(&left1.value, &left1, false, false, true) {
+ // Ok(bytes) => instr.bytes = bytes,
+ // Err(e) => {
+ // let msg = String::from(
+ // "when parsing an instruction with indirect Y-indexed addressing: ",
+ // ) + &e.message;
+ // return Err(left1.parser_error(msg.as_str()));
+ // }
+ // }
+ // instr.mode = AddressingMode::IndirectY;
+ // } else {
+ // instr.bytes =
+ // Self::parse_hex_from(&left1.value, &left1, true, true, true)?;
+ // instr.mode = AddressingMode::Indirect;
+ // }
+ // }
+ // }
+ // } else {
+ // // At this point all of the syntax cases have been exhausted:
+ // // the programmer messed up. From this point on we try to figure
+ // // out how they messed up.
+
+ // if nleft.value.starts_with('=') {
+ // return Err(instr.mnemonic.parser_error(
+ // format!(
+ // "cannot use '{}' in an assignment because it's a word reserved for an instruction mnemonic",
+ // instr.mnemonic.value
+ // ).as_str(),
+ // ));
+ // }
+ // // TODO:
+ // // instr.mode = AddressingMode::Absolute;
+ // // return Err(instr.mnemonic.parser_error(
+ // // format!(
+ // // "unknown addressing mode for instruction '{}'",
+ // // instr.mnemonic.value
+ // // )
+ // // .as_str(),
+ // // ));
+ // }
+ // }
+
+ // Ok(())
+ // }
+
+ // fn update_literal_with_context(literal: &mut Literal, context: &Context) -> Result<()> {
+ // // If it has already been set, skip it.
+ // // TODO: add a proper `is_set` thingie to it instead of this hack.
+ // if literal.bytes[0] != 0 || literal.bytes[1] != 0 {
+ // return Ok(());
+ // }
+
+ // // Evaluate any possible variable being used inside of this literal.
+ // let (evaled, resolved) = Self::replace_variable(&literal.identifier, context)?;
+
+ // // It may happen that the literal is just a label that is to be resolved
+ // // in the future. If so, let's leave early.
+ // literal.resolved = resolved;
+ // if !resolved {
+ // return Ok(());
+ // }
+
+ // // Parse the numeric value after a possible variable has been replaced.
+ // let two_bytes_allowed = literal.size == 2;
+ // let res = Self::parse_numeric(
+ // evaled.value.as_str(),
+ // &literal.identifier,
+ // two_bytes_allowed,
+ // );
+
+ // // And finally assign the computed bytes.
+ // match res {
+ // Ok(bytes) => {
+ // literal.bytes = bytes;
+ // Ok(())
+ // }
+ // Err(e) => {
+ // let msg = String::from("when parsing a data literal: ") + &e.message;
+ // Err(literal.identifier.parser_error(msg.as_str()))
+ // }
+ // }
+ // }
+
+ // fn parse_numeric(string: &str, node: &PString, two_bytes_allowed: bool) -> Result<[u8; 2]> {
+ // if string.starts_with('$') {
+ // Ok(Self::parse_hex_from(
+ // string,
+ // node,
+ // two_bytes_allowed,
+ // false,
+ // true,
+ // )?)
+ // } else if string.starts_with('%') {
+ // Ok([Self::parse_binary_from(string, node)?, 0])
+ // } else {
+ // Ok([Self::parse_decimal_from(string, node)?, 0])
+ // }
+ // }
+
+ // fn split_left_arm(node: &PString) -> Result<(PString, Option<PString>)> {
+ // let mut chars = node.value.chars();
+ // chars.next();
+ // let string = chars.as_str();
+
+ // match string.find(|c: char| c == ',') {
+ // Some(idx) => {
+ // let left1 = string.get(..idx).unwrap_or("").trim();
+ // let left2 = string.get(idx + 1..).unwrap_or("").trim();
+
+ // Ok((
+ // PString {
+ // value: left1.to_string(),
+ // line: node.line,
+ // range: Range {
+ // start: node.range.start + 1,
+ // end: node.range.start + 1 + left1.len(),
+ // },
+ // },
+ // Some(PString {
+ // value: left2.to_string(),
+ // line: node.line,
+ // range: Range {
+ // start: node.range.start + 1 + idx,
+ // end: node.range.start + 1 + idx + left2.len(),
+ // },
+ // }),
+ // ))
+ // }
+ // None => Ok((
+ // PString {
+ // value: string.to_string(),
+ // line: node.line,
+ // range: Range {
+ // start: node.range.start + 1,
+ // end: node.range.end,
+ // },
+ // },
+ // None,
+ // )),
+ // }
+ // }
+
+ // fn parse_binary_from(string: &str, node: &PString) -> Result<u8> {
+ // let mut value = 0;
+ // let mut shift = 0;
+
+ // for c in string.get(1..).unwrap_or("").chars().rev() {
+ // if c == '1' {
+ // let val = 1 << shift;
+ // value += val;
+ // } else if c != '0' {
+ // return Err(
+ // node.parser_error(format!("bad binary format for '{}'", string).as_str())
+ // );
+ // }
+
+ // shift += 1;
+ // }
+
+ // if shift < 8 {
+ // Err(node.parser_error("missing binary digits to get a full byte"))
+ // } else if shift > 8 {
+ // Err(node.parser_error("too many binary digits for a single byte"))
+ // } else {
+ // Ok(value)
+ // }
+ // }
+
+ // // TODO: returns if resolved
+ // fn replace_variable(node: &PString, context: &Context) -> Result<(PString, bool)> {
+ // match node
+ // .value
+ // .find(|c: char| c.is_alphabetic() || c == '_' || c == '@')
+ // {
+ // Some(idx) => {
+ // // Before doing any replacement, let's check the character
+ // // before the one that was found. In this case, if it was a
+ // // proper ASCII digit, then it cannot be a variable but it's
+ // // part of a numeric literal (e.g. '1A'): then just let the
+ // // different numeric parsing functions do their job.
+ // if idx > 0 {
+ // let prev = node.value.chars().nth(idx - 1).unwrap_or(' ');
+ // if prev.is_ascii_digit() {
+ // return Ok((node.clone(), true));
+ // }
+ // }
+
+ // // The variable might still be before an inner comma (e.g.
+ // // sta ($20, x)). We will assume that variables can happen
+ // // only before that.
+ // let end = node.value.find(',').unwrap_or(node.value.len());
+ // let mut string = node.value.get(idx..end).unwrap_or("");
+ // let tail = node.value.get(end..).unwrap_or("");
+
+ // // Get the context that might be being referenced.
+ // let ctxt = match string.find("::") {
+ // Some(_) => {
+ // let tctxt = string.rsplit_once("::").unwrap_or(("", ""));
+ // if tctxt.0.is_empty() {
+ // context.current()
+ // } else {
+ // string = tctxt.1;
+ // context.find(tctxt.0)
+ // }
+ // }
+ // None => context.current(),
+ // };
+
+ // match ctxt {
+ // Some(hash) => {
+ // // If there was a comma before the "variable" (i.e. idx >
+ // // end and hence string == ""), or this is just the regular
+ // // X or Y index, just return early.
+ // match string.to_lowercase().as_str() {
+ // "x" | "y" | "" => return Ok((node.clone(), true)),
+ // _ => {}
+ // }
+
+ // // It's not any of the indices, let's look for a match on
+ // // the current scope.
+ // match hash.get(string) {
+ // Some(var) => {
+ // // If this is just a memory address (e.g.
+ // // label), then just return it as is.
+ // if var.label {
+ // return Ok((node.clone(), false));
+ // }
+
+ // let value = String::from(node.value.get(..idx).unwrap_or(""))
+ // + var.node.value.as_str();
+ // Ok((
+ // PString {
+ // value: value.clone() + tail,
+ // line: node.line,
+ // range: Range {
+ // start: node.range.start,
+ // end: node.range.start + value.len(),
+ // },
+ // },
+ // true,
+ // ))
+ // }
+ // None => {
+ // // If a variable could not be found, check that
+ // // this is not a purely hexadecimal number (e.g.
+ // // 'AA'). If that's the case, then just return
+ // // its value.
+ // if Self::parse_hex_from(string, node, true, false, false).is_ok() {
+ // return Ok((node.clone(), true));
+ // }
+
+ // // We've tried hard to not assume the programmer
+ // // messing up, but there's no other way around
+ // // it: it's an "unknown variable" error.
+ // return Err(node.parser_error(
+ // format!("unknown variable '{}'", string).as_str(),
+ // ));
+ // }
+ // }
+ // }
+ // None => {
+ // Err(node.parser_error(format!("unknown scope '{}'", "Global").as_str()))
+ // }
+ // }
+ // }
+ // None => Ok((node.clone(), true)),
+ // }
+ // }
+
+ // fn parse_macro_definition(&mut self, id: &PString, line: &str) -> Result<()> {
+ // self.skip_whitespace(line);
+
+ // let identifier = self.fetch_identifier(id, line)?;
+ // if identifier.is_reserved() {
+ // return Err(identifier.parser_error(
+ // format!(
+ // "cannot use reserved name '{}' for proc name",
+ // identifier.value
+ // )
+ // .as_str(),
+ // ));
+ // }
+
+ // self.mapping.current_macro = Some(identifier.value.clone());
+ // self.mapping.macros.entry(identifier.value).or_default();
+ // Ok(())
+ // }
+
+ // fn parse_macro_end(&mut self, id: &PString) -> Result<()> {
+ // match self.mapping.current_macro {
+ // Some(_) => self.mapping.current_macro = None,
+ // None => {
+ // return Err(id.parser_error(
+ // format!("bad `.endmacro`: we are not inside of a macro definition").as_str(),
+ // ))
+ // }
+ // }
+
+ // Ok(())
+ // }
+
+ // fn parse_proc_definition(&mut self, id: &PString, line: &str) -> Result<()> {
+ // self.skip_whitespace(line);
+
+ // let identifier = self.fetch_identifier(id, line)?;
+ // if identifier.is_reserved() {
+ // return Err(identifier.parser_error(
+ // format!(
+ // "cannot use reserved name '{}' for proc name",
+ // identifier.value
+ // )
+ // .as_str(),
+ // ));
+ // }
+
+ // // Insert the given identifier into the context.
+ // if let Some(entry) = self.context.current_mut() {
+ // match entry.entry(identifier.value.clone()) {
+ // Entry::Occupied(e) => {
+ // return Err(ParseError {
+ // line: self.line,
+ // message: format!(
+ // "proc '{}' already exists for this context: it was previously defined in line {}",
+ // id.value, e.get().node.line),
+ // })
+ // }
+ // Entry::Vacant(e) => e.insert(PValue {
+ // node: PString {
+ // value: identifier.value.clone(),
+ // line: self.line,
+ // range: Range {
+ // start: id.range.start,
+ // end: id.range.end,
+ // },
+ // },
+ // value: 0,
+ // label: true,
+ // }),
+ // };
+ // }
+
+ // // And add the node so it's picked up later.
+ // self.mapping.push(Node::Label(Label {
+ // value: identifier.value.to_string(),
+ // }));
+
+ // // TODO: lol
+ // self.context.push_stack(&identifier.value);
+
+ // self.mapping.push(Node::Scoped(Scoped {
+ // identifier: identifier.clone(),
+ // start: true,
+ // }));
+
+ // Ok(())
+ // }
+
+ // fn parse_proc_end(&mut self, id: &PString) -> Result<()> {
+ // if !self.context.pop() {
+ // return Err(id.parser_error("missmatched '.endproc': there is no proc to end"));
+ // }
+ // self.mapping.push(Node::Scoped(Scoped {
+ // identifier: PString::new(),
+ // start: false,
+ // }));
+
+ // Ok(())
+ // }
+
+ // fn parse_segment_definition(&mut self, id: &PString, line: &str) -> Result<()> {
+ // self.skip_whitespace(line);
+
+ // let identifier = self.fetch_possibly_quoted_identifier(id, line)?;
+ // self.mapping.switch(&identifier)?;
+
+ // Ok(())
+ // }
+
+ // fn parse_scope_definition(&mut self, id: &PString, line: &str) -> Result<()> {
+ // self.skip_whitespace(line);
+
+ // let identifier = self.fetch_identifier(id, line)?;
+ // if identifier.is_reserved() {
+ // return Err(identifier.parser_error(
+ // format!("cannot use reserved name '{}'", identifier.value).as_str(),
+ // ));
+ // }
+ // self.context.push(&identifier.value);
+ // self.mapping.push(Node::Scoped(Scoped {
+ // identifier,
+ // start: true,
+ // }));
+
+ // Ok(())
+ // }
+
+ // fn parse_scope_end(&mut self, id: &PString) -> Result<()> {
+ // if !self.context.pop() {
+ // return Err(id.parser_error("missmatched '.endscope': there is no scope to end"));
+ // }
+ // self.mapping.push(Node::Scoped(Scoped {
+ // identifier: PString::new(),
+ // start: false,
+ // }));
+
+ // Ok(())
+ // }
+
+ // fn parse_literal_bytes(
+ // &mut self,
+ // node: &PString,
+ // line: &str,
+ // two_bytes_allowed: bool,
+ // ) -> Result<()> {
+ // loop {
+ // self.skip_whitespace(line);
+
+ // match line.chars().nth(self.column) {
+ // Some(byte) => {
+ // let needle = if byte == '\'' {
+ // self.column += 1;
+ // self.skip_whitespace(line);
+ // '\''
+ // } else if byte == '"' {
+ // self.column += 1;
+ // self.skip_whitespace(line);
+ // '"'
+ // } else {
+ // ','
+ // };
+
+ // // Find the index of the needle. If it cannot be found, try
+ // // to find the first whitespace (e.g. to ditch out inline
+ // // comments or other artifacts). If neither of these are
+ // // found, it will simply return the end of the string.
+ // //
+ // // TODO: instead of ditching out what's right of the first
+ // // whitespace, try to error out on weird scenarios.
+ // let needle_idx = line
+ // .get(self.column..)
+ // .unwrap_or("")
+ // .find(|c: char| c == needle);
+ // let idx = match needle_idx {
+ // Some(v) => v,
+ // None => line
+ // .get(self.column..)
+ // .unwrap_or("")
+ // .find(|c: char| c.is_whitespace())
+ // .unwrap_or(line.len() - self.column),
+ // };
+
+ // // If this is the last character, the needle was a quote and
+ // // the last char is not the needle, then it means that the
+ // // quote was left open. Complain about this as well.
+ // if idx == line.len() - self.column {
+ // if line.chars().nth(idx).unwrap_or(' ') != needle
+ // && (needle == '"' || needle == '\'')
+ // {
+ // return Err(node.parser_error("non-terminated quote for byte literal"));
+ // }
+ // }
+
+ // // Now we have our string. Before pushing it, though, there
+ // // is a special case for alphabetic literals that need to be
+ // // translated.
+ // let string = line.get(self.column..self.column + idx).unwrap_or(" ");
+ // let mut bytes: [u8; 2] = [0, 0];
+ // if string.len() == 1 && string.chars().nth(0).unwrap().is_ascii_alphabetic() {
+ // let v = Vec::from(string);
+ // bytes[0] = v[0];
+ // }
+
+ // // NOTE: for now we push an incomplete literal. We need the
+ // // first pass to fill the context and then a second pass
+ // // will evaluate each literal as needed (e.g. replacing
+ // // values from variables being used in this literal).
+ // self.mapping.push(Node::Literal(Literal {
+ // identifier: PString {
+ // value: string.to_owned(),
+ // line: self.line,
+ // range: Range {
+ // start: self.column,
+ // end: self.column + idx,
+ // },
+ // },
+ // size: if two_bytes_allowed { 2 } else { 1 },
+ // bytes,
+ // resolved: true,
+ // }));
+
+ // self.column += idx;
+ // for c in line.get(self.column..).unwrap_or(" ").chars() {
+ // if c == ',' {
+ // break;
+ // }
+ // if c == ';' {
+ // return Ok(());
+ // }
+ // self.column += 1;
+ // }
+ // self.column += 1;
+ // self.skip_whitespace(line);
+ // }
+ // None => break,
+ // };
+ // }
+
+ // Ok(())
+ // }
+
+ // fn fetch_identifier(&mut self, id: &PString, line: &str) -> Result<PString> {
+ // let idx = line
+ // .get(self.column..)
+ // .unwrap_or(" ")
+ // .find(|c: char| c.is_whitespace());
+
+ // match idx {
+ // Some(offset) => {
+ // let end = self.column + offset;
+ // let rest = line.get(end..).unwrap_or("").trim();
+ // if !rest.is_empty() {
+ // if rest.chars().nth(0).unwrap_or(' ') != ';' {
+ // return Err(id.parser_error(
+ // "there should not be any further content besides the identifier",
+ // ));
+ // }
+ // }
+ // Ok(PString {
+ // value: line.get(self.column..end).unwrap_or(" ").trim().to_string(),
+ // line: self.line,
+ // range: Range {
+ // start: self.column,
+ // end,
+ // },
+ // })
+ // }
+ // None => Ok(PString {
+ // value: line.get(self.column..).unwrap_or(" ").trim().to_string(),
+ // line: self.line,
+ // range: Range {
+ // start: self.column,
+ // end: line.len(),
+ // },
+ // }),
+ // }
+ // }
+
+ // fn fetch_possibly_quoted_identifier(&mut self, id: &PString, line: &str) -> Result<PString> {
+ // let mut identifier = self.fetch_identifier(id, line)?;
+
+ // if identifier.value.starts_with('\'') || identifier.value.starts_with('`') {
+ // return Err(id.parser_error("use double quotes for the segment identifier instead"));
+ // } else if identifier.value.starts_with('"') {
+ // identifier.value = match identifier
+ // .value
+ // .get(1..(identifier.range.end - identifier.range.start - 1))
+ // {
+ // Some(v) => v.to_string(),
+ // None => return Err(id.parser_error("could not fetch quoted identifier")),
+ // };
+ // if identifier.value.contains('"') {
+ // return Err(id.parser_error("do not use double quotes inside of the identifier"));
+ // }
+ // identifier.range.start += 1;
+ // identifier.range.end -= 1;
+ // }
+
+ // Ok(identifier)
+ // }
+
+ // fn parse_label(&mut self, id: PString, _line: &str) -> Result<()> {
+ // let name = &id.value.as_str()[..id.value.len() - 1].to_string();
+
+ // // Forbid weird scenarios.
+ // if name.contains("::") {
+ // return Err(id.parser_error(
+ // format!(
+ // "the label '{}' is scoped: do not declare variables this way",
+ // id.value
+ // )
+ // .as_str(),
+ // ));
+ // }
+
+ // // Insert the given label into the context.
+ // if let Some(entry) = self.context.current_mut() {
+ // match entry.entry(name.clone()) {
+ // Entry::Occupied(e) => {
+ // return Err(ParseError {
+ // line: self.line,
+ // message: format!(
+ // "label '{}' already exists for this context: it was previously defined in line {}",
+ // id.value, e.get().node.line),
+ // })
+ // }
+ // Entry::Vacant(e) => e.insert(PValue {
+ // node: PString {
+ // value: name.clone(),
+ // line: self.line,
+ // range: Range {
+ // start: id.range.start,
+ // end: id.range.end,
+ // },
+ // },
+ // value: 0,
+ // label: true,
+ // }),
+ // };
+ // }
+
+ // // And add the node so it's picked up later.
+ // self.mapping.push(Node::Label(Label {
+ // value: name.to_string(),
+ // }));
+ // Ok(())
+ // }
fn parser_error(&self, msg: &str) -> ParseError {
ParseError {
message: String::from(msg),
line: self.line,
- }
- }
-
- pub fn from_byte_reader<R: Read>(&mut self, mut reader: R) -> Result<()> {
- loop {
- let mut buf = [0; 1];
- let n = reader.read(&mut buf)?;
- if n == 0 {
- break;
- }
-
- match OPCODES.get(&buf[0]) {
- Some(v) => {
- let mut bs = [0; 2];
- for i in 0..v.size - 1 {
- let nn = reader.read(&mut buf)?;
- if nn == 0 {
- break;
- }
- bs[i as usize] = buf[0];
- }
- self.mapping.push(Node::Instruction(Instruction {
- mnemonic: PString::from(&v.mnemonic),
- opcode: v.opcode,
- size: v.size,
- bytes: bs,
- left: None,
- right: None,
- mode: v.mode.to_owned(),
- cycles: v.cycles,
- affected_on_page: v.affected_on_page,
- address: 0, // TODO
- resolved: true,
- }))
- }
-
- None => {
- return Err(
- self.parser_error(format!("unknown byte '0x{:02X}'", buf[0]).as_str())
- )
- }
- }
- }
-
- Ok(())
- }
+ parse: true,
+ }
+ }
+
+ // fn from_byte_reader<R: Read>(&mut self, mut reader: R) -> Result<()> {
+ // loop {
+ // let mut buf = [0; 1];
+ // let n = reader.read(&mut buf)?;
+ // if n == 0 {
+ // break;
+ // }
+
+ // match OPCODES.get(&buf[0]) {
+ // Some(v) => {
+ // let mut bs = [0; 2];
+ // for i in 0..v.size - 1 {
+ // let nn = reader.read(&mut buf)?;
+ // if nn == 0 {
+ // break;
+ // }
+ // bs[i as usize] = buf[0];
+ // }
+ // self.mapping.push(Node::Instruction(Instruction {
+ // mnemonic: PString::from(&v.mnemonic),
+ // opcode: v.opcode,
+ // size: v.size,
+ // bytes: bs,
+ // left: None,
+ // right: None,
+ // mode: v.mode.to_owned(),
+ // cycles: v.cycles,
+ // affected_on_page: v.affected_on_page,
+ // address: 0, // TODO
+ // resolved: true,
+ // }))
+ // }
+
+ // None => {
+ // return Err(
+ // self.parser_error(format!("unknown byte '0x{:02X}'", buf[0]).as_str())
+ // )
+ // }
+ // }
+ // }
+
+ // Ok(())
+ // }
}
#[cfg(test)]
@@ -1625,57 +1668,25 @@ mod tests {
use super::*;
use crate::mapping::EMPTY;
- fn assert_hex(one: &dyn Encodable, expected: &[u8]) {
- assert_eq!(
- one.to_hex(),
- expected
- .iter()
- .map(|x| format!("{:02X}", x))
- .collect::<Vec<_>>()
- );
- }
-
fn instruction_test(line: &str, hex: &[u8], skip_disassemble: bool) {
- let mut parser = Assembler::new(EMPTY.to_vec());
- let res = parser.assemble(line.as_bytes());
-
- if res.is_err() {
- if let Err(e) = res.clone() {
- assert_eq!(
- e,
- ParseError {
- line: 0,
- message: String::from("")
- }
- )
- }
- }
-
- let vec = res.unwrap();
+ let mut asm = Assembler::new(EMPTY.to_vec());
+ let res = asm.assemble(line.as_bytes()).unwrap();
- assert_eq!(vec.len(), 1);
- assert_hex(vec[0], hex);
+ assert_eq!(res.len(), 1);
- // Now disassemble.
+ for i in 0..res[0].size {
+ assert_eq!(hex[i as usize], res[0].bytes[i as usize]);
+ }
if skip_disassemble {
return;
}
-
- parser.reset();
- let dis = parser.disassemble(hex);
- assert!(dis.is_ok());
-
- let dvec = dis.unwrap();
- assert_eq!(dvec.len(), 1);
-
- let dinstr = dvec[0];
- assert_eq!(dinstr.to_human(), line);
+ // TODO
}
fn instruction_err(line: &str, message: &str) {
- let mut parser = Assembler::new(EMPTY.to_vec());
- let err = parser.assemble(line.as_bytes());
+ let mut asm = Assembler::new(EMPTY.to_vec());
+ let err = asm.assemble(line.as_bytes());
assert!(err.is_err());
if let Err(e) = err {
@@ -1683,37 +1694,36 @@ mod tests {
}
}
- // Mainly errors.
-
#[test]
fn bad_addressing() {
instruction_err("unknown #$20", "unknown instruction 'unknown'");
instruction_err(
"adc ($2002, x)",
- "when parsing an instruction with indirect X-indexed addressing: only one byte of data is allowed here",
+ "address can only be one byte long on indirect X addressing",
);
instruction_err(
- "adc ($2002, x), y",
- "bad indirect mode, expecting an indirect X-indexed addressing mode",
+ "adc ($20, x), y",
+ "it has to be either X addressing or Y addressing, not all at once",
);
instruction_err(
"adc ($2002), y",
- "when parsing an instruction with indirect Y-indexed addressing: only one byte of data is allowed here",
+ "address can only be one byte long on indirect Y addressing",
);
instruction_err(
"adc ($20, y)",
- "the index in indirect X-indexed addressing must be X",
+ "only the X index is allowed on indirect X addressing",
);
instruction_err(
"adc ($20), x",
- "the index in indirect Y-indexed addressing must be Y",
+ "only the Y index is allowed on indirect Y addressing",
);
instruction_err("jmp ($20)", "expecting a full 16-bit address");
- instruction_err("adc $20, z", "index is neither X nor Y");
+ instruction_err("adc $20, z", "can only use X and Y as indices");
instruction_err(
"adc ($2000)",
- "bad addressing mode 'indirect' for the instruction 'adc'",
+ "cannot use indirect addressing mode for the instruction 'adc'",
);
+ instruction_err("lda 12", "no prefix was given to operand")
}
#[test]
@@ -1731,8 +1741,9 @@ mod tests {
#[test]
fn parse_hexadecimal() {
instruction_err("adc $", "expecting a number of 1 to 4 hexadecimal digits");
- instruction_err("adc #$", "expecting a number of 2 hexadecimal digits");
- instruction_err("adc $AW", "unknown variable 'AW'");
+ // TODO: see comment on literal_mode being a stack.
+ instruction_err("adc #$", "expecting a number of 1 to 4 hexadecimal digits");
+ instruction_err("adc $AW", "could not convert digit to hexadecimal");
instruction_test("adc $AA", &[0x65, 0xAA], false);
instruction_test("adc $10", &[0x65, 0x10], false);
instruction_test("adc $10AB", &[0x6D, 0xAB, 0x10], false);
@@ -1743,7 +1754,7 @@ mod tests {
instruction_err("adc #", "empty decimal literal");
instruction_err("adc #256", "decimal value is too big");
instruction_err("adc #2000", "decimal value is too big");
- instruction_err("adc #2A", "'A' is not a decimal value");
+ instruction_err("adc #2A", "unknown variable '2A'"); // TODO: not sure about this
instruction_test("adc #1", &[0x69, 0x01], true);
}
@@ -1848,6 +1859,33 @@ mod tests {
}
#[test]
+ fn load() {
+ // lda
+ instruction_test("lda #$20", &[0xA9, 0x20], false);
+ instruction_test("lda $20", &[0xA5, 0x20], false);
+ instruction_test("lda $20, x", &[0xB5, 0x20], false);
+ instruction_test("lda $2002", &[0xAD, 0x02, 0x20], false);
+ instruction_test("lda $2002, x", &[0xBD, 0x02, 0x20], false);
+ instruction_test("lda $2002, y", &[0xB9, 0x02, 0x20], false);
+ instruction_test("lda ($20, x)", &[0xA1, 0x20], false);
+ instruction_test("lda ($20), y", &[0xB1, 0x20], false);
+
+ // ldx
+ instruction_test("ldx #$20", &[0xA2, 0x20], false);
+ instruction_test("ldx $20", &[0xA6, 0x20], false);
+ instruction_test("ldx $20, y", &[0xB6, 0x20], false);
+ instruction_test("ldx $2002", &[0xAE, 0x02, 0x20], false);
+ instruction_test("ldx $2002, y", &[0xBE, 0x02, 0x20], false);
+
+ // ldy
+ instruction_test("ldy #$20", &[0xA0, 0x20], false);
+ instruction_test("ldy $20", &[0xA4, 0x20], false);
+ instruction_test("ldy $20, x", &[0xB4, 0x20], false);
+ instruction_test("ldy $2002", &[0xAC, 0x02, 0x20], false);
+ instruction_test("ldy $2002, x", &[0xBC, 0x02, 0x20], false);
+ }
+
+ #[test]
fn jump() {
instruction_test("jsr $2002", &[0x20, 0x02, 0x20], false);
@@ -1944,33 +1982,6 @@ mod tests {
}
#[test]
- fn load() {
- // lda
- instruction_test("lda #$20", &[0xA9, 0x20], false);
- instruction_test("lda $20", &[0xA5, 0x20], false);
- instruction_test("lda $20, x", &[0xB5, 0x20], false);
- instruction_test("lda $2002", &[0xAD, 0x02, 0x20], false);
- instruction_test("lda $2002, x", &[0xBD, 0x02, 0x20], false);
- instruction_test("lda $2002, y", &[0xB9, 0x02, 0x20], false);
- instruction_test("lda ($20, x)", &[0xA1, 0x20], false);
- instruction_test("lda ($20), y", &[0xB1, 0x20], false);
-
- // ldx
- instruction_test("ldx #$20", &[0xA2, 0x20], false);
- instruction_test("ldx $20", &[0xA6, 0x20], false);
- instruction_test("ldx $20, y", &[0xB6, 0x20], false);
- instruction_test("ldx $2002", &[0xAE, 0x02, 0x20], false);
- instruction_test("ldx $2002, y", &[0xBE, 0x02, 0x20], false);
-
- // ldy
- instruction_test("ldy #$20", &[0xA0, 0x20], false);
- instruction_test("ldy $20", &[0xA4, 0x20], false);
- instruction_test("ldy $20, x", &[0xB4, 0x20], false);
- instruction_test("ldy $2002", &[0xAC, 0x02, 0x20], false);
- instruction_test("ldy $2002, x", &[0xBC, 0x02, 0x20], false);
- }
-
- #[test]
fn store_instructions() {
//sta
instruction_test("sta $20", &[0x85, 0x20], false);
@@ -2001,9 +2012,15 @@ mod tests {
// Variables & scopes.
#[test]
+ fn using_variables() {
+ // TODO
+ // todo!()
+ }
+
+ #[test]
fn scoped_variable() {
- let mut parser = Assembler::new(EMPTY.to_vec());
- let res = parser
+ let mut asm = Assembler::new(EMPTY.to_vec());
+ let res = asm
.assemble(
r#"
.scope One ; This is a comment
@@ -2030,13 +2047,9 @@ adc #Another::Variable
let instrs: Vec<[u8; 2]> = vec![[0x69, 0x20], [0x69, 0x30], [0x69, 0x20], [0x69, 0x40]];
for i in 0..4 {
- assert_eq!(
- res[i].to_hex(),
- instrs[i]
- .iter()
- .map(|x| format!("{:02X}", x))
- .collect::<Vec<_>>()
- );
+ assert_eq!(res[i].size, 2);
+ assert_eq!(res[i].bytes[0], instrs[i][0]);
+ assert_eq!(res[i].bytes[1], instrs[i][1]);
}
}
@@ -2060,117 +2073,101 @@ Yet = 4
if let Err(e) = res {
assert_eq!(
e.message,
- "variable 'Yet' is being re-assigned: it was previously defined in line 6"
+ "variable 'Yet' is being re-assigned: it was previously defined in line 7"
);
}
}
#[test]
- fn bad_variable_names() {
- instruction_err("a = 2", "cannot use reserved name 'a'");
- instruction_err("X = 2", "cannot use reserved name 'X'");
-
- instruction_err(
- "AA = 2",
- "cannot use names which are valid hexadecimal values such as 'AA'",
- );
-
- instruction_err(
- "Scope::Variable = 2",
- "the name 'Scope::Variable' is scoped: do not declare variables this way",
- );
- }
-
- #[test]
fn bad_assignment() {
instruction_err("Variable =", "incomplete assignment");
instruction_err("Variable = ; comment", "incomplete assignment");
- instruction_err("adc = $12", "cannot use 'adc' in an assignment because it's a word reserved for an instruction mnemonic");
- }
-
- // Literals
-
- #[test]
- fn byte_literals_errors() {
- // TODO
- // instruction_err(
- // ".byte $0102",
- // "when parsing a data literal: only one byte of data is allowed here",
- // );
- // instruction_err(".byte '$01", "non-terminated quote for byte literal");
- // instruction_err(".byte '$01, $02", "non-terminated quote for byte literal");
- }
-
- #[test]
- fn byte_literals() {
- let mut asm = Assembler::new(EMPTY.to_vec());
-
- let mut res = asm.assemble(".byte $01".as_bytes()).unwrap();
- assert_eq!(res.len(), 1);
- assert_hex(res[0], &[0x01]);
-
- asm.reset();
- res = asm.assemble(".db $01, $02".as_bytes()).unwrap();
- assert_eq!(res.len(), 2);
- assert_hex(res[0], &[0x01]);
- assert_hex(res[1], &[0x02]);
-
- asm.reset();
- res = asm
- .assemble(".byte $01, 2, '%00000011', \"$04\"".as_bytes())
- .unwrap();
- assert_eq!(res.len(), 4);
- assert_hex(res[0], &[0x01]);
- assert_hex(res[1], &[0x02]);
- assert_hex(res[2], &[0x03]);
- assert_hex(res[3], &[0x04]);
- }
-
- #[test]
- fn word_literals() {
- let mut asm = Assembler::new(EMPTY.to_vec());
-
- let mut res = asm.assemble(".word $01".as_bytes()).unwrap();
- assert_eq!(res.len(), 1);
- assert_hex(res[0], &[0x01, 0x00]);
-
- asm.reset();
- res = asm.assemble(".dw $0102, $02".as_bytes()).unwrap();
- assert_eq!(res.len(), 2);
- assert_hex(res[0], &[0x02, 0x01]);
- assert_hex(res[1], &[0x02, 0x00]);
-
- asm.reset();
- res = asm
- .assemble(".word $0102, $0204, '$0308', \"$0410\"".as_bytes())
- .unwrap();
- assert_eq!(res.len(), 4);
- assert_hex(res[0], &[0x02, 0x01]);
- assert_hex(res[1], &[0x04, 0x02]);
- assert_hex(res[2], &[0x08, 0x03]);
- assert_hex(res[3], &[0x10, 0x04]);
- }
-
- #[test]
- fn variables_in_literals() {
- let mut asm = Assembler::new(EMPTY.to_vec());
- let res = asm
- .assemble(
- r#"
-.scope One
- Variable = $01
-.endscope
-
-Variable = $02
-.byte One::Variable, Variable, $03
-"#
- .as_bytes(),
- )
- .unwrap();
-
- assert_eq!(res.len(), 3);
- assert_hex(res[0], &[0x01]);
- assert_hex(res[1], &[0x02]);
- assert_hex(res[2], &[0x03]);
}
}
+
+// // Literals
+
+// #[test]
+// fn byte_literals_errors() {
+// // TODO
+// // instruction_err(
+// // ".byte $0102",
+// // "when parsing a data literal: only one byte of data is allowed here",
+// // );
+// // instruction_err(".byte '$01", "non-terminated quote for byte literal");
+// // instruction_err(".byte '$01, $02", "non-terminated quote for byte literal");
+// }
+
+// #[test]
+// fn byte_literals() {
+// let mut asm = Assembler::new(EMPTY.to_vec());
+
+// let mut res = asm.assemble(".byte $01".as_bytes()).unwrap();
+// assert_eq!(res.len(), 1);
+// assert_hex(res[0], &[0x01]);
+
+// asm.reset();
+// res = asm.assemble(".db $01, $02".as_bytes()).unwrap();
+// assert_eq!(res.len(), 2);
+// assert_hex(res[0], &[0x01]);
+// assert_hex(res[1], &[0x02]);
+
+// asm.reset();
+// res = asm
+// .assemble(".byte $01, 2, '%00000011', \"$04\"".as_bytes())
+// .unwrap();
+// assert_eq!(res.len(), 4);
+// assert_hex(res[0], &[0x01]);
+// assert_hex(res[1], &[0x02]);
+// assert_hex(res[2], &[0x03]);
+// assert_hex(res[3], &[0x04]);
+// }
+
+// #[test]
+// fn word_literals() {
+// let mut asm = Assembler::new(EMPTY.to_vec());
+
+// let mut res = asm.assemble(".word $01".as_bytes()).unwrap();
+// assert_eq!(res.len(), 1);
+// assert_hex(res[0], &[0x01, 0x00]);
+
+// asm.reset();
+// res = asm.assemble(".dw $0102, $02".as_bytes()).unwrap();
+// assert_eq!(res.len(), 2);
+// assert_hex(res[0], &[0x02, 0x01]);
+// assert_hex(res[1], &[0x02, 0x00]);
+
+// asm.reset();
+// res = asm
+// .assemble(".word $0102, $0204, '$0308', \"$0410\"".as_bytes())
+// .unwrap();
+// assert_eq!(res.len(), 4);
+// assert_hex(res[0], &[0x02, 0x01]);
+// assert_hex(res[1], &[0x04, 0x02]);
+// assert_hex(res[2], &[0x08, 0x03]);
+// assert_hex(res[3], &[0x10, 0x04]);
+// }
+
+// #[test]
+// fn variables_in_literals() {
+// let mut asm = Assembler::new(EMPTY.to_vec());
+// let res = asm
+// .assemble(
+// r#"
+// .scope One
+// Variable = $01
+// .endscope
+
+// Variable = $02
+// .byte One::Variable, Variable, $03
+// "#
+// .as_bytes(),
+// )
+// .unwrap();
+
+// assert_eq!(res.len(), 3);
+// assert_hex(res[0], &[0x01]);
+// assert_hex(res[1], &[0x02]);
+// assert_hex(res[2], &[0x03]);
+// }
+// }
diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/context.rs
index 32f9de9..2ac6da8 100644
--- a/lib/xixanta/src/context.rs
+++ b/lib/xixanta/src/context.rs
@@ -1,12 +1,13 @@
-use crate::instruction::PString;
+use crate::instruction::Bundle;
+use crate::parser::PNode;
use std::collections::HashMap;
const GLOBAL_CONTEXT: &str = "Global";
#[derive(Debug)]
pub struct PValue {
- pub node: PString,
- pub value: usize,
+ pub node: PNode,
+ pub value: Bundle,
pub label: bool,
}
@@ -30,10 +31,6 @@ impl Context {
}
}
- pub fn global(&mut self) {
- self.stack = vec![];
- }
-
pub fn find(&self, name: &str) -> Option<&HashMap<String, PValue>> {
self.map.get(name)
}
@@ -52,6 +49,17 @@ impl Context {
}
}
+ pub fn is_global(&self) -> bool {
+ self.stack.is_empty()
+ }
+
+ pub fn name(&self) -> &str {
+ match self.stack.last() {
+ Some(name) => name,
+ None => GLOBAL_CONTEXT,
+ }
+ }
+
pub fn push(&mut self, identifier: &String) {
let name = match self.stack.last() {
Some(n) => n.to_owned() + &String::from("::") + identifier,
@@ -62,14 +70,14 @@ impl Context {
self.map.entry(name).or_default();
}
- pub fn push_stack(&mut self, identifier: &String) {
- let name = match self.stack.last() {
- Some(n) => n.to_owned() + &String::from("::") + identifier,
- None => identifier.to_string(),
- };
+ // pub fn push_stack(&mut self, identifier: &String) {
+ // let name = match self.stack.last() {
+ // Some(n) => n.to_owned() + &String::from("::") + identifier,
+ // None => identifier.to_string(),
+ // };
- self.stack.push(name.clone());
- }
+ // self.stack.push(name.clone());
+ // }
pub fn pop(&mut self) -> bool {
if self.stack.is_empty() {
diff --git a/lib/xixanta/src/errors.rs b/lib/xixanta/src/errors.rs
index d9a12cd..6c99f57 100644
--- a/lib/xixanta/src/errors.rs
+++ b/lib/xixanta/src/errors.rs
@@ -1,10 +1,12 @@
use std::fmt;
// TODO: global error
+// TODO: more errors, the `parse` thing is a hack!
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub line: usize,
pub message: String,
+ pub parse: bool,
}
impl std::error::Error for ParseError {}
@@ -15,6 +17,7 @@ impl From<std::io::Error> for ParseError {
ParseError {
line: 0,
message: err.to_string(),
+ parse: true,
}
}
}
diff --git a/lib/xixanta/src/instruction.rs b/lib/xixanta/src/instruction.rs
index 2d6c044..ced452a 100644
--- a/lib/xixanta/src/instruction.rs
+++ b/lib/xixanta/src/instruction.rs
@@ -32,17 +32,99 @@ impl PString {
ParseError {
line: self.line,
message: String::from(message),
+ parse: true,
}
}
- pub fn is_reserved(&self) -> bool {
- matches!(self.value.to_lowercase().as_str(), "x" | "y" | "a")
+ pub fn is_valid(&self) -> bool {
+ !(self.value.is_empty() || self.range.is_empty())
+ }
+
+ pub fn is_valid_identifier(&self) -> Result<(), String> {
+ if self.value.trim().is_empty() {
+ return Err(format!("empty identifier"));
+ }
+
+ // You cannot assign into a name which is reserved.
+ if matches!(self.value.to_lowercase().as_str(), "x" | "y" | "a") {
+ return Err(format!("cannot use reserved name '{}'", self.value));
+ }
+
+ // You cannot assign into scoped names: declare them into their
+ // respective scopes instead.
+ if self.value.contains("::") {
+ return Err(format!(
+ "the name '{}' is scoped: do not declare things this way",
+ self.value
+ ));
+ }
+
+ // Let's gather info from the variable name which is relevant to later
+ // checks.
+ let mut alpha_seen = false;
+ let mut valid_hex = match self.value.len() {
+ 1 | 2 | 3 | 4 => true,
+ _ => false,
+ };
+ for c in self.value.to_lowercase().chars() {
+ if c == '_' {
+ valid_hex = false;
+ } else {
+ if c.is_alphabetic() {
+ alpha_seen = true;
+ if c > 'f' && c <= 'z' {
+ valid_hex = false;
+ }
+ }
+ }
+ }
+
+ // We need at least one alphabetic character. Otherwise it might be
+ // confusing with numbers.
+ if !alpha_seen {
+ return Err(format!(
+ "name '{}' requires at least one alphabetic character",
+ self.value
+ ));
+ }
+
+ // To avoid problems down the line, you cannot assign into names which
+ // are proper hexadecimal values.
+ if valid_hex {
+ return Err(format!(
+ "cannot use names which are valid hexadecimal values such as '{}'",
+ self.value
+ ));
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Bundle {
+ pub bytes: [u8; 3],
+ pub size: u8,
+ pub address: usize,
+ pub cycles: u8,
+ pub affected_on_page: bool,
+}
+
+impl Bundle {
+ pub fn new() -> Self {
+ Self {
+ bytes: [0, 0, 0],
+ size: 0,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ }
}
}
#[derive(Eq, Hash, PartialEq, Debug, Clone)]
pub enum AddressingMode {
- Unknown,
+ Unknown, // TODO: is this really used?
Implied,
Immediate,
Absolute,
@@ -222,8 +304,8 @@ impl Encodable for Instruction {
#[derive(Debug, Clone, PartialEq)]
pub struct Generic {
pub identifier: PString,
- pub left: Option<PString>,
- pub right: Option<PString>,
+ pub left: Option<Box<Node>>,
+ pub right: Option<Box<Node>>,
}
#[derive(Debug, Clone, PartialEq)]
@@ -328,3 +410,42 @@ impl Node {
)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn is_err(line: &str, message: &str) {
+ let pstring = PString {
+ value: line.to_string(),
+ line: 0,
+ range: Range::default(),
+ };
+ let ret = pstring.is_valid_identifier();
+
+ assert!(ret.is_err());
+ if let Err(e) = ret {
+ assert_eq!(e, message);
+ }
+ }
+
+ #[test]
+ fn bad_variable_names() {
+ is_err("a", "cannot use reserved name 'a'");
+ is_err("X", "cannot use reserved name 'X'");
+
+ is_err(
+ "AA",
+ "cannot use names which are valid hexadecimal values such as 'AA'",
+ );
+
+ is_err("11", "name '11' requires at least one alphabetic character");
+
+ is_err("__", "name '__' requires at least one alphabetic character");
+
+ is_err(
+ "Scope::Variable",
+ "the name 'Scope::Variable' is scoped: do not declare things this way",
+ );
+ }
+}
diff --git a/lib/xixanta/src/lib.rs b/lib/xixanta/src/lib.rs
index fd3419c..5e81eba 100644
--- a/lib/xixanta/src/lib.rs
+++ b/lib/xixanta/src/lib.rs
@@ -9,3 +9,4 @@ mod errors;
pub mod instruction;
pub mod mapping;
mod opcodes;
+pub mod parser;
diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs
index a716545..e849126 100644
--- a/lib/xixanta/src/opcodes.rs
+++ b/lib/xixanta/src/opcodes.rs
@@ -19,6 +19,12 @@ pub struct Entry {
pub affected_on_page: bool,
}
+#[derive(Debug)]
+pub struct Control {
+ pub has_identifier: bool,
+ pub required_args: Option<usize>,
+}
+
lazy_static! {
pub static ref INSTRUCTIONS: HashMap<String, HashMap<AddressingMode, ShortEntry>> = {
let mut instrs = HashMap::new();
@@ -669,4 +675,25 @@ lazy_static! {
opcodes
};
+
+ pub static ref CONTROL_FUNCTIONS: HashMap<String, Control> = {
+ let mut functions = HashMap::new();
+
+ functions.insert(String::from(".hibyte"), Control { has_identifier: false, required_args: Some(1) });
+ functions.insert(String::from(".lobyte"), Control { has_identifier: false, required_args: Some(1) });
+ functions.insert(String::from(".macro"), Control { has_identifier: true, required_args: None });
+ functions.insert(String::from(".proc"), Control { has_identifier: true, required_args: Some(0) });
+ functions.insert(String::from(".scope"), Control { has_identifier: true, required_args: Some(0) });
+ functions.insert(String::from(".end"), Control { has_identifier: false, required_args: Some(0) });
+ functions.insert(String::from(".endscope"), Control { has_identifier: false, required_args: Some(0) });
+ functions.insert(String::from(".endproc"), Control { has_identifier: false, required_args: Some(0) });
+ functions.insert(String::from(".endmacro"), Control { has_identifier: false, required_args: Some(0) });
+ functions.insert(String::from(".segment"), Control { has_identifier: false, required_args: Some(1) });
+ functions.insert(String::from(".byte"), Control { has_identifier: false, required_args: None });
+ functions.insert(String::from(".db"), Control { has_identifier: false, required_args: None });
+ functions.insert(String::from(".word"), Control { has_identifier: false, required_args: None });
+ functions.insert(String::from(".dw"), Control { has_identifier: false, required_args: None });
+
+ functions
+ };
}
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
new file mode 100644
index 0000000..d11e98c
--- /dev/null
+++ b/lib/xixanta/src/parser.rs
@@ -0,0 +1,1496 @@
+use crate::errors::ParseError;
+use crate::instruction::PString;
+use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS};
+use std::io::{self, BufRead, Read};
+use std::ops::Range;
+
+// TODO: add cargo-fuzz
+
+type Result<T> = std::result::Result<T, ParseError>;
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum NodeType {
+ Value,
+ Instruction,
+ Indirection,
+ Assignment,
+ Control,
+ Literal,
+ Identifier,
+ Label,
+ Call,
+ Empty,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct PNode {
+ pub node_type: NodeType,
+ pub value: PString,
+ pub left: Option<Box<PNode>>,
+ pub right: Option<Box<PNode>>,
+ pub args: Option<Vec<Box<PNode>>>,
+}
+
+impl PNode {
+ pub fn empty() -> PNode {
+ Self {
+ node_type: NodeType::Empty,
+ value: PString::new(),
+ left: None,
+ right: None,
+ args: None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Parser {
+ line: usize,
+ column: usize,
+ offset: usize,
+ pub nodes: Vec<Box<PNode>>,
+ pub errors: Vec<ParseError>,
+}
+
+impl Parser {
+ pub fn new() -> Self {
+ Self {
+ line: 0,
+ column: 0,
+ offset: 0,
+ nodes: Vec::new(),
+ errors: Vec::new(),
+ }
+ }
+
+ pub fn reset(&mut self) {
+ self.line = 0;
+ self.column = 0;
+ self.offset = 0;
+ self.nodes = Vec::new();
+ self.errors = Vec::new();
+ }
+
+ pub fn parse(&mut self, reader: impl Read) -> Result<()> {
+ for line in io::BufReader::new(reader).lines() {
+ if let Err(err) = self.parse_line(line?.as_str()) {
+ self.errors.push(err);
+ }
+ self.line += 1;
+ }
+
+ println!("NODES: {:#?}", self.nodes);
+
+ match self.errors.last() {
+ Some(err) => Err(err.clone()),
+ None => Ok(()),
+ }
+ }
+
+ fn parse_line(&mut self, line: &str) -> Result<()> {
+ self.column = 0;
+
+ // Skip until the first non-whitespace character. If that's not
+ // possible, then it's an empty line and we can return early.
+ if !self.skip_whitespace(line) {
+ return Ok(());
+ }
+
+ // Let's pin point the last character we need to care for parsing. This
+ // can be either the start position of an inline comment (i.e. ';'), or
+ // the real line end.
+ let end = if let Some(comment) = line.find(|c: char| c == ';') {
+ comment
+ } else {
+ line.len()
+ };
+
+ // It's safe to trim the end of the resulting string. Moreover, doing so
+ // can already show lines which are actually empty (e.g. a line which
+ // simply contains a comment). If this is the case, just return an empty
+ // node.
+ let mut l = line.get(self.column..end).unwrap_or_default().trim_end();
+ if l.is_empty() {
+ return Ok(());
+ }
+
+ // Fetch the first element of the line, which we will call it an
+ // "identifier" but might be a label or a statement. The label might be
+ // followed by more code. Hence, push it first, then fetch the next
+ // identifier and finally fall through.
+ self.offset = 0;
+ let (mut id, mut nt) = self.parse_identifier(l)?;
+ if nt == NodeType::Label {
+ self.nodes.push(Box::new(PNode {
+ node_type: nt,
+ value: id,
+ left: None,
+ right: None,
+ args: None,
+ }));
+
+ self.skip_whitespace(l);
+
+ // Is it the label alone? If so return early.
+ l = line.get(self.column..end).unwrap_or_default().trim_end();
+ if l.is_empty() {
+ return Ok(());
+ }
+
+ // The label is followed by a statement. Let's parse the identifier
+ // for it and fall through.
+ self.offset = 0;
+ (id, nt) = self.parse_identifier(l)?;
+ if nt == NodeType::Label {
+ return Err(self.parser_error("cannot have multiple labels at the same location"));
+ }
+
+ self.skip_whitespace(l);
+ }
+
+ self.parse_statement(l, id)
+ }
+
+ fn parse_identifier(&mut self, line: &str) -> Result<(PString, NodeType)> {
+ let start = self.column;
+ let base_offset = self.offset;
+
+ // For the general case we just need to iterate until a whitespace
+ // character or an inline comment is found. Then our PString object
+ // is merely whatever is on the column..self.column range.
+ for c in line.get(self.offset..).unwrap_or("").chars() {
+ if c.is_whitespace() || c == ':' || c == '(' || c == ')' || c == '=' {
+ let val = String::from(line.get(base_offset..self.offset).unwrap_or("").trim());
+ let nt = if c == ':' {
+ NodeType::Label
+ } else {
+ NodeType::Value
+ };
+
+ // TODO
+ // self.next();
+ let end = if c == ':' {
+ self.next();
+ self.column - 1
+ } else {
+ self.column
+ };
+
+ return Ok((
+ PString {
+ value: val,
+ line: self.line,
+ range: Range {
+ start,
+ end, // TODO
+ // end: self.column - 1,
+ },
+ },
+ nt,
+ ));
+ } else if !c.is_alphanumeric() && c != '_' {
+ // TODO: on the contrary, if alphanumeric or _, just follow
+ // through. Otherwise always break. TODO NOT REALLY
+ // return Err(self.parser_error("bad character for possible identifier"));
+ }
+
+ self.next();
+ }
+
+ // The line is merely the identifier (e.g. instruction with implied
+ // addressing).
+ let id = String::from(line.get(base_offset..).unwrap_or("").trim());
+ Ok((
+ PString {
+ value: id,
+ line: self.line,
+ range: Range {
+ start,
+ end: self.column,
+ },
+ },
+ NodeType::Value,
+ ))
+ }
+
+ fn parse_statement(&mut self, line: &str, id: PString) -> Result<()> {
+ // There are only two top-level statements: instructions and
+ // assignments. Other kinds of expressions can also be used in the
+ // middle of assignments or instructions, and so they have to be handled
+ // as common expressions. Whether expressions make sense at the
+ // different levels is something to be figured out by the assembler.
+ match INSTRUCTIONS.get(&id.value) {
+ Some(_) => self.parse_instruction(line, id),
+ None => {
+ if line.contains('=') {
+ self.parse_assignment(line, id)
+ } else {
+ let node = self.parse_expression_with_identifier(id, line)?;
+ self.nodes.push(node);
+ Ok(())
+ }
+ }
+ }
+ }
+
+ fn parse_instruction(&mut self, line: &str, id: PString) -> Result<()> {
+ let mut paren = 0;
+
+ self.skip_whitespace(line);
+
+ if line.contains("=") {
+ return Err(self.parser_error(
+ format!("cannot used reserved name for the mnemonic '{}'", id.value).as_str(),
+ ));
+ }
+
+ let indirect = line.chars().nth(self.offset).unwrap_or(',') == '(';
+ let l = if indirect {
+ self.next();
+ self.skip_whitespace(line);
+ paren = self.find_matching_paren(line, self.offset)?;
+ line.get(self.offset..paren).unwrap_or_default()
+ } else {
+ line.get(self.offset..).unwrap_or_default()
+ };
+
+ self.offset = 0;
+ let mut left = if l.is_empty() {
+ None
+ } else {
+ Some(self.parse_left_arm(l)?)
+ };
+
+ self.skip_whitespace(l);
+
+ // The parsing of the left arm should have advanced the offset right
+ // into the right arm. If there is nothing there, then we have no right
+ // arm. Otherwise we have to parse the expression.
+ // TODO
+ let mut right_str = l.get(self.offset..).unwrap_or_default();
+ let mut right = if right_str.is_empty() {
+ None
+ } else {
+ self.offset = 0;
+ Some(self.parse_expression(right_str)?)
+ };
+
+ if indirect {
+ if left.is_none() {
+ return Err(self.parser_error("empty indirect addressing"));
+ }
+
+ left = Some(Box::new(PNode {
+ node_type: NodeType::Indirection,
+ value: PString::new(),
+ left,
+ right: right.clone(),
+ args: None,
+ }));
+
+ right_str = line.get(paren..).unwrap_or_default();
+ if !(right_str.is_empty() || right_str == ")") && right.is_some() {
+ return Err(self.parser_error("bad indirect addressing"));
+ }
+
+ right = if right_str.is_empty() || right_str == ")" {
+ None
+ } else {
+ self.offset = 0;
+
+ // TODO: ") ,"
+ self.next();
+ self.skip_whitespace(right_str);
+
+ // TODO: ", "
+ self.next();
+ self.skip_whitespace(right_str);
+
+ Some(self.parse_expression(right_str)?)
+ };
+ }
+
+ self.nodes.push(Box::new(PNode {
+ node_type: NodeType::Instruction,
+ value: id,
+ left,
+ right,
+ args: None,
+ }));
+
+ Ok(())
+ }
+
+ fn parse_assignment(&mut self, line: &str, id: PString) -> Result<()> {
+ if let Err(msg) = id.is_valid_identifier() {
+ return Err(self.parser_error(&msg));
+ }
+
+ // Skip whitespaces and make sure that we have a '=' sign.
+ self.skip_whitespace(line);
+ if line.chars().nth(self.offset).unwrap_or(' ') != '=' {
+ return Err(self.parser_error(format!("unknown instruction '{}'", id.value).as_str()));
+ }
+
+ // Skip the '=' sign and any possible whitespaces.
+ self.next();
+ self.skip_whitespace(line);
+
+ // Parse the expression on the right side of the assignment.
+ let rest = line.get(self.offset..).unwrap_or("").trim_end();
+ if rest.is_empty() {
+ return Err(self.parser_error("incomplete assignment"));
+ };
+ self.offset = 0;
+ let left = Some(self.parse_expression(rest)?);
+
+ self.nodes.push(Box::new(PNode {
+ node_type: NodeType::Assignment,
+ value: id.clone(),
+ left,
+ right: None,
+ args: None,
+ }));
+
+ Ok(())
+ }
+
+ fn parse_arguments(&mut self, line: &str) -> Result<Vec<Box<PNode>>> {
+ // Skip any possible whitespace before the optional opening paren.
+ self.skip_whitespace(line);
+
+ // Scope the end of the argument list. If the arguments are enclosed on
+ // parenthesis, take that into account, otherwise we will parse until
+ // the end of the cleaned line.
+ let paren = line.chars().nth(self.offset).unwrap_or_default() == '(';
+ let end = if paren {
+ self.next();
+ self.skip_whitespace(line);
+ self.find_matching_paren(line, self.offset)?
+ } else {
+ line.len()
+ };
+
+ let mut args = Vec::new();
+
+ loop {
+ // TODO: trimmed_str out?
+ let trimmed_str = line.get(..end).unwrap_or_default().trim_end();
+ println!("TRIMME: {:#?}", trimmed_str.get(self.offset..));
+
+ let (arg_end, comma) = self.find_left_end(trimmed_str, false)?;
+ let arg_untrimmed = line.get(self.offset..arg_end).unwrap_or_default();
+ let arg = arg_untrimmed.trim_end();
+ let diff = arg_untrimmed.len() - arg.len();
+ // .trim_end();
+ println!(
+ "ARG_END: {:#?} -- ARG: {:#?} - DIFF: {}",
+ arg_end, arg, diff
+ );
+ if arg.is_empty() {
+ break;
+ }
+ // if !comma {
+ // s = arg.to_owned() + " ";
+ // arg = s.as_str();
+ // }
+
+ self.offset = 0;
+ args.push(self.parse_expression(arg)?);
+
+ self.offset = arg_end;
+ self.column += diff;
+ println!("{:#?}", line.get(self.offset..end));
+ self.skip_whitespace(line); // TODO
+ if comma {
+ self.next();
+ self.skip_whitespace(line);
+ }
+ }
+
+ println!("ARGS: {:#?}", args);
+
+ Ok(args)
+ }
+
+ fn parse_left_arm(&mut self, line: &str) -> Result<Box<PNode>> {
+ let start_column = self.column;
+
+ // We track the start value of the offset and we will keep track of the
+ // movement of it on `end`. This allows us to preserve the value on
+ // inner calls that might modify the offset value.
+ // TODO
+ let (end, comma) = self.find_left_end(line, false)?;
+
+ // Set the offset to 0 since we are constraining the string to be
+ // parsed.
+ let str = line.get(..end).unwrap_or_default().trim_end();
+ self.offset = 0;
+
+ // Parse the expression that we can get from the current offset to the
+ // computed end.
+ let expr = self.parse_expression(str);
+
+ // Set the offset to the end of the line that is shared with the caller.
+ // let diff_column = (end - start) - (self.column - start_column);
+ self.offset = end;
+ self.column = start_column + end;
+ if comma {
+ self.next();
+ }
+ expr
+ }
+
+ // TODO: revisit inside_paren
+ fn find_left_end(&self, line: &str, inside_paren: bool) -> Result<(usize, bool)> {
+ let mut idx = self.offset;
+ let mut parens = if inside_paren { 1 } else { 0 };
+ let mut comma = false;
+
+ for c in line.get(self.offset..).unwrap_or_default().chars() {
+ if c == ',' {
+ if parens == 0 {
+ comma = true;
+ break;
+ }
+ } else if c == '(' {
+ parens += 1;
+ } else if c == ')' {
+ parens -= 1;
+ }
+
+ idx += 1;
+
+ if parens < 0 {
+ return Err(self.parser_error("too many closing parenthesis"));
+ }
+ }
+ if parens > 0 {
+ return Err(self.parser_error("unclosed parenthesis"));
+ }
+
+ Ok((idx, comma))
+ }
+
+ fn find_matching_paren(&self, line: &str, init: usize) -> Result<usize> {
+ let mut idx = init;
+ let mut parens = 1;
+
+ for c in line.get(init..).unwrap_or_default().chars() {
+ if c == '(' {
+ parens += 1;
+ } else if c == ')' {
+ parens -= 1;
+ }
+
+ if parens == 0 {
+ return Ok(idx);
+ } else if parens < 0 {
+ return Err(self.parser_error("too many closing parenthesis"));
+ }
+
+ idx += 1;
+ }
+ if parens > 0 {
+ return Err(self.parser_error("unclosed parenthesis"));
+ }
+
+ Ok(idx)
+ }
+
+ // Parse the expression under `line`. Indeces such as `self.column` and
+ // `self.offset` are assumed to be correct at this point for the given
+ // `line` (e.g. the line might not be a full line but rather a limited range
+ // and the offset has been set accordingly). Returns a new node for the
+ // expression at hand.
+ fn parse_expression(&mut self, line: &str) -> Result<Box<PNode>> {
+ let (id, nt) = self.parse_identifier(line)?;
+
+ if nt == NodeType::Label {
+ Err(self.parser_error("not expecting a label defined here"))
+ } else {
+ self.parse_expression_with_identifier(id, line)
+ }
+ }
+
+ // Parse the expression under `line` by taking into consideration that a
+ // part of it has already been parsed and evaluated as the given `id`.
+ // Indeces such as `self.column` and `self.offset` are assumed to be correct
+ // at this point. Returns a new node for the expression at hand.
+ fn parse_expression_with_identifier(&mut self, id: PString, line: &str) -> Result<Box<PNode>> {
+ // Reaching this condition is usually a bad sign, but there is so many
+ // ways in which it could go wrong, that an `assert!` wouldn't be fair
+ // either. Hence, just error out.
+ if !id.is_valid() {
+ return Err(self.parser_error("invalid identifier"));
+ }
+
+ if id.value.starts_with(".") {
+ self.parse_control(id, line)
+ } else if line.starts_with('$') || line.starts_with('#') || line.starts_with('%') {
+ self.parse_literal(id, line)
+ } else {
+ // If there is an indication that it might be a macro call, process
+ // it as such.
+ self.skip_whitespace(line);
+ if !line
+ .get(self.offset..)
+ .unwrap_or_default()
+ .trim_end()
+ .is_empty()
+ {
+ let args = self.parse_arguments(line)?;
+ return Ok(Box::new(PNode {
+ node_type: NodeType::Call,
+ value: id,
+ left: None,
+ right: None,
+ args: if args.is_empty() { None } else { Some(args) },
+ }));
+ }
+
+ // Blindly return the identifier as a PNode. This might be either a
+ // value as-is, or a macro call which we can't make sense at the
+ // moment. Eitherway, let the assembler decide.
+ Ok(Box::new(PNode {
+ node_type: NodeType::Value,
+ value: id,
+ left: None,
+ right: None,
+ args: None,
+ }))
+ }
+ }
+
+ // Returns a NodeType::Control node with whatever could be parsed
+ // considering the given `id` and rest of the `line`.
+ fn parse_control(&mut self, id: PString, line: &str) -> Result<Box<PNode>> {
+ let mut left = None;
+ let required;
+
+ // Ensure that this is a function that we know of. In the past this was
+ // not done and it brought too many problems that made the more
+ // "abstract" way of handling this just too complicated.
+ if let Some(control) = CONTROL_FUNCTIONS.get(&id.value.to_lowercase()) {
+ required = control.required_args;
+
+ // If this control function has an identifier (e.g. `.macro
+ // Identifier(args...)`), let's parse it now.
+ if control.has_identifier {
+ self.skip_whitespace(line);
+ left = Some(Box::new(PNode {
+ node_type: NodeType::Value,
+ value: self.parse_identifier(line)?.0,
+ left: None,
+ right: None,
+ args: None,
+ }));
+ }
+ } else {
+ return Err(self.parser_error(format!("unknown function '{}'", id.value).as_str()));
+ }
+
+ // At this point we reached the arguments (i.e. any identifier required
+ // by the control function has already been parsed and set in `left`).
+ // Then, just parse the arguments and ensure that it matches the amount
+ // required by the function.
+ let args = self.parse_arguments(line)?;
+ if let Some(args_required) = required {
+ if args.len() != args_required {
+ return Err(self.parser_error(
+ format!("wrong number of arguments for function '{}'", id.value).as_str(),
+ ));
+ }
+ }
+
+ Ok(Box::new(PNode {
+ node_type: NodeType::Control,
+ value: id,
+ left,
+ right: None,
+ args: if args.is_empty() { None } else { Some(args) },
+ }))
+ }
+
+ // Returns a NodeType::Literal node with whatever could be parsed
+ // considering the given `id` and rest of the `line`.
+ fn parse_literal(&mut self, id: PString, line: &str) -> Result<Box<PNode>> {
+ // Force the column to point to the literal character just in case
+ // 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.offset = 0;
+ self.next();
+ self.skip_whitespace(line);
+
+ // With this, just fetch the inner expression and return the literal
+ // node.
+ let inner = line.get(self.offset..).unwrap_or("");
+ self.offset = 0;
+ let left = self.parse_expression(inner)?;
+
+ Ok(Box::new(PNode {
+ node_type: NodeType::Literal,
+ value: id,
+ left: Some(left),
+ right: None,
+ args: None,
+ }))
+ }
+
+ // Returns a new ParseError by using the current line.
+ fn parser_error(&self, msg: &str) -> ParseError {
+ ParseError {
+ message: String::from(msg),
+ line: self.line,
+ parse: true,
+ }
+ }
+
+ // Advances `self.column` and `self.offset` until a non-whitespace character
+ // is found. Note that the initial index is bound to `self.offset`. Returns
+ // false if the line can be skipped entirely, true otherwise.
+ fn skip_whitespace(&mut self, line: &str) -> bool {
+ if line.is_empty() {
+ return false;
+ }
+
+ for c in line.get(self.offset..).unwrap_or("").chars() {
+ if !c.is_whitespace() {
+ if c == ';' {
+ return false;
+ }
+ return true;
+ }
+
+ self.next();
+ }
+
+ true
+ }
+
+ // Increment `self.column` and `self.offset` by one.
+ fn next(&mut self) {
+ self.column += 1;
+ self.offset += 1;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn assert_one_valid(parser: &mut Parser, line: &str) {
+ assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.nodes.len() == 1);
+ }
+
+ fn assert_node(node: &Box<PNode>, nt: NodeType, line: &str, value: &str) {
+ assert_eq!(node.node_type, nt);
+ assert_eq!(
+ node.value.value.as_str(),
+ line.get(node.value.range.clone()).unwrap()
+ );
+ assert_eq!(node.value.value.as_str(), value);
+ }
+
+ // Empty
+
+ #[test]
+ fn empty_line() {
+ let mut parser = Parser::new();
+ assert!(!parser.parse("".as_bytes()).is_err());
+ assert_eq!(parser.nodes.len(), 0);
+ }
+
+ #[test]
+ fn spaced_line() {
+ let mut parser = Parser::new();
+ assert!(!parser.parse(" ".as_bytes()).is_err());
+ assert_eq!(parser.nodes.len(), 0);
+ }
+
+ #[test]
+ fn just_a_comment_line() {
+ for line in vec![";; This is a comment", " ;; Comment"].into_iter() {
+ let mut parser = Parser::new();
+ assert!(!parser.parse(line.as_bytes()).is_err());
+ assert_eq!(parser.nodes.len(), 0);
+ }
+ }
+
+ // Labels
+
+ #[test]
+ fn anonymous_label() {
+ let mut parser = Parser::new();
+ 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);
+
+ parser = Parser::new();
+ 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);
+ }
+
+ #[test]
+ fn named_label() {
+ let mut parser = Parser::new();
+ 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);
+
+ parser = Parser::new();
+ 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);
+ }
+
+ #[test]
+ fn label_with_instruction() {
+ let line = "label: dex";
+
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+ assert_eq!(parser.nodes.len(), 2);
+
+ // 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);
+
+ // Instruction
+ assert_node(
+ parser.nodes.last().unwrap(),
+ NodeType::Instruction,
+ line,
+ "dex",
+ )
+ }
+
+ // Literals
+
+ #[test]
+ fn parse_pound_literal() {
+ for line in vec!["#20", " #20 ", " #20 ; Comment", " label: # 20"].into_iter() {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_eq!(node.node_type, NodeType::Literal);
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ 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");
+ }
+ }
+
+ #[test]
+ fn parse_compound_literal() {
+ let line = "#$20";
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_eq!(node.node_type, NodeType::Literal);
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ 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");
+
+ 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");
+ }
+
+ #[test]
+ fn parse_variable_in_literal() {
+ let line = "#Variable";
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_eq!(node.node_type, NodeType::Literal);
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ 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");
+ }
+
+ // Regular instructions.
+
+ #[test]
+ fn instruction_with_implied() {
+ for line in vec![
+ "dex",
+ " dex",
+ " dex ",
+ " dex ; Comment",
+ " label: dex",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "dex");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+ }
+ }
+
+ #[test]
+ fn instruction_with_implied_explicit() {
+ for line in vec!["inc a", " inc a", " inc a "].into_iter() {
+ let mut parser = Parser::new();
+ assert_one_valid(&mut parser, line);
+
+ let node = parser.nodes.first().unwrap();
+ assert_node(node, NodeType::Instruction, line, "inc");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ assert_node(&node.left.clone().unwrap(), NodeType::Value, line, "a");
+ }
+ }
+
+ #[test]
+ fn instruction_with_zeropage() {
+ for line in vec!["inc $20", " inc $20", " inc $20 "].into_iter() {
+ let mut parser = Parser::new();
+ assert_one_valid(&mut parser, line);
+
+ let node = parser.nodes.first().unwrap();
+ assert_node(node, NodeType::Instruction, line, "inc");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ assert_node(&node.left.clone().unwrap(), NodeType::Literal, line, "$20");
+ }
+ }
+
+ #[test]
+ fn instruction_with_immediate() {
+ for line in vec!["adc #$20", " adc #$20 ", " adc #$20 "].into_iter() {
+ let mut parser = Parser::new();
+ assert_one_valid(&mut parser, line);
+
+ let node = parser.nodes.first().unwrap();
+ assert_node(node, NodeType::Instruction, line, "adc");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ assert_node(&node.left.clone().unwrap(), NodeType::Literal, line, "#$20");
+ }
+ }
+
+ #[test]
+ fn instruction_with_absolute() {
+ for line in vec!["inc $2002", " inc $2002", " inc $2002 "].into_iter() {
+ let mut parser = Parser::new();
+ assert_one_valid(&mut parser, line);
+
+ let node = parser.nodes.first().unwrap();
+ assert_node(node, NodeType::Instruction, line, "inc");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ assert_node(
+ &node.left.clone().unwrap(),
+ NodeType::Literal,
+ line,
+ "$2002",
+ );
+ }
+ }
+
+ #[test]
+ fn instruction_with_absolute_x() {
+ for line in vec![
+ "inc $2002, x",
+ " inc $2002, x",
+ " inc $2002, x ",
+ " label: inc $2002, x ; Comment",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "inc");
+ assert!(node.args.is_none());
+
+ assert_node(
+ &node.left.clone().unwrap(),
+ NodeType::Literal,
+ line,
+ "$2002",
+ );
+ assert_node(&node.right.clone().unwrap(), NodeType::Value, line, "x");
+ }
+ }
+
+ #[test]
+ fn indirect_addressing_bare() {
+ for line in vec![
+ "lda ($2000)",
+ " lda ( $2000 ) ; Comment",
+ " : lda ( $2000)",
+ "lda($2000)",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+ assert!(node.right.is_none());
+
+ let left = node.left.clone().unwrap();
+ assert_eq!(left.node_type, NodeType::Indirection);
+ assert_node(&left.left.unwrap(), NodeType::Literal, line, "$2000");
+ assert!(left.right.is_none());
+ }
+ }
+
+ #[test]
+ fn indirect_addressing_x() {
+ for line in vec![
+ "lda ($20, x)",
+ " lda ($20, x)",
+ " lda ($20,x) ",
+ " : lda ($20 , x) ; Comment",
+ " lda ( $20 , x ) ",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+ assert!(node.right.is_none());
+
+ let left = node.left.clone().unwrap();
+ assert_eq!(left.node_type, NodeType::Indirection);
+ assert_node(&left.left.unwrap(), NodeType::Literal, line, "$20");
+ assert_node(&left.right.unwrap(), NodeType::Value, line, "x");
+ }
+ }
+
+ #[test]
+ fn bad_indirect_addressing_x() {
+ let mut parser = Parser::new();
+
+ let err = parser.parse("lda (Variable, x), y".as_bytes());
+ assert_eq!(err.unwrap_err().message, "bad indirect addressing");
+ }
+
+ #[test]
+ fn indirect_addressing_y() {
+ for line in vec!["lda ($20), y"].into_iter() {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+
+ let left = node.left.clone().unwrap();
+ assert_eq!(left.node_type, NodeType::Indirection);
+ assert_node(&left.left.unwrap(), NodeType::Literal, line, "$20");
+ assert!(left.right.is_none());
+
+ let right = node.right.clone().unwrap();
+ assert_node(&right, NodeType::Value, line, "y");
+ }
+ }
+
+ #[test]
+ fn variable_in_instruction() {
+ let line = "lda Variable, x";
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+ assert!(node.args.is_none());
+
+ assert_node(
+ &node.left.clone().unwrap(),
+ NodeType::Value,
+ line,
+ "Variable",
+ );
+ assert_node(&node.right.clone().unwrap(), NodeType::Value, line, "x");
+ }
+
+ #[test]
+ fn variable_literal_in_instruction() {
+ let line = "lda #Variable, x";
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+ assert!(node.args.is_none());
+
+ assert_node(
+ &node.left.clone().unwrap(),
+ NodeType::Literal,
+ line,
+ "#Variable",
+ );
+ assert_node(&node.right.clone().unwrap(), NodeType::Value, line, "x");
+ }
+
+ // Assignments
+
+ #[test]
+ fn bad_assignments() {
+ let mut parser = Parser::new();
+
+ let mut err = parser.parse("abc = $10".as_bytes());
+ assert_eq!(
+ err.unwrap_err().message,
+ "cannot use names which are valid hexadecimal values such as 'abc'"
+ );
+
+ parser = Parser::new();
+ err = parser.parse("var =".as_bytes());
+ assert_eq!(err.unwrap_err().message, "incomplete assignment");
+
+ parser = Parser::new();
+ err = parser.parse("var = ".as_bytes());
+ assert_eq!(err.unwrap_err().message, "incomplete assignment");
+
+ parser = Parser::new();
+ err = parser.parse("var = ; Comment".as_bytes());
+ assert_eq!(err.unwrap_err().message, "incomplete assignment");
+ }
+
+ // Control statements.
+
+ #[test]
+ fn parse_control_no_args() {
+ for line in vec![".end", " .end", " label: .end ; Comment"].into_iter() {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Control, line, ".end");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+ }
+ }
+
+ #[test]
+ fn parse_control_one_arg() {
+ for line in vec![
+ ".hibyte $2000",
+ " .hibyte $2000",
+ " label: .hibyte $2000 ; Comment",
+ " .hibyte($2000)",
+ " .hibyte ( $2000 )",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Control, line, ".hibyte");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+
+ let args = node.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Literal, line, "$2000");
+ }
+ }
+
+ #[test]
+ fn parse_control_multiple_args() {
+ for line in vec![
+ ".byte $10, $20",
+ " .byte $10, $20",
+ " label: .byte $10, $20 ; Comment",
+ " .byte($10, $20)",
+ " .byte ( $10 , $20 )",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Control, line, ".byte");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+
+ let args = node.args.clone().unwrap();
+ assert_eq!(args.len(), 2);
+ assert_node(args.first().unwrap(), NodeType::Literal, line, "$10");
+ assert_node(args.last().unwrap(), NodeType::Literal, line, "$20");
+ }
+ }
+
+ #[test]
+ fn parse_control_id_no_args() {
+ for line in vec![
+ ".scope Scope",
+ " .scope Scope",
+ " label: .scope Scope ; Comment",
+ " .scope Scope",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Control, line, ".scope");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ let left = node.left.clone().unwrap();
+ assert_node(&left, NodeType::Value, line, "Scope");
+ }
+ }
+
+ #[test]
+ fn parse_control_id_one_arg() {
+ for line in vec![
+ ".macro Macro(arg1)",
+ ".macro Macro arg1 ",
+ " .macro Macro(arg1)",
+ " label: .macro Macro(arg1) ; Comment",
+ " .macro Macro ( arg1 )",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Control, line, ".macro");
+ assert!(node.right.is_none());
+
+ let left = node.left.clone().unwrap();
+ assert_node(&left, NodeType::Value, line, "Macro");
+
+ let args = node.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Value, line, "arg1");
+ }
+ }
+
+ #[test]
+ fn parse_control_id_multiple_args() {
+ for line in vec![
+ ".macro Macro(arg1, arg2)",
+ ".macro Macro arg1, arg2 ",
+ " .macro Macro(arg1, arg2)",
+ " label: .macro Macro(arg1, arg2) ; Comment",
+ " .macro Macro ( arg1 , arg2 )",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Control, line, ".macro");
+ assert!(node.right.is_none());
+
+ let left = node.left.clone().unwrap();
+ assert_node(&left, NodeType::Value, line, "Macro");
+
+ let args = node.args.clone().unwrap();
+ assert_eq!(args.len(), 2);
+ assert_node(args.first().unwrap(), NodeType::Value, line, "arg1");
+ assert_node(args.last().unwrap(), NodeType::Value, line, "arg2");
+ }
+ }
+
+ #[test]
+ fn parse_control_bad_number_args() {
+ for line in vec![".hibyte", ".hibyte($20, $22)"].into_iter() {
+ let mut parser = Parser::new();
+ assert_eq!(
+ parser.parse(line.as_bytes()).unwrap_err().message,
+ "wrong number of arguments for function '.hibyte'"
+ );
+ }
+ }
+
+ #[test]
+ fn parse_control_in_instructions() {
+ for line in vec!["lda #.hibyte($2010)", " label: lda #.hibyte $2010 "].into_iter() {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ let left = node.left.clone().unwrap();
+ assert_node(&left, NodeType::Literal, line, "#.hibyte");
+ assert!(left.right.is_none());
+ assert!(left.args.is_none());
+
+ let control = left.left.clone().unwrap();
+ assert_node(&control, NodeType::Control, line, ".hibyte");
+ assert!(control.left.is_none());
+ assert!(control.right.is_none());
+
+ let args = control.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010");
+ }
+ }
+
+ #[test]
+ fn parse_control_in_indirect_x_instructions() {
+ for line in vec![
+ "lda (#.hibyte($2010), x)",
+ " label: lda (#.hibyte ( $2010 ) , x)",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ let ind = node.left.clone().unwrap();
+ assert_eq!(ind.node_type, NodeType::Indirection);
+ assert!(ind.args.is_none());
+
+ let left = ind.left.clone().unwrap();
+ assert_node(&left, NodeType::Literal, line, "#.hibyte");
+ assert!(left.right.is_none());
+ assert!(left.args.is_none());
+
+ let control = left.left.clone().unwrap();
+ assert_node(&control, NodeType::Control, line, ".hibyte");
+ assert!(control.left.is_none());
+ assert!(control.right.is_none());
+
+ let args = control.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010");
+
+ let right = ind.right.clone().unwrap();
+ assert_node(&right, NodeType::Value, line, "x");
+ }
+ }
+
+ #[test]
+ fn parse_control_in_indirect_y_instructions() {
+ for line in vec![
+ "lda (#.hibyte($2010)), y",
+ " label: lda ( #.hibyte( $2010 ) ) , y",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Instruction, line, "lda");
+ assert!(node.args.is_none());
+
+ let ind = node.left.clone().unwrap();
+ assert_eq!(ind.node_type, NodeType::Indirection);
+ assert!(ind.right.is_none());
+ assert!(ind.args.is_none());
+
+ let left = ind.left.clone().unwrap();
+ assert_node(&left, NodeType::Literal, line, "#.hibyte");
+ assert!(left.right.is_none());
+ assert!(left.args.is_none());
+
+ let control = left.left.clone().unwrap();
+ assert_node(&control, NodeType::Control, line, ".hibyte");
+ assert!(control.left.is_none());
+ assert!(control.right.is_none());
+
+ let args = control.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010");
+
+ let right = node.right.clone().unwrap();
+ assert_node(&right, NodeType::Value, line, "y");
+ }
+ }
+
+ #[test]
+ fn parse_control_in_assignments() {
+ for line in vec![
+ "lala = #.hibyte($2010)",
+ " lala = #.hibyte($2010)",
+ "label: lala = #.hibyte($2010) ; comment",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Assignment, line, "lala");
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+
+ let left = node.left.clone().unwrap();
+ assert_node(&left, NodeType::Literal, line, "#.hibyte");
+ assert!(left.right.is_none());
+ assert!(left.args.is_none());
+
+ let control = left.left.clone().unwrap();
+ assert_node(&control, NodeType::Control, line, ".hibyte");
+ assert!(control.left.is_none());
+ assert!(control.right.is_none());
+
+ let args = control.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010");
+ }
+ }
+
+ #[test]
+ fn parse_unknown_control() {
+ let mut parser = Parser::new();
+ assert_eq!(
+ parser.parse(".".as_bytes()).unwrap_err().message,
+ "unknown function '.'"
+ );
+
+ parser = Parser::new();
+ assert_eq!(
+ parser.parse(".whatever".as_bytes()).unwrap_err().message,
+ "unknown function '.whatever'"
+ );
+ }
+
+ // Macro calls.
+
+ #[test]
+ fn parse_macro_call_no_args_variable_lookalike() {
+ for line in vec![
+ "MACRO_CALL",
+ " MACRO_CALL ",
+ " label: MACRO_CALL ; comment",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Value, line, "MACRO_CALL");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+ }
+ }
+
+ #[test]
+ fn parse_macro_call_no_args() {
+ for line in vec![
+ "MACRO_CALL()",
+ " MACRO_CALL() ",
+ " MACRO_CALL () ",
+ " MACRO_CALL ( ) ",
+ " label: MACRO_CALL () ; comment",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Call, line, "MACRO_CALL");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+ assert!(node.args.is_none());
+ }
+ }
+
+ #[test]
+ fn parse_macro_call_one_arg() {
+ for line in vec![
+ "MACRO_CALL(arg1)",
+ "MACRO_CALL arg1 ",
+ " MACRO_CALL (arg1)",
+ " label: MACRO_CALL( arg1 ) ; Comment",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Call, line, "MACRO_CALL");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+
+ let args = node.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Value, line, "arg1");
+ }
+ }
+
+ #[test]
+ fn parse_macro_call_multiple_args() {
+ for line in vec![
+ "MACRO_CALL(arg1, arg2)",
+ "MACRO_CALL arg1, arg2 ",
+ " MACRO_CALL (arg1,arg2)",
+ " label: MACRO_CALL( arg1 , arg2 ) ; Comment",
+ ]
+ .into_iter()
+ {
+ let mut parser = Parser::new();
+ assert!(parser.parse(line.as_bytes()).is_ok());
+
+ let node = parser.nodes.last().unwrap();
+ assert_node(node, NodeType::Call, line, "MACRO_CALL");
+ assert!(node.left.is_none());
+ assert!(node.right.is_none());
+
+ let args = node.args.clone().unwrap();
+ assert_eq!(args.len(), 2);
+ assert_node(args.first().unwrap(), NodeType::Value, line, "arg1");
+ assert_node(args.last().unwrap(), NodeType::Value, line, "arg2");
+ }
+ }
+}