Skip to content

Commit

Permalink
feat: add avr assembler
Browse files Browse the repository at this point in the history
  • Loading branch information
WillLillis committed Dec 26, 2024
1 parent 3cfc940 commit 0ccbc20
Show file tree
Hide file tree
Showing 28 changed files with 572 additions and 8 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ Valid options for the `assembler` field include:
- `"masm"`
- `"nasm"`
- `"ca65"`
- `"avr"`

Don't see an architecture and/or assembler that you'd like to work with? File an
[issue](https://github.com/bergercookie/asm-lsp/issues/new/choose)! We would be
Expand Down Expand Up @@ -234,6 +235,8 @@ git repository.
- PowerISA instruction documentation sourced from the [open-power-sdk](https://github.com/open-power-sdk)
PowerISA [documentation](https://github.com/open-power-sdk/PowerISA?tab=Apache-2.0-1-ov-file)*

- AVR assembler documentation sourced from the [AVR documentation](https://ww1.microchip.com/downloads/en/DeviceDoc/40001917A.pdf)

<details><summary>* Licensed under Apache 2.0</summary><p>

```
Expand Down
3 changes: 2 additions & 1 deletion asm-lsp/config_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ const ARCH_LIST: [Arch; 9] = [
Arch::PowerISA,
];

const ASSEMBLER_LIST: [Assembler; 5] = [
const ASSEMBLER_LIST: [Assembler; 6] = [
Assembler::Gas,
Assembler::Go,
Assembler::Masm,
Assembler::Nasm,
Assembler::Ca65,
Assembler::Avr,
];

#[derive(Args, Debug, Clone)]
Expand Down
6 changes: 4 additions & 2 deletions asm-lsp/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,8 +826,9 @@ pub fn get_hover_resp(
if config.is_assembler_enabled(Assembler::Gas)
|| config.is_assembler_enabled(Assembler::Masm)
|| config.is_assembler_enabled(Assembler::Ca65)
|| config.is_assembler_enabled(Assembler::Avr)
{
// all gas directives have a '.' prefix, some masm directives do
// all gas and AVR directives have a '.' prefix, some masm directives do
let directive_lookup =
get_directive_hover_resp(word, &store.names_to_info.directives, config);
if directive_lookup.is_some() {
Expand Down Expand Up @@ -1216,12 +1217,13 @@ pub fn get_comp_resp(
});
}
}
// prepend all GAS, all Ca65, some MASM, some NASM directives with "."
// prepend all GAS, all Ca65, all AVR, some MASM, some NASM directives with "."
Some(".") => {
if config.is_assembler_enabled(Assembler::Gas)
|| config.is_assembler_enabled(Assembler::Masm)
|| config.is_assembler_enabled(Assembler::Nasm)
|| config.is_assembler_enabled(Assembler::Ca65)
|| config.is_assembler_enabled(Assembler::Avr)
{
return Some(CompletionList {
is_incomplete: true,
Expand Down
97 changes: 97 additions & 0 deletions asm-lsp/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,103 @@ pub fn populate_gas_directives(xml_contents: &str) -> Result<Vec<Directive>> {
Ok(directives_map.into_values().collect())
}

/// Parse the provided XML contents and return a vector of all the directives based on that.
/// If parsing fails, the appropriate error will be returned instead.
///
/// Current function assumes that the XML file is already read and that it's been given a reference
/// to its contents (`&str`).
///
/// # Errors
///
/// This function is highly specialized to parse a handful of files and will panic or return
/// `Err` for most mal-formed/unexpected inputs
///
/// # Panics
///
/// This function is highly specialized to parse a handful of files and will panic or return
/// `Err` for most mal-formed/unexpected inputs
pub fn populate_avr_directives(xml_contents: &str) -> Result<Vec<Directive>> {
let mut directives_map = HashMap::<String, Directive>::new();

// iterate through the XML
let mut reader = Reader::from_str(xml_contents);

// ref to the assembler directive that's currently under construction
let mut curr_directive = Directive::default();
let mut assembler = Assembler::None;

debug!("Parsing directive XML contents...");
loop {
match reader.read_event() {
// start event
Ok(Event::Start(ref e)) => {
match e.name() {
QName(b"Assembler") => {
for attr in e.attributes() {
let Attribute { key, value } = attr.unwrap();
if b"name" == key.into_inner() {
assembler = Assembler::from_str(ustr::get_str(&value)).unwrap();
}
}
}
QName(b"Directive") => {
// start of a new directive
curr_directive = Directive::default();
curr_directive.assembler = assembler;

// iterate over the attributes
for attr in e.attributes() {
let Attribute { key, value } = attr.unwrap();
match key.into_inner() {
b"name" => {
let name = ustr::get_str(&value);
curr_directive.name = name.to_ascii_lowercase();
}
b"description" => {
let description = ustr::get_str(&value);
curr_directive.description =
unescape(description).unwrap().to_string();
}
_ => {}
}
}
}
QName(b"Signature") => {
for attr in e.attributes() {
let Attribute { key, value } = attr.unwrap();
if b"sig" == key.into_inner() {
let sig = ustr::get_str(&value);
curr_directive
.signatures
.push(unescape(sig).unwrap().to_string());
}
}
}
_ => {} // unknown event
}
}
// end event
Ok(Event::End(ref e)) => {
if QName(b"Directive") == e.name() {
// finish directive
directives_map.insert(curr_directive.name.clone(), curr_directive.clone());
}
}
Ok(Event::Eof) => break,
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
_ => {} // rest of events that we don't consider
}
}

// Since directive entries have their assembler labeled on a per-instance basis,
// we check to make sure all of them have been assigned correctly
for directive in directives_map.values() {
assert_ne!(directive.assembler, Assembler::None);
}

Ok(directives_map.into_values().collect())
}

/// Parse the provided HTML contents and return a vector of all the directives based on that.
/// If parsing fails, the appropriate error will be returned instead.
///
Expand Down
Binary file added asm-lsp/serialized/directives/avr
Binary file not shown.
Binary file modified asm-lsp/serialized/directives/gas
Binary file not shown.
Binary file modified asm-lsp/serialized/directives/masm
Binary file not shown.
Binary file modified asm-lsp/serialized/directives/nasm
Binary file not shown.
Binary file modified asm-lsp/serialized/opcodes/arm
Binary file not shown.
Binary file modified asm-lsp/serialized/opcodes/arm64
Binary file not shown.
Binary file modified asm-lsp/serialized/opcodes/riscv
Binary file not shown.
Binary file modified asm-lsp/serialized/opcodes/x86
Binary file not shown.
Binary file modified asm-lsp/serialized/opcodes/x86_64
Binary file not shown.
Binary file modified asm-lsp/serialized/opcodes/z80
Binary file not shown.
Binary file modified asm-lsp/serialized/registers/6502
Binary file not shown.
Binary file modified asm-lsp/serialized/registers/arm
Binary file not shown.
Binary file modified asm-lsp/serialized/registers/arm64
Binary file not shown.
Binary file modified asm-lsp/serialized/registers/power-isa
Binary file not shown.
Binary file modified asm-lsp/serialized/registers/x86
Binary file not shown.
Binary file modified asm-lsp/serialized/registers/x86_64
Binary file not shown.
Binary file modified asm-lsp/serialized/registers/z80
Binary file not shown.
118 changes: 114 additions & 4 deletions asm-lsp/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ mod tests {
get_comp_resp, get_completes, get_hover_resp, get_word_from_pos_params,
instr_filter_targets,
parser::{
populate_6502_instructions, populate_arm_instructions, populate_ca65_directives,
populate_masm_nasm_directives, populate_power_isa_instructions,
populate_riscv_instructions,
populate_6502_instructions, populate_arm_instructions, populate_avr_directives,
populate_ca65_directives, populate_masm_nasm_directives,
populate_power_isa_instructions, populate_riscv_instructions,
},
populate_gas_directives, populate_instructions, populate_name_to_directive_map,
populate_name_to_instruction_map, populate_name_to_register_map, populate_registers, Arch,
Expand All @@ -42,6 +42,16 @@ mod tests {
}
}

fn avr_assembler_test_config() -> Config {
Config {
version: None,
assembler: Assembler::Avr,
instruction_set: Arch::None,
opts: Some(ConfigOptions::default()),
client: None,
}
}

fn power_isa_test_config() -> Config {
Config {
version: None,
Expand Down Expand Up @@ -169,6 +179,7 @@ mod tests {
masm_directives: Vec<Directive>,
nasm_directives: Vec<Directive>,
ca65_directives: Vec<Directive>,
avr_directives: Vec<Directive>,
}

impl GlobalInfo {
Expand All @@ -194,6 +205,7 @@ mod tests {
masm_directives: Vec::new(),
nasm_directives: Vec::new(),
ca65_directives: Vec::new(),
avr_directives: Vec::new(),
}
}
}
Expand Down Expand Up @@ -355,6 +367,13 @@ mod tests {
Vec::new()
};

info.avr_directives = if config.is_assembler_enabled(Assembler::Avr) {
let avr_dirs = include_bytes!("serialized/directives/avr");
bincode::deserialize(avr_dirs)?
} else {
Vec::new()
};

Ok(info)
}

Expand Down Expand Up @@ -481,6 +500,12 @@ mod tests {
&mut store.names_to_info.directives,
);

populate_name_to_directive_map(
Assembler::Avr,
&info.avr_directives,
&mut store.names_to_info.directives,
);

store.completion_items.instructions = get_completes(
&store.names_to_info.instructions,
Some(CompletionItemKind::OPERATOR),
Expand Down Expand Up @@ -724,6 +749,78 @@ mod tests {
);
}

/**************************************************************************
* AVR Tests
*************************************************************************/
#[test]
fn handle_autocomplete_avr_assembler_it_provides_dir_comps_no_args() {
test_directive_autocomplete(
".und<cursor>",
&avr_assembler_test_config(),
CompletionTriggerKind::INVOKED,
None,
);
}

#[test]
fn handle_autocomplete_avr_assembler_it_provides_dir_comps_args_1() {
test_directive_autocomplete(
".or<cursor> 0x120 ; Set SRAM address to hex 120",
&avr_assembler_test_config(),
CompletionTriggerKind::INVOKED,
None,
);
}

#[test]
fn handle_autocomplete_avr_assembler_it_provides_dir_comps_args_2() {
test_directive_autocomplete(
".incl<cursor> <foo>",
&avr_assembler_test_config(),
CompletionTriggerKind::INVOKED,
None,
);
}

#[test]
fn handle_hover_avr_assembler_it_provides_dir_info_1() {
test_hover(
".incl<cursor>ude <foo>",
r#".include [avr]
Include another file.
The INCLUDE directive tells the Assembler to start reading from a specified file. The Assembler then
assembles the specified file until end of file (EOF) or an EXIT directive is encountered. An included file
may contain INCLUDE directives itself. The difference between the two forms is that the first one search
the current directory first, the second one does not.
- .INCLUDE "filename"
- .INCLUDE <filename>"#,
&avr_assembler_test_config(),
);
}

#[test]
fn handle_hover_avr_assembler_it_provides_dir_info_2() {
test_hover(
".or<cursor>g 0x0000",
".org [avr]
Set program origin.
The ORG directive sets the location counter to an absolute value. The value to set is given as a
parameter. If an ORG directive is given within a Data Segment, then it is the SRAM location counter
which is set, if the directive is given within a Code Segment, then it is the Program memory counter which
is set and if the directive is given within an EEPROM Segment, it is the EEPROM location counter which
is set.
The default values of the Code and the EEPROM location counters are zero, and the default value of the
SRAM location counter is the address immediately following the end of I/O address space (0x60 for
devices without extended I/O, 0x100 or more for devices with extended I/O) when the assembling is
started. Note that the SRAM and EEPROM location counters count bytes whereas the Program memory
location counter counts words. Also note that some devices lack SRAM and/or EEPROM.
- .ORG expression",
&avr_assembler_test_config(),
);
}

/**************************************************************************
* PowerISA Tests
*************************************************************************/
Expand Down Expand Up @@ -2544,7 +2641,12 @@ Width: 8 bits",
let ser_vec = bincode::deserialize::<Vec<Directive>>(dirs_ser).unwrap();

let dirs_raw = include_str!($raw_path);
let raw_vec = $populate_fn(dirs_raw).unwrap();
let mut raw_vec = $populate_fn(dirs_raw).unwrap();

// HACK: Windows line endings...
for dir in &mut raw_vec {
dir.description = dir.description.replace('\r', "");
}

for dir in ser_vec {
*cmp_map.entry(dir.clone()).or_insert(0) += 1;
Expand Down Expand Up @@ -2597,4 +2699,12 @@ Width: 8 bits",
populate_ca65_directives
);
}
#[test]
fn serialized_avr_directives_are_up_to_date() {
serialized_directives_test!(
"serialized/directives/avr",
"../docs_store/directives/raw/avr.xml",
populate_avr_directives
);
}
}
4 changes: 4 additions & 0 deletions asm-lsp/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,9 @@ pub enum Assembler {
#[strum(serialize = "ca65")]
#[serde(rename = "ca65")]
Ca65,
#[strum(serialize = "avr")]
#[serde(rename = "avr")]
Avr,
#[serde(skip)]
None,
}
Expand Down Expand Up @@ -887,6 +890,7 @@ impl Assembler {
Self::Nasm => load_directives_with_path!(Self::Nasm, "serialized/directives/nasm"),
Self::Go => warn!("There is currently no Go-specific assembler documentation"),
Self::Ca65 => load_directives_with_path!(Self::Ca65, "serialized/directives/ca65"),
Self::Avr => load_directives_with_path!(Self::Avr, "serialized/directives/avr"),
Self::None => unreachable!(),
}
}
Expand Down
1 change: 1 addition & 0 deletions asm_docs_parsing/regenerate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ cargo build --release
../target/release/asm_docs_parsing ../docs_store/directives/raw/masm.xml -o ../asm-lsp/serialized/directives/masm --doc-type directive --assembler masm
../target/release/asm_docs_parsing ../docs_store/directives/raw/nasm.xml -o ../asm-lsp/serialized/directives/nasm --doc-type directive --assembler nasm
../target/release/asm_docs_parsing ../docs_store/directives/raw/ca65.html -o ../asm-lsp/serialized/directives/ca65 --doc-type directive --assembler ca65
../target/release/asm_docs_parsing ../docs_store/directives/raw/avr.xml -o ../asm-lsp/serialized/directives/avr --doc-type directive --assembler avr
4 changes: 3 additions & 1 deletion asm_docs_parsing/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use ::asm_lsp::parser::{
populate_registers, populate_riscv_instructions, populate_riscv_registers,
};
use asm_lsp::{
parser::populate_power_isa_instructions, Arch, Assembler, Directive, Instruction, Register,
parser::{populate_avr_directives, populate_power_isa_instructions},
Arch, Assembler, Directive, Instruction, Register,
};

use anyhow::{anyhow, Result};
Expand Down Expand Up @@ -144,6 +145,7 @@ fn run(opts: &SerializeDocs) -> Result<()> {
Assembler::Gas | Assembler::Go => populate_gas_directives(&conts)?,
Assembler::Masm | Assembler::Nasm => populate_masm_nasm_directives(&conts)?,
Assembler::Ca65 => populate_ca65_directives(&conts)?,
Assembler::Avr => populate_avr_directives(&conts)?,
Assembler::None => unreachable!(),
},
};
Expand Down
Empty file added docs_store/directives/raw/ab
Empty file.
Loading

0 comments on commit 0ccbc20

Please sign in to comment.