Skip to content

Commit

Permalink
round out spot assembler
Browse files Browse the repository at this point in the history
  • Loading branch information
dfgordon committed Jul 28, 2024
1 parent 9e3e5ef commit f30f498
Show file tree
Hide file tree
Showing 20 changed files with 213 additions and 79 deletions.
14 changes: 3 additions & 11 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ jobs:
- name: Compress
run: |
mkdir -p ./artifacts
CLI=("a2kit")
SERVERS=("server-applesoft" "server-integerbasic" "server-merlin")
if [[ $OS =~ ^windows.*$ ]]; then
EXE=".exe"
Expand All @@ -80,18 +79,11 @@ jobs:
else
TAG=$GITHUB_SHA
fi
cli_list="completions"
for i in ${CLI[@]}; do
mv ./target/$TARGET/release/${i}$EXE ./${i}$EXE
cli_list+=" ${i}$EXE"
done
tar -czf ./artifacts/a2kit-$TARGET-$TAG.tar.gz $cli_list
server_list=""
mv ./target/$TARGET/release/a2kit$EXE ./a2kit$EXE
tar -czf ./artifacts/a2kit-$TARGET-$TAG.tar.gz completions a2kit$EXE
for i in ${SERVERS[@]}; do
mv ./target/$TARGET/release/${i}$EXE ./${i}$EXE
server_list+=" ${i}$EXE"
mv ./target/$TARGET/release/${i}$EXE ./artifacts/${i}-${TARGET}$EXE
done
tar -czf ./artifacts/server-$TARGET-$TAG.tar.gz $server_list
- name: Archive artifact
uses: actions/upload-artifact@v4
with:
Expand Down
1 change: 1 addition & 0 deletions completions/_a2kit
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ _arguments "${_arguments_options[@]}" : \
'--assembler+[assembler variant]:NAME:(m8 m16 m16+ m32)' \
'-w+[workspace directory]:PATH: ' \
'--workspace+[workspace directory]:PATH: ' \
'--literals[assign values to disassembled hex labels]' \
'-h[Print help]' \
'--help[Print help]' \
&& ret=0
Expand Down
1 change: 1 addition & 0 deletions completions/_a2kit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ Register-ArgumentCompleter -Native -CommandName 'a2kit' -ScriptBlock {
[CompletionResult]::new('--assembler', 'assembler', [CompletionResultType]::ParameterName, 'assembler variant')
[CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'workspace directory')
[CompletionResult]::new('--workspace', 'workspace', [CompletionResultType]::ParameterName, 'workspace directory')
[CompletionResult]::new('--literals', 'literals', [CompletionResultType]::ParameterName, 'assign values to disassembled hex labels')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
break
Expand Down
2 changes: 1 addition & 1 deletion completions/a2kit.bash
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ _a2kit() {
return 0
;;
a2kit__asm)
opts="-a -w -h --assembler --workspace --help"
opts="-a -w -h --assembler --workspace --literals --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down
1 change: 1 addition & 0 deletions completions/a2kit.elv
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ set edit:completion:arg-completer[a2kit] = {|@words|
cand --assembler 'assembler variant'
cand -w 'workspace directory'
cand --workspace 'workspace directory'
cand --literals 'assign values to disassembled hex labels'
cand -h 'Print help'
cand --help 'Print help'
}
Expand Down
1 change: 1 addition & 0 deletions completions/a2kit.fish
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ complete -c a2kit -n "__fish_a2kit_using_subcommand dtok" -s t -l type -d 'type
complete -c a2kit -n "__fish_a2kit_using_subcommand dtok" -s h -l help -d 'Print help'
complete -c a2kit -n "__fish_a2kit_using_subcommand asm" -s a -l assembler -d 'assembler variant' -r -f -a "{m8\t'',m16\t'',m16+\t'',m32\t''}"
complete -c a2kit -n "__fish_a2kit_using_subcommand asm" -s w -l workspace -d 'workspace directory' -r
complete -c a2kit -n "__fish_a2kit_using_subcommand asm" -l literals -d 'assign values to disassembled hex labels'
complete -c a2kit -n "__fish_a2kit_using_subcommand asm" -s h -l help -d 'Print help'
complete -c a2kit -n "__fish_a2kit_using_subcommand dasm" -s p -l proc -d 'processor target' -r -f -a "{6502\t'',65c02\t'',65802\t'',65816\t''}"
complete -c a2kit -n "__fish_a2kit_using_subcommand dasm" -l mx -d 'MX status bits' -r -f -a "{00\t'',01\t'',10\t'',11\t''}"
Expand Down
7 changes: 4 additions & 3 deletions src/bin/server-merlin/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde_json;
use std::collections::HashMap;
use std::sync::Arc;
use a2kit::lang::server::{Checkpoint, Tokens};
use a2kit::lang::{normalize_client_uri,normalize_client_uri_str,disk_server};
use a2kit::lang::{disk_server, merlin, normalize_client_uri, normalize_client_uri_str};
use a2kit::lang::merlin::formatter;
use a2kit::lang::merlin::disassembly::DasmRange;
use a2kit::lang::merlin::ProcessorType;
Expand Down Expand Up @@ -235,8 +235,9 @@ 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()) {
tools.assembler.use_shared_symbols(chk.shared_symbols());
resp = match tools.assembler.spot_assemble(program, beg, end) {
let dasm_symbols = merlin::assembly::Assembler::dasm_symbols(chk.shared_symbols());
tools.assembler.use_shared_symbols(Arc::new(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)
Expand Down
11 changes: 7 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,18 +313,18 @@ Detokenize from image: `a2kit get -f prog -t atok -d myimg.dsk | a2kit detokeniz
main_cmd = main_cmd.subcommand(
Command::new("tree")
.arg(
arg!(-d --dimg <PATH> "path to disk image")
Arg::new("dimg").short('d').long("dimg").help("path to disk image").value_name("PATH")
.value_hint(ValueHint::FilePath)
.required(false),
)
.arg(arg!(--meta "include metadata").action(ArgAction::SetTrue))
.arg(Arg::new("meta").long("meta").help("include metadata").action(ArgAction::SetTrue))
.about("write directory tree as a JSON string to stdout")
.after_help(IN_HELP),
);
main_cmd = main_cmd.subcommand(
Command::new("stat")
.arg(
arg!(-d --dimg <PATH> "path to disk image")
Arg::new("dimg").short('d').long("dimg").help("path to disk image").value_name("PATH")
.value_hint(ValueHint::FilePath)
.required(false),
)
Expand All @@ -334,7 +334,7 @@ Detokenize from image: `a2kit get -f prog -t atok -d myimg.dsk | a2kit detokeniz
main_cmd = main_cmd.subcommand(
Command::new("geometry")
.arg(
arg!(-d --dimg <PATH> "path to disk image")
Arg::new("dimg").short('d').long("dimg").help("path to disk image").value_name("PATH")
.value_hint(ValueHint::FilePath)
.required(false),
)
Expand Down Expand Up @@ -377,6 +377,9 @@ Detokenize from image: `a2kit get -f prog -t atok -d myimg.dsk | a2kit detokeniz
Arg::new("workspace").short('w').long("workspace").help("workspace directory").value_name("PATH")
.required(false)
)
.arg(
Arg::new("literals").long("literals").help("assign values to disassembled hex labels").action(ArgAction::SetTrue)
)
.about("read from stdin, assemble, write to stdout")
.after_help("At present this is limited, it will error out if program counter or symbol value cannot be determined.")
);
Expand Down
130 changes: 118 additions & 12 deletions src/lang/merlin/assembly.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
//! Assembler for any Merlin version.
//! # Assembler for any Merlin version.
//!
//! Currently we provide only "spot assembly," which is useful
//! for overriding the disassembler's preference for code over data. This operates
//! on lines where neither the program counter nor the symbol values are needed, and makes
//! an assumption that labels of the form `_XXXX` are hex strings giving a literal address.
//! Currently we provide only "spot assembly," which is useful for re-assembling sections
//! of code as data after disassembly.
//!
//! If assembly fails an error should be returned gracefully.
//! 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
//! under these conditions. It will acquire the program counter from ORG or symbol data if possible.
//!
//! ## Scope
//!
//! The assembler handles the full 65816 instruction set. It handles all data and string pseudo-operations.
//! It handles any valid Merlin expression, assuming symbol values can be resolved. It handles all Merlin
//! prefix and suffix modifiers. It responds to the MX pseudo-operation based on processor target.
//!
//! The following pseudo-operations are not handled and will yield an error:
//!
//! * Includes (PUT, USE)
//! * Macros (MAC, PMC, EOM)
//! * Modules (REL, EXT, EXD, ENT)
//! * Control (IF, DO, ELSE, FIN, LUP, --^, END, DUM, DEND, CHK, ERR)
//! * Assignment (EQU, VAR)
//!
//! The following pseudo-operations are silently ignored: AST, CAS, CYC, DSK, EXP, KBD, LST, LSTDO, OBJ, PAG, PAU, SAV, SKP, TR, TTL, TYP, XC.
//!
//! ## Configuration and Symbols
//!
//! The assembler can be configured using `set_config`, but `use_shared_symbols` is more important.
//! In particular, the processor target and assembler variant are packaged within `merlin::Symbols`, and these
//! will take precedence, since they reflect the configuration at the time the symbols were generated. For this
//! reason XC is ignored.
//!
//! If symbol values are available, the spot-assembler will use them. It does not calculate symbol values, but it does
//! check them against the program counter.

use std::sync::Arc;
use super::settings::Settings;
Expand All @@ -17,7 +43,7 @@ use crate::lang::merlin::{Operation,ProcessorType};
use crate::lang::{node_radix, node_text, Navigation, Navigate};
use crate::{STDRESULT,DYNERR};

const IGNORED_PSOPS: [&str;16] = ["ast", "cas", "cyc", "dsk", "exp", "kbd", "lst", "lstdo", "obj", "pag", "pau", "sav", "skp", "tr", "ttl", "typ"];
const IGNORED_PSOPS: [&str;17] = ["ast", "cas", "cyc", "dsk", "exp", "kbd", "lst", "lstdo", "obj", "pag", "pau", "sav", "skp", "tr", "ttl", "typ", "xc"];

/// closely parallels Merlin 8/16 error messages
#[derive(Error,Debug)]
Expand Down Expand Up @@ -52,7 +78,7 @@ pub enum Error {
ForwardRef,
#[error("illegal relative address")]
IllegalRelAddr,
#[error("latest pass changed value")]
#[error("label value changed unexpectedly")]
Misalignment,
#[error("nesting too deep")]
Nesting,
Expand Down Expand Up @@ -441,11 +467,26 @@ impl Assembler {
self.code.append(&mut ans);
Ok(())
}
/// Assign values to labels with textual form `_XXXX...`, where X is a hex digit.
/// The text is assumed to give the value, as is the case after disassembly.
/// The returned symbols are generally turned into a new Arc pointer that is fed into the assembler.
pub fn dasm_symbols(symbols: Arc<Symbols>) -> Symbols {
let mut ans = symbols.as_ref().clone();
for (txt,sym) in &mut ans.globals {
if txt.starts_with("_") {
if let Ok(val) = u32::from_str_radix(&txt[1..], 16) {
sym.value = Some(val as i64);
}
}
}
ans
}
/// Try to assemble lines in a circumstance where the symbol values and program counter
/// are not necessarily known. The spot assembler will proceed as far as it can with
/// whatever information is available, and error out if it hits something that cannot
/// be handled (e.g. a relative branch with unknown PC).
pub fn spot_assemble(&mut self, txt: String, beg: isize, end: isize) -> Result<Vec<u8>,DYNERR> {
pub fn spot_assemble(&mut self, txt: String, beg: isize, end: isize, pc: Option<usize>) -> Result<Vec<u8>,DYNERR> {
self.pc = pc;
self.code = Vec::new();
self.row = 0;
for line in txt.lines() {
Expand Down Expand Up @@ -473,13 +514,38 @@ impl Assembler {

impl Navigate for Assembler {
fn visit(&mut self,curs: &tree_sitter::TreeCursor) -> Result<Navigation,DYNERR> {
if ["macro_call","program_counter","comment","heading"].contains(&curs.node().kind()) {
if curs.node().kind() == "macro_call" {
log::error!("macro calls are not supported");
return Err(Box::new(Error::CannotAssemble));
}
if curs.node().has_error() {
return Err(Box::new(Error::Syntax));
}

// If no program counter, initialize using the first label definition with a value.
// (this is useful for assembling data following disassembly)
// Check subsequent label definitions with values for misalignment.
if curs.node().kind()=="label_def" {
let txt = node_text(&curs.node(), &self.line);
if let Some(sym) = self.symbols.globals.get(&txt) {
if sym.flags & super::symbol_flags::EXT > 0 {
return Ok(Navigation::GotoSibling);
}
if let Some(val) = sym.value {
if let Some(pc) = self.pc {
if val != pc as i64 {
log::error!("label value is {} but PC is {}",val,pc);
return Err(Box::new(Error::Misalignment));
}
} else {
log::debug!("set program counter to {}",val);
self.pc = Some(usize::try_from(val)?);
}
}
return Ok(Navigation::GotoSibling);
}
}

if curs.node().kind().starts_with("op_") {
let txt = node_text(&curs.node(), &self.line);
if let Some(op) = self.op_handbook.get(&txt) {
Expand All @@ -497,6 +563,9 @@ impl Navigate for Assembler {
let src = self.code.pop().unwrap();
self.code.push(dst);
self.code.push(src);
if let Some(pc) = self.pc.as_mut() {
*pc += 3;
}
return Ok(Navigation::Exit);
}
return Err(Box::new(Error::Syntax));
Expand Down Expand Up @@ -526,6 +595,9 @@ impl Navigate for Assembler {
for mode in op.modes {
if ["accum","impl","s"].contains(&mode.mnemonic.as_str()) {
self.code.push(mode.code as u8);
if let Some(pc) = self.pc.as_mut() {
*pc += 1;
}
return Ok(Navigation::Exit);
}
}
Expand All @@ -543,18 +615,36 @@ impl Navigate for Assembler {
match arg.kind() {
"arg_mx" => {
if let Some(child) = arg.named_child(0) {
// TODO: should we allow prefixes here?
match self.eval_expr(&child,&self.line) {
Ok(val) => {
self.m8bit = val & 0b10 > 0;
self.x8bit = val & 0b01 > 0;
if self.symbols.processor == ProcessorType::_6502 || self.symbols.processor == ProcessorType::_65c02 {
if !self.m8bit || !self.x8bit {
return Err(Box::new(Error::BadAddressMode))
}
}
return Ok(Navigation::Exit);
},
Err(e) => return Err(e)
}
}
return Err(Box::new(Error::Syntax));
},
"arg_org" => {
if self.code.len() > 0 {
return Err(Box::new(Error::BadOrg));
}
if let Some(child) = arg.named_child(0) {
match self.eval_expr(&child,&self.line) {
Ok(val) => {
log::debug!("set program counter to {}",val);
self.pc = Some(usize::try_from(val)?);
},
Err(e) => return Err(e)
}
}
},
"arg_asc" => {
if let Some(child) = arg.named_child(0) {
if child.kind() == "num_str_prefix" {
Expand Down Expand Up @@ -631,6 +721,9 @@ impl Navigate for Assembler {
"arg_hex" => {
let txt = node_text(&arg, &self.line).replace(",","");
let mut hex = hex::decode(&txt)?;
if let Some(pc) = self.pc.as_mut() {
*pc += hex.len();
}
self.code.append(&mut hex);
return Ok(Navigation::Exit);
},
Expand All @@ -640,7 +733,15 @@ impl Navigate for Assembler {
let mut maybe_reps: Option<usize> = None;
while let Some(child) = iter.next() {
if child.kind() == "new_page" {
return Err(Box::new(Error::CannotAssemble));
if let Some(pc) = self.pc {
let page_boundary = 256 * (pc / 256 + 1);
if page_boundary - pc == 256 {
return Ok(Navigation::Exit);
}
maybe_reps = Some(page_boundary - pc);
} else {
return Err(Box::new(Error::UnresolvedProgramCounter));
}
} else if child.kind() == "data" {
let val = self.eval_data(&child, 1, false)?;
if let Some(reps) = maybe_reps {
Expand All @@ -655,6 +756,11 @@ impl Navigate for Assembler {
return Err(Box::new(Error::Syntax));
}
}
if arg.named_child_count() == 1 && maybe_reps.is_some() {
for _i in 0..maybe_reps.unwrap() {
self.push_data(0,1,false);
}
}
return Ok(Navigation::Exit);
}
_ => return Err(Box::new(Error::CannotAssemble))
Expand Down
2 changes: 1 addition & 1 deletion src/lang/merlin/diagnostics/labels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ pub fn visit_verify(curs: &TreeCursor, ctx: &mut Context, ws: &Workspace, symbol
push(rng, "macro substitution variable referenced outside macro", lsp::DiagnosticSeverity::ERROR);
} else if child.is_some() && node.kind()=="label_def" {
let ck = child.unwrap().kind();
if ck == "global_label" {
if ck == "global_label" && !in_macro {
ctx.enter_scope(&txt,symbols);
} else if ck=="local_label" {
if let Some(next) = node.next_named_sibling() {
Expand Down
Loading

0 comments on commit f30f498

Please sign in to comment.