1use crate::grammar::{CharClassItem, Grammar, GrammarExpr, GrammarFormat, GrammarRule, RuleKind};
2
3use super::{EmitReport, GrammarEmitError, ordered_rules, unsupported_error};
4
5const FORMAT: GrammarFormat = GrammarFormat::TreeSitter;
6const EXTRAS_RULE: &str = "_extras";
7const PRECEDENCE_TYPES: [(&str, &str); 4] = [
8 ("prec=", "PREC"),
9 ("prec_left=", "PREC_LEFT"),
10 ("prec_right=", "PREC_RIGHT"),
11 ("prec_dynamic=", "PREC_DYNAMIC"),
12];
13const MAX_SAFE_INTEGER: i64 = 9_007_199_254_740_991;
14
15pub fn emit_tree_sitter_json(grammar: &Grammar) -> Result<(String, EmitReport), GrammarEmitError> {
23 let mut report = EmitReport::default();
24 let mut rules = Vec::new();
25 let mut inline = Vec::new();
26 let mut extras = None;
27 for rule in ordered_rules(grammar) {
28 if rule.name() == EXTRAS_RULE && rule.kind() == RuleKind::Silent {
29 extras = Some(extras_members(rule.expr(), &mut report)?);
30 continue;
31 }
32 if rule.kind() == RuleKind::Silent {
33 report.add_lossy(format!(
34 "tree-sitter emits RuleKind::Silent rule {:?} in the inline list",
35 rule.name()
36 ));
37 inline.push(Json::string(rule.name()));
38 }
39 rules.push((rule.name().to_string(), emit_rule(rule, &mut report)?));
40 }
41
42 let mut document = vec![
43 ("name".to_string(), Json::string(&grammar_name(grammar))),
44 ("rules".to_string(), Json::Object(rules)),
45 ];
46 if let Some(extras) = extras {
47 document.push(("extras".to_string(), Json::Array(extras)));
48 }
49 if !inline.is_empty() {
50 document.push(("inline".to_string(), Json::Array(inline)));
51 }
52 let mut output = String::new();
53 Json::Object(document).write(&mut output, 0);
54 output.push('\n');
55 Ok((output, report))
56}
57
58fn emit_rule(rule: &GrammarRule, report: &mut EmitReport) -> Result<Json, GrammarEmitError> {
59 if rule.kind() == RuleKind::Token
60 && let GrammarExpr::Capture {
61 label: Some(label),
62 expr,
63 } = rule.expr()
64 && label == "immediate_token"
65 {
66 return Ok(node(
67 "IMMEDIATE_TOKEN",
68 vec![("content", emit_node(expr, report)?)],
69 ));
70 }
71 if rule.kind() == RuleKind::Atomic {
72 report.add_lossy(format!(
73 "tree-sitter emits RuleKind::Atomic rule {:?} as a token",
74 rule.name()
75 ));
76 }
77 let body = emit_node(rule.expr(), report)?;
78 Ok(match rule.kind() {
79 RuleKind::Token | RuleKind::Atomic => node("TOKEN", vec![("content", body)]),
80 RuleKind::Normal | RuleKind::Silent => body,
81 })
82}
83
84fn extras_members(
85 expr: &GrammarExpr,
86 report: &mut EmitReport,
87) -> Result<Vec<Json>, GrammarEmitError> {
88 match expr {
89 GrammarExpr::Choice {
90 ordered: false,
91 alternatives,
92 } => emit_nodes(alternatives, report),
93 expr => Ok(vec![emit_node(expr, report)?]),
94 }
95}
96
97fn emit_nodes(
98 items: &[GrammarExpr],
99 report: &mut EmitReport,
100) -> Result<Vec<Json>, GrammarEmitError> {
101 items.iter().map(|item| emit_node(item, report)).collect()
102}
103
104fn emit_node(expr: &GrammarExpr, report: &mut EmitReport) -> Result<Json, GrammarEmitError> {
105 Ok(match expr {
106 GrammarExpr::Empty => blank(),
107 GrammarExpr::Terminal(value) => node("STRING", vec![("value", Json::string(value))]),
108 GrammarExpr::TerminalInsensitive(value) => {
109 report.add_lossy(format!(
110 "tree-sitter expands case-insensitive terminal {value:?} to a pattern"
111 ));
112 pattern(case_insensitive_pattern(value))
113 }
114 GrammarExpr::CharRange(start, end) => pattern(format!(
115 "[{}-{}]",
116 escape_class_char(*start),
117 escape_class_char(*end)
118 )),
119 GrammarExpr::CharClass { negated, items } => pattern(char_class_pattern(*negated, items)?),
120 GrammarExpr::AnyChar => pattern(".".to_string()),
121 GrammarExpr::NonTerminal(name) => node("SYMBOL", vec![("name", Json::string(name))]),
122 GrammarExpr::Choice {
123 ordered,
124 alternatives,
125 } => {
126 if alternatives.is_empty() {
127 return Err(unsupported_error(FORMAT, "empty Choice"));
128 }
129 if *ordered {
130 report.add_lossy("tree-sitter treats ordered choice as unordered choice");
131 }
132 node(
133 "CHOICE",
134 vec![("members", Json::Array(emit_nodes(alternatives, report)?))],
135 )
136 }
137 GrammarExpr::Sequence(items) => node(
138 "SEQ",
139 vec![("members", Json::Array(emit_nodes(items, report)?))],
140 ),
141 GrammarExpr::Optional(inner) => optional(emit_node(inner, report)?),
142 GrammarExpr::ZeroOrMore(inner) => {
143 node("REPEAT", vec![("content", emit_node(inner, report)?)])
144 }
145 GrammarExpr::OneOrMore(inner) => {
146 node("REPEAT1", vec![("content", emit_node(inner, report)?)])
147 }
148 GrammarExpr::Repeat { expr, min, max } => emit_repeat(expr, *min, *max, report)?,
149 GrammarExpr::And(_) | GrammarExpr::Not(_) => {
150 return Err(unsupported_error(FORMAT, "predicate"));
151 }
152 GrammarExpr::Capture { label, expr } => emit_capture(label.as_deref(), expr, report)?,
153 GrammarExpr::Feature(feature) => {
154 return Err(unsupported_error(GrammarFormat::TreeSitter, feature.head()));
155 }
156 })
157}
158
159fn emit_repeat(
160 expr: &GrammarExpr,
161 min: usize,
162 max: Option<usize>,
163 report: &mut EmitReport,
164) -> Result<Json, GrammarEmitError> {
165 if let Some(max) = max
166 && max < min
167 {
168 return Err(unsupported_error(
169 FORMAT,
170 format!("Repeat with min {min} greater than max Some({max})"),
171 ));
172 }
173 report.add_lossy(format!(
174 "tree-sitter desugared Repeat with min {min} and max {max:?}"
175 ));
176 let content = emit_node(expr, report)?;
177 let mut members = vec![content.clone(); min];
178 match max {
179 None => members.push(node("REPEAT", vec![("content", content)])),
180 Some(max) => {
181 for _ in min..max {
182 members.push(optional(content.clone()));
183 }
184 }
185 }
186 Ok(match members.len() {
187 0 => blank(),
188 1 => members.remove(0),
189 _ => node("SEQ", vec![("members", Json::Array(members))]),
190 })
191}
192
193fn emit_capture(
194 label: Option<&str>,
195 expr: &GrammarExpr,
196 report: &mut EmitReport,
197) -> Result<Json, GrammarEmitError> {
198 let Some(label) = label else {
199 report.add_lossy("tree-sitter dropped anonymous capture");
200 return emit_node(expr, report);
201 };
202 if label == "regex"
203 && let GrammarExpr::Terminal(value) = expr
204 {
205 return Ok(pattern(value.clone()));
206 }
207 for (prefix, kind) in PRECEDENCE_TYPES {
208 if let Some(value) = label.strip_prefix(prefix) {
209 return Ok(node(
210 kind,
211 vec![
212 ("value", precedence_value(value)),
213 ("content", emit_node(expr, report)?),
214 ],
215 ));
216 }
217 }
218 if let Some(value) = label.strip_prefix("alias:") {
219 return Ok(node(
220 "ALIAS",
221 vec![
222 ("content", emit_node(expr, report)?),
223 ("named", Json::Bool(is_identifier(value))),
224 ("value", Json::string(value)),
225 ],
226 ));
227 }
228 let content = emit_node(expr, report)?;
229 Ok(match label {
230 "token" => node("TOKEN", vec![("content", content)]),
231 "immediate_token" => node("IMMEDIATE_TOKEN", vec![("content", content)]),
232 "reserved" => node("RESERVED", vec![("content", content)]),
233 label => match label.strip_prefix("reserved:") {
234 Some(context_name) => node(
235 "RESERVED",
236 vec![
237 ("context_name", Json::string(context_name)),
238 ("content", content),
239 ],
240 ),
241 None => node(
242 "FIELD",
243 vec![("name", Json::string(label)), ("content", content)],
244 ),
245 },
246 })
247}
248
249fn char_class_pattern(negated: bool, items: &[CharClassItem]) -> Result<String, GrammarEmitError> {
250 if items.is_empty() {
251 return Err(unsupported_error(FORMAT, "empty CharClass"));
252 }
253 let mut output = String::from("[");
254 if negated {
255 output.push('^');
256 }
257 for item in items {
258 match item {
259 CharClassItem::Char(value) => output.push_str(&escape_class_char(*value)),
260 CharClassItem::Range(start, end) => {
261 output.push_str(&escape_class_char(*start));
262 output.push('-');
263 output.push_str(&escape_class_char(*end));
264 }
265 }
266 }
267 output.push(']');
268 Ok(output)
269}
270
271fn case_insensitive_pattern(value: &str) -> String {
272 value
273 .chars()
274 .map(|character| {
275 let lower = character.to_lowercase().collect::<Vec<_>>();
276 let upper = character.to_uppercase().collect::<Vec<_>>();
277 if lower == upper || lower.len() != 1 || upper.len() != 1 {
278 escape_pattern_char(character)
279 } else {
280 format!(
281 "[{}{}]",
282 escape_class_char(lower[0]),
283 escape_class_char(upper[0])
284 )
285 }
286 })
287 .collect()
288}
289
290fn escape_class_char(character: char) -> String {
291 match character {
292 '\n' => "\\n".to_string(),
293 '\r' => "\\r".to_string(),
294 '\t' => "\\t".to_string(),
295 '\\' | ']' | '[' | '-' | '^' => format!("\\{character}"),
296 character => character.to_string(),
297 }
298}
299
300fn escape_pattern_char(character: char) -> String {
301 match character {
302 '\n' => "\\n".to_string(),
303 '\r' => "\\r".to_string(),
304 '\t' => "\\t".to_string(),
305 '\\' | '^' | '$' | '.' | '|' | '?' | '*' | '+' | '(' | ')' | '[' | ']' | '{' | '}'
306 | '/' => format!("\\{character}"),
307 character => character.to_string(),
308 }
309}
310
311fn precedence_value(value: &str) -> Json {
312 let digits = value.strip_prefix('-').unwrap_or(value);
313 let canonical = digits == "0"
314 || (digits.starts_with(|c: char| ('1'..='9').contains(&c))
315 && digits.chars().all(|c| c.is_ascii_digit()));
316 match value.parse::<i64>() {
317 Ok(number) if canonical && number.abs() <= MAX_SAFE_INTEGER => Json::Number(number),
318 _ => Json::string(value),
319 }
320}
321
322fn grammar_name(grammar: &Grammar) -> String {
323 let source = grammar
324 .start_rule()
325 .or_else(|| grammar.rules().first())
326 .map_or("grammar", GrammarRule::name);
327 let name = source
328 .chars()
329 .map(|c| {
330 if c.is_ascii_alphanumeric() || c == '_' {
331 c
332 } else {
333 '_'
334 }
335 })
336 .collect::<String>();
337 if name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
338 name
339 } else {
340 format!("_{name}")
341 }
342}
343
344fn is_identifier(value: &str) -> bool {
345 value.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
346 && value.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
347}
348
349fn node(kind: &str, fields: Vec<(&str, Json)>) -> Json {
350 let mut entries = vec![("type".to_string(), Json::string(kind))];
351 entries.extend(
352 fields
353 .into_iter()
354 .map(|(key, value)| (key.to_string(), value)),
355 );
356 Json::Object(entries)
357}
358
359fn blank() -> Json {
360 node("BLANK", Vec::new())
361}
362
363fn pattern(value: String) -> Json {
364 node("PATTERN", vec![("value", Json::String(value))])
365}
366
367fn optional(content: Json) -> Json {
368 node(
369 "CHOICE",
370 vec![("members", Json::Array(vec![content, blank()]))],
371 )
372}
373
374#[derive(Clone, Debug)]
376enum Json {
377 String(String),
378 Number(i64),
379 Bool(bool),
380 Array(Vec<Self>),
381 Object(Vec<(String, Self)>),
382}
383
384impl Json {
385 fn string(value: &str) -> Self {
386 Self::String(value.to_string())
387 }
388
389 fn write(&self, output: &mut String, indent: usize) {
390 match self {
391 Self::String(value) => write_json_string(output, value),
392 Self::Number(value) => output.push_str(&value.to_string()),
393 Self::Bool(value) => output.push_str(if *value { "true" } else { "false" }),
394 Self::Array(items) if items.is_empty() => output.push_str("[]"),
395 Self::Object(entries) if entries.is_empty() => output.push_str("{}"),
396 Self::Array(items) => {
397 output.push_str("[\n");
398 for (index, item) in items.iter().enumerate() {
399 push_separator(output, index, indent + 2);
400 item.write(output, indent + 2);
401 }
402 push_close(output, indent, ']');
403 }
404 Self::Object(entries) => {
405 output.push_str("{\n");
406 for (index, (key, value)) in entries.iter().enumerate() {
407 push_separator(output, index, indent + 2);
408 write_json_string(output, key);
409 output.push_str(": ");
410 value.write(output, indent + 2);
411 }
412 push_close(output, indent, '}');
413 }
414 }
415 }
416}
417
418fn push_separator(output: &mut String, index: usize, indent: usize) {
419 if index > 0 {
420 output.push_str(",\n");
421 }
422 output.push_str(&" ".repeat(indent));
423}
424
425fn push_close(output: &mut String, indent: usize, close: char) {
426 output.push('\n');
427 output.push_str(&" ".repeat(indent));
428 output.push(close);
429}
430
431fn write_json_string(output: &mut String, value: &str) {
432 output.push_str(&serde_json::to_string(value).unwrap_or_else(|_| format!("{value:?}")));
435}