Skip to main content

links_notation/
parser.rs

1use crate::quotes::{read_reference, DelimitedReferences, Reading};
2use nom::{
3    branch::alt,
4    bytes::complete::{tag, take_while, take_while1},
5    character::complete::{char, line_ending},
6    combinator::eof,
7    multi::{many0, many1},
8    sequence::{preceded, terminated},
9    IResult, Parser,
10};
11use std::cell::{OnceCell, RefCell};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct Link {
16    pub id: Option<String>,
17    pub values: Vec<Link>,
18    pub children: Vec<Link>,
19    pub is_indented_id: bool,
20    /// Body of a parenthesized group, kept unflattened until the whole document
21    /// is transformed. `None` for every link that is not a parenthesized group.
22    pub nested: Option<Vec<Link>>,
23}
24
25impl Link {
26    pub fn new_singlet(id: String) -> Self {
27        Link {
28            id: Some(id),
29            values: vec![],
30            children: vec![],
31            is_indented_id: false,
32            nested: None,
33        }
34    }
35
36    pub fn new_indented_id(id: String) -> Self {
37        Link {
38            id: Some(id),
39            values: vec![],
40            children: vec![],
41            is_indented_id: true,
42            nested: None,
43        }
44    }
45
46    pub fn new_value(values: Vec<Link>) -> Self {
47        Link {
48            id: None,
49            values,
50            children: vec![],
51            is_indented_id: false,
52            nested: None,
53        }
54    }
55
56    pub fn new_link(id: Option<String>, values: Vec<Link>) -> Self {
57        Link {
58            id,
59            values,
60            children: vec![],
61            is_indented_id: false,
62            nested: None,
63        }
64    }
65
66    /// Creates a link that stands for a parenthesized group, keeping the links
67    /// parsed inside the parentheses as they were written.
68    pub fn new_nested(body: Vec<Link>) -> Self {
69        Link {
70            id: None,
71            values: vec![],
72            children: vec![],
73            is_indented_id: false,
74            nested: Some(body),
75        }
76    }
77
78    pub fn with_children(mut self, children: Vec<Link>) -> Self {
79        self.children = children;
80        self
81    }
82}
83
84pub struct ParserState {
85    indentation_stack: RefCell<Vec<usize>>,
86    base_indentation: RefCell<Option<usize>>,
87    nested_depth: RefCell<usize>,
88    furthest: RefCell<FurthestFailure>,
89    unreadable_lines: RefCell<HashMap<LineKey, LineFailure>>,
90    /// The delimited references of the document being parsed.
91    references: OnceCell<DelimitedReferences>,
92}
93
94/// Where a line starts, as an address into the document, and whether it is
95/// inside a parenthesized group. Reading a line depends on nothing else: a group
96/// starts a fresh indentation context, and indentation only decides which lines
97/// become children, so a line that could not be read once never can be.
98type LineKey = (usize, bool);
99
100/// How far past the start of an unreadable line the parser failed, and the
101/// `nom` error kind it failed with.
102type LineFailure = (usize, nom::error::ErrorKind);
103
104/// The furthest position any alternative reached before failing, and what could
105/// have continued the document there.
106///
107/// The parser backtracks, so the position the last alternative happens to fail
108/// at says little about where the document stops making sense: a defect in the
109/// middle of line two is reported by `nom` as "expected end of input" at the
110/// start of line two, because that is where the document last parsed cleanly.
111/// The furthest position reached is what a PEG parser points at, and it is what
112/// the JavaScript port reports.
113#[derive(Debug, Clone, Default)]
114struct FurthestFailure {
115    /// Address of the furthest failing position, as a pointer into the document
116    /// being parsed. Turned into an offset once the document is at hand again.
117    address: Option<usize>,
118    expected: Vec<&'static str>,
119}
120
121/// Where the parser stopped, and what it could have accepted there.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct ParseFailure {
124    /// Byte offset into the document the parser stopped at.
125    pub offset: usize,
126    /// What could have continued the document at `offset`, in the wording used
127    /// by the error message. Empty when the failure came from a place that
128    /// names no expectation.
129    pub expected: Vec<&'static str>,
130    /// The `nom` error kind. An internal detail of this parser: it says which
131    /// combinator gave up, not what is wrong with the document, so it is kept
132    /// out of the error message and reachable only through `Debug`.
133    pub kind: Option<nom::error::ErrorKind>,
134}
135
136/// Indentation state of the context a parenthesized group was opened in.
137pub struct SavedContext {
138    indentation_stack: Vec<usize>,
139    base_indentation: Option<usize>,
140}
141
142impl Default for ParserState {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl ParserState {
149    pub fn new() -> Self {
150        ParserState {
151            indentation_stack: RefCell::new(vec![0]),
152            base_indentation: RefCell::new(None),
153            nested_depth: RefCell::new(0),
154            furthest: RefCell::new(FurthestFailure::default()),
155            unreadable_lines: RefCell::new(HashMap::new()),
156            references: OnceCell::new(),
157        }
158    }
159
160    pub fn set_base_indentation(&self, indent: usize) {
161        let mut base = self.base_indentation.borrow_mut();
162        if base.is_none() {
163            *base = Some(indent);
164        }
165    }
166
167    pub fn get_base_indentation(&self) -> usize {
168        self.base_indentation.borrow().unwrap_or(0)
169    }
170
171    pub fn normalize_indentation(&self, indent: usize) -> usize {
172        let base = self.get_base_indentation();
173        indent.saturating_sub(base)
174    }
175
176    pub fn push_indentation(&self, indent: usize) {
177        self.indentation_stack.borrow_mut().push(indent);
178    }
179
180    pub fn pop_indentation(&self) {
181        let mut stack = self.indentation_stack.borrow_mut();
182        if stack.len() > 1 {
183            stack.pop();
184        }
185    }
186
187    pub fn current_indentation(&self) -> usize {
188        *self.indentation_stack.borrow().last().unwrap_or(&0)
189    }
190
191    pub fn check_indentation(&self, indent: usize) -> bool {
192        indent >= self.current_indentation()
193    }
194
195    /// Opens a nested context: the group body starts fresh at indentation level
196    /// zero and follows the same rules as the root document.
197    pub fn enter_nested_context(&self) -> SavedContext {
198        let saved = SavedContext {
199            indentation_stack: self.indentation_stack.replace(vec![0]),
200            base_indentation: self.base_indentation.replace(None),
201        };
202        *self.nested_depth.borrow_mut() += 1;
203        saved
204    }
205
206    /// Restores the context the parenthesized group was opened in.
207    pub fn exit_nested_context(&self, saved: SavedContext) {
208        *self.indentation_stack.borrow_mut() = saved.indentation_stack;
209        *self.base_indentation.borrow_mut() = saved.base_indentation;
210        let mut depth = self.nested_depth.borrow_mut();
211        if *depth > 0 {
212            *depth -= 1;
213        }
214    }
215
216    pub fn is_inside_nested_context(&self) -> bool {
217        *self.nested_depth.borrow() > 0
218    }
219
220    /// Records that `what` could have continued the document at `at`, and that
221    /// nothing there did. Only the furthest such position is kept; every
222    /// expectation recorded at that same position is kept alongside it.
223    fn expected_at(&self, at: &str, what: &'static str) {
224        let address = at.as_ptr() as usize;
225        let mut furthest = self.furthest.borrow_mut();
226        match furthest.address {
227            Some(recorded) if recorded > address => {}
228            Some(recorded) if recorded == address => {
229                if !furthest.expected.contains(&what) {
230                    furthest.expected.push(what);
231                }
232            }
233            _ => {
234                furthest.address = Some(address);
235                furthest.expected = vec![what];
236            }
237        }
238    }
239
240    /// Turns everything recorded during a failed parse into a position in
241    /// `document`.
242    ///
243    /// `nom`'s own error position is the fallback and the floor: the parser
244    /// reached at least that far, whatever the tracked alternatives say.
245    fn failure(&self, document: &str, error: &nom::Err<nom::error::Error<&str>>) -> ParseFailure {
246        let base = document.as_ptr() as usize;
247        let (nom_offset, kind) = match error {
248            nom::Err::Error(e) | nom::Err::Failure(e) => (
249                (e.input.as_ptr() as usize).saturating_sub(base),
250                Some(e.code),
251            ),
252            nom::Err::Incomplete(_) => (document.len(), None),
253        };
254        let furthest = self.furthest.borrow();
255        let tracked = furthest
256            .address
257            .map(|address| address.saturating_sub(base))
258            .unwrap_or(0);
259        let offset = tracked.max(nom_offset).min(document.len());
260        let expected = if tracked == offset {
261            furthest.expected.clone()
262        } else {
263            // The tracked expectations belong to an earlier position, so they
264            // do not describe the place being reported.
265            Vec::new()
266        };
267        ParseFailure {
268            offset,
269            expected,
270            kind,
271        }
272    }
273}
274
275/// Fails the way `nom` does, after recording what was expected at `input`.
276fn expected<'a, T>(
277    input: &'a str,
278    state: &ParserState,
279    what: &'static str,
280    kind: nom::error::ErrorKind,
281) -> IResult<&'a str, T> {
282    state.expected_at(input, what);
283    Err(nom::Err::Error(nom::error::Error::new(input, kind)))
284}
285
286pub(crate) fn is_whitespace_char(c: char) -> bool {
287    c == ' ' || c == '\t' || c == '\n' || c == '\r'
288}
289
290fn is_horizontal_whitespace(c: char) -> bool {
291    c == ' ' || c == '\t'
292}
293
294fn is_reference_char(c: char) -> bool {
295    !is_whitespace_char(c) && c != '(' && c != ':' && c != ')'
296}
297
298fn horizontal_whitespace(input: &str) -> IResult<&str, &str> {
299    take_while(is_horizontal_whitespace)(input)
300}
301
302fn whitespace(input: &str) -> IResult<&str, &str> {
303    take_while(is_whitespace_char)(input)
304}
305
306fn simple_reference(input: &str) -> IResult<&str, String> {
307    take_while1(is_reference_char)
308        .map(|s: &str| s.to_string())
309        .parse(input)
310}
311
312/// The offset just past the delimited reference that starts at `start`, or
313/// `None` when nothing that far into `document` opens one.
314///
315/// Comment stripping needs to know how far a delimited reference reaches so
316/// that a `#` written inside one stays content, and it has to agree with the
317/// parser about it, which is why both read references the same way.
318pub fn quoted_reference_end(document: &str, start: usize) -> Option<usize> {
319    let reading = read_reference(document.get(start..)?)?;
320    Some(start + reading.length)
321}
322
323/// A delimited reference with any number of delimiters. A run of an even
324/// number of delimiters that does not open a reference with a substantive body
325/// is the empty reference.
326fn delimited_reference<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, String> {
327    let references = state
328        .references
329        .get_or_init(|| DelimitedReferences::new(input));
330    match references.read(input) {
331        Some(Reading { value, length }) => Ok((&input[length..], value)),
332        None => Err(nom::Err::Error(nom::error::Error::new(
333            input,
334            nom::error::ErrorKind::Tag,
335        ))),
336    }
337}
338
339fn reference<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, String> {
340    // Try quoted strings with dynamic quote detection (supports any N quotes)
341    // Then fall back to simple unquoted reference
342    let parsed = alt((|i| delimited_reference(i, state), simple_reference)).parse(input);
343    if parsed.is_err() {
344        state.expected_at(input, "a reference");
345    }
346    parsed
347}
348
349fn eol<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
350    let parsed = alt((
351        preceded(horizontal_whitespace, alt((line_ending, tag("\r")))),
352        preceded(horizontal_whitespace, eof),
353        |i| nested_group_end(i, state),
354    ))
355    .parse(input);
356    if parsed.is_err() {
357        state.expected_at(input, "end of line");
358    }
359    parsed
360}
361
362/// Inside a parenthesized group the closing parenthesis ends the last line,
363/// just like a line break does at the root.
364fn nested_group_end<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
365    if !state.is_inside_nested_context() {
366        return Err(nom::Err::Error(nom::error::Error::new(
367            input,
368            nom::error::ErrorKind::Verify,
369        )));
370    }
371    let (rest, _) = horizontal_whitespace(input)?;
372    if rest.starts_with(')') {
373        Ok((rest, ""))
374    } else {
375        expected(rest, state, "\")\"", nom::error::ErrorKind::Char)
376    }
377}
378
379/// Skips the line breaks and blank lines that separate `(` from the first line
380/// of the group body.
381fn skip_empty_lines(input: &str) -> &str {
382    let mut rest = input;
383    loop {
384        let line_start = rest.trim_start_matches(is_horizontal_whitespace);
385        match strip_line_ending(line_start) {
386            Some(next) => rest = next,
387            None => return rest,
388        }
389    }
390}
391
392fn strip_line_ending(input: &str) -> Option<&str> {
393    input
394        .strip_prefix("\r\n")
395        .or_else(|| input.strip_prefix('\n'))
396        .or_else(|| input.strip_prefix('\r'))
397}
398
399fn reference_or_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
400    alt((
401        |i| nested_group(i, state),
402        (|i| reference(i, state)).map(Link::new_singlet),
403    ))
404    .parse(input)
405}
406
407fn single_line_value_and_whitespace<'a>(
408    input: &'a str,
409    state: &ParserState,
410) -> IResult<&'a str, Link> {
411    preceded(horizontal_whitespace, |i| reference_or_link(i, state)).parse(input)
412}
413
414fn single_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
415    many1(|i| single_line_value_and_whitespace(i, state)).parse(input)
416}
417
418fn single_line_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
419    let (input, _) = horizontal_whitespace(input)?;
420    let (input, id) = reference(input, state)?;
421    let (input, _) = horizontal_whitespace(input)?;
422    let (input, _) = colon(input, state)?;
423    let (input, values) = single_line_values(input, state)?;
424    Ok((input, Link::new_link(Some(id), values)))
425}
426
427/// The colon that separates an identifier from its values.
428fn colon<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, char> {
429    character(':', input, state, "\":\"")
430}
431
432/// Matches one character, recording what was expected when it is not there.
433fn character<'a>(
434    wanted: char,
435    input: &'a str,
436    state: &ParserState,
437    what: &'static str,
438) -> IResult<&'a str, char> {
439    let parsed: IResult<&'a str, char> = char(wanted).parse(input);
440    match parsed {
441        Ok(parsed) => Ok(parsed),
442        Err(_) => expected(input, state, what, nom::error::ErrorKind::Char),
443    }
444}
445
446fn single_line_value_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
447    (|i| single_line_values(i, state))
448        .map(Link::new_value)
449        .parse(input)
450}
451
452fn indented_id_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
453    let (input, id) = reference(input, state)?;
454    let (input, _) = horizontal_whitespace(input)?;
455    let (input, _) = colon(input, state)?;
456    let (input, _) = eol(input, state)?;
457    Ok((input, Link::new_indented_id(id)))
458}
459
460/// A parenthesized group opens a nested context: its body starts fresh at
461/// indentation level zero and is parsed with the same rules as the root
462/// document, so indentation is structural inside parentheses as well.
463fn nested_group<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
464    let (body_input, _) = character('(', input, state, "\"(\"")?;
465    let saved = state.enter_nested_context();
466    let result = nested_group_body(body_input, state);
467    state.exit_nested_context(saved);
468    result
469}
470
471fn nested_group_body<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
472    if let Ok((rest, body)) = links(skip_empty_lines(input), state) {
473        let (rest, _) = whitespace(rest)?;
474        let (rest, _) = closing_parenthesis(rest, state)?;
475        return Ok((rest, Link::new_nested(body)));
476    }
477    let (rest, _) = whitespace(input)?;
478    let (rest, _) = closing_parenthesis(rest, state)?;
479    Ok((rest, Link::new_nested(vec![])))
480}
481
482/// The parenthesis that closes a group.
483fn closing_parenthesis<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, char> {
484    character(')', input, state, "\")\"")
485}
486
487fn single_line_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
488    alt((
489        terminated(|i| single_line_link(i, state), |i| eol(i, state)),
490        terminated(|i| single_line_value_link(i, state), |i| eol(i, state)),
491    ))
492    .parse(input)
493}
494
495/// Reads one line, or fails at once when the line was already found unreadable.
496///
497/// A line that does not parse as the first child of the line above it is tried
498/// again as a sibling at every enclosing indentation level. When the line holds
499/// a group, each of those attempts reads the whole group again, so without this
500/// the work doubles with every indented level (issue #314).
501fn any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
502    let key = (input.as_ptr() as usize, state.is_inside_nested_context());
503    if let Some(&(offset, kind)) = state.unreadable_lines.borrow().get(&key) {
504        return Err(nom::Err::Error(nom::error::Error::new(
505            &input[offset..],
506            kind,
507        )));
508    }
509    let parsed = read_any_link(input, state);
510    if let Err(nom::Err::Error(error)) = &parsed {
511        let offset = error.input.as_ptr() as usize - input.as_ptr() as usize;
512        state
513            .unreadable_lines
514            .borrow_mut()
515            .insert(key, (offset, error.code));
516    }
517    parsed
518}
519
520/// A line that starts with a parenthesized group reads that group once and then
521/// branches on what follows it: the end of the line makes the group the whole
522/// link, and more values make it the first value of a value link.
523///
524/// Every other alternative would have to read the group again, which doubles
525/// the work at each level of nesting and makes a few dozen bytes take seconds
526/// ([#314](https://github.com/link-foundation/links-notation/issues/314)).
527fn read_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
528    let (rest, group) = match nested_group(input, state) {
529        Ok(parsed) => parsed,
530        // Neither an indented ID nor a single-line link can start with a
531        // parenthesis, and a value link would begin with this same group, so
532        // nothing else can read a line that opens one. Report it the way the
533        // single-line value link does, as a missing reference.
534        Err(_) if input.starts_with('(') => {
535            return reference(input, state).map(|(rest, id)| (rest, Link::new_singlet(id)))
536        }
537        Err(_) => {
538            return alt((
539                |i| indented_id_link(i, state),
540                |i| single_line_any_link(i, state),
541            ))
542            .parse(input)
543        }
544    };
545    if let Ok((rest, _)) = eol(rest, state) {
546        return Ok((rest, group));
547    }
548    let (rest, more) = many0(|i| single_line_value_and_whitespace(i, state)).parse(rest)?;
549    let (rest, _) = eol(rest, state)?;
550    let mut values = Vec::with_capacity(more.len() + 1);
551    values.push(group);
552    values.extend(more);
553    Ok((rest, Link::new_value(values)))
554}
555
556fn count_indentation(input: &str) -> IResult<&str, usize> {
557    take_while(|c| c == ' ').map(|s: &str| s.len()).parse(input)
558}
559
560fn push_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
561    let (input, spaces) = count_indentation(skip_empty_lines(input))?;
562    let normalized_spaces = state.normalize_indentation(spaces);
563    let current = state.current_indentation();
564
565    if normalized_spaces > current {
566        state.push_indentation(normalized_spaces);
567        Ok((input, ()))
568    } else {
569        Err(nom::Err::Error(nom::error::Error::new(
570            input,
571            nom::error::ErrorKind::Verify,
572        )))
573    }
574}
575
576fn check_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
577    let (input, spaces) = count_indentation(input)?;
578    let normalized_spaces = state.normalize_indentation(spaces);
579
580    if state.check_indentation(normalized_spaces) {
581        Ok((input, ()))
582    } else {
583        Err(nom::Err::Error(nom::error::Error::new(
584            input,
585            nom::error::ErrorKind::Verify,
586        )))
587    }
588}
589
590fn element<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
591    let (input, link) = any_link(input, state)?;
592
593    let indentation = state.indentation_stack.borrow().clone();
594    if let Ok((child_input, _)) = push_indentation(input, state) {
595        if let Ok((rest, children)) = links(child_input, state) {
596            return Ok((rest, link.with_children(children)));
597        }
598        // No child line followed the indentation. Backtrack to the link so
599        // the document can consume the remaining spaces as whitespace.
600        state.indentation_stack.replace(indentation);
601    }
602    Ok((input, link))
603}
604
605fn first_line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
606    // Set base indentation from the first line and consume it, so that the first
607    // line is parsed exactly like every following line.
608    let (input, spaces) = count_indentation(input)?;
609    state.set_base_indentation(spaces);
610    element(input, state)
611}
612
613fn line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
614    // Blank lines do not break a document, they are simply skipped
615    preceded(|i| check_indentation(i, state), |i| element(i, state)).parse(skip_empty_lines(input))
616}
617
618fn links<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
619    let (input, first) = first_line(input, state)?;
620    let (input, rest) = many0(|i| line(i, state)).parse(input)?;
621
622    state.pop_indentation();
623
624    let mut result = vec![first];
625    result.extend(rest);
626    Ok((input, result))
627}
628
629pub fn parse_document(input: &str) -> IResult<&str, Vec<Link>> {
630    let state = ParserState::new();
631    document(input, &state)
632}
633
634/// Parses a document and, when it does not parse, says where it stopped.
635///
636/// `parse_document` reports a failure the way `nom` does: with the whole
637/// unconsumed remainder of the input and the combinator that gave up. Neither
638/// tells a reader which line to look at, and the remainder grows with the size
639/// of the document. This is the entry point the library uses.
640pub fn parse_document_with_diagnostics(input: &str) -> Result<Vec<Link>, ParseFailure> {
641    let state = ParserState::new();
642    match document(input, &state) {
643        Ok((_, links)) => Ok(links),
644        Err(error) => Err(state.failure(input, &error)),
645    }
646}
647
648fn document<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
649    // Every reference is read from a part of this document, so the runs of
650    // delimiters are listed from all of it.
651    state
652        .references
653        .get_or_init(|| DelimitedReferences::new(input));
654
655    // Skip leading blank lines but preserve the line structure
656    let document = skip_empty_lines(input);
657
658    // Handle empty or whitespace-only documents
659    if document.trim_matches(is_whitespace_char).is_empty() {
660        return Ok(("", vec![]));
661    }
662
663    let (rest, result) = links(document, state)?;
664    let (rest, _) = whitespace(rest)?;
665    let end: IResult<&'a str, &'a str> = eof(rest);
666    let (rest, _) = match end {
667        Ok(parsed) => parsed,
668        Err(_) => return expected(rest, state, "end of input", nom::error::ErrorKind::Eof),
669    };
670
671    Ok((rest, result))
672}