links-notation 0.20.0

Rust implementation of the Links Notation parser
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
use nom::{
    branch::alt,
    bytes::complete::{take_while, take_while1},
    character::complete::{char, line_ending},
    combinator::eof,
    multi::{many0, many1},
    sequence::{preceded, terminated},
    IResult, Parser,
};
use std::cell::RefCell;

#[derive(Debug, Clone, PartialEq)]
pub struct Link {
    pub id: Option<String>,
    pub values: Vec<Link>,
    pub children: Vec<Link>,
    pub is_indented_id: bool,
    /// Body of a parenthesized group, kept unflattened until the whole document
    /// is transformed. `None` for every link that is not a parenthesized group.
    pub nested: Option<Vec<Link>>,
}

impl Link {
    pub fn new_singlet(id: String) -> Self {
        Link {
            id: Some(id),
            values: vec![],
            children: vec![],
            is_indented_id: false,
            nested: None,
        }
    }

    pub fn new_indented_id(id: String) -> Self {
        Link {
            id: Some(id),
            values: vec![],
            children: vec![],
            is_indented_id: true,
            nested: None,
        }
    }

    pub fn new_value(values: Vec<Link>) -> Self {
        Link {
            id: None,
            values,
            children: vec![],
            is_indented_id: false,
            nested: None,
        }
    }

    pub fn new_link(id: Option<String>, values: Vec<Link>) -> Self {
        Link {
            id,
            values,
            children: vec![],
            is_indented_id: false,
            nested: None,
        }
    }

    /// Creates a link that stands for a parenthesized group, keeping the links
    /// parsed inside the parentheses as they were written.
    pub fn new_nested(body: Vec<Link>) -> Self {
        Link {
            id: None,
            values: vec![],
            children: vec![],
            is_indented_id: false,
            nested: Some(body),
        }
    }

    pub fn with_children(mut self, children: Vec<Link>) -> Self {
        self.children = children;
        self
    }
}

pub struct ParserState {
    indentation_stack: RefCell<Vec<usize>>,
    base_indentation: RefCell<Option<usize>>,
    nested_depth: RefCell<usize>,
    furthest: RefCell<FurthestFailure>,
}

/// The furthest position any alternative reached before failing, and what could
/// have continued the document there.
///
/// The parser backtracks, so the position the last alternative happens to fail
/// at says little about where the document stops making sense: a defect in the
/// middle of line two is reported by `nom` as "expected end of input" at the
/// start of line two, because that is where the document last parsed cleanly.
/// The furthest position reached is what a PEG parser points at, and it is what
/// the JavaScript port reports.
#[derive(Debug, Clone, Default)]
struct FurthestFailure {
    /// Address of the furthest failing position, as a pointer into the document
    /// being parsed. Turned into an offset once the document is at hand again.
    address: Option<usize>,
    expected: Vec<&'static str>,
}

/// Where the parser stopped, and what it could have accepted there.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseFailure {
    /// Byte offset into the document the parser stopped at.
    pub offset: usize,
    /// What could have continued the document at `offset`, in the wording used
    /// by the error message. Empty when the failure came from a place that
    /// names no expectation.
    pub expected: Vec<&'static str>,
    /// The `nom` error kind. An internal detail of this parser: it says which
    /// combinator gave up, not what is wrong with the document, so it is kept
    /// out of the error message and reachable only through `Debug`.
    pub kind: Option<nom::error::ErrorKind>,
}

/// Indentation state of the context a parenthesized group was opened in.
pub struct SavedContext {
    indentation_stack: Vec<usize>,
    base_indentation: Option<usize>,
}

impl Default for ParserState {
    fn default() -> Self {
        Self::new()
    }
}

impl ParserState {
    pub fn new() -> Self {
        ParserState {
            indentation_stack: RefCell::new(vec![0]),
            base_indentation: RefCell::new(None),
            nested_depth: RefCell::new(0),
            furthest: RefCell::new(FurthestFailure::default()),
        }
    }

    pub fn set_base_indentation(&self, indent: usize) {
        let mut base = self.base_indentation.borrow_mut();
        if base.is_none() {
            *base = Some(indent);
        }
    }

    pub fn get_base_indentation(&self) -> usize {
        self.base_indentation.borrow().unwrap_or(0)
    }

    pub fn normalize_indentation(&self, indent: usize) -> usize {
        let base = self.get_base_indentation();
        indent.saturating_sub(base)
    }

    pub fn push_indentation(&self, indent: usize) {
        self.indentation_stack.borrow_mut().push(indent);
    }

    pub fn pop_indentation(&self) {
        let mut stack = self.indentation_stack.borrow_mut();
        if stack.len() > 1 {
            stack.pop();
        }
    }

    pub fn current_indentation(&self) -> usize {
        *self.indentation_stack.borrow().last().unwrap_or(&0)
    }

    pub fn check_indentation(&self, indent: usize) -> bool {
        indent >= self.current_indentation()
    }

    /// Opens a nested context: the group body starts fresh at indentation level
    /// zero and follows the same rules as the root document.
    pub fn enter_nested_context(&self) -> SavedContext {
        let saved = SavedContext {
            indentation_stack: self.indentation_stack.replace(vec![0]),
            base_indentation: self.base_indentation.replace(None),
        };
        *self.nested_depth.borrow_mut() += 1;
        saved
    }

    /// Restores the context the parenthesized group was opened in.
    pub fn exit_nested_context(&self, saved: SavedContext) {
        *self.indentation_stack.borrow_mut() = saved.indentation_stack;
        *self.base_indentation.borrow_mut() = saved.base_indentation;
        let mut depth = self.nested_depth.borrow_mut();
        if *depth > 0 {
            *depth -= 1;
        }
    }

    pub fn is_inside_nested_context(&self) -> bool {
        *self.nested_depth.borrow() > 0
    }

    /// Records that `what` could have continued the document at `at`, and that
    /// nothing there did. Only the furthest such position is kept; every
    /// expectation recorded at that same position is kept alongside it.
    fn expected_at(&self, at: &str, what: &'static str) {
        let address = at.as_ptr() as usize;
        let mut furthest = self.furthest.borrow_mut();
        match furthest.address {
            Some(recorded) if recorded > address => {}
            Some(recorded) if recorded == address => {
                if !furthest.expected.contains(&what) {
                    furthest.expected.push(what);
                }
            }
            _ => {
                furthest.address = Some(address);
                furthest.expected = vec![what];
            }
        }
    }

    /// Turns everything recorded during a failed parse into a position in
    /// `document`.
    ///
    /// `nom`'s own error position is the fallback and the floor: the parser
    /// reached at least that far, whatever the tracked alternatives say.
    fn failure(&self, document: &str, error: &nom::Err<nom::error::Error<&str>>) -> ParseFailure {
        let base = document.as_ptr() as usize;
        let (nom_offset, kind) = match error {
            nom::Err::Error(e) | nom::Err::Failure(e) => (
                (e.input.as_ptr() as usize).saturating_sub(base),
                Some(e.code),
            ),
            nom::Err::Incomplete(_) => (document.len(), None),
        };
        let furthest = self.furthest.borrow();
        let tracked = furthest
            .address
            .map(|address| address.saturating_sub(base))
            .unwrap_or(0);
        let offset = tracked.max(nom_offset).min(document.len());
        let expected = if tracked == offset {
            furthest.expected.clone()
        } else {
            // The tracked expectations belong to an earlier position, so they
            // do not describe the place being reported.
            Vec::new()
        };
        ParseFailure {
            offset,
            expected,
            kind,
        }
    }
}

/// Fails the way `nom` does, after recording what was expected at `input`.
fn expected<'a, T>(
    input: &'a str,
    state: &ParserState,
    what: &'static str,
    kind: nom::error::ErrorKind,
) -> IResult<&'a str, T> {
    state.expected_at(input, what);
    Err(nom::Err::Error(nom::error::Error::new(input, kind)))
}

fn is_whitespace_char(c: char) -> bool {
    c == ' ' || c == '\t' || c == '\n' || c == '\r'
}

fn is_horizontal_whitespace(c: char) -> bool {
    c == ' ' || c == '\t'
}

fn is_reference_char(c: char) -> bool {
    !is_whitespace_char(c) && c != '(' && c != ':' && c != ')'
}

fn horizontal_whitespace(input: &str) -> IResult<&str, &str> {
    take_while(is_horizontal_whitespace)(input)
}

fn whitespace(input: &str) -> IResult<&str, &str> {
    take_while(is_whitespace_char)(input)
}

fn simple_reference(input: &str) -> IResult<&str, String> {
    take_while1(is_reference_char)
        .map(|s: &str| s.to_string())
        .parse(input)
}

/// Parse a multi-quote string with a given quote character and count.
/// For N quotes: opening = N quotes, closing = N quotes, escape = 2*N quotes -> N quotes
fn parse_multi_quote_string(
    input: &str,
    quote_char: char,
    quote_count: usize,
) -> IResult<&str, String> {
    let open_close = quote_char.to_string().repeat(quote_count);
    let escape_seq = quote_char.to_string().repeat(quote_count * 2);
    let escape_val = quote_char.to_string().repeat(quote_count);

    // Check for opening quotes
    if !input.starts_with(&open_close) {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Tag,
        )));
    }

    let mut remaining = &input[open_close.len()..];
    let mut content = String::new();

    loop {
        if remaining.is_empty() {
            return Err(nom::Err::Error(nom::error::Error::new(
                input,
                nom::error::ErrorKind::Tag,
            )));
        }

        // Check for escape sequence (2*N quotes)
        if remaining.starts_with(&escape_seq) {
            content.push_str(&escape_val);
            remaining = &remaining[escape_seq.len()..];
            continue;
        }

        // Check for closing quotes (N quotes not followed by more quotes)
        if remaining.starts_with(&open_close) {
            let after_close = &remaining[open_close.len()..];
            // Make sure this is exactly N quotes (not more)
            if after_close.is_empty() || !after_close.starts_with(quote_char) {
                return Ok((after_close, content));
            }
        }

        // Take the next character
        let c = remaining.chars().next().unwrap();
        content.push(c);
        remaining = &remaining[c.len_utf8()..];
    }
}

/// A body written between an even run of delimiters is substantive when it
/// holds at least one visible character and does not straddle a parenthesis.
/// An even run can always be read as delimiter pairs enclosing nothing, so the
/// n-quote reading is only taken when it carries something the pairs cannot.
fn is_substantive_body(content: &str) -> bool {
    let mut depth: isize = 0;
    let mut has_visible = false;

    for c in content.chars() {
        match c {
            '(' => depth += 1,
            ')' => {
                depth -= 1;
                if depth < 0 {
                    return false;
                }
            }
            _ => {}
        }
        if !c.is_whitespace() {
            has_visible = true;
        }
    }

    has_visible && depth == 0
}

/// Parse a quoted string with dynamically detected quote count.
///
/// Counts opening quotes and uses that count for parsing. A run of an even
/// number of delimiters that does not open a reference with a substantive body
/// is the empty reference: the shortest reading, a bare delimiter pair
/// enclosing nothing, wins over a longer n-quote delimiter.
fn parse_dynamic_quote_string(input: &str, quote_char: char) -> IResult<&str, String> {
    // Count opening quotes
    let quote_count = input.chars().take_while(|&c| c == quote_char).count();

    if quote_count == 0 {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Tag,
        )));
    }

    let is_even_run = quote_count % 2 == 0;

    if let Ok((rest, content)) = parse_multi_quote_string(input, quote_char, quote_count) {
        if !is_even_run || is_substantive_body(&content) {
            return Ok((rest, content));
        }
    }

    if is_even_run {
        return Ok((&input[quote_count * quote_char.len_utf8()..], String::new()));
    }

    Err(nom::Err::Error(nom::error::Error::new(
        input,
        nom::error::ErrorKind::Tag,
    )))
}

/// The offset just past the delimited reference that starts at `start`, or
/// `None` when nothing that far into `document` opens one.
///
/// Comment stripping needs to know how far a delimited reference reaches so
/// that a `#` written inside one stays content, and it has to agree with the
/// parser about it, which is why it asks the parser rather than scanning again.
pub fn quoted_reference_end(document: &str, start: usize) -> Option<usize> {
    let rest = document.get(start..)?;
    let quote = rest.chars().next()?;
    if !matches!(quote, '"' | '\'' | '`') {
        return None;
    }
    let (remaining, _) = parse_dynamic_quote_string(rest, quote).ok()?;
    Some(document.len() - remaining.len())
}

fn double_quoted_dynamic(input: &str) -> IResult<&str, String> {
    parse_dynamic_quote_string(input, '"')
}

fn single_quoted_dynamic(input: &str) -> IResult<&str, String> {
    parse_dynamic_quote_string(input, '\'')
}

fn backtick_quoted_dynamic(input: &str) -> IResult<&str, String> {
    parse_dynamic_quote_string(input, '`')
}

fn reference<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, String> {
    // Try quoted strings with dynamic quote detection (supports any N quotes)
    // Then fall back to simple unquoted reference
    let parsed = alt((
        double_quoted_dynamic,
        single_quoted_dynamic,
        backtick_quoted_dynamic,
        simple_reference,
    ))
    .parse(input);
    if parsed.is_err() {
        state.expected_at(input, "a reference");
    }
    parsed
}

fn eol<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
    let parsed = alt((
        preceded(horizontal_whitespace, line_ending),
        preceded(horizontal_whitespace, eof),
        |i| nested_group_end(i, state),
    ))
    .parse(input);
    if parsed.is_err() {
        state.expected_at(input, "end of line");
    }
    parsed
}

/// Inside a parenthesized group the closing parenthesis ends the last line,
/// just like a line break does at the root.
fn nested_group_end<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
    if !state.is_inside_nested_context() {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Verify,
        )));
    }
    let (rest, _) = horizontal_whitespace(input)?;
    if rest.starts_with(')') {
        Ok((rest, ""))
    } else {
        expected(rest, state, "\")\"", nom::error::ErrorKind::Char)
    }
}

/// Skips the line breaks and blank lines that separate `(` from the first line
/// of the group body.
fn skip_empty_lines(input: &str) -> &str {
    let mut rest = input;
    loop {
        let line_start = rest.trim_start_matches(is_horizontal_whitespace);
        match strip_line_ending(line_start) {
            Some(next) => rest = next,
            None => return rest,
        }
    }
}

fn strip_line_ending(input: &str) -> Option<&str> {
    input
        .strip_prefix("\r\n")
        .or_else(|| input.strip_prefix('\n'))
}

fn reference_or_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    alt((
        |i| nested_group(i, state),
        (|i| reference(i, state)).map(Link::new_singlet),
    ))
    .parse(input)
}

fn single_line_value_and_whitespace<'a>(
    input: &'a str,
    state: &ParserState,
) -> IResult<&'a str, Link> {
    preceded(horizontal_whitespace, |i| reference_or_link(i, state)).parse(input)
}

fn single_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
    many1(|i| single_line_value_and_whitespace(i, state)).parse(input)
}

fn single_line_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    let (input, _) = horizontal_whitespace(input)?;
    let (input, id) = reference(input, state)?;
    let (input, _) = horizontal_whitespace(input)?;
    let (input, _) = colon(input, state)?;
    let (input, values) = single_line_values(input, state)?;
    Ok((input, Link::new_link(Some(id), values)))
}

/// The colon that separates an identifier from its values.
fn colon<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, char> {
    character(':', input, state, "\":\"")
}

/// Matches one character, recording what was expected when it is not there.
fn character<'a>(
    wanted: char,
    input: &'a str,
    state: &ParserState,
    what: &'static str,
) -> IResult<&'a str, char> {
    let parsed: IResult<&'a str, char> = char(wanted).parse(input);
    match parsed {
        Ok(parsed) => Ok(parsed),
        Err(_) => expected(input, state, what, nom::error::ErrorKind::Char),
    }
}

fn single_line_value_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    (|i| single_line_values(i, state))
        .map(|values| {
            if values.len() == 1
                && values[0].id.is_some()
                && values[0].values.is_empty()
                && values[0].children.is_empty()
            {
                Link::new_singlet(values[0].id.clone().unwrap())
            } else {
                Link::new_value(values)
            }
        })
        .parse(input)
}

fn indented_id_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    let (input, id) = reference(input, state)?;
    let (input, _) = horizontal_whitespace(input)?;
    let (input, _) = colon(input, state)?;
    let (input, _) = eol(input, state)?;
    Ok((input, Link::new_indented_id(id)))
}

/// A parenthesized group opens a nested context: its body starts fresh at
/// indentation level zero and is parsed with the same rules as the root
/// document, so indentation is structural inside parentheses as well.
fn nested_group<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    let (body_input, _) = character('(', input, state, "\"(\"")?;
    let saved = state.enter_nested_context();
    let result = nested_group_body(body_input, state);
    state.exit_nested_context(saved);
    result
}

fn nested_group_body<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    if let Ok((rest, body)) = links(skip_empty_lines(input), state) {
        let (rest, _) = whitespace(rest)?;
        let (rest, _) = closing_parenthesis(rest, state)?;
        return Ok((rest, Link::new_nested(body)));
    }
    let (rest, _) = whitespace(input)?;
    let (rest, _) = closing_parenthesis(rest, state)?;
    Ok((rest, Link::new_nested(vec![])))
}

/// The parenthesis that closes a group.
fn closing_parenthesis<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, char> {
    character(')', input, state, "\")\"")
}

fn single_line_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    alt((
        terminated(|i| single_line_link(i, state), |i| eol(i, state)),
        terminated(|i| single_line_value_link(i, state), |i| eol(i, state)),
    ))
    .parse(input)
}

fn any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    alt((
        terminated(|i| nested_group(i, state), |i| eol(i, state)),
        |i| indented_id_link(i, state),
        |i| single_line_any_link(i, state),
    ))
    .parse(input)
}

fn count_indentation(input: &str) -> IResult<&str, usize> {
    take_while(|c| c == ' ').map(|s: &str| s.len()).parse(input)
}

fn push_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
    let (input, spaces) = count_indentation(skip_empty_lines(input))?;
    let normalized_spaces = state.normalize_indentation(spaces);
    let current = state.current_indentation();

    if normalized_spaces > current {
        state.push_indentation(normalized_spaces);
        Ok((input, ()))
    } else {
        Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Verify,
        )))
    }
}

fn check_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
    let (input, spaces) = count_indentation(input)?;
    let normalized_spaces = state.normalize_indentation(spaces);

    if state.check_indentation(normalized_spaces) {
        Ok((input, ()))
    } else {
        Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Verify,
        )))
    }
}

fn element<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    let (input, link) = any_link(input, state)?;

    if let Ok((input, _)) = push_indentation(input, state) {
        let (input, children) = links(input, state)?;
        Ok((input, link.with_children(children)))
    } else {
        Ok((input, link))
    }
}

fn first_line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    // Set base indentation from the first line and consume it, so that the first
    // line is parsed exactly like every following line.
    let (input, spaces) = count_indentation(input)?;
    state.set_base_indentation(spaces);
    element(input, state)
}

fn line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
    // Blank lines do not break a document, they are simply skipped
    preceded(|i| check_indentation(i, state), |i| element(i, state)).parse(skip_empty_lines(input))
}

fn links<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
    let (input, first) = first_line(input, state)?;
    let (input, rest) = many0(|i| line(i, state)).parse(input)?;

    state.pop_indentation();

    let mut result = vec![first];
    result.extend(rest);
    Ok((input, result))
}

pub fn parse_document(input: &str) -> IResult<&str, Vec<Link>> {
    let state = ParserState::new();
    document(input, &state)
}

/// Parses a document and, when it does not parse, says where it stopped.
///
/// `parse_document` reports a failure the way `nom` does: with the whole
/// unconsumed remainder of the input and the combinator that gave up. Neither
/// tells a reader which line to look at, and the remainder grows with the size
/// of the document. This is the entry point the library uses.
pub fn parse_document_with_diagnostics(input: &str) -> Result<Vec<Link>, ParseFailure> {
    let state = ParserState::new();
    match document(input, &state) {
        Ok((_, links)) => Ok(links),
        Err(error) => Err(state.failure(input, &error)),
    }
}

fn document<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
    // Skip leading blank lines but preserve the line structure
    let document = skip_empty_lines(input);

    // Handle empty or whitespace-only documents
    if document.trim().is_empty() {
        return Ok(("", vec![]));
    }

    let (rest, result) = links(document, state)?;
    let (rest, _) = whitespace(rest)?;
    let end: IResult<&'a str, &'a str> = eof(rest);
    let (rest, _) = match end {
        Ok(parsed) => parsed,
        Err(_) => return expected(rest, state, "end of input", nom::error::ErrorKind::Eof),
    };

    Ok((rest, result))
}