From 4b12eab927c5b6cb95d5055a55109ad382a3d252 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Wed, 13 Mar 2024 13:02:32 +0200 Subject: [PATCH] feat(lib): implement Display for Node --- cli/src/test.rs | 138 ++++------------------------------------ lib/binding_rust/lib.rs | 123 +++++++++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 128 deletions(-) diff --git a/cli/src/test.rs b/cli/src/test.rs index 74d8af48..56617699 100644 --- a/cli/src/test.rs +++ b/cli/src/test.rs @@ -8,12 +8,11 @@ use regex::bytes::{Regex as ByteRegex, RegexBuilder as ByteRegexBuilder}; use regex::Regex; use std::collections::BTreeMap; use std::ffi::OsStr; -use std::fmt::Write as FmtWrite; use std::fs; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::str; -use tree_sitter::{Language, LogType, Parser, Query}; +use tree_sitter::{format_sexp, Language, LogType, Parser, Query}; use walkdir::WalkDir; lazy_static! { @@ -163,8 +162,8 @@ pub fn run_tests_at_path(parser: &mut Parser, opts: &mut TestOptions) -> Result< print_diff_key(); for (i, (name, actual, expected)) in failures.iter().enumerate() { println!("\n {}. {name}:", i + 1); - let actual = format_sexp_indented(actual, 2); - let expected = format_sexp_indented(expected, 2); + let actual = format_sexp(actual, 2); + let expected = format_sexp(expected, 2); print_diff(&actual, &expected); } @@ -287,7 +286,7 @@ fn run_tests( println!("✓ {}", Colour::Green.paint(&name)); if opts.update { let input = String::from_utf8(input.clone()).unwrap(); - let output = format_sexp(&output); + let output = format_sexp(&output, 0); corrected_entries.push(( name.clone(), input, @@ -299,8 +298,8 @@ fn run_tests( } else { if opts.update { let input = String::from_utf8(input.clone()).unwrap(); - let expected_output = format_sexp(&output); - let actual_output = format_sexp(&actual); + let expected_output = format_sexp(&output, 0); + let actual_output = format_sexp(&actual, 0); // Only bail early before updating if the actual is not the output, sometimes // users want to test cases that are intended to have errors, hence why this @@ -408,115 +407,6 @@ fn run_tests( Ok(true) } -fn format_sexp(sexp: &str) -> String { - format_sexp_indented(sexp, 0) -} - -fn format_sexp_indented(sexp: &str, initial_indent_level: u32) -> String { - let mut formatted = String::new(); - - if sexp.is_empty() { - return formatted; - } - - let mut indent_level = initial_indent_level; - let mut has_field = false; - - let mut c_iter = sexp.chars().peekable(); - let mut s = String::with_capacity(sexp.len()); - let mut quote = '\0'; - let mut saw_paren = false; - let mut did_last = false; - - let mut fetch_next_str = |next: &mut String| { - next.clear(); - while let Some(c) = c_iter.next() { - if c == '\'' || c == '"' { - quote = c; - } else if c == ' ' || (c == ')' && quote != '\0') { - if let Some(next_c) = c_iter.peek() { - if *next_c == quote { - next.push(c); - next.push(*next_c); - c_iter.next(); - quote = '\0'; - continue; - } - } - break; - } - if c == ')' { - saw_paren = true; - break; - } - next.push(c); - } - - // at the end - if c_iter.peek().is_none() && next.is_empty() { - if saw_paren { - // but did we see a ) before ending? - saw_paren = false; - return Some(()); - } - if !did_last { - // but did we account for the end empty string as if we're splitting? - did_last = true; - return Some(()); - } - return None; - } - Some(()) - }; - - while fetch_next_str(&mut s).is_some() { - if s.is_empty() && indent_level > 0 { - // ")" - indent_level -= 1; - write!(formatted, ")").unwrap(); - } else if s.starts_with('(') { - if has_field { - has_field = false; - } else { - if indent_level > 0 { - writeln!(formatted).unwrap(); - for _ in 0..indent_level { - write!(formatted, " ").unwrap(); - } - } - indent_level += 1; - } - - // "(node_name" - write!(formatted, "{s}").unwrap(); - - // "(MISSING node_name" or "(UNEXPECTED 'x'" - if s.starts_with("(MISSING") || s.starts_with("(UNEXPECTED") { - fetch_next_str(&mut s).unwrap(); - if s.is_empty() { - while indent_level > 0 { - indent_level -= 1; - write!(formatted, ")").unwrap(); - } - } else { - write!(formatted, " {s}").unwrap(); - } - } - } else if s.ends_with(':') { - // "field:" - writeln!(formatted).unwrap(); - for _ in 0..indent_level { - write!(formatted, " ").unwrap(); - } - write!(formatted, "{s} ").unwrap(); - has_field = true; - indent_level += 1; - } - } - - formatted -} - fn write_tests( file_path: &Path, corrected_entries: &[(String, String, String, usize, usize)], @@ -878,9 +768,9 @@ abc #[test] fn test_format_sexp() { - assert_eq!(format_sexp(""), ""); + assert_eq!(format_sexp("", 0), ""); assert_eq!( - format_sexp("(a b: (c) (d) e: (f (g (h (MISSING i)))))"), + format_sexp("(a b: (c) (d) e: (f (g (h (MISSING i)))))", 0), r" (a b: (c) @@ -892,11 +782,8 @@ abc " .trim() ); - assert_eq!(format_sexp("()"), "()"); - assert_eq!(format_sexp("(A (M (B)))"), "(A\n (M\n (B)))"); - assert_eq!(format_sexp("(A (U (B)))"), "(A\n (U\n (B)))"); assert_eq!( - format_sexp("(program (ERROR (UNEXPECTED ' ')) (identifier))"), + format_sexp("(program (ERROR (UNEXPECTED ' ')) (identifier))", 0), r" (program (ERROR @@ -906,7 +793,7 @@ abc .trim() ); assert_eq!( - format_sexp(r#"(source_file (MISSING ")"))"#), + format_sexp(r#"(source_file (MISSING ")"))"#, 0), r#" (source_file (MISSING ")")) @@ -914,7 +801,10 @@ abc .trim() ); assert_eq!( - format_sexp(r"(source_file (ERROR (UNEXPECTED 'f') (UNEXPECTED '+')))"), + format_sexp( + r"(source_file (ERROR (UNEXPECTED 'f') (UNEXPECTED '+')))", + 0 + ), r#" (source_file (ERROR diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 3cc2cc01..c3ee61ae 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -11,7 +11,8 @@ use std::os::windows::io::AsRawHandle; use std::{ char, error, ffi::CStr, - fmt, hash, iter, + fmt::{self, Write}, + hash, iter, marker::PhantomData, mem::MaybeUninit, num::NonZeroU16, @@ -953,7 +954,7 @@ impl Tree { } impl fmt::Debug for Tree { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{{Tree {:?}}}", self.root_node()) } } @@ -1462,7 +1463,7 @@ impl hash::Hash for Node<'_> { } impl fmt::Debug for Node<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{{Node {} {} - {}}}", @@ -1473,6 +1474,19 @@ impl fmt::Debug for Node<'_> { } } +impl fmt::Display for Node<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let sexp = self.to_sexp(); + if sexp.is_empty() { + write!(f, "") + } else if !f.alternate() { + write!(f, "{}", sexp) + } else { + write!(f, "{}", format_sexp(&sexp, f.width().unwrap_or(0))) + } + } +} + impl<'cursor> TreeCursor<'cursor> { /// Get the tree cursor's current [`Node`]. #[doc(alias = "ts_tree_cursor_current_node")] @@ -2713,7 +2727,7 @@ impl Point { } impl fmt::Display for Point { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.row, self.column) } } @@ -2871,6 +2885,107 @@ impl fmt::Display for QueryError { } } +#[doc(hidden)] +pub fn format_sexp(sexp: &str, initial_indent_level: usize) -> String { + let mut indent_level = initial_indent_level; + let mut formatted = String::new(); + let mut has_field = false; + + let mut c_iter = sexp.chars().peekable(); + let mut s = String::with_capacity(sexp.len()); + let mut quote = '\0'; + let mut saw_paren = false; + let mut did_last = false; + + let mut fetch_next_str = |next: &mut String| { + next.clear(); + while let Some(c) = c_iter.next() { + if c == '\'' || c == '"' { + quote = c; + } else if c == ' ' || (c == ')' && quote != '\0') { + if let Some(next_c) = c_iter.peek() { + if *next_c == quote { + next.push(c); + next.push(*next_c); + c_iter.next(); + quote = '\0'; + continue; + } + } + break; + } + if c == ')' { + saw_paren = true; + break; + } + next.push(c); + } + + // at the end + if c_iter.peek().is_none() && next.is_empty() { + if saw_paren { + // but did we see a ) before ending? + saw_paren = false; + return Some(()); + } + if !did_last { + // but did we account for the end empty string as if we're splitting? + did_last = true; + return Some(()); + } + return None; + } + Some(()) + }; + + while fetch_next_str(&mut s).is_some() { + if s.is_empty() && indent_level > 0 { + // ")" + indent_level -= 1; + write!(formatted, ")").unwrap(); + } else if s.starts_with('(') { + if has_field { + has_field = false; + } else { + if indent_level > 0 { + writeln!(formatted).unwrap(); + for _ in 0..indent_level { + write!(formatted, " ").unwrap(); + } + } + indent_level += 1; + } + + // "(node_name" + write!(formatted, "{s}").unwrap(); + + // "(MISSING node_name" or "(UNEXPECTED 'x'" + if s.starts_with("(MISSING") || s.starts_with("(UNEXPECTED") { + fetch_next_str(&mut s).unwrap(); + if s.is_empty() { + while indent_level > 0 { + indent_level -= 1; + write!(formatted, ")").unwrap(); + } + } else { + write!(formatted, " {s}").unwrap(); + } + } + } else if s.ends_with(':') { + // "field:" + writeln!(formatted).unwrap(); + for _ in 0..indent_level { + write!(formatted, " ").unwrap(); + } + write!(formatted, "{s} ").unwrap(); + has_field = true; + indent_level += 1; + } + } + + formatted +} + pub fn wasm_stdlib_symbols() -> impl Iterator { const WASM_STDLIB_SYMBOLS: &str = include_str!(concat!(env!("OUT_DIR"), "/stdlib-symbols.txt"));