pub mod applesoft;
pub mod integer;
pub mod merlin;
mod linenum;
pub mod server;
pub mod disk_server;
use tree_sitter;
use lsp_types as lsp;
use colored::*;
use thiserror::Error;
use std::{collections::HashSet, io};
use std::io::Write;
use num_traits::Num;
use std::str::FromStr;
use std::collections::BTreeMap;
use atty;
use crate::{STDRESULT,DYNERR};
const RCH: &str = "unreachable was reached";
pub enum Navigation {
GotoSelf,
GotoChild,
GotoSibling,
GotoParentSibling,
Descend,
Exit,
Abort
}
#[derive(Error,Debug)]
pub enum Error {
#[error("Syntax error")]
Syntax,
#[error("Invalid Line Number")]
LineNumber,
#[error("Tokenization error")]
Tokenization,
#[error("Detokenization error")]
Detokenization,
#[error("Parsing error")]
ParsingError,
#[error("Path not found")]
PathNotFound,
#[error("Out of range")]
OutOfRange,
#[error("Could not parse URL")]
BadUrl
}
pub fn normalize_client_uri(uri: lsp::Uri) -> lsp::Uri {
match pathbuf_from_uri(&uri) {
Ok(path) => match uri_from_path(&path) {
Ok(ans) => return ans,
Err(_) => {}
},
Err(_) => {}
}
uri
}
pub fn normalize_client_uri_str(uri: &str) -> Result<lsp::Uri,DYNERR> {
Ok(normalize_client_uri(lsp::Uri::from_str(uri)?))
}
pub fn uri_from_path(path: &std::path::Path) -> Result<lsp::Uri,DYNERR> {
let url = match url::Url::from_file_path(path) {
Ok(ans) => ans,
Err(()) => return Err(Box::new(Error::PathNotFound))
};
let uri = fluent_uri::Uri::from_str(url.as_str())?.normalize();
Ok(lsp::Uri::from_str(uri.as_str())?)
}
pub fn uri_from_path_str(path_str: &str) -> Result<lsp::Uri,DYNERR> {
uri_from_path(&std::path::PathBuf::from_str(path_str)?)
}
pub fn pathbuf_from_uri(uri: &lsp::Uri) -> Result<std::path::PathBuf,DYNERR> {
let url_crate_uri = match url::Url::from_str(uri.as_str()) {
Ok(ans) => ans,
Err(e) => return Err(Box::new(e))
};
match url_crate_uri.to_file_path() {
Ok(ans) => Ok(ans),
Err(_) => Err(Box::new(Error::BadUrl))
}
}
fn match_emulation_path(emu_path: &str, sep: &str, doc: &Document) -> usize {
let mut quality = 0;
let Some(scheme) = doc.uri.scheme() else { return quality; };
if scheme.as_str() != "file" { return quality; }
let Ok(doc_path) = crate::lang::pathbuf_from_uri(&doc.uri) else {
log::trace!("error while parsing {}",doc.uri.as_str());
return quality;
};
let mut doc_segs = doc_path.iter().rev();
let emu_segs = emu_path.split(sep).map(|x| x.to_string()).collect::<Vec<String>>();
for emu_seg in emu_segs.iter().rev() {
if let Some(doc_seg) = doc_segs.next() {
if let Some(s) = doc_seg.to_str() {
if s.to_lowercase() == emu_seg.to_lowercase() {
quality += 1;
} else {
break;
}
}
}
}
quality
}
fn get_emulation_match(docs: &Vec<Document>, emu_path: &str, sep: &str) -> Vec<lsp::Uri> {
let mut set = HashSet::new();
let mut best_quality = 0;
let mut shortest = usize::MAX; log::debug!("search for file `{}`",emu_path);
for doc in docs {
let quality = match_emulation_path(&emu_path, sep, &doc);
let l = doc.uri.as_str().len();
log::trace!("match {} to {} Q={}",emu_path,doc.uri.as_str(),quality);
if quality > best_quality {
set = HashSet::new();
set.insert(doc.uri.clone());
best_quality = quality;
shortest = l;
} else if quality > 0 && quality == best_quality {
if l < shortest {
set = HashSet::new();
set.insert(doc.uri.clone());
shortest = l;
} else if l == shortest {
set.insert(doc.uri.clone());
}
}
}
log::debug!("found {} URI candidates",set.len());
let mut ans = Vec::new();
for uri in &set {
log::trace!(" {}",uri.as_str());
ans.push(uri.clone())
}
ans
}
#[derive(Clone)]
pub struct Document {
pub uri: lsp::Uri,
pub version: Option<i32>,
pub text: String
}
impl Document {
pub fn new(uri: lsp::Uri,text: String) -> Self {
Self {
uri,
version: None,
text
}
}
pub fn from_string(text: String, id: u64) -> Self {
Self {
uri: lsp::Uri::from_str(&format!("string:{}",id)).expect(RCH),
version: None,
text
}
}
pub fn from_macro(text: String, label: String) -> Self {
Self {
uri: lsp::Uri::from_str(&format!("macro:{}",label)).expect(RCH),
version: None,
text
}
}
pub fn from_file_path(path: &std::path::Path) -> Result<Self,DYNERR> {
let by = std::fs::read(path)?;
Ok(Self {
uri: uri_from_path(path)?,
version: None,
text: String::from_utf8(by)?
})
}
}
pub fn range_contains_pos(rng: &lsp::Range, pos: &lsp::Position) -> bool
{
if pos.line < rng.start.line || pos.line > rng.end.line {
return false;
}
if pos.line == rng.start.line && pos.character < rng.start.character {
return false;
}
if pos.line == rng.end.line && pos.character > rng.end.character {
return false;
}
return true;
}
pub fn range_contains_range(outer: &lsp::Range, inner: &lsp::Range) -> bool
{
if inner.start.line < outer.start.line || inner.end.line > outer.end.line {
return false;
}
if inner.start.line == outer.start.line && inner.start.character < outer.start.character {
return false;
}
if inner.end.line == outer.end.line && inner.end.character > outer.end.character {
return false;
}
return true;
}
pub fn translate_pos(pos: &lsp::Position, dl: isize, dc: isize) -> lsp::Position {
let mut ans = lsp::Position::new(0,0);
ans.line = match pos.line as isize + dl < 0 {
true => 0,
false => (pos.line as isize + dl) as u32
};
ans.character = match pos.character as isize + dc < 0 {
true => 0,
false => (pos.character as isize + dc) as u32
};
ans
}
pub fn range_union(r1: &lsp::Range,r2: &lsp::Range) -> lsp::Range {
lsp::Range::new(
lsp::Position::new(
match r1.start.line < r2.start.line { true => r1.start.line, false => r2.start.line },
match r1.start.line < r2.start.line || r1.start.line == r2.start.line && r1.start.character < r2.start.character {
true => r1.start.character,
false => r2.start.character
}
),
lsp::Position::new(
match r2.end.line > r1.end.line { true => r2.end.line, false => r1.end.line },
match r2.end.line > r1.end.line || r2.end.line == r1.end.line && r2.end.character > r1.end.character {
true => r2.end.character,
false => r1.end.character
}
)
)
}
pub fn lsp_range(rng: tree_sitter::Range,row: isize,col: isize) -> lsp::Range {
lsp::Range {
start: lsp::Position { line: (row + rng.start_point.row as isize) as u32, character: (col + rng.start_point.column as isize) as u32 },
end: lsp::Position { line: (row + rng.end_point.row as isize) as u32, character: (col + rng.end_point.column as isize) as u32}
}
}
pub fn node_text(node: &tree_sitter::Node,source: &str) -> String {
if let Ok(ans) = node.utf8_text(source.as_bytes()) {
return ans.to_string();
}
return "".to_string();
}
pub fn node_integer<T: FromStr>(node: &tree_sitter::Node,source: &str) -> Option<T> {
let txt = node_text(&node,source).replace(" ","");
match txt.parse::<T>() {
Ok(num) => Some(num),
Err(_) => None
}
}
pub fn node_radix<T: Num>(node: &tree_sitter::Node, source: &str, hex: &str, bin: &str) -> Option<T> {
if let Ok(s) = node.utf8_text(source.as_bytes()) {
let mut trimmed = s.to_string().replace(" ","").replace("_","");
if s.starts_with("_") && (hex=="_" || bin=="_") {
trimmed = ["_",&trimmed].concat();
}
if trimmed.starts_with(hex) {
match T::from_str_radix(&trimmed[1..],16) {
Ok(ans) => Some(ans),
Err(_) => None
}
} else if trimmed.starts_with(bin) {
match T::from_str_radix(&trimmed[1..],2) {
Ok(ans) => Some(ans),
Err(_) => None
}
} else {
match T::from_str_radix(&trimmed,10) {
Ok(ans) => Some(ans),
Err(_) => None
}
}
} else {
None
}
}
pub fn named_sibling(node: tree_sitter::Node,skip: usize) -> Option<tree_sitter::Node> {
let mut maybe = Some(node);
for _i in 0..skip {
maybe = maybe.unwrap().next_named_sibling();
if maybe.is_none() {
return None;
}
}
maybe
}
pub fn extended_range(node: &tree_sitter::Node,end_col: usize) -> tree_sitter::Range {
let mut ans = node.range();
if ans.start_point.column == ans.end_point.column {
if ans.start_point.column > 0 {
ans.start_point.column -= 1;
}
if ans.end_point.column + 1 < end_col {
ans.end_point.column += 1;
}
}
ans
}
pub fn update_json_bool(maybe_obj: &serde_json::Value, key: &str, curr: &mut bool) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
match x.as_bool() { Some(x) => *curr = x, _ => {} };
}
}
}
pub fn update_json_i64(maybe_obj: &serde_json::Value, key: &str, curr: &mut i64) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
match x.as_i64() { Some(x) => *curr = x, _ => {} };
}
}
}
pub fn update_json_f64(maybe_obj: &serde_json::Value, key: &str, curr: &mut f64) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
match x.as_f64() { Some(x) => *curr = x, _ => {} };
}
}
}
pub fn update_json_string_opt(maybe_obj: &serde_json::Value, key: &str, curr: &mut Option<String>) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
match x.as_str() { Some(x) => *curr = Some(x.to_string()), _ => {} };
}
}
}
pub fn update_json_string(maybe_obj: &serde_json::Value, key: &str, curr: &mut String) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
match x.as_str() { Some(x) => *curr = x.to_string(), _ => {} };
}
}
}
pub fn update_json_severity(maybe_obj: &serde_json::Value, key: &str, curr: &mut Option<lsp::DiagnosticSeverity>) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
match x.as_str() {
Some("ignore") => *curr = None,
Some("hint") => *curr = Some(lsp::DiagnosticSeverity::HINT),
Some("info") => *curr = Some(lsp::DiagnosticSeverity::INFORMATION),
Some("warn") => *curr = Some(lsp::DiagnosticSeverity::WARNING),
Some("error") => *curr = Some(lsp::DiagnosticSeverity::ERROR),
_ => {}
}
}
}
}
pub fn update_json_vec(maybe_obj: &serde_json::Value, key: &str, curr: &mut Vec<i64>) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
let mut ans: Vec<i64> = Vec::new();
if let Some(a) = x.as_array() {
for v in a {
match v.as_i64() {
Some(i) => ans.push(i),
None => return
}
}
*curr = ans;
}
}
}
}
pub fn update_json_vec_str(maybe_obj: &serde_json::Value, key: &str, curr: &mut Vec<String>) {
if let Some(outer) = maybe_obj.as_object() {
if let Some(x) = outer.get(key) {
let mut ans: Vec<String> = Vec::new();
if let Some(a) = x.as_array() {
for v in a {
match v.as_str() {
Some(s) => ans.push(s.to_owned()),
None => return
}
}
*curr = ans;
}
}
}
}
pub trait Navigate {
fn visit(&mut self,curs: &tree_sitter::TreeCursor) -> Result<Navigation,DYNERR>;
fn descend(&mut self,_curs: &tree_sitter::TreeCursor) -> Result<Navigation,DYNERR> {
Ok(Navigation::GotoSibling)
}
fn walk(&mut self,tree: &tree_sitter::Tree) -> Result<(),DYNERR>
{
let mut curs = tree.walk();
let mut choice = Navigation::GotoSelf;
while ! matches!(choice,Navigation::Exit | Navigation::Abort)
{
if matches!(choice,Navigation::GotoSelf) {
choice = self.visit(&curs)?;
} else if matches!(choice,Navigation::Descend) {
choice = self.descend(&curs)?;
} else if matches!(choice,Navigation::GotoChild) && curs.goto_first_child() {
choice = self.visit(&curs)?;
} else if matches!(choice,Navigation::GotoParentSibling) && curs.goto_parent() && curs.goto_next_sibling() {
choice = self.visit(&curs)?;
} else if matches!(choice,Navigation::GotoSibling) && curs.goto_next_sibling() {
choice = self.visit(&curs)?;
} else if curs.goto_next_sibling() {
choice = self.visit(&curs)?;
} else if curs.goto_parent() {
choice = Navigation::GotoSibling;
} else {
choice = Navigation::Exit;
}
}
Ok(())
}
}
pub fn is_lang(lang: tree_sitter::Language,code: &str) -> bool {
let mut parser = tree_sitter::Parser::new();
parser.set_language(&lang).expect("language not found");
let mut iter = code.lines();
let mut line_count = 0;
let mut good_lines = 0;
let mut great_lines = 0;
while let Some(line) = iter.next()
{
line_count += 1;
if let Some(tree) = parser.parse(String::from(line) + "\n",None) {
let curs = tree.walk();
if !curs.node().has_error() {
good_lines += 1;
match lang.name() {
Some("merlin6502") => {
match curs.node().child(0) {
Some(child) => match child.kind() {
"operation" => great_lines += 1,
"pseudo_operation" => great_lines += 1,
_ => {}
},
None => {}
}
},
_ => great_lines += 1
}
}
}
}
if line_count == 0 {
log::warn!("encountered empty file");
return false;
}
if (great_lines==0 || good_lines != line_count) && good_lines * 3 > line_count {
log::warn!("{} {} parsed as {} but merit test failed",good_lines,match good_lines==1 { true => "line", false => "lines" },lang.name().unwrap_or("unknown"));
}
good_lines == line_count && great_lines > 0
}
pub fn verify_str(lang: tree_sitter::Language,code: &str) -> STDRESULT {
let mut parser = tree_sitter::Parser::new();
parser.set_language(&lang)?;
let mut iter = code.lines();
let mut row = 0;
while let Some(line) = iter.next()
{
match parser.parse(String::from(line) + "\n",None) {
Some(tree) => {
let curs = tree.walk();
if curs.node().has_error() {
log::error!("syntax error in row {}, use `verify` for more details",row);
return Err(Box::new(Error::Syntax));
}
},
None => {
log::error!("unable to parse row {}",row);
return Err(Box::new(Error::Syntax));
}
}
row += 1;
}
Ok(())
}
pub fn eprint_lines_sexpr(lang: tree_sitter::Language, program: &str, unwraps: usize) {
let mut parser = tree_sitter::Parser::new();
parser.set_language(&lang).expect("Error loading grammar");
let mut iter = program.lines();
eprintln!();
while let Some(line) = iter.next()
{
if let Some(tree) = parser.parse(String::from(line) + "\n",None) {
let mut curs = tree.walk();
for _i in 0..unwraps {
curs.goto_first_child();
}
eprintln!("{}",line.to_string());
eprintln!("{}",curs.node().to_sexp());
}
}
}
pub fn line_entry(lang: tree_sitter::Language,prompt: &str) -> String
{
let mut parser = tree_sitter::Parser::new();
parser.set_language(&lang).expect("Error loading grammar");
let mut code = String::new();
if atty::is(atty::Stream::Stdin) {
eprintln!("Line entry interface.");
eprintln!("This is a blind accumulation of lines.");
eprintln!("Verify occurs when entry is terminated.");
eprintln!("Accumulated lines can be piped.");
eprintln!("`bye` terminates.");
loop {
eprint!("{} ",prompt);
let mut line = String::new();
io::stderr().flush().expect("could not flush stderr");
io::stdin().read_line(&mut line).expect("could not read stdin");
if line=="bye\n" || line=="bye\r\n" {
break;
}
code += &line;
}
return code;
} else {
panic!("line_entry was called with piped input");
}
}
pub fn eprint_diagnostic(diag: &lsp::Diagnostic, program: &str) {
if let Some(sev) = diag.severity {
if sev == lsp::DiagnosticSeverity::HINT {
return;
}
}
let mut lines = program.lines();
let mut maybe_line = None;
for _i in 0..diag.range.start.line+1 {
maybe_line = lines.next();
}
let [announcement,squiggle] = match diag.severity {
Some(lsp::DiagnosticSeverity::ERROR) => ["Error".red(),"^".red()],
Some(lsp::DiagnosticSeverity::WARNING) => ["Warning".bright_yellow(),"^".bright_yellow()],
Some(lsp::DiagnosticSeverity::INFORMATION) => ["Information".bright_blue(),"^".bright_blue()],
_ => ["Unexpected Notice".red(),"^".red()]
};
eprintln!("{} on line {}: {}",announcement,diag.range.start.line,diag.message);
if let Some(line) = maybe_line {
eprintln!(" {}",line);
for _i in 0..diag.range.start.character+2 {
eprint!(" ");
}
for _i in diag.range.start.character..diag.range.end.character {
eprint!("{}",squiggle);
}
eprintln!();
}
}
fn replace_range(doc: &mut String, rng: lsp::Range, raw_new: &str) -> STDRESULT {
let new = raw_new.replace("\r\n","\n");
let mut start_char = 0;
let mut end_char = 0;
let mut curr_line = 0;
let mut found_start = false;
let mut found_end = false;
for line in doc.lines() {
if rng.start.line == curr_line {
start_char += rng.start.character;
found_start = true;
}
if !found_start {
start_char += line.chars().count() as u32 + 1;
}
if rng.end.line == curr_line {
end_char += rng.end.character;
found_end = true;
break;
}
if !found_end {
end_char += line.chars().count() as u32 + 1;
}
curr_line += 1;
}
if found_start && found_end {
doc.replace_range(start_char as usize..end_char as usize,&new);
return Ok(());
}
let line_count = doc.lines().count() as u32;
if rng.start.line==line_count && rng.start.character==0 && rng.end.line==line_count && rng.end.character==0 {
doc.push_str(&new);
return Ok(());
}
Err(Box::new(Error::LineNumber))
}
pub fn apply_edits(doc: &str, edits: &Vec<lsp::TextEdit>, row: u32) -> Result<String,DYNERR> {
let line_sep = match doc.split("\r\n").count() == doc.split("\n").count() {
true => "\r\n",
false => "\n"
};
let mut ans = String::from(doc);
ans = ans.replace("\r\n","\n");
let mut sorted = BTreeMap::new();
let mut idx: u32 = 0; for edit in edits {
let key = (edit.range.start.line,edit.range.start.character,idx);
sorted.insert(key,edit.clone());
idx += 1;
}
for edit in sorted.values().rev() {
let offset_rng = lsp::Range::new(
lsp::Position::new(edit.range.start.line - row,edit.range.start.character),
lsp::Position::new(edit.range.end.line - row,edit.range.end.character)
);
log::trace!("replace {:?}",offset_rng);
replace_range(&mut ans,offset_rng,&edit.new_text)?;
}
if line_sep == "\r\n" {
ans = ans.replace("\n","\r\n");
}
Ok(ans)
}