A simple 6502 assembler written in Rust
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
12 KiB

/*
nyasm, a simple 6502 assembler written in Rust
Copyright (C) 2021 tromino <trominode@pm.me>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::num;
// "Assembler number", a number/address type used in various places
pub type AssNumber<'a> = (&'a str, u8, u8, bool); // (type, lsb, optional msb, is label?)
// Function to process different "number" values in the ASM file, such as addresses and immediate values, and return them in a consistent format
pub fn parse_number<'a>(
number: &str,
relative_addresses: bool, // Whether we're expecting a relative (branch) value
enable_labels: bool, // Whether labels should be expected to be allowed in this case
label_requests: &mut Vec<(u16, String, bool)>,
org: &mut Box<u16>,
counter: u16,
did_error: &mut bool
) -> AssNumber<'a> {
let mut return_number: AssNumber = ("", 0, 0, false);
// Determine what type of value we're dealing with and process it accordingly
if number == "a" {
return_number.0 = "a";
} else if number.len() == 4 && &number[.. 2] == "#$" {
return_number.0 = "literal";
return_number.1 = handle_u8_error(u8::from_str_radix(&number[2 .. 4], 16), number, did_error);
} else if number.len() == 10 && &number[.. 2] == "#%" {
return_number.0 = "literal";
return_number.1 = handle_u8_error(u8::from_str_radix(&number[2 .. 10], 2), number, did_error);
} else if number.len() == 3 && &number[.. 1] == "$" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "zero";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[1 .. 3], 16), number, did_error);
if relative_addresses {
return_number = make_relative(tmp_return_number, counter, org, did_error);
} else {
return_number = tmp_return_number;
}
} else if number.len() == 9 && &number[.. 1] == "%" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "zero";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[1 .. 9], 2), number, did_error);
if relative_addresses {
return_number = make_relative(tmp_return_number, counter, org, did_error);
} else {
return_number = tmp_return_number;
}
} else if number.len() == 5 && &number[.. 1] == "$" && &number[3 ..] == ",x" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "zero,x";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[1 .. 3], 16), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 11 && &number[.. 1] == "%" && &number[9 ..] == ",x" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "zero,x";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[1 .. 9], 2), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 5 && &number[.. 1] == "$" && &number[3 ..] == ",y" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "zero,y";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[1 .. 3], 16), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 11 && &number[.. 1] == "%" && &number[9 ..] == ",y" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "zero,y";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[1 .. 9], 2), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 5 && &number[.. 1] == "$" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "address";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[3 .. 5], 16), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[1 .. 3], 16), number, did_error);
if relative_addresses {
return_number = make_relative(tmp_return_number, counter, org, did_error);
} else {
return_number = tmp_return_number;
}
} else if number.len() == 17 && &number[.. 1] == "%" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "address";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[9 .. 17], 2), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[1 .. 9], 2), number, did_error);
if relative_addresses {
return_number = make_relative(tmp_return_number, counter, org, did_error);
} else {
return_number = tmp_return_number;
}
} else if number.len() == 7 && &number[.. 1] == "$" && &number[5 ..] == ",x" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "address,x";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[3 .. 5], 16), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[1 .. 3], 16), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 19 && &number[.. 1] == "%" && &number[17 ..] == ",x" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "address,x";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[9 .. 17], 2), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[1 .. 9], 2), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 7 && &number[.. 1] == "$" && &number[5 ..] == ",y" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "address,y";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[3 .. 5], 16), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[1 .. 3], 16), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 19 && &number[.. 1] == "%" && &number[17 ..] == ",y" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "address,y";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[9 .. 17], 2), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[1 .. 9], 2), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 7 && &number[.. 2] == "($" && &number[6 ..] == ")" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "(address)";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[4 .. 6], 16), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[2 .. 4], 16), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 19 && &number[.. 2] == "(%" && &number[18 ..] == ")" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "(address)";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[10 .. 18], 2), number, did_error);
tmp_return_number.2 = handle_u8_error(u8::from_str_radix(&number[2 .. 10], 2), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 7 && &number[.. 2] == "($" && &number[4 ..] == ",x)" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "(zero,x)";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[2 .. 4], 16), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 13 && &number[.. 2] == "(%" && &number[10 ..] == ",x)" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "(zero,x)";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[2 .. 10], 2), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 7 && &number[.. 2] == "($" && &number[4 ..] == "),y" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "(zero),y";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[2 .. 4], 16), number, did_error);
return_number = tmp_return_number;
} else if number.len() == 13 && &number[.. 2] == "(%" && &number[10 ..] == "),y" {
let mut tmp_return_number: AssNumber = ("", 0, 0, false);
tmp_return_number.0 = "(zero),y";
tmp_return_number.1 = handle_u8_error(u8::from_str_radix(&number[2 .. 10], 2), number, did_error);
return_number = tmp_return_number;
} else if &number[&number.len() - 2 ..] == ",x" { // address,x with label
if enable_labels {
label_requests.push((counter, String::from(&number[.. &number.len() - 2]), false));
}
return_number.0 = "address,x";
return_number.1 = 0u8;
return_number.2 = 0u8;
return_number.3 = true;
} else if &number[&number.len() - 2 ..] == ",y" { // address,y with label
if enable_labels {
label_requests.push((counter, String::from(&number[.. &number.len() - 2]), false));
}
return_number.0 = "address,y";
return_number.1 = 0u8;
return_number.2 = 0u8;
return_number.3 = true;
} else if &number[.. 1] == "(" && &number[&number.len() - 1 ..] == ")" { // (address) with label
if enable_labels {
label_requests.push((counter, String::from(&number[1 .. &number.len() - 1]), false));
}
return_number.0 = "(address)";
return_number.1 = 0u8;
return_number.2 = 0u8;
return_number.3 = true;
} else { // A regular address value with label
if enable_labels {
label_requests.push((counter, String::from(number), relative_addresses));
}
if relative_addresses {
return_number.0 = "branch";
} else {
return_number.0 = "address";
}
return_number.1 = 0u8;
return_number.2 = 0u8;
return_number.3 = true;
}
return return_number;
}
// Turn an absolute address into a relative one (for branches)
pub fn make_relative<'a>(
address: AssNumber,
counter: u16,
org: &mut Box<u16>,
did_error: &mut bool
) -> AssNumber<'a> {
let mut return_number: AssNumber = ("", 0, 0, false);
let address_as_number: i32; // Will store the input address as a signed i32 rather than two u8 numbers
if address.3 {
address_as_number = address.1 as i32 + ((address.2 as i32) << 8);
} else {
// If the input address didn't come from a label, then we need to take the origin into account to match non-branch behavior
address_as_number = (address.1 as i32 + ((address.2 as i32) << 8)) - **org as i32;
}
let branch_amount = (address_as_number as i32) - ((counter as i32) + 1); // The actual amount to branch by
return_number.0 = "branch";
// Branch range needs to be able to fit in a single byte
if branch_amount >= -128 && branch_amount <= 127 {
return_number.1 = branch_amount as u8; // Branch amount converted to unsigned byte (still treated as signed value by CPU)
} else {
assembler_error("Branch out of range", &branch_amount.to_string(), did_error);
}
return return_number;
}
fn handle_u8_error(res: Result<u8, num::ParseIntError>, number: &str, did_error: &mut bool) -> u8 {
if res.is_ok() {
return res.unwrap();
} else {
assembler_error("Invalid address or literal value", number, did_error);
return 0u8;
}
}
// This will show an error that will be visible to the user, and stop the assembly
pub fn assembler_error(message: &str, which: &str, did_error: &mut bool) {
*did_error = true;
eprintln!("\x1b[1;31merror:\x1b[0m {}:\n \x1b[1;31m{}\x1b[0m\n", message, which);
}