@@ -7,16 +7,10 @@ use std::{
77} ;
88
99use anyhow:: Result ;
10- use build_tables:: build_tables;
11- use grammars:: InputGrammar ;
12- pub use node_types:: VariableInfoError ;
13- use parse_grammar:: parse_grammar;
14- pub use parse_grammar:: ParseGrammarError ;
15- use prepare_grammar:: prepare_grammar;
16- pub use prepare_grammar:: PrepareGrammarError ;
1710use regex:: { Regex , RegexBuilder } ;
18- use render:: render_c_code;
1911use semver:: Version ;
12+ use serde:: { Deserialize , Serialize } ;
13+ use thiserror:: Error ;
2014
2115mod build_tables;
2216mod dedup;
@@ -30,9 +24,15 @@ mod render;
3024mod rules;
3125mod tables;
3226
27+ use build_tables:: build_tables;
3328pub use build_tables:: ParseTableBuilderError ;
34- use serde:: Serialize ;
35- use thiserror:: Error ;
29+ use grammars:: InputGrammar ;
30+ pub use node_types:: VariableInfoError ;
31+ use parse_grammar:: parse_grammar;
32+ pub use parse_grammar:: ParseGrammarError ;
33+ use prepare_grammar:: prepare_grammar;
34+ pub use prepare_grammar:: PrepareGrammarError ;
35+ use render:: render_c_code;
3636
3737static JSON_COMMENT_REGEX : LazyLock < Regex > = LazyLock :: new ( || {
3838 RegexBuilder :: new ( "^\\ s*//.*" )
@@ -67,6 +67,8 @@ pub enum GenerateError {
6767 VariableInfo ( #[ from] VariableInfoError ) ,
6868 #[ error( transparent) ]
6969 BuildTables ( #[ from] ParseTableBuilderError ) ,
70+ #[ error( transparent) ]
71+ ParseVersion ( #[ from] ParseVersionError ) ,
7072}
7173
7274impl From < std:: io:: Error > for GenerateError {
@@ -95,6 +97,16 @@ impl From<std::io::Error> for LoadGrammarError {
9597 }
9698}
9799
100+ #[ derive( Debug , Error , Serialize ) ]
101+ pub enum ParseVersionError {
102+ #[ error( "{0}" ) ]
103+ Version ( String ) ,
104+ #[ error( "{0}" ) ]
105+ JSON ( String ) ,
106+ #[ error( "{0}" ) ]
107+ IO ( String ) ,
108+ }
109+
98110pub type JSResult < T > = Result < T , JSError > ;
99111
100112#[ derive( Debug , Error , Serialize ) ]
@@ -178,11 +190,18 @@ pub fn generate_parser_in_directory(
178190 // Parse and preprocess the grammar.
179191 let input_grammar = parse_grammar ( & grammar_json) ?;
180192
193+ let semantic_version = read_grammar_version ( & repo_path) ?;
194+
181195 // Generate the parser and related files.
182196 let GeneratedParser {
183197 c_code,
184198 node_types_json,
185- } = generate_parser_for_grammar_with_opts ( & input_grammar, abi_version, report_symbol_name) ?;
199+ } = generate_parser_for_grammar_with_opts (
200+ & input_grammar,
201+ abi_version,
202+ semantic_version. map ( |v| ( v. major as u8 , v. minor as u8 , v. patch as u8 ) ) ,
203+ report_symbol_name,
204+ ) ?;
186205
187206 write_file ( & src_path. join ( "parser.c" ) , c_code) ?;
188207 write_file ( & src_path. join ( "node-types.json" ) , node_types_json) ?;
@@ -193,17 +212,25 @@ pub fn generate_parser_in_directory(
193212 Ok ( ( ) )
194213}
195214
196- pub fn generate_parser_for_grammar ( grammar_json : & str ) -> GenerateResult < ( String , String ) > {
215+ pub fn generate_parser_for_grammar (
216+ grammar_json : & str ,
217+ semantic_version : Option < ( u8 , u8 , u8 ) > ,
218+ ) -> GenerateResult < ( String , String ) > {
197219 let grammar_json = JSON_COMMENT_REGEX . replace_all ( grammar_json, "\n " ) ;
198220 let input_grammar = parse_grammar ( & grammar_json) ?;
199- let parser =
200- generate_parser_for_grammar_with_opts ( & input_grammar, tree_sitter:: LANGUAGE_VERSION , None ) ?;
221+ let parser = generate_parser_for_grammar_with_opts (
222+ & input_grammar,
223+ tree_sitter:: LANGUAGE_VERSION ,
224+ semantic_version,
225+ None ,
226+ ) ?;
201227 Ok ( ( input_grammar. name , parser. c_code ) )
202228}
203229
204230fn generate_parser_for_grammar_with_opts (
205231 input_grammar : & InputGrammar ,
206232 abi_version : usize ,
233+ semantic_version : Option < ( u8 , u8 , u8 ) > ,
207234 report_symbol_name : Option < & str > ,
208235) -> GenerateResult < GeneratedParser > {
209236 let ( syntax_grammar, lexical_grammar, inlines, simple_aliases) =
@@ -233,6 +260,7 @@ fn generate_parser_for_grammar_with_opts(
233260 lexical_grammar,
234261 simple_aliases,
235262 abi_version,
263+ semantic_version,
236264 supertype_symbol_map,
237265 ) ;
238266 Ok ( GeneratedParser {
@@ -241,6 +269,55 @@ fn generate_parser_for_grammar_with_opts(
241269 } )
242270}
243271
272+ /// This will read the `tree-sitter.json` config file and attempt to extract the version.
273+ ///
274+ /// If the file is not found in the current directory or any of its parent directories, this will
275+ /// return `None` to maintain backwards compatibility. If the file is found but the version cannot
276+ /// be parsed as semver, this will return an error.
277+ fn read_grammar_version ( repo_path : & Path ) -> Result < Option < Version > , ParseVersionError > {
278+ #[ derive( Deserialize ) ]
279+ struct TreeSitterJson {
280+ metadata : Metadata ,
281+ }
282+
283+ #[ derive( Deserialize ) ]
284+ struct Metadata {
285+ version : String ,
286+ }
287+
288+ let filename = "tree-sitter.json" ;
289+ let mut path = repo_path. join ( filename) ;
290+
291+ loop {
292+ let json = path
293+ . exists ( )
294+ . then ( || {
295+ let contents = fs:: read_to_string ( path. as_path ( ) ) . map_err ( |e| {
296+ ParseVersionError :: IO ( format ! ( "Failed to read `{}` -- {e}" , path. display( ) ) )
297+ } ) ?;
298+ serde_json:: from_str :: < TreeSitterJson > ( & contents) . map_err ( |e| {
299+ ParseVersionError :: JSON ( format ! ( "Failed to parse `{}` -- {e}" , path. display( ) ) )
300+ } )
301+ } )
302+ . transpose ( ) ?;
303+ if let Some ( json) = json {
304+ return Version :: parse ( & json. metadata . version )
305+ . map_err ( |e| {
306+ ParseVersionError :: Version ( format ! (
307+ "Failed to parse `{}` version as semver -- {e}" ,
308+ path. display( )
309+ ) )
310+ } )
311+ . map ( Some ) ;
312+ }
313+ path. pop ( ) ; // filename
314+ if !path. pop ( ) {
315+ return Ok ( None ) ;
316+ }
317+ path. push ( filename) ;
318+ }
319+ }
320+
244321pub fn load_grammar_file (
245322 grammar_path : & Path ,
246323 js_runtime : Option < & str > ,
0 commit comments