- 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.
59 lines
1.9 KiB
Rust
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}"),
|
|
}
|
|
}
|
|
}
|