1use nom::{
2 branch::alt,
3 bytes::complete::{take_while, take_while1},
4 character::complete::{char, line_ending},
5 combinator::eof,
6 multi::{many0, many1},
7 sequence::{preceded, terminated},
8 IResult, Parser,
9};
10use std::cell::RefCell;
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct Link {
14 pub id: Option<String>,
15 pub values: Vec<Link>,
16 pub children: Vec<Link>,
17 pub is_indented_id: bool,
18 pub nested: Option<Vec<Link>>,
21}
22
23impl Link {
24 pub fn new_singlet(id: String) -> Self {
25 Link {
26 id: Some(id),
27 values: vec![],
28 children: vec![],
29 is_indented_id: false,
30 nested: None,
31 }
32 }
33
34 pub fn new_indented_id(id: String) -> Self {
35 Link {
36 id: Some(id),
37 values: vec![],
38 children: vec![],
39 is_indented_id: true,
40 nested: None,
41 }
42 }
43
44 pub fn new_value(values: Vec<Link>) -> Self {
45 Link {
46 id: None,
47 values,
48 children: vec![],
49 is_indented_id: false,
50 nested: None,
51 }
52 }
53
54 pub fn new_link(id: Option<String>, values: Vec<Link>) -> Self {
55 Link {
56 id,
57 values,
58 children: vec![],
59 is_indented_id: false,
60 nested: None,
61 }
62 }
63
64 pub fn new_nested(body: Vec<Link>) -> Self {
67 Link {
68 id: None,
69 values: vec![],
70 children: vec![],
71 is_indented_id: false,
72 nested: Some(body),
73 }
74 }
75
76 pub fn with_children(mut self, children: Vec<Link>) -> Self {
77 self.children = children;
78 self
79 }
80}
81
82pub struct ParserState {
83 indentation_stack: RefCell<Vec<usize>>,
84 base_indentation: RefCell<Option<usize>>,
85 nested_depth: RefCell<usize>,
86}
87
88pub struct SavedContext {
90 indentation_stack: Vec<usize>,
91 base_indentation: Option<usize>,
92}
93
94impl Default for ParserState {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl ParserState {
101 pub fn new() -> Self {
102 ParserState {
103 indentation_stack: RefCell::new(vec![0]),
104 base_indentation: RefCell::new(None),
105 nested_depth: RefCell::new(0),
106 }
107 }
108
109 pub fn set_base_indentation(&self, indent: usize) {
110 let mut base = self.base_indentation.borrow_mut();
111 if base.is_none() {
112 *base = Some(indent);
113 }
114 }
115
116 pub fn get_base_indentation(&self) -> usize {
117 self.base_indentation.borrow().unwrap_or(0)
118 }
119
120 pub fn normalize_indentation(&self, indent: usize) -> usize {
121 let base = self.get_base_indentation();
122 indent.saturating_sub(base)
123 }
124
125 pub fn push_indentation(&self, indent: usize) {
126 self.indentation_stack.borrow_mut().push(indent);
127 }
128
129 pub fn pop_indentation(&self) {
130 let mut stack = self.indentation_stack.borrow_mut();
131 if stack.len() > 1 {
132 stack.pop();
133 }
134 }
135
136 pub fn current_indentation(&self) -> usize {
137 *self.indentation_stack.borrow().last().unwrap_or(&0)
138 }
139
140 pub fn check_indentation(&self, indent: usize) -> bool {
141 indent >= self.current_indentation()
142 }
143
144 pub fn enter_nested_context(&self) -> SavedContext {
147 let saved = SavedContext {
148 indentation_stack: self.indentation_stack.replace(vec![0]),
149 base_indentation: self.base_indentation.replace(None),
150 };
151 *self.nested_depth.borrow_mut() += 1;
152 saved
153 }
154
155 pub fn exit_nested_context(&self, saved: SavedContext) {
157 *self.indentation_stack.borrow_mut() = saved.indentation_stack;
158 *self.base_indentation.borrow_mut() = saved.base_indentation;
159 let mut depth = self.nested_depth.borrow_mut();
160 if *depth > 0 {
161 *depth -= 1;
162 }
163 }
164
165 pub fn is_inside_nested_context(&self) -> bool {
166 *self.nested_depth.borrow() > 0
167 }
168}
169
170fn is_whitespace_char(c: char) -> bool {
171 c == ' ' || c == '\t' || c == '\n' || c == '\r'
172}
173
174fn is_horizontal_whitespace(c: char) -> bool {
175 c == ' ' || c == '\t'
176}
177
178fn is_reference_char(c: char) -> bool {
179 !is_whitespace_char(c) && c != '(' && c != ':' && c != ')'
180}
181
182fn horizontal_whitespace(input: &str) -> IResult<&str, &str> {
183 take_while(is_horizontal_whitespace)(input)
184}
185
186fn whitespace(input: &str) -> IResult<&str, &str> {
187 take_while(is_whitespace_char)(input)
188}
189
190fn simple_reference(input: &str) -> IResult<&str, String> {
191 take_while1(is_reference_char)
192 .map(|s: &str| s.to_string())
193 .parse(input)
194}
195
196fn parse_multi_quote_string(
199 input: &str,
200 quote_char: char,
201 quote_count: usize,
202) -> IResult<&str, String> {
203 let open_close = quote_char.to_string().repeat(quote_count);
204 let escape_seq = quote_char.to_string().repeat(quote_count * 2);
205 let escape_val = quote_char.to_string().repeat(quote_count);
206
207 if !input.starts_with(&open_close) {
209 return Err(nom::Err::Error(nom::error::Error::new(
210 input,
211 nom::error::ErrorKind::Tag,
212 )));
213 }
214
215 let mut remaining = &input[open_close.len()..];
216 let mut content = String::new();
217
218 loop {
219 if remaining.is_empty() {
220 return Err(nom::Err::Error(nom::error::Error::new(
221 input,
222 nom::error::ErrorKind::Tag,
223 )));
224 }
225
226 if remaining.starts_with(&escape_seq) {
228 content.push_str(&escape_val);
229 remaining = &remaining[escape_seq.len()..];
230 continue;
231 }
232
233 if remaining.starts_with(&open_close) {
235 let after_close = &remaining[open_close.len()..];
236 if after_close.is_empty() || !after_close.starts_with(quote_char) {
238 return Ok((after_close, content));
239 }
240 }
241
242 let c = remaining.chars().next().unwrap();
244 content.push(c);
245 remaining = &remaining[c.len_utf8()..];
246 }
247}
248
249fn parse_dynamic_quote_string(input: &str, quote_char: char) -> IResult<&str, String> {
252 let quote_count = input.chars().take_while(|&c| c == quote_char).count();
254
255 if quote_count == 0 {
256 return Err(nom::Err::Error(nom::error::Error::new(
257 input,
258 nom::error::ErrorKind::Tag,
259 )));
260 }
261
262 parse_multi_quote_string(input, quote_char, quote_count)
263}
264
265fn double_quoted_dynamic(input: &str) -> IResult<&str, String> {
266 parse_dynamic_quote_string(input, '"')
267}
268
269fn single_quoted_dynamic(input: &str) -> IResult<&str, String> {
270 parse_dynamic_quote_string(input, '\'')
271}
272
273fn backtick_quoted_dynamic(input: &str) -> IResult<&str, String> {
274 parse_dynamic_quote_string(input, '`')
275}
276
277fn reference(input: &str) -> IResult<&str, String> {
278 alt((
281 double_quoted_dynamic,
282 single_quoted_dynamic,
283 backtick_quoted_dynamic,
284 simple_reference,
285 ))
286 .parse(input)
287}
288
289fn eol<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
290 alt((
291 preceded(horizontal_whitespace, line_ending),
292 preceded(horizontal_whitespace, eof),
293 |i| nested_group_end(i, state),
294 ))
295 .parse(input)
296}
297
298fn nested_group_end<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
301 if !state.is_inside_nested_context() {
302 return Err(nom::Err::Error(nom::error::Error::new(
303 input,
304 nom::error::ErrorKind::Verify,
305 )));
306 }
307 let (rest, _) = horizontal_whitespace(input)?;
308 if rest.starts_with(')') {
309 Ok((rest, ""))
310 } else {
311 Err(nom::Err::Error(nom::error::Error::new(
312 input,
313 nom::error::ErrorKind::Char,
314 )))
315 }
316}
317
318fn skip_empty_lines(input: &str) -> &str {
321 let mut rest = input;
322 loop {
323 let line_start = rest.trim_start_matches(is_horizontal_whitespace);
324 match strip_line_ending(line_start) {
325 Some(next) => rest = next,
326 None => return rest,
327 }
328 }
329}
330
331fn strip_line_ending(input: &str) -> Option<&str> {
332 input
333 .strip_prefix("\r\n")
334 .or_else(|| input.strip_prefix('\n'))
335}
336
337fn reference_or_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
338 alt((|i| nested_group(i, state), reference.map(Link::new_singlet))).parse(input)
339}
340
341fn single_line_value_and_whitespace<'a>(
342 input: &'a str,
343 state: &ParserState,
344) -> IResult<&'a str, Link> {
345 preceded(horizontal_whitespace, |i| reference_or_link(i, state)).parse(input)
346}
347
348fn single_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
349 many1(|i| single_line_value_and_whitespace(i, state)).parse(input)
350}
351
352fn single_line_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
353 (
354 horizontal_whitespace,
355 reference,
356 horizontal_whitespace,
357 char(':'),
358 |i| single_line_values(i, state),
359 )
360 .map(|(_, id, _, _, values)| Link::new_link(Some(id), values))
361 .parse(input)
362}
363
364fn single_line_value_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
365 (|i| single_line_values(i, state))
366 .map(|values| {
367 if values.len() == 1
368 && values[0].id.is_some()
369 && values[0].values.is_empty()
370 && values[0].children.is_empty()
371 {
372 Link::new_singlet(values[0].id.clone().unwrap())
373 } else {
374 Link::new_value(values)
375 }
376 })
377 .parse(input)
378}
379
380fn indented_id_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
381 (reference, horizontal_whitespace, char(':'), |i| {
382 eol(i, state)
383 })
384 .map(|(id, _, _, _)| Link::new_indented_id(id))
385 .parse(input)
386}
387
388fn nested_group<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
392 let (body_input, _) = char('(').parse(input)?;
393 let saved = state.enter_nested_context();
394 let result = nested_group_body(body_input, state);
395 state.exit_nested_context(saved);
396 result
397}
398
399fn nested_group_body<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
400 if let Ok((rest, body)) = links(skip_empty_lines(input), state) {
401 let (rest, _) = whitespace(rest)?;
402 let (rest, _) = char(')').parse(rest)?;
403 return Ok((rest, Link::new_nested(body)));
404 }
405 let (rest, _) = whitespace(input)?;
406 let (rest, _) = char(')').parse(rest)?;
407 Ok((rest, Link::new_nested(vec![])))
408}
409
410fn single_line_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
411 alt((
412 terminated(|i| single_line_link(i, state), |i| eol(i, state)),
413 terminated(|i| single_line_value_link(i, state), |i| eol(i, state)),
414 ))
415 .parse(input)
416}
417
418fn any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
419 alt((
420 terminated(|i| nested_group(i, state), |i| eol(i, state)),
421 |i| indented_id_link(i, state),
422 |i| single_line_any_link(i, state),
423 ))
424 .parse(input)
425}
426
427fn count_indentation(input: &str) -> IResult<&str, usize> {
428 take_while(|c| c == ' ').map(|s: &str| s.len()).parse(input)
429}
430
431fn push_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
432 let (input, spaces) = count_indentation(skip_empty_lines(input))?;
433 let normalized_spaces = state.normalize_indentation(spaces);
434 let current = state.current_indentation();
435
436 if normalized_spaces > current {
437 state.push_indentation(normalized_spaces);
438 Ok((input, ()))
439 } else {
440 Err(nom::Err::Error(nom::error::Error::new(
441 input,
442 nom::error::ErrorKind::Verify,
443 )))
444 }
445}
446
447fn check_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
448 let (input, spaces) = count_indentation(input)?;
449 let normalized_spaces = state.normalize_indentation(spaces);
450
451 if state.check_indentation(normalized_spaces) {
452 Ok((input, ()))
453 } else {
454 Err(nom::Err::Error(nom::error::Error::new(
455 input,
456 nom::error::ErrorKind::Verify,
457 )))
458 }
459}
460
461fn element<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
462 let (input, link) = any_link(input, state)?;
463
464 if let Ok((input, _)) = push_indentation(input, state) {
465 let (input, children) = links(input, state)?;
466 Ok((input, link.with_children(children)))
467 } else {
468 Ok((input, link))
469 }
470}
471
472fn first_line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
473 let (input, spaces) = count_indentation(input)?;
476 state.set_base_indentation(spaces);
477 element(input, state)
478}
479
480fn line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
481 preceded(|i| check_indentation(i, state), |i| element(i, state)).parse(skip_empty_lines(input))
483}
484
485fn links<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
486 let (input, first) = first_line(input, state)?;
487 let (input, rest) = many0(|i| line(i, state)).parse(input)?;
488
489 state.pop_indentation();
490
491 let mut result = vec![first];
492 result.extend(rest);
493 Ok((input, result))
494}
495
496pub fn parse_document(input: &str) -> IResult<&str, Vec<Link>> {
497 let state = ParserState::new();
498
499 let input = skip_empty_lines(input);
501
502 if input.trim().is_empty() {
504 return Ok(("", vec![]));
505 }
506
507 let (input, result) = links(input, &state)?;
508 let (input, _) = whitespace(input)?;
509 let (input, _) = eof(input)?;
510
511 Ok((input, result))
512}