use crate::lexer::{Lexer, Token};
use crate::opcodes::*;
use crate::symbol_table::{SourceLine, SymbolTable};
use crate::timex::{TIMEX_TABLE, TIMEX6_TABLE};
use crate::utils::*;
use std::cell::RefCell;
use std::fs;
use std::path::Path;
use std::rc::Rc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AssemblerError {
#[error("Parse error at line {line}: {message}")]
ParseError { line: usize, message: String },
#[error("Symbol error: {message}")]
SymbolError { message: String },
#[error("Value out of range: {message}")]
RangeError { message: String },
#[error("IO error: {message}")]
IoError { message: String },
}
pub struct Assembler {
symbol_table: SymbolTable,
source_lines: Vec<Rc<RefCell<SourceLine>>>,
current_line: usize,
filename: String,
include_path: String,
errors: Vec<String>,
max_line: usize,
}
#[allow(unused_assignments)]
impl Assembler {
pub fn new(filename: String) -> Self {
let base_dir = if let Some(dir) = Path::new(&filename).parent() {
dir.to_string_lossy().to_string()
} else {
".".to_string()
};
let include_path = Path::new(&base_dir)
.join("include")
.to_string_lossy()
.to_string();
let mut assembler = Self {
symbol_table: SymbolTable::new(),
source_lines: Vec::new(),
current_line: 0,
filename,
include_path,
errors: Vec::new(),
max_line: 0,
};
for kw in KEYWORDS {
assembler
.symbol_table
.define_keyword(kw.name, kw.value as i32);
}
assembler
}
pub fn reset_errors(&mut self) {
self.errors.clear();
}
pub fn get_errors(&self) -> &[String] {
&self.errors
}
pub fn add_source_line(&mut self, line_num: usize) {
while self.source_lines.len() <= line_num {
self.source_lines
.push(Rc::new(RefCell::new(SourceLine::new())));
}
if line_num >= self.max_line {
self.max_line = line_num + 1;
}
let offset = if line_num == 0 {
0x0110
} else {
let prev_line = &self.source_lines[line_num - 1];
let prev_ref = prev_line.borrow();
prev_ref.get_offset() + prev_ref.get_byte_count() as i32
};
self.source_lines[line_num]
.borrow_mut()
.set_offset(offset, false);
}
pub fn parse_line(&mut self, line: &str, line_number: usize) -> Result<(), AssemblerError> {
let mut lexer = Lexer::new(expand_tabs(line, 8));
let mut data = Vec::new();
let mut opcode = 0u16;
self.current_line = line_number;
{
let source_line_rc = self.source_lines[line_number].clone();
let source_line_weak = Rc::downgrade(&source_line_rc);
let mut source_line = source_line_rc.borrow_mut();
source_line.reset_references(source_line_weak.clone());
source_line.set_definition(None, source_line_weak);
source_line.set_relative(-1);
}
let mut token = lexer.get_token();
let mut label = String::new();
if let Token::Symbol(name) = &token {
if self.symbol_table.lookup_keyword(name).is_none() {
label = name.clone();
token = lexer.get_token();
if token == Token::Colon {
token = lexer.get_token();
}
}
}
if let Token::Symbol(name) = &token {
if let Some(op_value) = self.symbol_table.lookup_keyword(name) {
opcode = op_value as u16;
token = lexer.get_token();
}
}
if (opcode >> 8) != (CAT_EQU >> 8) && !label.is_empty() {
let source_line_weak = Rc::downgrade(&self.source_lines[line_number]);
let offset = self.source_lines[line_number].borrow().get_offset();
let symbol = self
.symbol_table
.define_symbol(&label, source_line_weak, offset);
if symbol.borrow().get_definition_count() != 1 {
self.emit_error(&format!("Symbol {} defined on more than one line", label));
}
}
match opcode >> 8 {
cat if cat == (CAT_INH >> 8) => {
data.push((opcode & 0xff) as u8);
}
cat if cat == (CAT_REL >> 8) => {
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
data.push((opcode & 0xff) as u8);
self.source_lines[line_number]
.borrow_mut()
.set_relative(value);
let branch_distance =
value - self.source_lines[line_number].borrow().get_offset() - 2;
if branch_distance > 0x7f || branch_distance < -0x80 {
self.emit_error(&format!(
"Branch target out of range (Distance={})",
branch_distance
));
}
data.push((branch_distance & 0xff) as u8);
}
cat if cat == (CAT_BSC >> 8) || cat == (CAT_BSCX >> 8) => {
if cat == (CAT_BSCX >> 8) {
let (new_token, bit_num) = self.parse_level8(&mut lexer, token)?;
token = new_token;
opcode += (bit_num * 2) as u16;
if token == Token::Comma {
token = lexer.get_token();
}
}
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
data.push((opcode & 0xff) as u8);
if value > 0xff || value < 0 {
self.emit_error(&format!(
"BSET/BCLR target ({:04x}) not in zero page",
value
));
}
data.push((value & 0xff) as u8);
}
cat if cat == (CAT_BTB >> 8) || cat == (CAT_BTBX >> 8) => {
if cat == (CAT_BTBX >> 8) {
let (new_token, bit_num) = self.parse_level8(&mut lexer, token)?;
token = new_token;
opcode += (bit_num * 2) as u16;
if token == Token::Comma {
token = lexer.get_token();
}
}
data.push((opcode & 0xff) as u8);
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
if value > 0xff || value < 0 {
self.emit_error(&format!(
"BRSET/BRCLR target ({:04x}) not in zero page",
value
));
}
data.push((value & 0xff) as u8);
if token == Token::Comma {
token = lexer.get_token();
}
let (new_token, branch_value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
self.source_lines[line_number]
.borrow_mut()
.set_relative(branch_value);
let branch_distance =
branch_value - self.source_lines[line_number].borrow().get_offset() - 3;
data.push((branch_distance & 0xff) as u8);
if branch_distance > 0x7f || branch_distance < -0x80 {
self.emit_error(&format!(
"Branch target out of range (Distance={})",
branch_distance
));
}
}
cat if cat == (CAT_IMM_DIR_EXT_IX1_IX1_IX >> 8) => {
if token == Token::Immed {
data.push((opcode & 0xff) as u8);
token = lexer.get_token();
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
if value > 0xff || value < 0 {
self.emit_error(&format!("Operand ({:04x}) out of range 00-FF", value));
}
data.push((value & 0xff) as u8);
} else {
self.process_addressing_mode(&mut lexer, &mut token, &mut data, opcode)?;
}
}
cat if cat == (CAT_DIR_EXT_IX1_IX1_IX >> 8) => {
self.process_addressing_mode(&mut lexer, &mut token, &mut data, opcode)?;
}
cat if cat == (CAT_DIR_IX1_IX >> 8) => {
self.process_dir_ix_addressing(&mut lexer, &mut token, &mut data, opcode)?;
}
cat if cat == (CAT_DB >> 8) => {
loop {
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
if value > 0xff || value < 0 {
self.emit_error(&format!("Operand ({:04x}) out of range $00-$FF", value));
}
data.push((value & 0xff) as u8);
if token != Token::Comma {
break;
}
token = lexer.get_token();
}
}
cat if cat == (CAT_DW >> 8) => {
loop {
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
data.push(((value >> 8) & 0xff) as u8);
data.push((value & 0xff) as u8);
if token != Token::Comma {
break;
}
token = lexer.get_token();
}
}
cat if cat == (CAT_EQU >> 8) => {
if label.is_empty() {
self.emit_error("No label for EQU statement");
} else {
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
let source_line_weak = Rc::downgrade(&self.source_lines[line_number]);
let symbol = self
.symbol_table
.define_symbol(&label, source_line_weak, value);
if symbol.borrow().get_definition_count() != 1 {
self.emit_error(&format!("Symbol {} defined on more than one line", label));
}
}
}
cat if cat == (CAT_ORIGIN >> 8) => {
let (new_token, value) = self.parse_level8(&mut lexer, token)?;
token = new_token;
self.source_lines[line_number]
.borrow_mut()
.set_offset(value, true);
}
cat if cat == (CAT_INCLUDE >> 8) => {
if let Token::String(filename) = token {
let symbol = self.symbol_table.lookup_symbol(&filename);
if symbol.borrow().get_value() == 0 {
match self.read_include_file(&filename) {
Ok(content) => {
let content_bytes = content.len();
let source_line_weak =
Rc::downgrade(&self.source_lines[line_number]);
symbol.borrow_mut().set_value(1);
symbol.borrow_mut().add_definition(source_line_weak);
self.source_lines[line_number]
.borrow_mut()
.set_include_bytes_read(content_bytes);
for line in content.lines() {
let mut include_lexer = Lexer::new(expand_tabs(line, 8));
let mut include_token = include_lexer.get_token();
if let Token::Symbol(symbol_name) = include_token {
include_token = include_lexer.get_token();
if let Token::Symbol(equ_name) = &include_token {
if equ_name.to_uppercase() == "EQU" {
include_token = include_lexer.get_token();
if let Ok((_, value)) =
self.parse_level8(&mut include_lexer, include_token)
{
self.symbol_table.define_symbol(
&symbol_name,
Rc::downgrade(
&self.source_lines[line_number],
),
value,
);
}
}
}
}
}
}
Err(e) => {
self.emit_error(&format!("Unable to open '{}': {}", filename, e));
}
}
}
token = lexer.get_token();
} else {
self.emit_error("Include file must be enclosed in quotes");
}
}
cat if cat == (CAT_ASCII >> 8) => {
if let Token::String(s) = token {
for ch in s.chars() {
data.push((ch as u8) & 0x7f);
}
token = lexer.get_token();
}
}
cat if cat == (CAT_TIMEX >> 8) => {
if let Token::String(s) = token {
for ch in s.chars() {
let idx = (ch as u8 & 0x7f) as usize;
if idx < TIMEX_TABLE.len() {
data.push(TIMEX_TABLE[idx]);
}
}
token = lexer.get_token();
}
}
cat if cat == (CAT_TIMEX6 >> 8) => {
if let Token::String(s) = token {
let mut chars: Vec<char> = s.chars().collect();
chars.resize(6, ' ');
for ch in chars.iter().take(6) {
let idx = (*ch as u8 & 0x7f) as usize;
if idx < TIMEX6_TABLE.len() {
data.push(TIMEX6_TABLE[idx]);
} else {
data.push(0x1d); }
}
token = lexer.get_token();
}
}
0 => {
}
_ => {
self.emit_error("Unrecognized opcode");
}
}
if token != Token::End {
self.emit_error("Junk past the end of the instruction");
}
let delta = self.source_lines[line_number]
.borrow_mut()
.set_data(&data);
self.source_lines[line_number]
.borrow_mut()
.mark_dirty(false);
if delta != 0 {
let current_offset = self.source_lines[line_number].borrow().get_offset();
for i in (line_number + 1)..self.max_line {
self.source_lines[i].borrow_mut().adjust_offset(delta);
let rel_offset = self.source_lines[i].borrow().get_relative_offset();
if rel_offset >= 0 && rel_offset < current_offset {
self.source_lines[i].borrow_mut().mark_dirty(true);
}
}
for i in (0..line_number).rev() {
if self.source_lines[i].borrow().get_offset() > current_offset {
self.source_lines[i].borrow_mut().mark_dirty(true);
}
}
}
Ok(())
}
fn process_addressing_mode(
&mut self,
lexer: &mut Lexer,
token: &mut Token,
data: &mut Vec<u8>,
opcode: u16,
) -> Result<(), AssemblerError> {
let value = if *token == Token::Comma {
0
} else {
let (new_token, val) = self.parse_level8(lexer, token.clone())?;
*token = new_token;
val
};
if *token == Token::Comma {
*token = lexer.get_token();
if let Token::Symbol(name) = token {
if stricmp(name, "X") != 0 {
self.emit_error("Missing X after ,");
} else {
*token = lexer.get_token();
}
}
if value == 0 {
data.push(((opcode & 0xff) + 0x50) as u8);
} else if value >= -256 && value < 256 {
data.push(((opcode & 0xff) + 0x40) as u8);
data.push((value & 0xff) as u8);
} else {
data.push(((opcode & 0xff) + 0x30) as u8);
data.push(((value >> 8) & 0xff) as u8);
data.push((value & 0xff) as u8);
}
} else {
if value >= -256 && value < 256 {
data.push(((opcode & 0xff) + 0x10) as u8);
data.push((value & 0xff) as u8);
} else {
data.push(((opcode & 0xff) + 0x20) as u8);
data.push(((value >> 8) & 0xff) as u8);
data.push((value & 0xff) as u8);
}
}
Ok(())
}
fn process_dir_ix_addressing(
&mut self,
lexer: &mut Lexer,
token: &mut Token,
data: &mut Vec<u8>,
opcode: u16,
) -> Result<(), AssemblerError> {
let value = if *token == Token::Comma {
0
} else {
let (new_token, val) = self.parse_level8(lexer, token.clone())?;
*token = new_token;
val
};
if *token == Token::Comma {
*token = lexer.get_token();
if let Token::Symbol(name) = token {
if stricmp(name, "X") != 0 {
self.emit_error("Missing X after ,");
} else {
*token = lexer.get_token();
}
}
if value == 0 {
data.push(((opcode & 0xff) + 0x40) as u8);
} else {
data.push(((opcode & 0xff) + 0x30) as u8);
if value > 0xff || value < 0 {
self.emit_error(&format!("Operand ({:04x}) out of range $00-$FF", value));
}
data.push((value & 0xff) as u8);
}
} else {
data.push((opcode & 0xff) as u8);
if value > 0xff || value < 0 {
self.emit_error(&format!("Operand ({:04x}) out of range $00-$FF", value));
}
data.push((value & 0xff) as u8);
}
Ok(())
}
fn parse_level1(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
match token {
Token::LParen => {
let next_token = lexer.get_token();
let (token, value) = self.parse_level8(lexer, next_token)?;
if token == Token::RParen {
Ok((lexer.get_token(), value))
} else {
Ok((token, value))
}
}
Token::Const(val) => Ok((lexer.get_token(), val)),
Token::Symbol(name) => {
let source_line_weak = Rc::downgrade(&self.source_lines[self.current_line]);
let symbol = self
.symbol_table
.lookup_symbol_with_reference(&name, source_line_weak);
let value = symbol.borrow().get_value();
if symbol.borrow().get_definition_count() == 0 {
self.emit_error(&format!("Undefined symbol '{}' Value={}", name, value));
}
Ok((lexer.get_token(), value))
}
Token::Star => {
let value = self.source_lines[self.current_line].borrow().get_offset();
Ok((lexer.get_token(), value))
}
Token::String(_) | Token::Comma | Token::End => Ok((token, 0)),
_ => {
self.emit_error("Unable to parse this line");
Ok((token, 0))
}
}
}
fn parse_level2(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
if token == Token::Not {
let next_token = lexer.get_token();
let (token, value) = self.parse_level1(lexer, next_token)?;
Ok((token, !value))
} else {
self.parse_level1(lexer, token)
}
}
fn parse_level3(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
let (mut token, mut value) = self.parse_level2(lexer, token)?;
if matches!(token, Token::Star | Token::Divide) {
let op = token;
let next_token = lexer.get_token();
let (new_token, right_value) = self.parse_level3(lexer, next_token)?;
token = new_token;
if op == Token::Star {
value *= right_value;
} else {
if right_value == 0 {
self.emit_error("Division by zero");
value = 0;
} else {
value /= right_value;
}
}
}
Ok((token, value))
}
fn parse_level4(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
let (mut token, mut value) = self.parse_level3(lexer, token)?;
if matches!(token, Token::Plus | Token::Minus) {
let op = token;
let next_token = lexer.get_token();
let (new_token, right_value) = self.parse_level4(lexer, next_token)?;
token = new_token;
if op == Token::Plus {
value += right_value;
} else {
value -= right_value;
}
}
Ok((token, value))
}
fn parse_level5(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
let (mut token, mut value) = self.parse_level4(lexer, token)?;
if matches!(token, Token::LShift | Token::RShift) {
let op = token;
let next_token = lexer.get_token();
let (new_token, right_value) = self.parse_level5(lexer, next_token)?;
token = new_token;
if op == Token::LShift {
value <<= right_value;
} else {
value >>= right_value;
}
}
Ok((token, value))
}
fn parse_level6(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
let (mut token, mut value) = self.parse_level5(lexer, token)?;
if token == Token::And {
let next_token = lexer.get_token();
let (new_token, right_value) = self.parse_level6(lexer, next_token)?;
token = new_token;
value &= right_value;
}
Ok((token, value))
}
fn parse_level7(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
let (mut token, mut value) = self.parse_level6(lexer, token)?;
if token == Token::Xor {
let next_token = lexer.get_token();
let (new_token, right_value) = self.parse_level7(lexer, next_token)?;
token = new_token;
value ^= right_value;
}
Ok((token, value))
}
fn parse_level8(
&mut self,
lexer: &mut Lexer,
token: Token,
) -> Result<(Token, i32), AssemblerError> {
let (mut token, mut value) = self.parse_level7(lexer, token)?;
if token == Token::Or {
let next_token = lexer.get_token();
let (new_token, right_value) = self.parse_level8(lexer, next_token)?;
token = new_token;
value |= right_value;
}
Ok((token, value))
}
fn read_include_file(&self, filename: &str) -> Result<String, std::io::Error> {
let include_file_path = Path::new(&self.include_path).join(filename);
fs::read_to_string(include_file_path)
}
fn emit_error(&mut self, message: &str) {
let error_msg = format!(
"{}({}): {}",
self.filename,
self.current_line + 1,
message
);
self.errors.push(error_msg);
}
pub fn get_hex(&self) -> String {
let mut result = String::new();
for line in &self.source_lines[..self.max_line] {
let line_ref = line.borrow();
let data = line_ref.get_data();
for byte in data {
result.push_str(&format!("{:02x}", byte));
}
}
result
}
pub fn generate_listing(&self, source_data: &[String]) -> Vec<String> {
let mut result = Vec::new();
for (line_num, line) in self.source_lines.iter().enumerate().take(self.max_line) {
let line_ref = line.borrow();
let offset = line_ref.get_offset();
let data = line_ref.get_data();
let source_line = source_data.get(line_num).map(|s| s.as_str()).unwrap_or("");
let listing_line = if data.is_empty() {
if let Some(bytes_read) = line_ref.include_bytes_read {
format!(
"{:4}| {} bytes read |{}",
line_num + 1,
bytes_read,
source_line
)
}
else if let Some(symbol_def) = &line_ref.definition {
if let Some(symbol) = symbol_def.upgrade() {
let value = symbol.borrow().get_value();
format!(
"{:4}| ={:04x} |{}",
line_num + 1,
value,
source_line
)
} else {
format!("{:4}| |{}", line_num + 1, source_line)
}
} else {
format!("{:4}| |{}", line_num + 1, source_line)
}
} else if data.len() == 1 {
format!(
"{:4}| {:04x}: {:02x} |{}",
line_num + 1,
offset,
data[0],
source_line
)
} else if data.len() == 2 {
format!(
"{:4}| {:04x}: {:02x} {:02x} |{}",
line_num + 1,
offset,
data[0],
data[1],
source_line
)
} else {
format!(
"{:4}| {:04x}: {:02x} {:02x} {:02x} |{}",
line_num + 1,
offset,
data[0],
data[1],
data[2],
source_line
)
};
result.push(listing_line);
}
result
}
}
pub fn assemble(
filename: String,
source_data: Vec<String>,
) -> Result<(Vec<String>, String, Vec<String>), Vec<AssemblerError>> {
let mut assembler = Assembler::new(filename);
let mut errors = Vec::new();
assembler.reset_errors();
for (line_num, line) in source_data.iter().enumerate() {
assembler.add_source_line(line_num);
if let Err(e) = assembler.parse_line(&expand_tabs(line, 8), line_num) {
errors.push(e);
}
}
assembler.reset_errors();
for (line_num, line) in source_data.iter().enumerate() {
if let Err(e) = assembler.parse_line(&expand_tabs(line, 8), line_num) {
errors.push(e);
}
}
let mut dirty = true;
while dirty {
dirty = false;
assembler.reset_errors();
for line_num in 0..assembler.max_line {
if assembler.source_lines[line_num].borrow().is_dirty() {
dirty = true;
if let Err(e) =
assembler.parse_line(&expand_tabs(&source_data[line_num], 8), line_num)
{
errors.push(e);
}
}
}
}
assembler.reset_errors();
for line_num in 0..assembler.max_line {
if let Err(e) = assembler.parse_line(&expand_tabs(&source_data[line_num], 8), line_num) {
errors.push(e);
}
}
if !errors.is_empty() {
}
let error_strings: Vec<String> = assembler.get_errors().to_vec();
let hex_output = assembler.get_hex();
let listing = assembler.generate_listing(&source_data);
Ok((error_strings, hex_output, listing))
}