1use std::fmt::Write as _;
25
26use links_notation::{LiNo, ParserConfig, parse_lino_to_links_with_config};
27
28use super::super::{
29 CharClassItem, FeatureExpr, Grammar, GrammarDeclarations, GrammarExpr, GrammarFormat,
30 GrammarImportError, GrammarKind, GrammarRule, GrammarSourceName, MATCHING_MODES, RuleKind,
31 settling_problem,
32};
33use super::feature_links::{
34 read_class, read_declaration, read_feature, read_rule_fields, read_source_names,
35 render_declaration_links, render_links_feature, render_rule_fields,
36};
37
38const MAX_BOUND_DIGITS: usize = 9;
40
41pub(super) type Node = LiNo<String>;
42
43pub(super) fn links_error(detail: impl AsRef<str>) -> GrammarImportError {
44 GrammarImportError::Parse {
45 format: GrammarFormat::MetaLanguage,
46 message: format!("links: {}", detail.as_ref()),
47 }
48}
49
50const fn is_plain(byte: u8) -> bool {
51 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.')
52}
53
54#[must_use]
56pub fn percent_encode_links_text(value: &str) -> String {
57 if value.is_empty() {
58 return "%".to_owned();
59 }
60 let mut encoded = String::with_capacity(value.len());
61 for &byte in value.as_bytes() {
62 if is_plain(byte) {
63 encoded.push(char::from(byte));
64 } else {
65 write!(encoded, "%{byte:02X}").expect("writing to a String never fails");
66 }
67 }
68 encoded
69}
70
71pub fn percent_decode_links_text(value: &str) -> Result<String, GrammarImportError> {
78 if value == "%" {
79 return Ok(String::new());
80 }
81 let bytes = value.as_bytes();
82 let mut decoded = Vec::with_capacity(bytes.len());
83 let mut index = 0;
84 while index < bytes.len() {
85 let byte = bytes[index];
86 if byte == b'%' {
87 let hex = bytes.get(index + 1..index + 3).filter(|hex| {
88 hex.iter()
89 .all(|digit| digit.is_ascii_digit() || (b'A'..=b'F').contains(digit))
90 });
91 let Some(hex) = hex else {
92 return Err(links_error(format!("invalid percent escape in {value}")));
93 };
94 let text = std::str::from_utf8(hex).expect("hex digits are ASCII");
95 decoded.push(u8::from_str_radix(text, 16).expect("two hex digits fit a byte"));
96 index += 3;
97 } else if is_plain(byte) {
98 decoded.push(byte);
99 index += 1;
100 } else {
101 return Err(links_error(format!("unescaped character in {value}")));
102 }
103 }
104 String::from_utf8(decoded).map_err(|_| links_error(format!("{value} is not UTF-8")))
105}
106
107#[must_use]
109pub fn render_grammar_links(grammar: &Grammar) -> String {
110 let mut header = vec!["grammar".to_owned()];
111 if let Some(format) = grammar.source_format() {
112 header.push(format!("(format {})", format.as_str()));
113 }
114 if let Some(start) = grammar.start_rule() {
115 header.push(format!(
116 "(start {})",
117 percent_encode_links_text(start.name())
118 ));
119 }
120 let declarations = grammar.declarations();
121 if let Some(matching) = &declarations.matching {
122 header.push(format!("(matching {matching})"));
123 }
124 if let Some(settling) = &declarations.settling {
125 header.push(format!("(settling {})", settling.join(" ")));
126 }
127 let mut lines = vec![format!("({})", header.join(" "))];
128 lines.extend(render_declaration_links(declarations));
129 lines.extend(grammar.kinds().iter().map(|kind| {
130 link(&[
131 "kind".to_owned(),
132 percent_encode_links_text(&kind.name),
133 render_source_names(&kind.source_names),
134 ])
135 }));
136 lines.extend(grammar.rules().iter().map(render_rule_link));
137 lines.iter().fold(String::new(), |mut text, line| {
138 text.push_str(line);
139 text.push('\n');
140 text
141 })
142}
143
144#[must_use]
147pub fn render_rule_link(rule: &GrammarRule) -> String {
148 let mut parts = vec![
149 "rule".to_owned(),
150 percent_encode_links_text(rule.name()),
151 rule.kind().as_str().to_owned(),
152 render_links_expression(rule.expr()),
153 ];
154 parts.extend(render_rule_fields(&rule.attributes));
155 if let Some(concept) = rule.concept() {
156 parts.push(format!("(concept {})", percent_encode_links_text(concept)));
157 }
158 if !rule.source_names().is_empty() {
159 parts.push(render_source_names(rule.source_names()));
160 }
161 if let Some(doc) = rule.doc() {
162 parts.push(format!("(doc {})", percent_encode_links_text(doc)));
163 }
164 link(&parts)
165}
166
167fn render_source_names(source_names: &[GrammarSourceName]) -> String {
169 let mut names = vec!["source-names".to_owned()];
170 names.extend(source_names.iter().map(|source_name| {
171 format!(
172 "({} {})",
173 percent_encode_links_text(&source_name.source),
174 percent_encode_links_text(&source_name.name)
175 )
176 }));
177 link(&names)
178}
179
180pub(super) fn link(parts: &[String]) -> String {
181 format!("({})", parts.join(" "))
182}
183
184pub(super) fn char_text(value: char) -> String {
185 percent_encode_links_text(value.encode_utf8(&mut [0; 4]))
186}
187
188#[must_use]
190pub fn render_links_expression(expr: &GrammarExpr) -> String {
191 let word = |value: &str| value.to_owned();
192 let headed = |head: &str, items: &[GrammarExpr]| {
193 let mut parts = vec![word(head)];
194 parts.extend(items.iter().map(render_links_expression));
195 link(&parts)
196 };
197 let unary =
198 |head: &str, inner: &GrammarExpr| link(&[word(head), render_links_expression(inner)]);
199 match expr {
200 GrammarExpr::Empty => word("empty"),
201 GrammarExpr::AnyChar => word("any"),
202 GrammarExpr::Terminal(value) => link(&[word("literal"), percent_encode_links_text(value)]),
203 GrammarExpr::TerminalInsensitive(value) => {
204 link(&[word("literalInsensitive"), percent_encode_links_text(value)])
205 }
206 GrammarExpr::CharRange(start, end) => {
207 link(&[word("range"), char_text(*start), char_text(*end)])
208 }
209 GrammarExpr::CharClass { negated, items } => {
210 let mut parts = vec![
211 word("class"),
212 word(if *negated { "negated" } else { "plain" }),
213 ];
214 parts.extend(items.iter().map(|item| match item {
215 CharClassItem::Char(value) => link(&[word("char"), char_text(*value)]),
216 CharClassItem::Range(start, end) => {
217 link(&[word("range"), char_text(*start), char_text(*end)])
218 }
219 }));
220 link(&parts)
221 }
222 GrammarExpr::NonTerminal(name) => link(&[word("ref"), percent_encode_links_text(name)]),
223 GrammarExpr::Choice {
224 ordered,
225 alternatives,
226 } => {
227 let mut parts = vec![
228 word("choice"),
229 word(if *ordered { "ordered" } else { "unordered" }),
230 ];
231 parts.extend(alternatives.iter().map(render_links_expression));
232 link(&parts)
233 }
234 GrammarExpr::Sequence(items) => headed("seq", items),
235 GrammarExpr::Optional(inner) => unary("optional", inner),
236 GrammarExpr::ZeroOrMore(inner) => unary("repeat0", inner),
237 GrammarExpr::OneOrMore(inner) => unary("repeat1", inner),
238 GrammarExpr::And(inner) => unary("and", inner),
239 GrammarExpr::Not(inner) => unary("not", inner),
240 GrammarExpr::Repeat { expr, min, max } => link(&[
241 word("repeat"),
242 min.to_string(),
243 max.map_or_else(|| word("unbounded"), |max| max.to_string()),
244 render_links_expression(expr),
245 ]),
246 GrammarExpr::Capture { label, expr } => label.as_ref().map_or_else(
247 || {
248 link(&[
249 word("capture"),
250 word("unlabeled"),
251 render_links_expression(expr),
252 ])
253 },
254 |label| {
255 link(&[
256 word("capture"),
257 word("labeled"),
258 percent_encode_links_text(label),
259 render_links_expression(expr),
260 ])
261 },
262 ),
263 GrammarExpr::Feature(feature) => render_links_feature(feature),
264 }
265}
266
267fn as_word(node: &Node) -> Option<&str> {
269 match node {
270 LiNo::Ref(word) => Some(word),
271 LiNo::Link {
272 id: Some(word),
273 values,
274 } if values.is_empty() => Some(word),
275 LiNo::Link { .. } => None,
276 }
277}
278
279pub(super) fn parts(node: &Node) -> Result<(&str, &[Node]), GrammarImportError> {
282 if let Some(word) = as_word(node) {
283 return Ok((word, &[]));
284 }
285 match node {
286 LiNo::Link { id: None, values } => match values.split_first() {
287 None => Err(links_error("empty link")),
288 Some((head, rest)) => as_word(head)
289 .map(|head| (head, rest))
290 .ok_or_else(|| links_error("a link must start with a word")),
291 },
292 LiNo::Link { id: Some(id), .. } => {
293 Err(links_error(format!("unexpected identified link {id}")))
294 }
295 LiNo::Ref(_) => unreachable!("a reference is a word"),
296 }
297}
298
299pub(super) fn word<'a>(node: Option<&'a Node>, what: &str) -> Result<&'a str, GrammarImportError> {
300 node.and_then(as_word)
301 .ok_or_else(|| links_error(format!("expected {what}")))
302}
303
304pub(super) fn decoded_word(node: Option<&Node>, what: &str) -> Result<String, GrammarImportError> {
305 percent_decode_links_text(word(node, what)?)
306}
307
308pub(super) fn character(node: Option<&Node>, what: &str) -> Result<char, GrammarImportError> {
309 let decoded = decoded_word(node, what)?;
310 let mut chars = decoded.chars();
311 match (chars.next(), chars.next()) {
312 (Some(value), None) => Ok(value),
313 _ => Err(links_error(format!("{what} must be one code point"))),
314 }
315}
316
317pub(super) fn arity(head: &str, args: &[Node], count: usize) -> Result<(), GrammarImportError> {
318 if args.len() == count {
319 Ok(())
320 } else {
321 Err(links_error(format!(
322 "{head} takes {count} value(s), not {}",
323 args.len()
324 )))
325 }
326}
327
328fn bound(node: &Node, allow_unbounded: bool) -> Result<Option<usize>, GrammarImportError> {
329 let text = word(Some(node), "a repetition bound")?;
330 if allow_unbounded && text == "unbounded" {
331 return Ok(None);
332 }
333 if text.is_empty() || !text.bytes().all(|byte| byte.is_ascii_digit()) {
334 return Err(links_error(format!("invalid repetition bound {text}")));
335 }
336 if text.len() > MAX_BOUND_DIGITS {
337 return Err(links_error(format!("repetition bound {text} is too large")));
338 }
339 Ok(Some(text.parse().expect("nine decimal digits fit usize")))
340}
341
342fn flag(node: &Node, yes: &str, no: &str) -> Result<bool, GrammarImportError> {
343 let text = word(Some(node), &format!("{yes} or {no}"))?;
344 if text != yes && text != no {
345 return Err(links_error(format!("expected {yes} or {no}, not {text}")));
346 }
347 Ok(text == yes)
348}
349
350fn inner(head: &str, args: &[Node]) -> Result<Box<GrammarExpr>, GrammarImportError> {
351 arity(head, args, 1)?;
352 Ok(Box::new(parse_node(&args[0])?))
353}
354
355pub(super) fn parse_node(node: &Node) -> Result<GrammarExpr, GrammarImportError> {
356 let (head, args) = parts(node)?;
357 Ok(match head {
358 "empty" | "any" => {
359 arity(head, args, 0)?;
360 if head == "empty" {
361 GrammarExpr::Empty
362 } else {
363 GrammarExpr::AnyChar
364 }
365 }
366 "literal" => {
367 arity(head, args, 1)?;
368 GrammarExpr::Terminal(decoded_word(args.first(), "a text")?)
369 }
370 "literalInsensitive" => {
371 arity(head, args, 1)?;
372 GrammarExpr::TerminalInsensitive(decoded_word(args.first(), "a text")?)
373 }
374 "range" => {
375 arity(head, args, 2)?;
376 GrammarExpr::CharRange(
377 character(args.first(), "a range start")?,
378 character(args.get(1), "a range end")?,
379 )
380 }
381 "class" => {
382 let Some((first, items)) = args.split_first() else {
383 return Err(links_error("class needs plain or negated"));
384 };
385 read_class(flag(first, "negated", "plain")?, items)?
386 }
387 "ref" => {
388 let Some((name, arguments)) = args.split_first() else {
389 return Err(links_error("ref takes a rule name"));
390 };
391 let name = decoded_word(Some(name), "a rule name")?;
392 if arguments.is_empty() {
393 GrammarExpr::NonTerminal(name)
394 } else {
395 GrammarExpr::feature(FeatureExpr::Call {
396 name,
397 arguments: arguments.iter().map(parse_node).collect::<Result<_, _>>()?,
398 })
399 }
400 }
401 "choice" => {
402 let Some((first, items)) = args.split_first() else {
403 return Err(links_error("choice needs ordered or unordered"));
404 };
405 let ordered = flag(first, "ordered", "unordered")?;
406 GrammarExpr::Choice {
407 ordered,
408 alternatives: items.iter().map(parse_node).collect::<Result<_, _>>()?,
409 }
410 }
411 "seq" => GrammarExpr::Sequence(args.iter().map(parse_node).collect::<Result<_, _>>()?),
412 "repeat" => {
413 arity(head, args, 3)?;
414 let min = bound(&args[0], false)?.expect("a lower bound is never unbounded");
415 let max = bound(&args[1], true)?;
416 if max.is_some_and(|max| max < min) {
417 return Err(links_error(format!(
418 "repetition bounds {min}, {} are reversed",
419 max.unwrap_or_default()
420 )));
421 }
422 GrammarExpr::Repeat {
423 expr: Box::new(parse_node(&args[2])?),
424 min,
425 max,
426 }
427 }
428 "capture" => {
429 let Some(first) = args.first() else {
430 return Err(links_error("capture needs labeled or unlabeled"));
431 };
432 if flag(first, "labeled", "unlabeled")? {
433 arity(head, args, 3)?;
434 GrammarExpr::Capture {
435 label: Some(decoded_word(args.get(1), "a capture label")?),
436 expr: Box::new(parse_node(&args[2])?),
437 }
438 } else {
439 arity(head, args, 2)?;
440 GrammarExpr::Capture {
441 label: None,
442 expr: Box::new(parse_node(&args[1])?),
443 }
444 }
445 }
446 "optional" => GrammarExpr::Optional(inner(head, args)?),
447 "repeat0" => GrammarExpr::ZeroOrMore(inner(head, args)?),
448 "repeat1" => GrammarExpr::OneOrMore(inner(head, args)?),
449 "and" => GrammarExpr::And(inner(head, args)?),
450 "not" => GrammarExpr::Not(inner(head, args)?),
451 other => {
452 return read_feature(other, args)?
453 .ok_or_else(|| links_error(format!("unknown expression {other}")));
454 }
455 })
456}
457
458pub fn parse_links_expression(source: &str) -> Result<GrammarExpr, GrammarImportError> {
465 let statements = parse_statements(source)?;
466 match statements.as_slice() {
467 [node] => parse_node(node),
468 _ => Err(links_error("expected one expression")),
469 }
470}
471
472fn parse_statements(source: &str) -> Result<Vec<Node>, GrammarImportError> {
473 parse_lino_to_links_with_config(source, &ParserConfig::without_comments())
474 .map_err(|error| links_error(error.to_string()))
475}
476
477pub fn parse_grammar_links(source: &str) -> Result<Grammar, GrammarImportError> {
485 let statements = parse_statements(source)?;
486 let Some((header, statements)) = statements.split_first() else {
487 return Err(links_error("the links define no grammar"));
488 };
489 let (header_head, header_args) = parts(header)?;
490 if header_head != "grammar" {
491 return Err(links_error("the first link must be the grammar link"));
492 }
493 let mut source_format = None;
494 let mut start = None;
495 let mut declarations = GrammarDeclarations::default();
496 for field in header_args {
497 let (key, values) = parts(field)?;
498 if key == "settling" && declarations.settling.is_none() {
499 let steps = values
500 .iter()
501 .map(|value| word(Some(value), "a settling step").map(str::to_owned))
502 .collect::<Result<Vec<_>, _>>()?;
503 if let Some(problem) = settling_problem(&steps) {
504 return Err(links_error(problem));
505 }
506 declarations.settling = Some(steps);
507 continue;
508 }
509 arity(key, values, 1)?;
510 if key == "format" && source_format.is_none() {
511 let tag = word(values.first(), "a format")?;
512 source_format = Some(
513 GrammarFormat::from_tag(tag)
514 .ok_or_else(|| links_error(format!("unknown source format {tag}")))?,
515 );
516 } else if key == "start" && start.is_none() {
517 start = Some(decoded_word(values.first(), "a start rule")?);
518 } else if key == "matching" && declarations.matching.is_none() {
519 let matching = word(values.first(), "a matching")?;
520 if !MATCHING_MODES.contains(&matching) {
521 return Err(links_error(format!("unknown matching {matching}")));
522 }
523 declarations.matching = Some(matching.to_owned());
524 } else {
525 return Err(links_error(format!("unexpected grammar field {key}")));
526 }
527 }
528 let mut rules: Vec<GrammarRule> = Vec::new();
529 let mut kinds: Vec<GrammarKind> = Vec::new();
530 for statement in statements {
531 let (head, args) = parts(statement)?;
532 if head == "kind" && rules.is_empty() {
533 arity(head, args, 2)?;
534 let name = decoded_word(args.first(), "a kind name")?;
535 if kinds.iter().any(|kind| kind.name == name) {
536 return Err(links_error(format!("kind {name} is given twice")));
537 }
538 let (field, names) = parts(&args[1])?;
539 if field != "source-names" {
540 return Err(links_error(format!(
541 "expected (source-names ...), not {field}"
542 )));
543 }
544 let source_names = read_source_names(names)?;
545 kinds.push(GrammarKind { name, source_names });
546 continue;
547 }
548 if head != "rule" {
549 if !rules.is_empty() || !read_declaration(head, args, &mut declarations)? {
550 return Err(links_error(format!("unexpected link {head}")));
551 }
552 continue;
553 }
554 if args.len() < 3 {
555 return Err(links_error(
556 "rule takes a name, a kind, an expression and optional fields",
557 ));
558 }
559 let name = decoded_word(args.first(), "a rule name")?;
560 let kind = word(args.get(1), "a rule kind")?;
561 let kind = RuleKind::from_tag(kind)
562 .ok_or_else(|| links_error(format!("unknown rule kind {kind}")))?;
563 if rules.iter().any(|rule| rule.name == name) {
564 return Err(links_error(format!("rule {name} is defined twice")));
565 }
566 let expr = parse_node(&args[2])?;
567 let fields = read_rule_fields(&args[3..])?;
568 let mut rule = GrammarRule::new(name, expr)
569 .with_kind(kind)
570 .with_attributes(fields.attributes)
571 .with_source_names(fields.source_names);
572 if let Some(concept) = fields.concept {
573 rule = rule.with_concept(concept);
574 }
575 if let Some(doc) = fields.doc {
576 rule = rule.with_doc(doc);
577 }
578 rules.push(rule);
579 }
580 let Some(first) = rules.first().map(|rule| rule.name.clone()) else {
581 return Err(links_error("the links define no rules"));
582 };
583 if let Some(start) = &start
584 && !rules.iter().any(|rule| &rule.name == start)
585 {
586 return Err(links_error(format!("start rule {start} is not defined")));
587 }
588 let mut grammar = Grammar::new();
589 for rule in rules {
590 grammar.add_rule(rule);
591 }
592 grammar.set_start(start.unwrap_or(first));
593 if let Some(format) = source_format {
594 grammar.set_source_format(format);
595 }
596 grammar.set_declarations(declarations);
597 grammar.set_kinds(kinds);
598 Ok(grammar)
599}