Finally generating code

This commit is contained in:
2022-04-01 22:00:18 -05:00
parent 150dda5c8e
commit 4ef4efcc11
3 changed files with 208 additions and 1 deletions
+6
View File
@@ -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
View File
@@ -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]));
}
}
+50
View File
@@ -306,6 +306,56 @@ impl<'a> TryFrom<&'a str> for Opcode {
}
}
impl From<Opcode> for u8 {
fn from(opcode: Opcode) -> Self {
match opcode {
Opcode::Nop => 0,
Opcode::Add => 1,
Opcode::Sub => 2,
Opcode::Mul => 3,
Opcode::Div => 4,
Opcode::Mod => 5,
Opcode::Rand => 6,
Opcode::And => 7,
Opcode::Or => 8,
Opcode::Xor => 9,
Opcode::Not => 10,
Opcode::Gt => 11,
Opcode::Lt => 12,
Opcode::Agt => 13,
Opcode::Alt => 14,
Opcode::Lshift => 15,
Opcode::Rshift => 16,
Opcode::Arshift => 17,
Opcode::Pop => 18,
Opcode::Dup => 19,
Opcode::Swap => 20,
Opcode::Pick => 21,
Opcode::Rot => 22,
Opcode::Jmp => 23,
Opcode::Jmpr => 24,
Opcode::Call => 25,
Opcode::Ret => 26,
Opcode::Brz => 27,
Opcode::Brnz => 28,
Opcode::Hlt => 29,
Opcode::Load => 30,
Opcode::Loadw => 31,
Opcode::Store => 32,
Opcode::Storew => 33,
Opcode::Inton => 34,
Opcode::Intoff => 35,
Opcode::Setiv => 36,
Opcode::Sdp => 37,
Opcode::Setsdp => 38,
Opcode::Pushr => 39,
Opcode::Popr => 40,
Opcode::Peekr => 41,
Opcode::Debug => 42,
}
}
}
#[test]
fn test_decode() {
assert_eq!(Opcode::try_from(18), Ok(Opcode::Pop));