1pub mod applesoft;
16pub mod integer;
17pub mod merlin;
18mod linenum;
19pub mod server;
20pub mod disk_server;
21
22use tree_sitter;
23use lsp_types as lsp;
24use colored::*;
25use thiserror::Error;
26use std::{collections::HashSet, io};
27use std::io::Write;
28use num_traits::Num;
29use std::str::FromStr;
30use std::collections::BTreeMap;
31use atty;
32
33use crate::{STDRESULT,DYNERR};
34const RCH: &str = "unreachable was reached";
35
36pub enum Navigation {
37 GotoSelf,
38 GotoChild,
39 GotoSibling,
40 GotoParentSibling,
41 Descend,
42 Exit,
43 Abort
44}
45
46#[derive(Error,Debug)]
47pub enum Error {
48 #[error("Syntax error")]
49 Syntax,
50 #[error("Invalid Line Number")]
51 LineNumber,
52 #[error("Tokenization error")]
53 Tokenization,
54 #[error("Detokenization error")]
55 Detokenization,
56 #[error("Parsing error")]
57 ParsingError,
58 #[error("Path not found")]
59 PathNotFound,
60 #[error("Out of range")]
61 OutOfRange,
62 #[error("Could not parse URL")]
63 BadUrl
64}
65
66pub fn normalize_client_uri(uri: lsp::Uri) -> lsp::Uri {
70 match pathbuf_from_uri(&uri) {
71 Ok(path) => match uri_from_path(&path) {
72 Ok(ans) => return ans,
73 Err(_) => {}
74 },
75 Err(_) => {}
76 }
77 uri
78}
79
80pub fn normalize_client_uri_str(uri: &str) -> Result<lsp::Uri,DYNERR> {
82 Ok(normalize_client_uri(lsp::Uri::from_str(uri)?))
83}
84
85pub fn uri_from_path(path: &std::path::Path) -> Result<lsp::Uri,DYNERR> {
86 let url = match url::Url::from_file_path(path) {
87 Ok(ans) => ans,
88 Err(()) => return Err(Box::new(Error::PathNotFound))
89 };
90 let uri = fluent_uri::Uri::from_str(url.as_str())?.normalize();
91 Ok(lsp::Uri::from_str(uri.as_str())?)
92}
93
94pub fn uri_from_path_str(path_str: &str) -> Result<lsp::Uri,DYNERR> {
96 uri_from_path(&std::path::PathBuf::from_str(path_str)?)
97}
98
99pub fn pathbuf_from_uri(uri: &lsp::Uri) -> Result<std::path::PathBuf,DYNERR> {
100 let url_crate_uri = match url::Url::from_str(uri.as_str()) {
101 Ok(ans) => ans,
102 Err(e) => return Err(Box::new(e))
103 };
104 match url_crate_uri.to_file_path() {
105 Ok(ans) => Ok(ans),
106 Err(_) => Err(Box::new(Error::BadUrl))
107 }
108}
109
110fn match_emulation_path(emu_path: &str, sep: &str, doc: &Document) -> usize {
114 let mut quality = 0;
115 let Some(scheme) = doc.uri.scheme() else { return quality; };
116 if scheme.as_str() != "file" { return quality; }
117 let Ok(doc_path) = crate::lang::pathbuf_from_uri(&doc.uri) else {
118 log::trace!("error while parsing {}",doc.uri.as_str());
119 return quality;
120 };
121 let mut doc_segs = doc_path.iter().rev();
122 let emu_segs = emu_path.split(sep).map(|x| x.to_string()).collect::<Vec<String>>();
123 for emu_seg in emu_segs.iter().rev() {
124 if let Some(doc_seg) = doc_segs.next() {
125 if let Some(s) = doc_seg.to_str() {
126 if s.to_lowercase() == emu_seg.to_lowercase() {
127 quality += 1;
128 } else {
129 break;
130 }
131 }
132 }
133 }
134 quality
135}
136
137fn get_emulation_match(docs: &Vec<Document>, emu_path: &str, sep: &str) -> Vec<lsp::Uri> {
142 let mut set = HashSet::new();
143 let mut best_quality = 0;
144 let mut shortest = usize::MAX; log::debug!("search for file `{}`",emu_path);
146 for doc in docs {
147 let quality = match_emulation_path(&emu_path, sep, &doc);
148 let l = doc.uri.as_str().len();
149 log::trace!("match {} to {} Q={}",emu_path,doc.uri.as_str(),quality);
150 if quality > best_quality {
151 set = HashSet::new();
152 set.insert(doc.uri.clone());
153 best_quality = quality;
154 shortest = l;
155 } else if quality > 0 && quality == best_quality {
156 if l < shortest {
157 set = HashSet::new();
158 set.insert(doc.uri.clone());
159 shortest = l;
160 } else if l == shortest {
161 set.insert(doc.uri.clone());
162 }
163 }
164 }
165 log::debug!("found {} URI candidates",set.len());
166 let mut ans = Vec::new();
167 for uri in &set {
168 log::trace!(" {}",uri.as_str());
169 ans.push(uri.clone())
170 }
171 ans
172 }
174
175#[derive(Clone)]
180pub struct Document {
181 pub uri: lsp::Uri,
182 pub version: Option<i32>,
183 pub text: String
184}
185
186impl Document {
187 pub fn new(uri: lsp::Uri,text: String) -> Self {
188 Self {
189 uri,
190 version: None,
191 text
192 }
193 }
194 pub fn from_string(text: String, id: u64) -> Self {
195 Self {
196 uri: lsp::Uri::from_str(&format!("string:{}",id)).expect(RCH),
197 version: None,
198 text
199 }
200 }
201 pub fn from_macro(text: String, label: String) -> Self {
202 Self {
203 uri: lsp::Uri::from_str(&format!("macro:{}",label)).expect(RCH),
204 version: None,
205 text
206 }
207 }
208 pub fn from_file_path(path: &std::path::Path) -> Result<Self,DYNERR> {
209 let by = std::fs::read(path)?;
210 Ok(Self {
211 uri: uri_from_path(path)?,
212 version: None,
213 text: String::from_utf8(by)?
214 })
215 }
216}
217
218pub fn range_contains_pos(rng: &lsp::Range, pos: &lsp::Position) -> bool
219{
220 if pos.line < rng.start.line || pos.line > rng.end.line {
221 return false;
222 }
223 if pos.line == rng.start.line && pos.character < rng.start.character {
224 return false;
225 }
226 if pos.line == rng.end.line && pos.character > rng.end.character {
227 return false;
228 }
229 return true;
230}
231
232pub fn range_contains_range(outer: &lsp::Range, inner: &lsp::Range) -> bool
233{
234 if inner.start.line < outer.start.line || inner.end.line > outer.end.line {
235 return false;
236 }
237 if inner.start.line == outer.start.line && inner.start.character < outer.start.character {
238 return false;
239 }
240 if inner.end.line == outer.end.line && inner.end.character > outer.end.character {
241 return false;
242 }
243 return true;
244}
245
246pub fn translate_pos(pos: &lsp::Position, dl: isize, dc: isize) -> lsp::Position {
247 let mut ans = lsp::Position::new(0,0);
248 ans.line = match pos.line as isize + dl < 0 {
249 true => 0,
250 false => (pos.line as isize + dl) as u32
251 };
252 ans.character = match pos.character as isize + dc < 0 {
253 true => 0,
254 false => (pos.character as isize + dc) as u32
255 };
256 ans
257}
258
259pub fn range_union(r1: &lsp::Range,r2: &lsp::Range) -> lsp::Range {
260 lsp::Range::new(
261 lsp::Position::new(
262 match r1.start.line < r2.start.line { true => r1.start.line, false => r2.start.line },
263 match r1.start.line < r2.start.line || r1.start.line == r2.start.line && r1.start.character < r2.start.character {
264 true => r1.start.character,
265 false => r2.start.character
266 }
267 ),
268 lsp::Position::new(
269 match r2.end.line > r1.end.line { true => r2.end.line, false => r1.end.line },
270 match r2.end.line > r1.end.line || r2.end.line == r1.end.line && r2.end.character > r1.end.character {
271 true => r2.end.character,
272 false => r1.end.character
273 }
274 )
275 )
276}
277
278pub fn lsp_range(rng: tree_sitter::Range,row: isize,col: isize) -> lsp::Range {
282 lsp::Range {
283 start: lsp::Position { line: (row + rng.start_point.row as isize) as u32, character: (col + rng.start_point.column as isize) as u32 },
284 end: lsp::Position { line: (row + rng.end_point.row as isize) as u32, character: (col + rng.end_point.column as isize) as u32}
285 }
286}
287
288pub fn node_text(node: &tree_sitter::Node,source: &str) -> String {
290 if let Ok(ans) = node.utf8_text(source.as_bytes()) {
291 return ans.to_string();
292 }
293 return "".to_string();
294}
295
296pub fn node_integer<T: FromStr>(node: &tree_sitter::Node,source: &str) -> Option<T> {
300 let txt = node_text(&node,source).replace(" ","");
301 match txt.parse::<T>() {
302 Ok(num) => Some(num),
303 Err(_) => None
304 }
305}
306
307pub fn node_radix<T: Num>(node: &tree_sitter::Node, source: &str, hex: &str, bin: &str) -> Option<T> {
310 if let Ok(s) = node.utf8_text(source.as_bytes()) {
311 let mut trimmed = s.to_string().replace(" ","").replace("_","");
312 if s.starts_with("_") && (hex=="_" || bin=="_") {
313 trimmed = ["_",&trimmed].concat();
314 }
315 if trimmed.starts_with(hex) {
316 match T::from_str_radix(&trimmed[1..],16) {
317 Ok(ans) => Some(ans),
318 Err(_) => None
319 }
320 } else if trimmed.starts_with(bin) {
321 match T::from_str_radix(&trimmed[1..],2) {
322 Ok(ans) => Some(ans),
323 Err(_) => None
324 }
325 } else {
326 match T::from_str_radix(&trimmed,10) {
327 Ok(ans) => Some(ans),
328 Err(_) => None
329 }
330 }
331 } else {
332 None
333 }
334}
335
336pub fn named_sibling(node: tree_sitter::Node,skip: usize) -> Option<tree_sitter::Node> {
337
338 let mut maybe = Some(node);
339 for _i in 0..skip {
340 maybe = maybe.unwrap().next_named_sibling();
341 if maybe.is_none() {
342 return None;
343 }
344 }
345 maybe
346}
347
348pub fn extended_range(node: &tree_sitter::Node,end_col: usize) -> tree_sitter::Range {
352 let mut ans = node.range();
353 if ans.start_point.column == ans.end_point.column {
354 if ans.start_point.column > 0 {
355 ans.start_point.column -= 1;
356 }
357 if ans.end_point.column + 1 < end_col {
358 ans.end_point.column += 1;
359 }
360 }
361 ans
362}
363
364pub fn update_json_bool(maybe_obj: &serde_json::Value, key: &str, curr: &mut bool) {
367 if let Some(outer) = maybe_obj.as_object() {
368 if let Some(x) = outer.get(key) {
369 match x.as_bool() { Some(x) => *curr = x, _ => {} };
370 }
371 }
372}
373
374pub fn update_json_i64(maybe_obj: &serde_json::Value, key: &str, curr: &mut i64) {
377 if let Some(outer) = maybe_obj.as_object() {
378 if let Some(x) = outer.get(key) {
379 match x.as_i64() { Some(x) => *curr = x, _ => {} };
380 }
381 }
382}
383
384pub fn update_json_f64(maybe_obj: &serde_json::Value, key: &str, curr: &mut f64) {
387 if let Some(outer) = maybe_obj.as_object() {
388 if let Some(x) = outer.get(key) {
389 match x.as_f64() { Some(x) => *curr = x, _ => {} };
390 }
391 }
392}
393
394pub fn update_json_string_opt(maybe_obj: &serde_json::Value, key: &str, curr: &mut Option<String>) {
397 if let Some(outer) = maybe_obj.as_object() {
398 if let Some(x) = outer.get(key) {
399 match x.as_str() { Some(x) => *curr = Some(x.to_string()), _ => {} };
400 }
401 }
402}
403
404pub fn update_json_string(maybe_obj: &serde_json::Value, key: &str, curr: &mut String) {
407 if let Some(outer) = maybe_obj.as_object() {
408 if let Some(x) = outer.get(key) {
409 match x.as_str() { Some(x) => *curr = x.to_string(), _ => {} };
410 }
411 }
412}
413
414pub fn update_json_severity(maybe_obj: &serde_json::Value, key: &str, curr: &mut Option<lsp::DiagnosticSeverity>) {
417 if let Some(outer) = maybe_obj.as_object() {
418 if let Some(x) = outer.get(key) {
419 match x.as_str() {
420 Some("ignore") => *curr = None,
421 Some("hint") => *curr = Some(lsp::DiagnosticSeverity::HINT),
422 Some("info") => *curr = Some(lsp::DiagnosticSeverity::INFORMATION),
423 Some("warn") => *curr = Some(lsp::DiagnosticSeverity::WARNING),
424 Some("error") => *curr = Some(lsp::DiagnosticSeverity::ERROR),
425 _ => {}
426 }
427 }
428 }
429}
430
431pub fn update_json_vec(maybe_obj: &serde_json::Value, key: &str, curr: &mut Vec<i64>) {
434 if let Some(outer) = maybe_obj.as_object() {
435 if let Some(x) = outer.get(key) {
436 let mut ans: Vec<i64> = Vec::new();
437 if let Some(a) = x.as_array() {
438 for v in a {
439 match v.as_i64() {
440 Some(i) => ans.push(i),
441 None => return
442 }
443 }
444 *curr = ans;
445 }
446 }
447 }
448}
449
450pub fn update_json_vec_str(maybe_obj: &serde_json::Value, key: &str, curr: &mut Vec<String>) {
453 if let Some(outer) = maybe_obj.as_object() {
454 if let Some(x) = outer.get(key) {
455 let mut ans: Vec<String> = Vec::new();
456 if let Some(a) = x.as_array() {
457 for v in a {
458 match v.as_str() {
459 Some(s) => ans.push(s.to_owned()),
460 None => return
461 }
462 }
463 *curr = ans;
464 }
465 }
466 }
467}
468
469pub trait Navigate {
471 fn visit(&mut self,curs: &tree_sitter::TreeCursor) -> Result<Navigation,DYNERR>;
472 fn descend(&mut self,_curs: &tree_sitter::TreeCursor) -> Result<Navigation,DYNERR> {
473 Ok(Navigation::GotoSibling)
474 }
475 fn walk(&mut self,tree: &tree_sitter::Tree) -> Result<(),DYNERR>
476 {
477 let mut curs = tree.walk();
478 let mut choice = Navigation::GotoSelf;
479 while ! matches!(choice,Navigation::Exit | Navigation::Abort)
480 {
481 if matches!(choice,Navigation::GotoSelf) {
482 choice = self.visit(&curs)?;
483 } else if matches!(choice,Navigation::Descend) {
484 choice = self.descend(&curs)?;
485 } else if matches!(choice,Navigation::GotoChild) && curs.goto_first_child() {
486 choice = self.visit(&curs)?;
487 } else if matches!(choice,Navigation::GotoParentSibling) && curs.goto_parent() && curs.goto_next_sibling() {
488 choice = self.visit(&curs)?;
489 } else if matches!(choice,Navigation::GotoSibling) && curs.goto_next_sibling() {
490 choice = self.visit(&curs)?;
491 } else if curs.goto_next_sibling() {
492 choice = self.visit(&curs)?;
493 } else if curs.goto_parent() {
494 choice = Navigation::GotoSibling;
495 } else {
496 choice = Navigation::Exit;
497 }
498 }
499 Ok(())
500 }
501}
502
503
504pub fn is_lang(lang: tree_sitter::Language,code: &str) -> bool {
509 let mut parser = tree_sitter::Parser::new();
510 parser.set_language(&lang).expect("language not found");
511 let mut iter = code.lines();
512 let mut line_count = 0;
513 let mut good_lines = 0;
514 let mut great_lines = 0;
515 while let Some(line) = iter.next()
516 {
517 line_count += 1;
518 if let Some(tree) = parser.parse(String::from(line) + "\n",None) {
519 let curs = tree.walk();
520 if !curs.node().has_error() {
521 good_lines += 1;
522 match lang.name() {
523 Some("merlin6502") => {
524 match curs.node().child(0) {
525 Some(child) => match child.kind() {
526 "operation" => great_lines += 1,
527 "pseudo_operation" => great_lines += 1,
528 _ => {}
529 },
530 None => {}
531 }
532 },
533 _ => great_lines += 1
534 }
535 }
536 }
537 }
538 if line_count == 0 {
539 log::warn!("encountered empty file");
540 return false;
541 }
542 if (great_lines==0 || good_lines != line_count) && good_lines * 3 > line_count {
543 log::warn!("{} {} parsed as {} but merit test failed",good_lines,match good_lines==1 { true => "line", false => "lines" },lang.name().unwrap_or("unknown"));
544 }
545 good_lines == line_count && great_lines > 0
546}
547
548pub fn verify_str(lang: tree_sitter::Language,code: &str) -> STDRESULT {
552 let mut parser = tree_sitter::Parser::new();
553 parser.set_language(&lang)?;
554 let mut iter = code.lines();
555 let mut row = 0;
556 while let Some(line) = iter.next()
557 {
558 match parser.parse(String::from(line) + "\n",None) {
559 Some(tree) => {
560 let curs = tree.walk();
561 if curs.node().has_error() {
562 log::error!("syntax error in row {}, use `verify` for more details",row);
563 return Err(Box::new(Error::Syntax));
564 }
565 },
566 None => {
567 log::error!("unable to parse row {}",row);
568 return Err(Box::new(Error::Syntax));
569 }
570 }
571 row += 1;
572 }
573 Ok(())
574}
575
576pub fn eprint_lines_sexpr(lang: tree_sitter::Language, program: &str, unwraps: usize) {
577 let mut parser = tree_sitter::Parser::new();
578 parser.set_language(&lang).expect("Error loading grammar");
579 let mut iter = program.lines();
580 eprintln!();
581 while let Some(line) = iter.next()
582 {
583 if let Some(tree) = parser.parse(String::from(line) + "\n",None) {
584 let mut curs = tree.walk();
585 for _i in 0..unwraps {
586 curs.goto_first_child();
587 }
588 eprintln!("{}",line.to_string());
589 eprintln!("{}",curs.node().to_sexp());
590 }
591 }
592}
593
594pub fn line_entry(lang: tree_sitter::Language,prompt: &str) -> String
596{
597 let mut parser = tree_sitter::Parser::new();
598 parser.set_language(&lang).expect("Error loading grammar");
599 let mut code = String::new();
600 if atty::is(atty::Stream::Stdin) {
601 eprintln!("Line entry interface.");
602 eprintln!("This is a blind accumulation of lines.");
603 eprintln!("Verify occurs when entry is terminated.");
604 eprintln!("Accumulated lines can be piped.");
605 eprintln!("`bye` terminates.");
606 loop {
607 eprint!("{} ",prompt);
608 let mut line = String::new();
609 io::stderr().flush().expect("could not flush stderr");
610 io::stdin().read_line(&mut line).expect("could not read stdin");
611 if line=="bye\n" || line=="bye\r\n" {
612 break;
613 }
614 code += &line;
615 }
616 return code;
617 } else {
618 panic!("line_entry was called with piped input");
619 }
620}
621
622pub fn eprint_diagnostic(diag: &lsp::Diagnostic, program: &str) {
623 if let Some(sev) = diag.severity {
625 if sev == lsp::DiagnosticSeverity::HINT {
626 return;
629 }
630 }
631 let mut lines = program.lines();
632 let mut maybe_line = None;
633 for _i in 0..diag.range.start.line+1 {
634 maybe_line = lines.next();
635 }
636 let [announcement,squiggle] = match diag.severity {
637 Some(lsp::DiagnosticSeverity::ERROR) => ["Error".red(),"^".red()],
638 Some(lsp::DiagnosticSeverity::WARNING) => ["Warning".bright_yellow(),"^".bright_yellow()],
639 Some(lsp::DiagnosticSeverity::INFORMATION) => ["Information".bright_blue(),"^".bright_blue()],
640 _ => ["Unexpected Notice".red(),"^".red()]
641 };
642 eprintln!("{} on line {}: {}",announcement,diag.range.start.line,diag.message);
643 if let Some(line) = maybe_line {
644 eprintln!(" {}",line);
645 for _i in 0..diag.range.start.character+2 {
646 eprint!(" ");
647 }
648 for _i in diag.range.start.character..diag.range.end.character {
649 eprint!("{}",squiggle);
650 }
651 eprintln!();
652 }
653}
654
655fn replace_range(doc: &mut String, rng: lsp::Range, raw_new: &str) -> STDRESULT {
658 let new = raw_new.replace("\r\n","\n");
659 let mut start_char = 0;
660 let mut end_char = 0;
661 let mut curr_line = 0;
662 let mut found_start = false;
663 let mut found_end = false;
664 for line in doc.lines() {
665 if rng.start.line == curr_line {
666 start_char += rng.start.character;
667 found_start = true;
668 }
669 if !found_start {
670 start_char += line.chars().count() as u32 + 1;
671 }
672 if rng.end.line == curr_line {
673 end_char += rng.end.character;
674 found_end = true;
675 break;
676 }
677 if !found_end {
678 end_char += line.chars().count() as u32 + 1;
679 }
680 curr_line += 1;
681 }
682 if found_start && found_end {
683 doc.replace_range(start_char as usize..end_char as usize,&new);
684 return Ok(());
685 }
686 let line_count = doc.lines().count() as u32;
688 if rng.start.line==line_count && rng.start.character==0 && rng.end.line==line_count && rng.end.character==0 {
689 doc.push_str(&new);
690 return Ok(());
691 }
692 Err(Box::new(Error::LineNumber))
693}
694
695pub fn apply_edits(doc: &str, edits: &Vec<lsp::TextEdit>, row: u32) -> Result<String,DYNERR> {
700 let line_sep = match doc.split("\r\n").count() == doc.split("\n").count() {
702 true => "\r\n",
703 false => "\n"
704 };
705 let mut ans = String::from(doc);
706 ans = ans.replace("\r\n","\n");
707 let mut sorted = BTreeMap::new();
708 let mut idx: u32 = 0; for edit in edits {
710 let key = (edit.range.start.line,edit.range.start.character,idx);
711 sorted.insert(key,edit.clone());
712 idx += 1;
713 }
714 for edit in sorted.values().rev() {
715 let offset_rng = lsp::Range::new(
716 lsp::Position::new(edit.range.start.line - row,edit.range.start.character),
717 lsp::Position::new(edit.range.end.line - row,edit.range.end.character)
718 );
719 log::trace!("replace {:?}",offset_rng);
720 replace_range(&mut ans,offset_rng,&edit.new_text)?;
721 }
722 if line_sep == "\r\n" {
723 ans = ans.replace("\n","\r\n");
724 }
725 Ok(ans)
726}