use lsp_types as lsp;
use std::collections::{HashSet,HashMap};
use std::fmt;
use std::str::FromStr;
use super::node_text;
mod context;
pub mod settings;
pub mod checkpoint;
pub mod hovers;
pub mod completions;
pub mod tokenizer;
pub mod formatter;
pub mod assembly;
pub mod disassembly;
pub mod diagnostics;
pub mod semantic_tokens;
pub mod handbook;
#[cfg(test)]
mod tests;
const RCH: &str = "unreachable was reached";
const COLUMN_SEPARATOR: char = '\u{0100}';
const CALL_TOK: char = '\u{0100}';
pub mod symbol_flags {
pub const ENT: u64 = 0x01;
pub const EXT: u64 = 0x02;
pub const SUB: u64 = 0x04;
pub const MAC: u64 = 0x08;
pub const ARG: u64 = 0x10;
pub const LOC: u64 = 0x20;
pub const MLC: u64 = 0x40;
pub const VAR: u64 = 0x80;
}
#[derive(PartialEq,Clone)]
pub enum SourceType {
Master,
Module,
UseAndPut,
Use,
Put,
MacroRef,
Linker
}
#[derive(Clone,PartialEq)]
pub enum MerlinVersion {
Merlin8,
Merlin16,
Merlin16Plus,
Merlin32
}
#[derive(Clone,PartialEq)]
pub enum ProcessorType {
_6502,
_65c02,
_65802,
_65c816
}
#[derive(Clone,PartialEq)]
pub enum LabelType {
Local,
Global,
Macro,
MacroLocal,
Variable
}
#[derive(Clone)]
pub struct AddressMode {
pub mnemonic: String,
pub code: i64,
pub cycles: i64,
pub processors: Vec<ProcessorType>,
pub m_sensitive: bool,
pub x_sensitive: bool
}
#[derive(Clone)]
pub struct Operation {
pub alt: Vec<String>,
pub brief: String,
pub desc: String,
pub modes: Vec<AddressMode>,
pub processors: Vec<ProcessorType>,
pub status: String,
pub m_sensitive: bool,
pub x_sensitive: bool,
pub abs_suffixable: bool,
pub absl_suffixable: bool,
pub absl_prefixable: bool
}
#[derive(Clone)]
pub struct MachineOperation {
pub mnemonic: String,
pub operand_snippet: String,
pub processors: Vec<ProcessorType>,
pub relative: bool,
pub m_sensitive: bool,
pub x_sensitive: bool,
pub abs_suffixable: bool,
pub absl_suffixable: bool,
pub absl_prefixable: bool
}
#[derive(Clone)]
pub struct PseudoOperation {
pub alt: Vec<String>,
pub brief: String,
pub category: String,
pub caveat: Option<String>,
pub desc: String,
pub eg: Vec<String>,
pub choices: Vec<String>,
pub v8x: Option<regex::Regex>,
pub v16x: Option<regex::Regex>,
pub version: Vec<MerlinVersion>
}
#[derive(Clone)]
pub struct Symbol {
name: String,
flags: u64,
decs: Vec<lsp::Location>,
defs: Vec<lsp::Location>,
refs: Vec<lsp::Location>,
fwd_refs: HashMap<lsp::Location,Vec<LabelType>>,
value: Option<i64>,
value_stack: Vec<Option<i64>>,
children: HashMap<String,Symbol>,
docstring: String,
defining_code: Option<String>,
dependencies: HashSet<String>,
checkpoints: Vec<(lsp::Location,Option<i64>)>
}
#[derive(Clone)]
pub struct Symbols {
assembler: MerlinVersion,
processor: ProcessorType,
master_doc_uri: String,
display_doc_uri: String,
display_doc_type: SourceType,
globals: HashMap<String,Symbol>,
vars: HashMap<String,Symbol>,
macros: HashMap<String,Symbol>,
mx: Symbol,
alt_parser_lines: HashSet<isize>
}
#[derive(Clone)]
pub struct Workspace {
pub ws_folders: Vec<lsp::Uri>,
pub docs: Vec<super::Document>,
pub put_map: HashMap<String, HashSet<String>>,
pub use_map: HashMap<String, HashSet<String>>,
pub includes: HashSet<String>,
pub entries: HashMap<String,Symbol>,
pub linker_frac: HashMap<String,f64>,
pub rel_modules: HashSet<String>
}
pub struct MerlinParser {
parser: tree_sitter::Parser,
op_book: handbook::operations::OperationHandbook,
psop_book: handbook::pseudo_ops::PseudoOperationHandbook,
col: isize,
adj_line: String,
c2_regex: regex::Regex
}
impl fmt::Display for SourceType {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Master => write!(f,"master"),
Self::Module => write!(f,"module"),
Self::UseAndPut => write!(f,"USE/PUT include"),
Self::Use => write!(f,"USE include"),
Self::Put => write!(f,"PUT include"),
Self::Linker => write!(f,"linker"),
_ => write!(f,"unknown")
}
}
}
impl fmt::Display for MerlinVersion {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Merlin8 => write!(f,"Merlin 8"),
Self::Merlin16 => write!(f,"Merlin 16"),
Self::Merlin16Plus => write!(f,"Merlin 16+"),
Self::Merlin32 => write!(f,"Merlin 32")
}
}
}
impl fmt::Display for ProcessorType {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::_6502 => write!(f,"6502"),
Self::_65c02 => write!(f,"65C02"),
Self::_65802 => write!(f,"65802"),
Self::_65c816 => write!(f,"65816")
}
}
}
impl Symbol {
pub fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
flags: 0,
decs: Vec::new(),
defs: Vec::new(),
refs: Vec::new(),
fwd_refs: HashMap::new(),
value: None,
value_stack: Vec::new(),
children: HashMap::new(),
docstring: String::new(),
defining_code: None,
dependencies: HashSet::new(),
checkpoints: Vec::new()
}
}
pub fn create(loc: lsp::Location, node: &tree_sitter::Node, source: &str) -> Self {
let mut ans = Self::new(&node_text(node, source));
ans.add_node(loc, node, source);
ans
}
pub fn add_node(&mut self, loc: lsp::Location, node: &tree_sitter::Node, _source: &str) {
if node.kind() == "var_mac" {
self.refs.push(loc);
self.flags |= symbol_flags::ARG | symbol_flags::VAR;
return;
}
let mut pushed_loc = false;
if let Some(parent) = node.parent() {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "arg_jsr" {
self.flags |= symbol_flags::SUB;
}
}
if parent.kind() == "arg_ent" {
self.flags |= symbol_flags::ENT;
self.decs.push(loc.clone());
pushed_loc = true;
}
if parent.kind() == "arg_ext" {
self.flags |= symbol_flags::EXT;
self.decs.push(loc.clone());
pushed_loc = true;
}
if parent.kind() == "arg_exd" {
self.flags |= symbol_flags::EXT;
self.decs.push(loc.clone());
pushed_loc = true;
}
}
if let Some(next) = node.next_named_sibling() {
if next.kind() == "psop_ent" {
self.flags |= symbol_flags::ENT;
self.defs.push(loc.clone());
pushed_loc = true;
}
if next.kind() == "psop_ext" {
self.flags |= symbol_flags::EXT;
self.decs.push(loc.clone());
pushed_loc = true;
}
if next.kind() == "psop_exd" {
self.flags |= symbol_flags::EXT;
self.decs.push(loc.clone());
pushed_loc = true;
}
}
if !pushed_loc {
match node.kind() {
"label_def" => self.defs.push(loc),
"macro_def" => { self.defs.push(loc); self.flags |= symbol_flags::MAC },
"label_ref" => self.refs.push(loc),
"macro_ref" => { self.refs.push(loc); self.flags |= symbol_flags::MAC},
_ => {}
};
}
if let Some(child) = node.named_child(0) {
if child.kind() == "local_label" {
self.flags |= symbol_flags::LOC;
} else if child.kind() == "var_label" {
self.flags |= symbol_flags::VAR;
}
}
}
pub fn add_node_ws(&mut self, loc: lsp::Location, node: &tree_sitter::Node, _source: &str) {
if let Some(parent) = node.parent() {
if parent.kind() == "arg_ent" {
self.flags |= symbol_flags::ENT;
self.decs.push(loc.clone());
}
if parent.kind() == "arg_ext" {
self.flags |= symbol_flags::EXT;
self.refs.push(loc.clone());
}
if parent.kind() == "arg_exd" {
self.flags |= symbol_flags::EXT;
self.refs.push(loc.clone());
}
}
if let Some(next) = node.next_named_sibling() {
if next.kind() == "psop_ent" {
self.flags |= symbol_flags::ENT;
self.defs.push(loc.clone());
}
if next.kind() == "psop_ext" {
self.flags |= symbol_flags::EXT;
self.refs.push(loc.clone());
}
if next.kind() == "psop_exd" {
self.flags |= symbol_flags::EXT;
self.refs.push(loc.clone());
}
}
}
fn add_dependency(&mut self,label: &str) {
self.dependencies.insert(label.to_string());
}
fn dependencies(&self) -> &HashSet<String> {
&self.dependencies
}
fn localize_value(&mut self,loc: &lsp::Location) {
let mut latest_val: Option<i64> = None;
for (prev_loc,val) in &self.checkpoints {
if prev_loc.uri == loc.uri {
if prev_loc.range.start.line >= loc.range.start.line {
break;
}
latest_val = *val;
}
}
self.value = latest_val;
}
fn unset_children(&mut self) {
for child in self.children.values_mut() {
child.value = None;
}
}
}
impl Symbols {
pub fn new() -> Self {
Self {
assembler: MerlinVersion::Merlin8,
processor: ProcessorType::_6502,
master_doc_uri: String::new(),
display_doc_uri: String::new(),
display_doc_type: SourceType::Master,
globals: HashMap::new(),
vars: HashMap::new(),
macros: HashMap::new(),
mx: Symbol::new("MX"),
alt_parser_lines: HashSet::new()
}
}
pub fn toolbar_info(&self) -> Vec<String> {
let mut ans = Vec::new();
let mut master = "unknown".to_string();
if let Ok(uri) = lsp::Uri::from_str(&self.display_doc_uri) {
if let Ok(path) = super::pathbuf_from_uri(&uri) {
if let Some(os) = path.file_name() {
if let Some(s) = os.to_str() {
master = s.to_string();
}
}
}
};
if let Ok(uri) = lsp::Uri::from_str(&self.master_doc_uri) {
if let Ok(path) = super::pathbuf_from_uri(&uri) {
if let Some(os) = path.file_name() {
if let Some(s) = os.to_str() {
master = s.to_string();
}
}
}
};
ans.push(master);
ans.push(self.display_doc_type.to_string());
ans
}
pub fn mac_defined(&self,txt: &str) -> bool {
match self.macros.get(txt) { Some(sym) => {
sym.defs.len() > 0
} _ => {
false
}}
}
pub fn mac_forward(&self,txt: &str,loc: &lsp::Location) -> bool {
match self.macros.get(txt) { Some(sym) => {
match sym.fwd_refs.get(loc) {
Some(fwd) => fwd.contains(&LabelType::Macro),
None => false
}
} _ => {
false
}}
}
pub fn global_declared_or_defined(&self,txt: &str) -> bool {
match self.globals.get(txt) { Some(sym) => {
sym.decs.len() + sym.defs.len() > 0
} _ => {
false
}}
}
pub fn global_declared(&self,txt: &str) -> bool {
match self.globals.get(txt) { Some(sym) => {
sym.decs.len() > 0
} _ => {
false
}}
}
pub fn global_defined(&self,txt: &str) -> bool {
match self.globals.get(txt) { Some(sym) => {
sym.defs.len() > 0
} _ => {
false
}}
}
pub fn global_forward(&self,txt: &str,loc: &lsp::Location) -> bool {
match self.globals.get(txt) { Some(sym) => {
match sym.fwd_refs.get(loc) {
Some(fwd) => fwd.contains(&LabelType::Global),
None => false
}
} _ => {
false
}}
}
pub fn var_defined(&self,txt: &str) -> bool {
match self.vars.get(txt) { Some(sym) => {
sym.defs.len() > 0
} _ => {
false
}}
}
pub fn var_forward(&self,txt: &str,loc: &lsp::Location) -> bool {
match self.vars.get(txt) { Some(sym) => {
match sym.fwd_refs.get(loc) {
Some(fwd) => fwd.contains(&LabelType::Variable),
None => false
}
} _ => {
false
}}
}
pub fn child_defined(&self,txt: &str,scope: &Symbol) -> bool {
if scope.flags & symbol_flags::MAC > 0 {
if let Ok(count) = self.count_macro_loc_definitions(scope, txt, 0, 15) {
return count > 0;
}
}
match scope.children.get(txt) { Some(sym) => {
sym.defs.len() > 0
} _ => {
false
}}
}
pub fn child_forward(&self,txt: &str,scope: &Symbol, loc: &lsp::Location) -> bool {
match scope.children.get(txt) { Some(sym) => {
match sym.fwd_refs.get(loc) {
Some(fwd) => fwd.contains(&LabelType::Local) || fwd.contains(&LabelType::MacroLocal),
None => false
}
} _ => {
false
}}
}
pub fn adjust_line(&self,row: isize,line: &str,term: &str) -> String {
let prefix = match self.alt_parser_lines.contains(&row) {
true => CALL_TOK.to_string(),
false => "".to_string()
};
[&prefix,line,term].concat()
}
pub fn update_row_data(&mut self,doc: &super::Document,row: isize,col: isize) {
if col<0 && doc.uri.to_string()==self.display_doc_uri {
self.alt_parser_lines.insert(row);
}
}
fn is_dependency(&self,label: &str,sym: &Symbol,curr_depth: usize,max_depth: usize) -> Result<bool,crate::DYNERR> {
if curr_depth > max_depth {
return Err(Box::new(assembly::Error::Nesting));
}
for m2 in sym.dependencies() {
if m2 == label {
log::debug!("indirect reference to {}",label);
log::debug!(" from {}",&sym.name);
return Ok(true);
}
if let Some(dep) = self.macros.get(m2) {
if self.is_dependency(label, dep,curr_depth+1,max_depth)? {
log::debug!(" from {}",&sym.name);
return Ok(true);
}
}
}
Ok(false)
}
fn is_label_referenced_or_ent(&self,label: &str,scope: Option<&Symbol>) -> bool {
let maybe_sym = match label.get(0..1) {
Some(":") => match scope {
Some(parent) => parent.children.get(label),
None => None
},
Some("]") => self.vars.get(label),
Some(_) => self.globals.get(label),
None => None
};
match maybe_sym {
Some(sym) => sym.refs.len() > 0 || sym.flags & symbol_flags::ENT > 0,
None => false
}
}
fn is_macro_referenced(&self,label: &str, max_depth: usize) -> Result<bool,crate::DYNERR> {
if let Some(sym) = self.macros.get(label) {
if sym.refs.len() > 0 {
return Ok(true);
}
}
for sym in self.macros.values() {
if sym.refs.len() > 0 {
if self.is_dependency(label, sym,0,max_depth)? {
return Ok(true);
}
}
}
Ok(false)
}
fn count_macro_loc_definitions(&self,mac: &Symbol, label: &str, curr_depth: usize, max_depth: usize) -> Result<usize,crate::DYNERR> {
let mut count: usize = 0;
if curr_depth > max_depth {
return Err(Box::new(assembly::Error::Nesting));
}
if let Some(child) = mac.children.get(label) {
count += child.defs.len();
}
for m2 in mac.dependencies() {
if let Some(sym) = self.macros.get(m2) {
count += self.count_macro_loc_definitions(sym,label,curr_depth+1,max_depth)?;
}
}
Ok(count)
}
fn detect_all_duplicates_in_macro(&self,mac: &Symbol) -> Result<Option<String>,crate::DYNERR> {
let mut ans = String::new();
for label in mac.children.keys() {
if self.count_macro_loc_definitions(mac, label, 0, 15)? > 1 {
ans += &label;
ans += ",";
}
}
if ans.len() > 0 {
ans.pop();
Ok(Some(ans))
} else {
Ok(None)
}
}
fn localize_all_variables(&mut self,loc: &lsp::Location) {
for var in self.vars.values_mut() {
var.localize_value(loc);
}
self.mx.localize_value(loc);
}
fn unset_all_variables(&mut self) {
for var in self.vars.values_mut() {
var.value = None;
}
self.mx.value = None;
}
fn stash_all_variables(&mut self) {
for var in self.vars.values_mut() {
var.value_stack.push(var.value);
}
self.mx.value_stack.push(self.mx.value);
}
fn restore_all_variables(&mut self) {
for var in self.vars.values_mut() {
if let Some(v) = var.value_stack.pop() {
var.value = v;
}
}
if let Some(v) = self.mx.value_stack.pop() {
self.mx.value = v;
}
}
fn checkpoint_all_variables(&mut self,loc: &lsp::Location) {
for var in self.vars.values_mut() {
var.checkpoints.push((loc.clone(),var.value));
}
self.mx.checkpoints.push((loc.clone(),self.mx.value));
}
}
impl MerlinParser {
pub fn new() -> Self {
let mut parser = tree_sitter::Parser::new();
parser.set_language(&tree_sitter_merlin6502::LANGUAGE.into()).expect(RCH);
Self {
parser,
op_book: handbook::operations::OperationHandbook::new(),
psop_book: handbook::pseudo_ops::PseudoOperationHandbook::new(),
col: 0,
adj_line: String::new(),
c2_regex: regex::Regex::new(r"\s+\S+").expect(RCH)
}
}
fn adjust_line(&mut self, old_line: &str, symbols: &Symbols) -> String {
self.col = 0;
if old_line.starts_with(CALL_TOK) {
self.col = -(CALL_TOK.len_utf8() as isize); return old_line.to_string();
}
if old_line.starts_with("*") || old_line.starts_with(";") {
return old_line.to_string();
}
let prefix = match self.c2_regex.find(old_line) {
Some(sep_c2) => {
let c2 = sep_c2.as_str().trim();
if c2.starts_with(";") ||
self.op_book.strong_match(c2,&symbols.processor) ||
self.psop_book.strong_match(c2,&symbols.assembler) {
"".to_string() } else if symbols.mac_defined(c2) {
CALL_TOK.to_string() } else if self.op_book.weak_match(&c2.to_lowercase(),&symbols.processor) ||
self.psop_book.weak_match(&c2.to_lowercase(),&symbols.assembler) {
"".to_string() } else {
CALL_TOK.to_string()
}
}
None => "".to_string()
};
self.col = -(prefix.len() as isize); prefix + old_line
}
pub fn col_offset(&self) -> isize {
self.col
}
pub fn line(&self) -> &str {
&self.adj_line
}
pub fn parse(&mut self, line: &str, symbols: &Symbols) -> Result<tree_sitter::Tree,crate::DYNERR> {
self.adj_line = self.adjust_line(line,symbols);
if !self.adj_line.ends_with("\n") {
self.adj_line += "\n";
}
if let Some(tree) = self.parser.parse(&self.adj_line,None) {
return Ok(tree);
}
Err(Box::new(super::Error::Syntax))
}
}