diff --git a/CHANGELOG.md b/CHANGELOG.md index ed25347..ef66043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.4.0] - 2024-11-17 + +### Fixes + +* Correct an issue with the assember's addressing-mode matcher +* Correct some issues that could come up in disassembly +* Various forms of `RUN` can end an Applesoft relocation flow + +### New Features + +* Support converting data to code in Merlin source files + +### New Behaviors + +* Prevent excessively long workspace scans + - Limit the directory count and recursion depth + - Skip `build`, `node_modules`, and `target` directories + ## [3.3.3] - 2024-10-26 ### Fixes diff --git a/Cargo.toml b/Cargo.toml index 5b7444e..6c3ed30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a2kit" -version = "3.3.3" +version = "3.4.0" edition = "2021" readme = "README.md" license = "MIT" diff --git a/src/bin/server-merlin/request.rs b/src/bin/server-merlin/request.rs index 3ad2b7c..c7d023c 100644 --- a/src/bin/server-merlin/request.rs +++ b/src/bin/server-merlin/request.rs @@ -238,7 +238,7 @@ pub fn handle_request( }; } }, - "merlin6502.toData" => { + cmd if cmd=="merlin6502.toData" || cmd=="merlin6502.toCode" => { if params.arguments.len()==4 { let prog_res = serde_json::from_value::(params.arguments[0].clone()); let uri_res = serde_json::from_value::(params.arguments[1].clone()); @@ -247,12 +247,31 @@ pub fn handle_request( if let (Ok(program),Ok(uri),Ok(beg),Ok(end)) = (prog_res,uri_res,beg_res,end_res) { let normalized_uri = normalize_client_uri_str(&uri).expect("could not parse URI"); if let Some(chk) = tools.doc_chkpts.get(&normalized_uri.to_string()) { - let dasm_symbols = merlin::assembly::Assembler::dasm_symbols(chk.shared_symbols()); - tools.assembler.use_shared_symbols(Arc::new(dasm_symbols)); + let dasm_symbols = Arc::new(merlin::assembly::Assembler::dasm_symbols(chk.shared_symbols())); + tools.assembler.use_shared_symbols(Arc::clone(&dasm_symbols)); + tools.disassembler.use_shared_symbols(Arc::clone(&dasm_symbols)); resp = match tools.assembler.spot_assemble(program, beg, end, None) { - Ok(img) => { - let dasm = tools.disassembler.disassemble_as_data(&img); - lsp_server::Response::new_ok(req.id,dasm) + Ok(buf) => { + let pc = tools.assembler.get_program_counter(); + tools.disassembler.set_program_counter(match pc { + Some(end) => Some(end - buf.len()), + None => None + }); + match (pc.is_some(),cmd) { + (_,"merlin6502.toData") => { + let dasm = tools.disassembler.disassemble_as_data(&buf); + lsp_server::Response::new_ok(req.id,dasm) + }, + (true,"merlin6502.toCode") => { + let [m,x] = tools.assembler.get_mx(); + tools.disassembler.set_mx(m,x); + let dasm = tools.disassembler.disassemble_as_code(&buf); + lsp_server::Response::new_ok(req.id,dasm) + }, + _ => { + lsp_server::Response::new_err(req.id,PARSE_ERROR,"program counter hint is required".to_string()) + } + } }, Err(e) => { let mess = format!("spot assembler failed: {}",e.to_string()); diff --git a/src/lang/applesoft/diagnostics.rs b/src/lang/applesoft/diagnostics.rs index 964430a..ebda527 100644 --- a/src/lang/applesoft/diagnostics.rs +++ b/src/lang/applesoft/diagnostics.rs @@ -75,6 +75,25 @@ impl FlowState { } None } + /// use print node to pop the interprogram branch in case we have a RUN command. + fn eval_ip_run(&mut self,print_node: &tree_sitter::Node) -> Option { + if self.ip_branch_stack.len() == 0 { + return None; + } + if let Some(nxt) = print_node.next_named_sibling() { + let txt = node_text(&nxt,&self.line).replace(" ","").to_lowercase(); + if txt.starts_with("chr$") || nxt.kind()=="var_str" { + if let Some(nxtnxt) = nxt.next_named_sibling() { + if node_text(&nxtnxt,&self.line).replace(" ","").to_lowercase().starts_with("\"run") { + self.ip_branch_stack = Vec::new(); + } + } + } else if txt.starts_with("\"\u{0004}run") || txt.starts_with("\"\\x04run") { + self.ip_branch_stack = Vec::new(); + } + } + None + } } pub struct Analyzer { @@ -581,6 +600,11 @@ impl Analyzer { } } } + else if curs.node().kind() == "tok_print" { + if let Some(diag) = self.flow.eval_ip_run(&curs.node()) { + self.diagnostics.push(diag); + } + } else if curs.node().kind() == "tok_call" { if let Some(addr) = curs.node().next_named_sibling() { self.value_range(addr,-32767.,65535.,true); diff --git a/src/lang/merlin/assembly.rs b/src/lang/merlin/assembly.rs index f25c14c..6ede53f 100644 --- a/src/lang/merlin/assembly.rs +++ b/src/lang/merlin/assembly.rs @@ -1,7 +1,7 @@ //! # Assembler for any Merlin version. //! -//! Currently we provide only "spot assembly," which is useful for re-assembling sections -//! of code as data after disassembly. +//! Currently we provide only "spot assembly," which is useful for disassembly workflows that +//! involve conversion of code to data and vice-versa. //! //! The spot assembler is designed to work under conditions where the program counter and/or //! symbol information are not necessarily known, i.e., it will proceed as far as it can @@ -263,9 +263,15 @@ impl Assembler { self.m8bit = m8bit; self.x8bit = x8bit; } + pub fn get_mx(&self) -> [bool;2] { + [self.m8bit,self.x8bit] + } pub fn set_program_counter(&mut self,pc: usize) { self.pc = Some(pc); } + pub fn get_program_counter(&self) -> Option { + self.pc + } fn prefix_shift(prefix: &str) -> usize { match prefix { "#>" | ">" => 1, @@ -345,19 +351,21 @@ impl Assembler { /// * prefix - expression modifier such as #, #<, etc. /// returns success or error fn push_instruction(&mut self, op: &Operation, op_node: &tree_sitter::Node, mode_node: &tree_sitter::Node, val: u32, prefix: &str) -> STDRESULT { - let mut val_bytes = u32::to_le_bytes(val); + + // First work out the byte range `beg..end` within `val` that will be written out to the object code. + let mut val_bytes = u32::to_le_bytes(val); let mut beg = 0; let mut end = 1; if val_bytes[1] > 0 { end = 2; } + + // Modify `beg..end` based on operand suffix, forcing absolute or long addressing. + // This must precede the prefix handling. let suffix = match op_node.named_child(0) { Some(s) => node_text(&s,&self.line), None => "".to_string() }; - - // Handle mnemonic suffix forcing absolute or long addressing, - // this must precede the prefix handling. if end == 1 { if self.symbols.assembler==MerlinVersion::Merlin8 { if suffix.len() > 0 && suffix != "D" && suffix != "d" { @@ -375,7 +383,7 @@ impl Assembler { } } - // Handle prefix modifiers and special cases + // Modify `beg..end` based on prefix modifiers and special cases. let is16bit = !self.x8bit && op.x_sensitive || !self.m8bit && op.m_sensitive; if mode_node.kind()=="data" { if op_node.kind()=="op_pea" { @@ -407,44 +415,50 @@ impl Assembler { } } - let mode = op.get_address_mode(mode_node.kind(), end-beg)?; + // Find the addressing mode + let mut maybe_mode: Option = None; + for padding in 0..3 { + match op.get_address_mode(mode_node.kind(), end-beg+padding) { + Ok(m) => { + maybe_mode = Some(m); + end += padding; + break; + }, + Err(_) => continue + }; + } + let mode = match maybe_mode { + Some(m) => m, + None => return Err(Box::new(Error::BadAddressMode)) + }; + + // We can now write the opcode self.code.push(mode.code as u8); - if mode.mnemonic=="rel" { - let abs = val_bytes[0] as isize + 0x100 * val_bytes[1] as isize; - if let Some(pc) = self.pc { - let rel = match abs - (pc as isize + 2) { - x if x >= 0 => x, - x => x + 0x100 - }; - if rel < 0 || rel > u8::MAX as isize { - return Err(Box::new(Error::BadBranch)); - } - val_bytes = u32::to_le_bytes(rel as u32); - beg = 0; - end = 1; - } else { - return Err(Box::new(Error::UnresolvedProgramCounter)); - } - } else if mode.mnemonic=="rell" { - let abs = val_bytes[0] as isize + 0x100 * val_bytes[1] as isize + 0x10000 * val_bytes[2] as isize; + + // The operand value needs to be transformed if this is a relative branch + if mode.mnemonic=="rel" || mode.mnemonic=="rell" { + let mut abs_addr = val_bytes[0] as usize + 0x100 * val_bytes[1] as usize; + (beg,end) = match mode.mnemonic=="rel" { + true => (0,1), + false => (0,2) + }; + abs_addr += (end-1) * 0x10000 * val_bytes[2] as usize; if let Some(pc) = self.pc { - let rel = match abs - (pc as isize + 3) { - x if x >= 0 => x, - x => x + 0x10000 + let rel = match OperationHandbook::abs_to_rel(pc, abs_addr, end-beg) { + Some(x) => x, + None => return Err(Box::new(Error::BadBranch)) }; - if rel < 0 || rel > u16::MAX as isize { - return Err(Box::new(Error::BadBranch)); - } val_bytes = u32::to_le_bytes(rel as u32); - beg = 0; - end = 2; } else { return Err(Box::new(Error::UnresolvedProgramCounter)); } } + + // We can now write the operand bytes for i in beg..end { self.code.push(val_bytes[i]); } + // Update the program counter if let Some(pc) = self.pc.as_mut() { *pc += 1 + end - beg; } diff --git a/src/lang/merlin/diagnostics/workspace.rs b/src/lang/merlin/diagnostics/workspace.rs index 876fa92..fd2a32d 100644 --- a/src/lang/merlin/diagnostics/workspace.rs +++ b/src/lang/merlin/diagnostics/workspace.rs @@ -1,4 +1,6 @@ use std::collections::{HashSet,HashMap}; +use std::ffi::OsString; +use std::path::PathBuf; use lsp_types as lsp; use tree_sitter::TreeCursor; use super::super::{SourceType,Symbol,Workspace}; @@ -6,6 +8,13 @@ use crate::lang::{Document,Navigate,Navigation,node_text,lsp_range}; use crate::{DYNERR,STDRESULT}; const RCH: &str = "unreachable was reached"; +const MAX_DIRS: usize = 1000; +const MAX_DEPTH: usize = 10; +const IGNORE_DIRS: [&str;3] = [ + "build", + "node_modules", + "target" +]; impl Workspace { pub fn new() -> Self { @@ -138,6 +147,9 @@ pub struct WorkspaceScanner { line: String, curr_uri: Option, curr_row: isize, + curr_depth: usize, + file_count: usize, + dir_count: usize, ws: Workspace, running_docstring: String, scan_patt: regex::Regex, @@ -153,6 +165,9 @@ impl WorkspaceScanner { line: String::new(), curr_uri: None, curr_row: 0, + curr_depth: 0, + file_count: 0, + dir_count: 0, ws: Workspace::new(), running_docstring: String::new(), scan_patt: regex::Regex::new(r"^\S*\s+(ENT|PUT|USE|REL|ent|put|use|rel)(\s+|$)").expect(RCH), @@ -185,48 +200,78 @@ impl WorkspaceScanner { self.ws.docs.push(doc); } } - /// Buffer all documents matching `**/*.s` in any of `dirs`, up to maximum count `max_files` + /// Recursively gather files from this directory + fn gather_from_dir(&mut self, base: &PathBuf, max_files: usize) -> STDRESULT { + self.dir_count += 1; + self.curr_depth += 1; + let opt = glob::MatchOptions { + case_sensitive: false, + require_literal_leading_dot: false, + require_literal_separator: false + }; + // first scan source files + let patt = base.join("*.s"); + if let Some(globable) = patt.as_os_str().to_str() { + if let Ok(paths) = glob::glob_with(globable,opt) { + for entry in paths { + if let Ok(path) = &entry { + let full_path = base.join(path); + if let (Ok(uri),Ok(txt)) = (lsp::Url::from_file_path(full_path),std::fs::read_to_string(path.clone())) { + log::trace!("{}",uri.as_str()); + self.ws.docs.push(Document::new(uri, txt)); + } + } + self.file_count += 1; + if self.file_count >= max_files { + log::error!("aborting due to excessive source file count of {}",self.file_count); + return Err(Box::new(crate::lang::Error::OutOfRange)); + } + } + } + } else { + log::warn!("directory {} could not be globbed",base.display()); + } + // now go into subdirectories + if self.curr_depth < MAX_DEPTH { + for entry in std::fs::read_dir(base)? { + let entry = entry?; + let ignore_dirs = IGNORE_DIRS.iter().map(|x| OsString::from(x)).collect::>(); + if ignore_dirs.contains(&entry.file_name()) { + continue; + } + let path = entry.path(); + if path.is_dir() && self.dir_count < MAX_DIRS { + self.gather_from_dir(&path,max_files)? + } + } + } + self.curr_depth -= 1; + Ok(()) + } + /// Buffer all documents matching `*.s` in any of `dirs`, up to maximum count `max_files`, + /// searching at most MAX_DIRS directories, using at most MAX_DEPTH recursions, and ignoring IGNORE_DIRS. pub fn gather_docs(&mut self, dirs: &Vec, max_files: usize) -> STDRESULT { self.ws.ws_folders = Vec::new(); self.ws.docs = Vec::new(); + self.curr_depth = 0; + self.file_count = 0; + self.dir_count = 0; // copy the workspace url's to the underlying workspace object for dir in dirs { self.ws.ws_folders.push(dir.clone()); } for dir in dirs { - let base = match dir.to_file_path() { + log::debug!("scanning {}",dir.as_str()); + let path = match dir.to_file_path() { Ok(b) => b, Err(_) => return Err(Box::new(crate::lang::Error::BadUrl)) }; - let opt = glob::MatchOptions { - case_sensitive: false, - require_literal_leading_dot: false, - require_literal_separator: false - }; - log::debug!("scanning {}",dir.as_str()); - let patt = base.join("**").join("*.s"); - if let Some(globable) = patt.as_os_str().to_str() { - if let Ok(paths) = glob::glob_with(globable,opt) { - let mut count = 0; - for entry in paths { - if let Ok(path) = &entry { - let full_path = base.join(path); - if let (Ok(uri),Ok(txt)) = (lsp::Url::from_file_path(full_path),std::fs::read_to_string(path.clone())) { - log::trace!("{}",uri.as_str()); - self.ws.docs.push(Document::new(uri, txt)); - } - } - count += 1; - if count >= max_files { - return Err(Box::new(crate::lang::Error::OutOfRange)); - } - } - log::info!("there were {} sources in the workspace",count); - } - } else { - log::warn!("directory {} could not be globbed",dir.to_string()); - } + self.gather_from_dir(&path,max_files)?; } + if self.dir_count >= MAX_DIRS { + log::warn!("scan was aborted after {} directories",self.dir_count); + } + log::info!("there were {} sources in the workspace",self.file_count); Ok(()) } /// Scan buffered documents for entries and includes. diff --git a/src/lang/merlin/disassembly.rs b/src/lang/merlin/disassembly.rs index 7cfbc74..dc4bf1f 100644 --- a/src/lang/merlin/disassembly.rs +++ b/src/lang/merlin/disassembly.rs @@ -4,10 +4,11 @@ //! part of a language server, wherein live human intervention is possible. //! However, it can also be used from the command line for simple disassemblies. +use std::sync::Arc; use std::collections::{HashSet,HashMap}; use hex::ToHex; use crate::lang; -use crate::lang::merlin::{ProcessorType,settings::Settings,MachineOperation}; +use crate::lang::merlin::{ProcessorType,settings::Settings,MachineOperation,Symbols}; use crate::lang::merlin::handbook::operations::OperationHandbook; use super::formatter; use crate::DYNERR; @@ -85,8 +86,10 @@ impl DasmLine { pub struct Disassembler { config: Settings, + pc: Option, m8bit: bool, x8bit: bool, + symbols: Arc, dasm_map: HashMap, dasm_lines: Vec, std_patt: regex::Regex, @@ -142,8 +145,10 @@ impl Disassembler { let book = OperationHandbook::new(); Self { config: Settings::new(), + pc: None, m8bit: true, x8bit: true, + symbols: Arc::new(Symbols::new()), dasm_map: book.create_dasm_map(), dasm_lines: Vec::new(), std_patt: regex::Regex::new(r"[0-9]").expect(super::RCH), @@ -153,6 +158,12 @@ impl Disassembler { pub fn set_config(&mut self,config: Settings) { self.config = config; } + pub fn use_shared_symbols(&mut self,sym: Arc) { + self.symbols = sym; + } + pub fn set_program_counter(&mut self,pc: Option) { + self.pc = pc; + } pub fn set_mx(&mut self, m8bit: bool, x8bit: bool) { self.m8bit = m8bit; self.x8bit = x8bit; @@ -330,24 +341,16 @@ impl Disassembler { } else if operand_bytes > 0 { let mut val = u32_from_operand(&img[addr..addr+operand_bytes]) as usize; if op.relative { - let ival = match operand_bytes { - 1 => match val < 128 { - true => (addr + operand_bytes + val) as i64, - false => addr as i64 + operand_bytes as i64 + val as i64 - 256 - }, - _ => match val < 0x8000 { - true => (addr + operand_bytes + val) as i64, - false => addr as i64 + operand_bytes as i64 + val as i64 - 0x10000 + val = match OperationHandbook::rel_to_abs(addr-1, val, operand_bytes) { + Some(dest) => dest, + None => { + // if out of range interpret as data + log::debug!("branch out of bounds: {} + {}",addr-1,val); + self.push_data_pattern(addr-1,img,operand_bytes+1,1); + addr += operand_bytes; + return Ok(addr) } }; - if ival < 0 || ival > 0xffff { - // if out of range interpret as data - log::debug!("branch out of bounds: {} -> {}",addr,ival); - self.push_data_pattern(addr-1,img,operand_bytes+1,1); - addr += operand_bytes; - return Ok(addr); - } - val = usize::try_from(ival)?; } if !op.operand_snippet.starts_with("#") { new_line.references.push(val); @@ -372,30 +375,15 @@ impl Disassembler { self.dasm_lines.push(new_line); Ok(addr) } - pub fn disassemble(&mut self, img: &[u8], range: DasmRange, proc: ProcessorType, labeling: &str) -> Result { - let addr_range = match range { - DasmRange::All => [0,img.len()], - DasmRange::LastBloadDos33 => dos33_bload_range(img)?, - DasmRange::LastBloadProDos => prodos_bload_range(img)?, - DasmRange::Range([beg,end]) => [beg,end] + fn format_lines(&self,labeling: &str) -> String { + let mut last_addr = usize::MAX; + let widths = [self.config.columns.c1 as usize,self.config.columns.c2 as usize,self.config.columns.c3 as usize]; + let pc_bytes = match self.dasm_lines.iter().map(|x| x.address > 0xffff).collect::>().contains(&true) { + true => 3, + false => 2 }; - let mut addr = addr_range[0]; - let mut code = String::new(); - - self.dasm_lines = Vec::new(); - let mut labels = HashSet::new(); - while addr < addr_range[1] { - if let Some((op,operand_bytes)) = self.is_instruction(img[addr],addr,addr_range[1],&proc) { - addr = self.push_instruction(img, addr, op, operand_bytes)?; - } else { - let data_bytes = self.try_data_run(img, addr, addr_range[1]); - addr += data_bytes; - if data_bytes == 0 { - self.push_data_psop(addr, self.modify("DFB"), hex_from_val("$",img[addr] as u32,1)); - addr += 1; - } - } - } + let mut code = String::new(); + let mut labels = HashSet::new(); // gather references let mut references = HashSet::new(); for line in &self.dasm_lines { @@ -407,16 +395,11 @@ impl Disassembler { for i in 0..self.dasm_lines.len() { if labeling.contains("all") { labels.insert(self.dasm_lines[i].address); - } else if labeling.contains("some") && references.contains(&self.dasm_lines[i].address) { + } else if labeling.contains("some") && (i==0 || references.contains(&self.dasm_lines[i].address)) { labels.insert(self.dasm_lines[i].address); } } - let widths = [self.config.columns.c1 as usize,self.config.columns.c2 as usize,self.config.columns.c3 as usize]; - let pc_bytes = match proc { - ProcessorType::_65c816 => 3, - _ => 2 - }; - let mut last_addr = usize::MAX; + // loop over lines for i in 0..self.dasm_lines.len() { let mut line = String::new(); if labels.contains(&self.dasm_lines[i].address) && self.dasm_lines[i].address != last_addr { @@ -430,7 +413,7 @@ impl Disassembler { if let Some(operand) = &self.dasm_lines[i].operand { line.push(super::COLUMN_SEPARATOR); line += &self.dasm_lines[i].prefix; - if operand.num.len() == 1 && labels.contains(&(operand.num[0] as usize)) { + if operand.num.len() == 1 && labels.contains(&(operand.num[0] as usize)) && !operand.txt.starts_with("#") { line += "_"; line += &hex_from_val("",operand.num[0] as u32,pc_bytes); } else { @@ -441,33 +424,79 @@ impl Disassembler { code += &line; code += "\n"; } - Ok(code) + code + } + /// Disassemble a range of bytes within `img`, which can be thought of as a RAM image. + /// If the data source is a file, the data should be copied to `img` at the appropriate offset. + /// In particular, the starting address will be taken as `range[0]`. + /// Data sections are triggered by any failure to match an instruction. + pub fn disassemble(&mut self, img: &[u8], range: DasmRange, proc: ProcessorType, labeling: &str) -> Result { + let addr_range = match range { + DasmRange::All => [0,img.len()], + DasmRange::LastBloadDos33 => dos33_bload_range(img)?, + DasmRange::LastBloadProDos => prodos_bload_range(img)?, + DasmRange::Range([beg,end]) => [beg,end] + }; + let mut addr = addr_range[0]; + + self.dasm_lines = Vec::new(); + while addr < addr_range[1] { + if let Some((op,operand_bytes)) = self.is_instruction(img[addr],addr,addr_range[1],&proc) { + addr = self.push_instruction(img, addr, op, operand_bytes)?; + } else { + let data_bytes = self.try_data_run(img, addr, addr_range[1]); + addr += data_bytes; + if data_bytes == 0 { + self.push_data_psop(addr, self.modify("DFB"), hex_from_val("$",img[addr] as u32,1)); + addr += 1; + } + } + } + Ok(self.format_lines(labeling)) } - pub fn disassemble_as_data(&mut self, img: &[u8]) -> String { - let mut addr = 0; - let mut code = String::new(); + /// Disassemble as pure data. + /// Various Merlin pseudo-operations are used to express the result. + /// Purpose is to re-disassemble specified lines during an iterative disassembly process. + /// This will label "some lines" if self.pc.is_some(). + pub fn disassemble_as_data(&mut self, buf: &[u8]) -> String { self.dasm_lines = Vec::new(); + let mut addr = match self.pc { + Some(a) => a, + None => 0x8000 + }; + let mut img: Vec = vec![0;addr]; + img.append(&mut buf.to_vec()); while addr < img.len() { - let data_bytes = self.try_data_run(img, addr, img.len()); + let data_bytes = self.try_data_run(&img, addr, img.len()); addr += data_bytes; if data_bytes == 0 { self.push_data_psop(addr, self.modify("DFB"), hex_from_val("$",img[addr] as u32,1)); addr += 1; } } - let widths = [self.config.columns.c1 as usize,self.config.columns.c2 as usize,self.config.columns.c3 as usize]; - for i in 0..self.dasm_lines.len() { - let mut line = String::new(); - line.push(super::COLUMN_SEPARATOR); - line += &self.dasm_lines[i].instruction; - if let Some(operand) = &self.dasm_lines[i].operand { - line.push(super::COLUMN_SEPARATOR); - line += &operand.txt; - } - line = formatter::format_tokens(&line, &formatter::ColumnStyle::Variable, widths); - code += &line; - code += "\n"; + self.format_lines(match self.pc { Some(_) => "some", None => "none"}) + } + /// Disassemble with the most aggressive possible interpretation as code. + /// Purpose is to re-disassemble specified lines during an iterative disassembly process. + /// If self.pc.is_none() then ORG 0x8000 is assumed, otherwise self.pc is used and "some lines" are labeled. + pub fn disassemble_as_code(&mut self, buf: &[u8]) -> String { + self.dasm_lines = Vec::new(); + let mut addr = match self.pc { + Some(pc) => pc, + None => 0x8000 + }; + let mut img: Vec = vec![0;addr]; + img.append(&mut buf.to_vec()); + while addr < img.len() { + if let Some((op,operand_bytes)) = self.is_instruction(img[addr],addr,img.len(),&self.symbols.processor) { + if let Ok(a) = self.push_instruction(&img, addr, op, operand_bytes) { + addr = a; + continue + } + } + self.push_data_psop(addr, self.modify("DFB"), hex_from_val("$",img[addr] as u32,1)); + addr += 1; } - code + self.format_lines(match self.pc { Some(_) => "some", None => "none"}) } } \ No newline at end of file diff --git a/src/lang/merlin/handbook/operations.rs b/src/lang/merlin/handbook/operations.rs index cffa1ec..f182092 100644 --- a/src/lang/merlin/handbook/operations.rs +++ b/src/lang/merlin/handbook/operations.rs @@ -401,4 +401,28 @@ impl OperationHandbook { } ans } + /// pc is the address of the branch instruction, addr is the destination + pub fn abs_to_rel(pc: usize,addr: usize,operand_bytes: usize) -> Option { + let h = 0x80 * match operand_bytes { 1 => 1, _ => 0x100 }; + let f = 0x100 * match operand_bytes { 1 => 1, _ => 0x100 }; + match addr as i64 - (pc as i64 + operand_bytes as i64 + 1) { + x if x >= 0 && x < h => Some(x as usize), + x if x >= -h && x < 0 => Some((x + f) as usize), + _ => None + } + } + /// pc is the address of the branch instruction, rel is the operand value + pub fn rel_to_abs(pc: usize,rel: usize,operand_bytes: usize) -> Option { + let h = 0x80 * match operand_bytes { 1 => 1, _ => 0x100 }; + let f = 0x100 * match operand_bytes { 1 => 1, _ => 0x100 }; + let dest = match rel as i64 { + x if x >=0 && x < h => x as i64 + pc as i64 + operand_bytes as i64 + 1, + x if x >=h && x < f => x as i64 + pc as i64 + operand_bytes as i64 + 1 - f, + _ => return None + }; + if dest < 0 || dest > 0xffff { + return None + } + Some(dest as usize) + } } diff --git a/src/lang/merlin/tests/assembly_6502_test.rs b/src/lang/merlin/tests/assembly_6502_test.rs index d7bc6c8..967201d 100644 --- a/src/lang/merlin/tests/assembly_6502_test.rs +++ b/src/lang/merlin/tests/assembly_6502_test.rs @@ -408,4 +408,65 @@ mod bitwise { test_code += " ROR $1000,X\n"; super::test_assembler(hex, test_code, 0); } -} \ No newline at end of file +} + +// Check that ZP addresses are padded in cases where +// there is no ZP addressing mode available. +mod requires_padding { + #[test] + fn adc() { + let hex = "791000"; + let mut test_code = String::new(); + test_code += " ADC $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } + #[test] + fn and() { + let hex = "391000"; + let mut test_code = String::new(); + test_code += " AND $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } + #[test] + fn cmp() { + let hex = "d91000"; + let mut test_code = String::new(); + test_code += " CMP $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } + #[test] + fn eor() { + let hex = "591000"; + let mut test_code = String::new(); + test_code += " EOR $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } + #[test] + fn lda() { + let hex = "b91000"; + let mut test_code = String::new(); + test_code += " LDA $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } + #[test] + fn ora() { + let hex = "191000"; + let mut test_code = String::new(); + test_code += " ORA $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } + #[test] + fn sbc() { + let hex = "f91000"; + let mut test_code = String::new(); + test_code += " SBC $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } + #[test] + fn sta() { + let hex = "991000"; + let mut test_code = String::new(); + test_code += " STA $10,Y\n"; + super::test_assembler(hex, test_code, 0); + } +}