John Breaux 417ef03e41 0.2.0: Feature update and Refactor
- Each major module (lexer, parser, assembler) has its own error type
  - These error types are somewhat interconnected, but their
    dependency relationships are one-way and well defined
- The AST is no longer responsible for assembling itself
  - The Assembler (assembler::Assembler) will now visit every AST node
    and accumulate words
  - Words are assumed to be little-endian.
- There are now a set of assembler directives that affect the
  generated output:
  - .word <Number>: inserts a single word in the output
  - .words [<Number>,*]: inserts multiple words in the output
  - .byte <Number>: Alias for .word
  - .bytes [<Number>,*]: Alias for .words
  - .string "String": inserts a null-terminated UTF-8 encoded string
  - .strings ["String",*]: "" multiple strings
  - Data is always word-aligned at the moment.
- There are now assembler directives that affect the AST during
  parsing:
  - .include "path/to/file": Parses the contents of a file directly
    into the AST
    - Included files have their own defines, but *share* labels.
      This is because .defines are a tokenizer construct, and
      including a file creates a new buffer and tokenizer.
    - Circular includes are NOT checked for at the moment.
      It is very easy to exhaust the stack.
- General cleanup of several functions, comments, TODOs, etc.
- main.rs was moved to make room for upcoming improvements to the UI

TODO:
- REPL mode is only partially compatible with .define directive
- Branching to a label will branch to the data AT the label,
  not the label itself. I doubt this is correct behavior.
  - In case br <label> is meant to use the absolute address,
    I've created a .org directive (currently unimplemented)
    for specifying the load address of the program.
2023-09-05 01:54:50 -05:00

59 lines
1.9 KiB
Rust

// © 2023 John Breaux
//! A [`JumpTarget`] contains the [pc-relative offset](Number) or [label](Identifier)
//! for a [Jump](Encoding::Jump) [instruction]
use super::*;
/// Contains the [pc-relative offset](Number) or [label](Identifier)
/// for a [Jump](Encoding::Jump) [Instruction]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum JumpTarget {
Number(Number),
Identifier(Identifier),
}
impl JumpTarget {
pub fn word(&self) -> Option<u16> {
match self {
JumpTarget::Number(n) => Some(u16::from(*n) & 0x3ff),
JumpTarget::Identifier(_) => None,
}
}
pub fn squish(value: isize) -> Result<u16, ParseError> {
match value {
i if i % 2 != 0 => Err(ParseError::JumpedOdd(i))?,
i if (-1024..=1022).contains(&(i - 2)) => Ok(((value >> 1) - 1) as u16 & 0x3ff),
i => Err(ParseError::JumpedTooFar(i))?,
}
}
pub fn unsquish(value: u16) -> isize { (value as isize + 1) << 1 }
}
impl Parsable for JumpTarget {
// - Identifier
// - Number
fn parse<'text, T>(p: &Parser, stream: &mut T) -> Result<Self, ParseError>
where T: crate::TokenStream<'text> {
// Try to parse a number
if let Some(num) = Number::try_parse(p, stream)? {
Self::try_from(num)
} else {
// if that fails, try to parse an identifier instead
Ok(Self::Identifier(Identifier::parse(p, stream)?))
}
}
}
impl TryFrom<Number> for JumpTarget {
type Error = ParseError;
fn try_from(value: Number) -> Result<Self, Self::Error> { Ok(Self::Number(Self::squish(value.into())?.into())) }
}
impl Display for JumpTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Number(num) => write!(f, "{:x}", Self::unsquish(u16::from(*num))),
Self::Identifier(id) => write!(f, "{id}"),
}
}
}