fork of https://github.com/tree-sitter/tree-sitter-graph

Pretty errors for TSG parsing and checking

Changed files
+143 -6
src
bin
tree-sitter-graph
+8
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## Unreleased 9 + 10 + ### CLI 11 + 12 + #### Added 13 + 14 + - Errors from parsing and checking the TSG file are now displayed with source excerpts. 15 + 8 16 ## v0.9.0 -- 2023-03-31 9 17 10 18 ### Library
+7 -2
src/bin/tree-sitter-graph/main.rs
··· 98 98 let tsg = std::fs::read(tsg_path) 99 99 .with_context(|| format!("Cannot read TSG file {}", tsg_path.display()))?; 100 100 let tsg = String::from_utf8(tsg)?; 101 - let file = File::from_str(language, &tsg) 102 - .with_context(|| format!("Cannot parsing TSG file {}", tsg_path.display()))?; 101 + let file = match File::from_str(language, &tsg) { 102 + Ok(file) => file, 103 + Err(err) => { 104 + eprintln!("{}", err.display_pretty(tsg_path, &tsg)); 105 + return Err(anyhow!("Cannot parse TSG file {}", tsg_path.display())); 106 + } 107 + }; 103 108 104 109 let source = std::fs::read(source_path) 105 110 .with_context(|| format!("Cannot read source file {}", source_path.display()))?;
+57 -4
src/checker.rs
··· 5 5 // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. 6 6 // ------------------------------------------------------------------------------------------------ 7 7 8 + use std::path::Path; 9 + 8 10 use thiserror::Error; 9 11 use tree_sitter::CaptureQuantifier; 10 12 use tree_sitter::CaptureQuantifier::One; ··· 14 16 use tree_sitter::Query; 15 17 16 18 use crate::ast; 19 + use crate::parse_error::Excerpt; 17 20 use crate::parser::FULL_MATCH; 18 21 use crate::variables::MutVariables; 19 22 use crate::variables::VariableError; ··· 41 44 UndefinedSyntaxCapture(String, Location), 42 45 #[error("Undefined variable {0} at {1}")] 43 46 UndefinedVariable(String, Location), 44 - #[error("{0}: {1}")] 45 - Variable(VariableError, String), 47 + #[error("{0}: {1} at {2}")] 48 + Variable(VariableError, String, Location), 49 + } 50 + 51 + impl CheckError { 52 + pub fn display_pretty<'a>( 53 + &'a self, 54 + path: &'a Path, 55 + source: &'a str, 56 + ) -> impl std::fmt::Display + 'a { 57 + DisplayCheckErrorPretty { 58 + error: self, 59 + path, 60 + source, 61 + } 62 + } 63 + } 64 + 65 + struct DisplayCheckErrorPretty<'a> { 66 + error: &'a CheckError, 67 + path: &'a Path, 68 + source: &'a str, 69 + } 70 + 71 + impl std::fmt::Display for DisplayCheckErrorPretty<'_> { 72 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 73 + let location = match self.error { 74 + CheckError::CannotHideGlobalVariable(_, location) => *location, 75 + CheckError::CannotSetGlobalVariable(_, location) => *location, 76 + CheckError::DuplicateGlobalVariable(_, location) => *location, 77 + CheckError::ExpectedListValue(location) => *location, 78 + CheckError::ExpectedLocalValue(location) => *location, 79 + CheckError::ExpectedOptionalValue(location) => *location, 80 + CheckError::NullableRegex(_, location) => *location, 81 + CheckError::UndefinedSyntaxCapture(_, location) => *location, 82 + CheckError::UndefinedVariable(_, location) => *location, 83 + CheckError::Variable(_, _, location) => *location, 84 + }; 85 + writeln!(f, "{}", self.error)?; 86 + write!( 87 + f, 88 + "{}", 89 + Excerpt::from_source( 90 + self.path, 91 + self.source, 92 + location.row, 93 + location.to_column_range(), 94 + 0 95 + ) 96 + )?; 97 + Ok(()) 98 + } 46 99 } 47 100 48 101 /// Checker context ··· 567 620 } 568 621 ctx.locals 569 622 .add(self.name.clone(), value, mutable) 570 - .map_err(|e| CheckError::Variable(e, format!("{}", self.name))) 623 + .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location)) 571 624 } 572 625 573 626 fn check_set( ··· 589 642 value.is_local = false; 590 643 ctx.locals 591 644 .set(self.name.clone(), value) 592 - .map_err(|e| CheckError::Variable(e, format!("{}", self.name))) 645 + .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location)) 593 646 } 594 647 595 648 fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
+71
src/parser.rs
··· 8 8 use std::fmt::Display; 9 9 use std::iter::Peekable; 10 10 use std::ops::Range; 11 + use std::path::Path; 11 12 use std::str::Chars; 12 13 13 14 use regex::Regex; ··· 23 24 use tree_sitter::QueryError; 24 25 25 26 use crate::ast; 27 + use crate::parse_error::Excerpt; 26 28 use crate::Identifier; 27 29 28 30 pub const FULL_MATCH: &str = "__tsg__full_match"; ··· 45 47 Parser::new(content).parse_into_file(self) 46 48 } 47 49 } 50 + 51 + // ---------------------------------------------------------------------------- 52 + // Parse errors 48 53 49 54 /// An error that can occur while parsing a graph DSL file 50 55 #[derive(Debug, Error)] ··· 77 82 Check(#[from] crate::checker::CheckError), 78 83 } 79 84 85 + impl ParseError { 86 + pub fn display_pretty<'a>( 87 + &'a self, 88 + path: &'a Path, 89 + source: &'a str, 90 + ) -> impl std::fmt::Display + 'a { 91 + DisplayParseErrorPretty { 92 + error: self, 93 + path, 94 + source, 95 + } 96 + } 97 + } 98 + 99 + struct DisplayParseErrorPretty<'a> { 100 + error: &'a ParseError, 101 + path: &'a Path, 102 + source: &'a str, 103 + } 104 + 105 + impl std::fmt::Display for DisplayParseErrorPretty<'_> { 106 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 107 + let location = match self.error { 108 + ParseError::ExpectedQuantifier(location) => *location, 109 + ParseError::ExpectedToken(_, location) => *location, 110 + ParseError::ExpectedVariable(location) => *location, 111 + ParseError::ExpectedUnscopedVariable(location) => *location, 112 + ParseError::InvalidRegex(_, location) => *location, 113 + ParseError::InvalidRegexCapture(location) => *location, 114 + ParseError::QueryError(err) => Location { 115 + row: err.row, 116 + column: err.column, 117 + }, 118 + ParseError::UnexpectedCharacter(_, _, location) => *location, 119 + ParseError::UnexpectedEOF(location) => *location, 120 + ParseError::UnexpectedKeyword(_, location) => *location, 121 + ParseError::UnexpectedLiteral(_, location) => *location, 122 + ParseError::UnexpectedQueryPatterns(location) => *location, 123 + ParseError::Check(err) => { 124 + write!(f, "{}", err.display_pretty(self.path, self.source))?; 125 + return Ok(()); 126 + } 127 + }; 128 + writeln!(f, "{}", self.source)?; 129 + writeln!(f, "{}", self.error)?; 130 + write!( 131 + f, 132 + "{}", 133 + Excerpt::from_source( 134 + self.path, 135 + self.source, 136 + location.row, 137 + location.to_column_range(), 138 + 0 139 + ) 140 + )?; 141 + Ok(()) 142 + } 143 + } 144 + 145 + // ---------------------------------------------------------------------------- 146 + // Location 147 + 80 148 /// The location of a graph DSL entity within its file 81 149 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 82 150 pub struct Location { ··· 104 172 write!(f, "({}, {})", self.row + 1, self.column + 1) 105 173 } 106 174 } 175 + 176 + // ---------------------------------------------------------------------------- 177 + // Parser 107 178 108 179 struct Parser<'a> { 109 180 source: &'a str,