Skip to content

Commit

Permalink
smarter workspace scan, asm, dasm
Browse files Browse the repository at this point in the history
  • Loading branch information
dfgordon committed Nov 17, 2024
1 parent 58ee485 commit 6becba9
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 138 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "a2kit"
version = "3.3.3"
version = "3.4.0"
edition = "2021"
readme = "README.md"
license = "MIT"
Expand Down
31 changes: 25 additions & 6 deletions src/bin/server-merlin/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<String>(params.arguments[0].clone());
let uri_res = serde_json::from_value::<String>(params.arguments[1].clone());
Expand All @@ -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());
Expand Down
24 changes: 24 additions & 0 deletions src/lang/applesoft/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<lsp::Diagnostic> {
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 {
Expand Down Expand Up @@ -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);
Expand Down
82 changes: 48 additions & 34 deletions src/lang/merlin/assembly.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<usize> {
self.pc
}
fn prefix_shift(prefix: &str) -> usize {
match prefix {
"#>" | ">" => 1,
Expand Down Expand Up @@ -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" {
Expand All @@ -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" {
Expand Down Expand Up @@ -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<super::AddressMode> = 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;
}
Expand Down
Loading

0 comments on commit 6becba9

Please sign in to comment.