diff --git a/cli/src/loader.rs b/cli/src/loader.rs index 70056404..6dd4e4db 100644 --- a/cli/src/loader.rs +++ b/cli/src/loader.rs @@ -56,12 +56,8 @@ impl Loader { let entry = entry?; if let Some(parser_dir_name) = entry.file_name().to_str() { if parser_dir_name.starts_with("tree-sitter-") { - if self - .find_language_at_path(&parser_container_dir.join(parser_dir_name)) - .is_err() - { - eprintln!("Error loading {}", parser_dir_name); - } + self.find_language_at_path(&parser_container_dir.join(parser_dir_name)) + .ok(); } } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 1860ecc2..aaf45cb1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -37,7 +37,12 @@ fn main() { fn run() -> error::Result<()> { let matches = App::new("tree-sitter") - .version(concat!(env!("CARGO_PKG_VERSION"), " (", env!("BUILD_SHA"), ")")) + .version(concat!( + env!("CARGO_PKG_VERSION"), + " (", + env!("BUILD_SHA"), + ")" + )) .setting(AppSettings::SubcommandRequiredElseHelp) .author("Max Brunsfeld ") .about("Generates and tests parsers") @@ -57,9 +62,16 @@ fn run() -> error::Result<()> { .subcommand( SubCommand::with_name("parse") .about("Parse a file") - .arg(Arg::with_name("path").index(1).required(true)) + .arg( + Arg::with_name("path") + .index(1) + .multiple(true) + .required(true), + ) .arg(Arg::with_name("debug").long("debug").short("d")) - .arg(Arg::with_name("debug-graph").long("debug-graph").short("D")), + .arg(Arg::with_name("debug-graph").long("debug-graph").short("D")) + .arg(Arg::with_name("quiet").long("quiet").short("q")) + .arg(Arg::with_name("time").long("time").short("t")), ) .subcommand( SubCommand::with_name("test") @@ -116,12 +128,35 @@ fn run() -> error::Result<()> { } else if let Some(matches) = matches.subcommand_matches("parse") { let debug = matches.is_present("debug"); let debug_graph = matches.is_present("debug-graph"); + let quiet = matches.is_present("quiet"); + let time = matches.is_present("time"); loader.find_all_languages(&vec![home_dir.join("github")])?; - let source_path = Path::new(matches.value_of("path").unwrap()); - if let Some((language, _)) = loader.language_configuration_for_file_name(source_path)? { - parse::parse_file_at_path(language, source_path, debug, debug_graph)?; - } else { - eprintln!("No language found"); + let paths = matches + .values_of("path") + .unwrap() + .into_iter() + .collect::>(); + let max_path_length = paths.iter().map(|p| p.chars().count()).max().unwrap(); + for path in paths { + let path = Path::new(path); + let language = + if let Some((l, _)) = loader.language_configuration_for_file_name(path)? { + l + } else if let Some(l) = loader.language_at_path(¤t_dir)? { + l + } else { + eprintln!("No language found"); + return Ok(()); + }; + parse::parse_file_at_path( + language, + path, + max_path_length, + quiet, + time, + debug, + debug_graph, + )?; } } diff --git a/cli/src/parse.rs b/cli/src/parse.rs index 38b6a61c..54c02ad2 100644 --- a/cli/src/parse.rs +++ b/cli/src/parse.rs @@ -3,21 +3,25 @@ use super::util; use std::fs; use std::io::{self, Write}; use std::path::Path; +use std::time::Instant; use tree_sitter::{Language, LogType, Parser}; pub fn parse_file_at_path( language: Language, path: &Path, + max_path_length: usize, + quiet: bool, + print_time: bool, debug: bool, debug_graph: bool, ) -> Result<()> { - let mut log_session = None; + let mut _log_session = None; let mut parser = Parser::new(); parser.set_language(language)?; - let source_code = fs::read_to_string(path)?; + let source_code = fs::read(path)?; if debug_graph { - log_session = Some(util::log_graphs(&mut parser, "log.html")?); + _log_session = Some(util::log_graphs(&mut parser, "log.html")?); } else if debug { parser.set_logger(Some(Box::new(|log_type, message| { if log_type == LogType::Lex { @@ -27,64 +31,111 @@ pub fn parse_file_at_path( }))); } + let time = Instant::now(); let tree = parser - .parse_str(&source_code, None) + .parse_utf8(&mut |byte, _| &source_code[byte..], None) .expect("Incompatible language version"); + let duration = time.elapsed(); + let duration_ms = duration.as_secs() * 1000 + duration.subsec_nanos() as u64 / 1000000; - drop(log_session); + let mut cursor = tree.walk(); let stdout = io::stdout(); let mut stdout = stdout.lock(); - let mut cursor = tree.walk(); - let mut needs_newline = false; - let mut indent_level = 0; - let mut did_visit_children = false; + + if !quiet { + let mut needs_newline = false; + let mut indent_level = 0; + let mut did_visit_children = false; + loop { + let node = cursor.node(); + let is_named = node.is_named(); + if did_visit_children { + if is_named { + stdout.write(b")")?; + needs_newline = true; + } + if cursor.goto_next_sibling() { + did_visit_children = false; + } else if cursor.goto_parent() { + did_visit_children = true; + indent_level -= 1; + } else { + break; + } + } else { + if is_named { + if needs_newline { + stdout.write(b"\n")?; + } + for _ in 0..indent_level { + stdout.write(b" ")?; + } + let start = node.start_position(); + let end = node.end_position(); + write!( + &mut stdout, + "({} [{}, {}] - [{}, {}]", + node.kind(), + start.row, + start.column, + end.row, + end.column + )?; + needs_newline = true; + } + if cursor.goto_first_child() { + did_visit_children = false; + indent_level += 1; + } else { + did_visit_children = true; + } + } + } + cursor.reset(tree.root_node()); + println!(""); + } + + let mut first_error = None; loop { let node = cursor.node(); - let is_named = node.is_named(); - if did_visit_children { - if is_named { - stdout.write(b")")?; - needs_newline = true; - } - if cursor.goto_next_sibling() { - did_visit_children = false; - } else if cursor.goto_parent() { - did_visit_children = true; - indent_level -= 1; - } else { + if node.has_error() { + if node.is_error() || node.is_missing() { + first_error = Some(node); break; - } - } else { - if is_named { - if needs_newline { - stdout.write(b"\n")?; - } - for _ in 0..indent_level { - stdout.write(b" ")?; - } - let start = node.start_position(); - let end = node.end_position(); - write!( - &mut stdout, - "({} [{}, {}] - [{}, {}]", - node.kind(), - start.row, - start.column, - end.row, - end.column - )?; - needs_newline = true; - } - if cursor.goto_first_child() { - did_visit_children = false; - indent_level += 1; } else { - did_visit_children = true; + cursor.goto_first_child(); + } + } else if !cursor.goto_next_sibling() { + if !cursor.goto_parent() { + break; } } } - println!(""); + if first_error.is_some() || print_time { + write!( + &mut stdout, + "{:width$}\t{} ms", + path.to_str().unwrap(), + duration_ms, + width = max_path_length + )?; + if let Some(node) = first_error { + let start = node.start_position(); + let end = node.end_position(); + write!( + &mut stdout, + "\t({} [{}, {}] - [{}, {}]", + node.kind(), + start.row, + start.column, + end.row, + end.column + )?; + } + write!(&mut stdout, "\n")?; + } + Ok(()) } diff --git a/lib/binding/lib.rs b/lib/binding/lib.rs index fdb243ec..8143fd6b 100644 --- a/lib/binding/lib.rs +++ b/lib/binding/lib.rs @@ -19,6 +19,7 @@ use std::marker::PhantomData; use std::os::raw::{c_char, c_void}; use std::ptr; use std::str; +use std::u16; #[derive(Clone, Copy)] #[repr(transparent)] @@ -479,6 +480,14 @@ impl<'tree> Node<'tree> { unsafe { ffi::ts_node_has_error(self.0) } } + pub fn is_error(&self) -> bool { + self.kind_id() == u16::MAX + } + + pub fn is_missing(&self) -> bool { + unsafe { ffi::ts_node_is_missing(self.0) } + } + pub fn start_byte(&self) -> usize { unsafe { ffi::ts_node_start_byte(self.0) as usize } } @@ -622,6 +631,10 @@ impl<'a> TreeCursor<'a> { Some(result as usize) } } + + pub fn reset(&mut self, node: Node<'a>) { + unsafe { ffi::ts_tree_cursor_reset(&mut self.0, node.0) }; + } } impl<'a> Drop for TreeCursor<'a> {