lino/
parser.rs

1use nom::{
2    IResult,
3    branch::alt,
4    bytes::complete::{take_while, take_while1, is_not},
5    character::complete::{char, line_ending},
6    combinator::eof,
7    multi::{many0, many1},
8    sequence::{preceded, terminated, delimited},
9    Parser,
10};
11use std::cell::RefCell;
12
13#[derive(Debug, Clone, PartialEq)]
14pub struct Link {
15    pub id: Option<String>,
16    pub values: Vec<Link>,
17    pub children: Vec<Link>,
18}
19
20impl Link {
21    pub fn new_point(id: String) -> Self {
22        Link {
23            id: Some(id),
24            values: vec![],
25            children: vec![],
26        }
27    }
28
29    pub fn new_value(values: Vec<Link>) -> Self {
30        Link {
31            id: None,
32            values,
33            children: vec![],
34        }
35    }
36
37    pub fn new_link(id: Option<String>, values: Vec<Link>) -> Self {
38        Link {
39            id,
40            values,
41            children: vec![],
42        }
43    }
44
45    pub fn with_children(mut self, children: Vec<Link>) -> Self {
46        self.children = children;
47        self
48    }
49}
50
51pub struct ParserState {
52    indentation_stack: RefCell<Vec<usize>>,
53}
54
55impl ParserState {
56    pub fn new() -> Self {
57        ParserState {
58            indentation_stack: RefCell::new(vec![0]),
59        }
60    }
61
62    pub fn push_indentation(&self, indent: usize) {
63        self.indentation_stack.borrow_mut().push(indent);
64    }
65
66    pub fn pop_indentation(&self) {
67        let mut stack = self.indentation_stack.borrow_mut();
68        if stack.len() > 1 {
69            stack.pop();
70        }
71    }
72
73    pub fn current_indentation(&self) -> usize {
74        *self.indentation_stack.borrow().last().unwrap_or(&0)
75    }
76
77    pub fn check_indentation(&self, indent: usize) -> bool {
78        indent == self.current_indentation()
79    }
80}
81
82fn is_whitespace_char(c: char) -> bool {
83    c == ' ' || c == '\t' || c == '\n' || c == '\r'
84}
85
86fn is_horizontal_whitespace(c: char) -> bool {
87    c == ' ' || c == '\t'
88}
89
90fn is_reference_char(c: char) -> bool {
91    !is_whitespace_char(c) && c != '(' && c != ':' && c != ')'
92}
93
94fn horizontal_whitespace(input: &str) -> IResult<&str, &str> {
95    take_while(is_horizontal_whitespace)(input)
96}
97
98fn whitespace(input: &str) -> IResult<&str, &str> {
99    take_while(is_whitespace_char)(input)
100}
101
102fn simple_reference(input: &str) -> IResult<&str, String> {
103    take_while1(is_reference_char)
104        .map(|s: &str| s.to_string())
105        .parse(input)
106}
107
108fn double_quoted_reference(input: &str) -> IResult<&str, String> {
109    delimited(
110        char('"'),
111        is_not("\""),
112        char('"')
113    )
114    .map(|s: &str| s.to_string())
115    .parse(input)
116}
117
118fn single_quoted_reference(input: &str) -> IResult<&str, String> {
119    delimited(
120        char('\''),
121        is_not("'"),
122        char('\'')
123    )
124    .map(|s: &str| s.to_string())
125    .parse(input)
126}
127
128fn reference(input: &str) -> IResult<&str, String> {
129    alt((
130        double_quoted_reference,
131        single_quoted_reference,
132        simple_reference,
133    )).parse(input)
134}
135
136fn eol(input: &str) -> IResult<&str, &str> {
137    alt((
138        preceded(horizontal_whitespace, line_ending),
139        preceded(horizontal_whitespace, eof),
140    )).parse(input)
141}
142
143fn point_link(input: &str) -> IResult<&str, Link> {
144    reference.map(Link::new_point).parse(input)
145}
146
147fn single_line_point_link(input: &str) -> IResult<&str, Link> {
148    preceded(horizontal_whitespace, point_link).parse(input)
149}
150
151fn multi_line_point_link(input: &str) -> IResult<&str, Link> {
152    delimited(
153        (char('('), whitespace),
154        point_link,
155        (whitespace, char(')'))
156    ).parse(input)
157}
158
159fn reference_or_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
160    alt((
161        |i| multi_line_any_link(i, state),
162        reference.map(Link::new_point),
163    )).parse(input)
164}
165
166fn multi_line_value_and_whitespace<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
167    terminated(
168        |i| reference_or_link(i, state),
169        whitespace
170    ).parse(input)
171}
172
173fn multi_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
174    preceded(
175        whitespace,
176        many0(|i| multi_line_value_and_whitespace(i, state))
177    ).parse(input)
178}
179
180fn single_line_value_and_whitespace<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
181    preceded(
182        horizontal_whitespace,
183        |i| reference_or_link(i, state)
184    ).parse(input)
185}
186
187fn single_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
188    many1(|i| single_line_value_and_whitespace(i, state)).parse(input)
189}
190
191fn single_line_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
192    alt((
193        (
194            horizontal_whitespace,
195            reference,
196            horizontal_whitespace,
197            char(':'),
198            |i| single_line_values(i, state)
199        ).map(|(_, id, _, _, values)| Link::new_link(Some(id), values)),
200        (
201            horizontal_whitespace,
202            char(':'),
203            |i| single_line_values(i, state)
204        ).map(|(_, _, values)| Link::new_link(None, values)),
205    )).parse(input)
206}
207
208fn multi_line_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
209    alt((
210        (
211            char('('),
212            whitespace,
213            reference,
214            whitespace,
215            char(':'),
216            |i| multi_line_values(i, state),
217            whitespace,
218            char(')')
219        ).map(|(_, _, id, _, _, values, _, _)| Link::new_link(Some(id), values)),
220        (
221            char('('),
222            whitespace,
223            char(':'),
224            |i| multi_line_values(i, state),
225            whitespace,
226            char(')')
227        ).map(|(_, _, _, values, _, _)| Link::new_link(None, values)),
228    )).parse(input)
229}
230
231fn single_line_value_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
232    (|i| single_line_values(i, state))
233        .map(Link::new_value)
234        .parse(input)
235}
236
237fn multi_line_value_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
238    (
239        char('('),
240        |i| multi_line_values(i, state),
241        whitespace,
242        char(')')
243    ).map(|(_, values, _, _)| Link::new_value(values))
244    .parse(input)
245}
246
247fn multi_line_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
248    alt((
249        multi_line_point_link,
250        |i| multi_line_value_link(i, state),
251        |i| multi_line_link(i, state),
252    )).parse(input)
253}
254
255fn single_line_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
256    alt((
257        terminated(|i| single_line_link(i, state), eol),
258        terminated(single_line_point_link, eol),
259        terminated(|i| single_line_value_link(i, state), eol),
260    )).parse(input)
261}
262
263fn any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
264    alt((
265        terminated(|i| multi_line_any_link(i, state), eol),
266        |i| single_line_any_link(i, state),
267    )).parse(input)
268}
269
270fn count_indentation(input: &str) -> IResult<&str, usize> {
271    take_while(|c| c == ' ')
272        .map(|s: &str| s.len())
273        .parse(input)
274}
275
276fn push_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
277    let (input, spaces) = count_indentation(input)?;
278    let current = state.current_indentation();
279    
280    if spaces > current {
281        state.push_indentation(spaces);
282        Ok((input, ()))
283    } else {
284        Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)))
285    }
286}
287
288fn check_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
289    let (input, spaces) = count_indentation(input)?;
290    
291    if state.check_indentation(spaces) {
292        Ok((input, ()))
293    } else {
294        Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)))
295    }
296}
297
298fn element<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
299    let (input, link) = any_link(input, state)?;
300    
301    if let Ok((input, _)) = push_indentation(input, state) {
302        let (input, children) = links(input, state)?;
303        Ok((input, link.with_children(children)))
304    } else {
305        Ok((input, link))
306    }
307}
308
309fn first_line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
310    element(input, state)
311}
312
313fn line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
314    preceded(
315        |i| check_indentation(i, state),
316        |i| element(i, state)
317    ).parse(input)
318}
319
320fn links<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
321    let (input, first) = first_line(input, state)?;
322    let (input, rest) = many0(|i| line(i, state)).parse(input)?;
323    
324    state.pop_indentation();
325    
326    let mut result = vec![first];
327    result.extend(rest);
328    Ok((input, result))
329}
330
331pub fn parse_document(input: &str) -> IResult<&str, Vec<Link>> {
332    let state = ParserState::new();
333    
334    // Skip leading whitespace but preserve the line structure
335    let input = input.trim_start_matches(|c: char| c == '\n' || c == '\r');
336    
337    // Handle empty or whitespace-only documents
338    if input.trim().is_empty() {
339        return Ok(("", vec![]));
340    }
341    
342    let (input, result) = links(input, &state)?;
343    let (input, _) = whitespace(input)?;
344    let (input, _) = eof(input)?;
345    
346    Ok((input, result))
347}
348