use std::collections::{HashSet,HashMap};
use tree_sitter;
use std::io;
use std::io::Read;
use std::fmt::Write;
use lsp_types::{self as lsp, DiagnosticSeverity};
use crate::lang::{Document, lsp_range, node_text, node_integer};
use crate::lang::server::basic_diag;
use workspace::WorkspaceScanner;
use super::{Workspace,Variable,Line,Symbols,Collisions};
use super::settings::Settings;
use crate::{DYNERR, STDRESULT};
use crate::lang::{Navigate,Navigation};
use crate::lang::server::Analysis;
use super::name_range;
mod workspace;
mod pass1;
mod pass2;
pub mod chain;
pub mod expression;
fn update_collisions<'a>(collisions: &'a mut Collisions,long_key: &String) -> Option<&'a HashSet<String>> {
let mut word_len = 0;
for c in long_key.chars() {
if !c.is_ascii_alphanumeric() {
break;
}
word_len += 1;
}
let trailing = match word_len {
l if l == long_key.len() => String::new(),
l if l >= 2 => long_key[l..].to_string(),
_ => return None
};
if word_len >= 2 {
let short_key = [long_key[0..2].to_string(),trailing].concat();
if !collisions.contains_key(&short_key) {
collisions.insert(short_key.clone(),HashSet::new());
}
let set = collisions.get_mut(&short_key).unwrap();
set.insert(long_key.to_string());
Some(set)
} else {
None
}
}
struct FlowState {
ip_branch_stack: Vec<lsp::Range>,
curr_line_num: Option<u16>,
row: isize,
col: isize,
line: String
}
impl FlowState {
fn new() -> Self {
Self {
ip_branch_stack: Vec::new(),
curr_line_num: None,
row: 0,
col: 0,
line: String::new()
}
}
fn new_line(&mut self,txt: &str,row: isize) {
self.ip_branch_stack = Vec::new();
self.curr_line_num = None;
self.line = txt.to_string();
self.row = row;
self.col = 0;
}
fn eval_ip_start(&mut self,addr: &tree_sitter::Node) {
if let Some(val) = node_integer::<u8>(&addr,&self.line) {
if val == 103 || val == 104 {
self.ip_branch_stack.push(lsp_range(addr.range(), self.row, self.col));
}
}
}
fn eval_ip_branch(&mut self,linenum: &tree_sitter::Node) -> Option<lsp::Diagnostic> {
if self.ip_branch_stack.len() == 0 {
return None;
}
self.ip_branch_stack = Vec::new(); let end_rng = lsp_range(linenum.range(),self.row,self.col);
if let Some(new) = node_integer::<u16>(&linenum,&self.line) {
if let Some(curr) = self.curr_line_num {
if curr/256 >= new/256 {
return Some(basic_diag(end_rng, "interprogram branch", lsp::DiagnosticSeverity::INFORMATION));
} else {
return Some(basic_diag(end_rng,"interprogram branch violates search condition",lsp::DiagnosticSeverity::ERROR));
}
}
}
None
}
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 {
row: isize,
col: isize,
pass: usize,
config: Settings,
line: String,
diagnostics: Vec<lsp::Diagnostic>,
fcollisions: Collisions,
vcollisions: Collisions,
symbols: Symbols,
last_good_line_number: i64,
flow: FlowState,
depth_of_def: u32,
dummy_var_key: String,
curr_backlinks: HashSet<lsp::Uri>,
scanner: WorkspaceScanner,
workspace_folders: Vec<lsp::Uri>
}
impl Navigate for Analyzer {
fn visit(&mut self,curs: &tree_sitter::TreeCursor) -> Result<Navigation,DYNERR> {
match self.pass {
1 => self.visit_primaries(curs),
2 => self.visit_node(curs),
_ => panic!("unexpected number of visit passes")
}
}
}
impl Analysis for Analyzer {
fn init_workspace(&mut self,source_dirs: Vec<lsp_types::Uri>,volatile_docs: Vec<crate::lang::Document>) -> STDRESULT {
self.workspace_folders = source_dirs;
self.scanner.gather_docs(&self.workspace_folders, 1000)?;
self.scanner.append_volatile_docs(volatile_docs);
self.scanner.scan()
}
fn analyze(&mut self,doc: &Document) -> Result<(),DYNERR> {
self.flow = FlowState::new();
self.diagnostics = Vec::new();
self.symbols = Symbols::new();
self.fcollisions = HashMap::new();
self.vcollisions = HashMap::new();
self.curr_backlinks = HashSet::new();
self.scanner.update_doc(doc);
self.scanner.get_workspace().get_all_backlinks(&mut self.curr_backlinks, &doc.uri, &doc.uri, 0, 8);
for backlink in &self.curr_backlinks {
if let Some(collisions) = self.scanner.get_workspace().ws_collisions.get(backlink) {
for (k,added) in collisions {
let aggregate = match self.vcollisions.get(k) {
Some(set) => set.union(added).map(|s| s.to_owned()).collect(),
None => added.clone()
};
self.vcollisions.insert(k.to_owned(),aggregate);
}
}
}
let mut parser = tree_sitter::Parser::new();
parser.set_language(&tree_sitter_applesoft::LANGUAGE.into())?;
for pass in 1..3 {
self.last_good_line_number = -1;
self.row = 0;
self.col = 0;
self.pass = pass;
for line in doc.text.lines() {
log::trace!("analyze row {}",self.row);
if line.trim_start().len()==0 {
self.row += 1;
continue;
}
self.line = String::from(line) + "\n";
self.flow.new_line(&self.line, self.row);
match parser.parse(&self.line,None) {
Some(tree) => self.walk(&tree)?,
None => return Err(Box::new(crate::lang::Error::ParsingError))
};
if self.flow.ip_branch_stack.len() > 0 {
let rng = self.flow.ip_branch_stack.pop().unwrap();
self.diagnostics.push(basic_diag(rng, "interprogram branch is not taken on this line", DiagnosticSeverity::ERROR));
}
self.row += 1;
}
}
Ok(())
}
fn update_config(&mut self,json_str: &str) -> STDRESULT {
match super::settings::parse(json_str) {
Ok(c) => {
self.config = c;
Ok(())
},
Err(e) => Err(e)
}
}
fn read_stdin(&self) -> String {
if atty::is(atty::Stream::Stdin) {
return crate::lang::line_entry(tree_sitter_applesoft::LANGUAGE.into(), "]");
}
let mut program = String::new();
io::stdin().read_to_string(&mut program).expect("failed to read stdin");
program
}
fn get_diags(&self,_doc: &Document) -> Vec<lsp::Diagnostic> {
self.diagnostics.clone()
}
fn get_folds(&self,_doc: &crate::lang::Document) -> Vec<lsp_types::FoldingRange> {
Vec::new()
}
fn err_warn_info_counts(&self) -> [usize;3] {
let mut err = 0;
let mut warn = 0;
let mut info = 0;
for item in &self.diagnostics {
match item.severity {
Some(lsp::DiagnosticSeverity::ERROR) => err += 1,
Some(lsp::DiagnosticSeverity::WARNING) => warn += 1,
Some(lsp::DiagnosticSeverity::INFORMATION) => info += 1,
_ => {}
}
}
[err,warn,info]
}
fn eprint_lines_sexpr(&self,program: &str) {
crate::lang::eprint_lines_sexpr(tree_sitter_applesoft::LANGUAGE.into(), program,1);
}
}
impl Analyzer {
pub fn new() -> Self {
Self {
row: 0,
col: 0,
pass: 0,
config: Settings::new(),
line: String::new(),
diagnostics: Vec::new(),
fcollisions: HashMap::new(),
vcollisions: HashMap::new(),
symbols: Symbols::new(),
last_good_line_number: -1,
flow: FlowState::new(),
depth_of_def: 0,
dummy_var_key: "".to_string(),
curr_backlinks: HashSet::new(),
scanner: WorkspaceScanner::new(),
workspace_folders: Vec::new()
}
}
pub fn set_config(&mut self,config: Settings) {
self.config = config;
}
pub fn get_symbols(&self) -> Symbols {
self.symbols.clone()
}
pub fn get_workspace(&self) -> &Workspace {
self.scanner.get_workspace()
}
pub fn set_workspace(&mut self, ws: Workspace) {
self.scanner.set_workspace(ws);
}
pub fn rescan_workspace(&mut self,gather: bool) -> STDRESULT {
if gather {
log::debug!("GATHER WORKSPACE DOCUMENTS");
self.scanner.gather_docs(&self.workspace_folders, 1000)?;
}
log::debug!("SCAN WORKSPACE DOCUMENTS");
self.scanner.scan()
}
pub fn rescan_workspace_and_update(&mut self,checkpoints: Vec<Document>) -> STDRESULT {
log::debug!("GATHER WORKSPACE DOCUMENTS");
self.scanner.gather_docs(&self.workspace_folders, 1000)?;
for doc in checkpoints {
self.scanner.update_doc(&doc);
}
log::debug!("SCAN WORKSPACE DOCUMENTS");
self.scanner.scan()
}
fn create(&self,rng: tree_sitter::Range,mess: &str,severity: lsp::DiagnosticSeverity) -> lsp::Diagnostic {
basic_diag(lsp_range(rng,self.row,self.col),mess,severity)
}
fn push(&mut self,rng: tree_sitter::Range,mess: &str,severity: lsp::DiagnosticSeverity) {
self.diagnostics.push(self.create(rng,mess,severity));
}
fn linenum_range(&self,node: &tree_sitter::Node,source: &str) -> lsp_types::Range {
let len = node_text(node,source).trim_end().len() as u32;
let rng = lsp_range(node.range(),self.row,self.col);
lsp_types::Range::new(rng.start,lsp_types::Position::new(rng.start.line,rng.start.character + len))
}
fn value_range(&mut self,node: tree_sitter::Node, low: f64, high: f64, expect_int: bool) {
let Ok(val) = expression::eval_aexpr(&node, &self.line, &self.symbols) else {
return;
};
let is_int = val.floor() == val;
if expect_int && !is_int {
self.push(node.range(),"Expected an integer",lsp::DiagnosticSeverity::ERROR);
}
if val < low || val > high {
let mut mess = "Out of range (".to_string();
match low >= -32767. && high <= 65535. {
false => write!(&mut mess,"{low:e},{high:e})").expect("could not format"),
true => write!(&mut mess,"{low},{high})").expect("could not format")
};
self.push(node.range(),&mess,lsp::DiagnosticSeverity::ERROR);
}
}
fn check_case(&mut self, node: tree_sitter::Node, rng: tree_sitter::Range) {
let txt = node_text(&node,&self.line);
if self.config.flag.case_sensitive.is_some() && txt.to_uppercase() != txt {
self.push(rng, "settings require upper case", self.config.flag.case_sensitive.unwrap());
}
}
}