Finally generating code
This commit is contained in:
@@ -56,4 +56,10 @@ impl<'a> VASMLine<'a> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn zero_length(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+152
-1
@@ -9,6 +9,7 @@ pub enum AssembleError<'a> {
|
||||
EquDuplicateError(i32, &'a str),
|
||||
OrgResolveError(i32, EvalError<'a>),
|
||||
ArgError(i32, EvalError<'a>),
|
||||
NoCode,
|
||||
}
|
||||
|
||||
impl<'a> Display for AssembleError<'a> {
|
||||
@@ -26,6 +27,9 @@ impl<'a> Display for AssembleError<'a> {
|
||||
AssembleError::ArgError(line, err) => {
|
||||
write!(f, "Cannot calculate argument on line {}: {}", line, err)
|
||||
}
|
||||
AssembleError::NoCode => {
|
||||
write!(f, "No output would be generated by this code")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +182,106 @@ fn calculate_args<'a>(
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn poke_word(code: &mut Vec<u8>, at: usize, word: i32) {
|
||||
let [low, mid, high, _] = word.to_le_bytes();
|
||||
code[at] = low;
|
||||
code[at + 1] = mid;
|
||||
code[at + 2] = high;
|
||||
}
|
||||
|
||||
/// Find the lower and upper bounds where this program will place memory
|
||||
fn code_bounds<'a>(
|
||||
lines: &[VASMLine<'a>],
|
||||
line_addresses: &LineAddresses,
|
||||
line_lengths: &LineLengths,
|
||||
) -> Result<(usize, usize), AssembleError<'a>> {
|
||||
let mut actual_lines = lines
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.zero_length());
|
||||
let (first_idx, _) = actual_lines.next().ok_or(AssembleError::NoCode)?;
|
||||
let start = line_addresses[&(first_idx as i32 + 1)] as usize;
|
||||
|
||||
let actual_lines = lines
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.zero_length());
|
||||
let (last_idx, _) = actual_lines.last().unwrap();
|
||||
let end = line_addresses[&(last_idx as i32 + 1)] as usize;
|
||||
let end_length = line_lengths[&(last_idx as i32 + 1)];
|
||||
|
||||
Ok((start, end + end_length - 1))
|
||||
}
|
||||
|
||||
/// At this point all lines have addresses and lengths, and all arguments are reduced to
|
||||
/// numeric constants. It's time to generate code.
|
||||
///
|
||||
/// - Make an array of zeroes, length (end - start)
|
||||
/// - Go through the list of instructions, generating code for them:
|
||||
/// - .db instructions turn into byte values starting at `address - start`
|
||||
/// - Opcodes turn into instruction bytes at `address - start` followed (maybe) by
|
||||
/// arguments.
|
||||
/// - Any other directive is skipped (even .orgs, we already have the addresses calculated)
|
||||
///
|
||||
/// The instruction bytes are formed of six bits defining the instruction followed by two
|
||||
/// bits denoting how many bytes of argument follow it.
|
||||
///
|
||||
/// Vulcan is a little-endian architecture: multi-byte arguments / .dbs will store the
|
||||
/// least-significant byte at the lowest address, then the more significant bytes following.
|
||||
fn generate_code<'a>(
|
||||
lines: &[VASMLine<'a>],
|
||||
scope: Scope,
|
||||
line_addresses: LineAddresses,
|
||||
line_lengths: LineLengths,
|
||||
) -> Result<Vec<u8>, AssembleError<'a>> {
|
||||
let line_args = calculate_args(lines, scope, &line_addresses)?;
|
||||
let (start, end) = code_bounds(lines, &line_addresses, &line_lengths)?;
|
||||
let mut code = vec![0u8; end - start + 1];
|
||||
let mut current_addr = start;
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
let line_num = (line_idx + 1) as i32;
|
||||
match line {
|
||||
VASMLine::Instruction(_, opcode, None) => {
|
||||
code[current_addr] = u8::from(*opcode) << 2;
|
||||
current_addr += 1;
|
||||
}
|
||||
VASMLine::Instruction(_, opcode, Some(_)) => {
|
||||
let arg = line_args[&line_num];
|
||||
let len = arg_length(arg);
|
||||
let instr = (u8::from(*opcode) << 2) + len as u8;
|
||||
code[current_addr - start] = instr;
|
||||
let [low, mid, high, _] = arg.to_le_bytes();
|
||||
code[current_addr - start + 1] = low;
|
||||
if len > 1 {
|
||||
code[current_addr - start + 2] = mid
|
||||
}
|
||||
if len > 2 {
|
||||
code[current_addr - start + 3] = high
|
||||
}
|
||||
current_addr += len + 1;
|
||||
}
|
||||
VASMLine::Db(_, _) => {
|
||||
let arg = line_args[&line_num];
|
||||
poke_word(&mut code, current_addr - start, arg);
|
||||
current_addr += 3;
|
||||
}
|
||||
VASMLine::StringDb(_, string) => {
|
||||
for ch in string.as_bytes() {
|
||||
code[current_addr - start] = *ch;
|
||||
current_addr += 1;
|
||||
}
|
||||
}
|
||||
VASMLine::Org(_, _) => {
|
||||
current_addr = line_addresses[&(line_num + 1)] as usize;
|
||||
}
|
||||
VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(code)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::AssembleError::*;
|
||||
@@ -203,11 +307,24 @@ mod test {
|
||||
}
|
||||
|
||||
fn calculate_args_pass<'a>(lines: &'a [&str]) -> Result<LineArgs, AssembleError<'a>> {
|
||||
let (line_addresses, scope) = place_labels_pass(lines)?;
|
||||
let lines = parse(lines);
|
||||
calculate_args(&lines, scope, &line_addresses)
|
||||
}
|
||||
|
||||
fn bounds<'a>(lines: &'a [&str]) -> Result<(usize, usize), AssembleError<'a>> {
|
||||
let (line_addresses, scope) = place_labels_pass(lines)?;
|
||||
let lines = parse(lines);
|
||||
let lengths = measure_instructions(&lines, &scope);
|
||||
code_bounds(&lines, &line_addresses, &lengths)
|
||||
}
|
||||
|
||||
fn generate_code_pass<'a>(lines: &'a [&str]) -> Result<Vec<u8>, AssembleError<'a>> {
|
||||
let lines = parse(lines);
|
||||
let scope = solve_equs(&lines).unwrap();
|
||||
let lengths = measure_instructions(&lines, &scope);
|
||||
let (line_addresses, scope) = place_labels(&lines, scope, &lengths)?;
|
||||
calculate_args(&lines, scope, &line_addresses)
|
||||
generate_code(&lines, scope, line_addresses, lengths)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -347,4 +464,38 @@ mod test {
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bounds() {
|
||||
assert_eq!(bounds(&["add"]), Ok((0, 0)));
|
||||
assert_eq!(bounds(&["add -4"]), Ok((0, 3)));
|
||||
assert_eq!(bounds(&["add 7"]), Ok((0, 1)));
|
||||
assert_eq!(bounds(&[".org 0x400", "add"]), Ok((1024, 1024)));
|
||||
assert_eq!(
|
||||
bounds(&["start: .equ 1024", ".org start", "add"]),
|
||||
Ok((1024, 1024))
|
||||
);
|
||||
assert_eq!(
|
||||
bounds(&[".org 0x400", "add", ".org 0x800"]),
|
||||
Ok((1024, 1024))
|
||||
);
|
||||
assert_eq!(
|
||||
bounds(&[".org 0x400", "add", ".org 0x800", ".db 5", "blah:"]),
|
||||
Ok((1024, 2050))
|
||||
);
|
||||
assert_eq!(bounds(&[]), Err(AssembleError::NoCode));
|
||||
assert_eq!(bounds(&[".org 0x400"]), Err(AssembleError::NoCode));
|
||||
assert_eq!(
|
||||
bounds(&["foo: .equ 3", ".org 0x400"]),
|
||||
Err(AssembleError::NoCode)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_code() {
|
||||
assert_eq!(generate_code_pass(&["add"]), Ok(vec![4]));
|
||||
assert_eq!(generate_code_pass(&[".org 0x400", "add 7"]), Ok(vec![5, 7]));
|
||||
assert_eq!(generate_code_pass(&[".db 57"]), Ok(vec![57, 0, 0]));
|
||||
assert_eq!(generate_code_pass(&[".db \"AZ\0\""]), Ok(vec![65, 90, 0]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user