Add debug and debug-graph flags to parse and test commands

This commit is contained in:
Max Brunsfeld 2019-01-08 21:03:51 -08:00
parent 6c4d00aad5
commit 98807d2053
10 changed files with 182 additions and 32 deletions

View file

@ -14,6 +14,7 @@ mod loader;
mod logger;
mod parse;
mod test;
mod util;
use self::loader::Loader;
use clap::{App, Arg, SubCommand};
@ -48,9 +49,22 @@ fn run() -> error::Result<()> {
.subcommand(
SubCommand::with_name("parse")
.about("Parse a file")
.arg(Arg::with_name("path").index(1)),
.arg(Arg::with_name("path").index(1).required(true))
.arg(Arg::with_name("debug").long("debug").short("d"))
.arg(Arg::with_name("debug-graph").long("debug-graph").short("D")),
)
.subcommand(
SubCommand::with_name("test")
.about("Run a parser's tests")
.arg(
Arg::with_name("filter")
.long("filter")
.short("f")
.takes_value(true),
)
.arg(Arg::with_name("debug").long("debug").short("d"))
.arg(Arg::with_name("debug-graph").long("debug-graph").short("D")),
)
.subcommand(SubCommand::with_name("test").about("Run a parser's tests"))
.get_matches();
let home_dir = dirs::home_dir().unwrap();
@ -74,20 +88,23 @@ fn run() -> error::Result<()> {
generate::generate_parser_for_grammar(&grammar_path, minimize, state_ids_to_log)?;
println!("{}", code);
return Ok(());
} else if let Some(_matches) = matches.subcommand_matches("test") {
} else if let Some(matches) = matches.subcommand_matches("test") {
let debug = matches.is_present("debug");
let debug_graph = matches.is_present("debug-graph");
let filter = matches.value_of("filter");
let corpus_path = current_dir.join("corpus");
let home_dir = dirs::home_dir().unwrap();
let mut loader = Loader::new(home_dir.join(".tree-sitter"));
if let Some((language, _)) = loader.language_configuration_at_path(&current_dir)? {
test::run_tests_at_path(language, &corpus_path)?;
test::run_tests_at_path(language, &corpus_path, debug, debug_graph, filter)?;
} else {
eprintln!("No language found");
}
} else if let Some(matches) = matches.subcommand_matches("parse") {
let debug = matches.is_present("debug");
let debug_graph = matches.is_present("debug-graph");
loader.find_parsers(&vec![home_dir.join("github")])?;
let source_path = Path::new(matches.value_of("path").unwrap());
if let Some((language, _)) = loader.language_for_file_name(source_path)? {
parse::parse_file_at_path(language, source_path)?;
parse::parse_file_at_path(language, source_path, debug, debug_graph)?;
} else {
eprintln!("No language found");
}

View file

@ -1,17 +1,41 @@
use super::error::Result;
use super::util;
use std::fs;
use std::path::Path;
use tree_sitter::{Language, Parser};
use std::io::{self, Write};
use std::path::Path;
use tree_sitter::{Language, LogType, Parser};
pub fn parse_file_at_path(language: Language, path: &Path) -> Result<()> {
pub fn parse_file_at_path(
language: Language,
path: &Path,
debug: bool,
debug_graph: bool,
) -> Result<()> {
let mut parser = Parser::new();
parser.set_language(language)?;
let source_code = fs::read_to_string(path)?;
let mut log_session = None;
if debug_graph {
log_session = Some(util::start_logging_graphs(&mut parser, "log.html")?);
} else if debug {
parser.set_logger(Some(Box::new(|log_type, message| {
if log_type == LogType::Lex {
io::stderr().write(b" ").unwrap();
}
write!(&mut io::stderr(), "{}\n", message).unwrap();
})));
}
let tree = parser
.parse_str(&source_code, None)
.expect("Incompatible language version");
if let Some(log_session) = log_session {
util::stop_logging_graphs(&mut parser, log_session)?;
}
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut cursor = tree.walk();

View file

@ -1,14 +1,15 @@
use super::error::Result;
use super::util;
use ansi_term::Colour;
use difference::{Changeset, Difference};
use regex::bytes::{Regex as ByteRegex, RegexBuilder as ByteRegexBuilder};
use regex::Regex;
use std::char;
use std::fs;
use std::io;
use std::io::{self, Write};
use std::path::Path;
use std::str;
use tree_sitter::{Language, Parser};
use tree_sitter::{Language, LogType, Parser};
lazy_static! {
static ref HEADER_REGEX: ByteRegex = ByteRegexBuilder::new(r"^===+\r?\n([^=]*)\r?\n===+\r?\n")
@ -35,15 +36,34 @@ pub enum TestEntry {
},
}
pub fn run_tests_at_path(language: Language, path: &Path) -> Result<()> {
pub fn run_tests_at_path(
language: Language,
path: &Path,
debug: bool,
debug_graph: bool,
filter: Option<&str>,
) -> Result<()> {
let test_entry = parse_tests(path)?;
let mut parser = Parser::new();
parser.set_language(language)?;
let mut log_session = None;
if debug_graph {
log_session = Some(util::start_logging_graphs(&mut parser, "log.html")?);
} else if debug {
parser.set_logger(Some(Box::new(|log_type, message| {
if log_type == LogType::Lex {
io::stderr().write(b" ").unwrap();
}
write!(&mut io::stderr(), "{}\n", message).unwrap();
})));
}
let mut failures = Vec::new();
if let TestEntry::Group { children, .. } = test_entry {
for child in children {
run_tests(&mut parser, child, 0, &mut failures)?;
run_tests(&mut parser, child, filter, 0, &mut failures)?;
}
}
@ -83,28 +103,38 @@ pub fn run_tests_at_path(language: Language, path: &Path) -> Result<()> {
}
}
if let Some(log_session) = log_session {
util::stop_logging_graphs(&mut parser, log_session)?;
}
Ok(())
}
fn run_tests(
parser: &mut Parser,
test_entry: TestEntry,
filter: Option<&str>,
mut indent_level: i32,
failures: &mut Vec<(String, String, String)>,
) -> Result<()> {
for _ in 0..indent_level {
print!(" ");
}
match test_entry {
TestEntry::Example {
name,
input,
output,
} => {
if let Some(filter) = filter {
if !name.contains(filter) {
return Ok(());
}
}
let tree = parser
.parse_utf8(&mut |byte_offset, _| &input[byte_offset..], None)
.unwrap();
let actual = tree.root_node().to_sexp();
for _ in 0..indent_level {
print!(" ");
}
if actual == output {
println!("{}", Colour::Green.paint(&name));
} else {
@ -113,10 +143,13 @@ fn run_tests(
}
}
TestEntry::Group { name, children } => {
for _ in 0..indent_level {
print!(" ");
}
println!("{}:", name);
indent_level += 1;
for child in children {
run_tests(parser, child, indent_level, failures)?;
run_tests(parser, child, filter, indent_level, failures)?;
}
}
}

36
cli/src/util.rs Normal file
View file

@ -0,0 +1,36 @@
use std::fs::File;
use std::io::{Result, Write};
use std::process::{Child, ChildStdin, Command, Stdio};
use std::str;
use tree_sitter::Parser;
pub(crate) struct LogSession(Child, ChildStdin);
pub(crate) fn start_logging_graphs(parser: &mut Parser, path: &str) -> Result<LogSession> {
let mut dot_file = File::create(path)?;
dot_file.write(b"<!DOCTYPE html>\n<style>svg { width: 100%; }</style>\n\n")?;
let mut dot_process = Command::new("dot")
.arg("-Tsvg")
.stdin(Stdio::piped())
.stdout(dot_file)
.spawn()
.expect("Failed to run Dot");
let dot_stdin = dot_process
.stdin
.take()
.expect("Failed to open stdin for Dot");
parser.print_dot_graphs(&dot_stdin);
Ok(LogSession(dot_process, dot_stdin))
}
pub(crate) fn stop_logging_graphs(parser: &mut Parser, mut session: LogSession) -> Result<()> {
drop(session.1);
parser.stop_printing_dot_graphs();
session.0.wait()?;
if cfg!(target_os = "macos") {
Command::new("open").arg("log.html").output()?;
}
Ok(())
}