on master help sign in
Add a parser-combinator crate

Parser-combinators are one of the simpler tools for building ad-hoc parsers. They're a good fit because they are... * Small: each parser / parser-combinator is around 10 LOC. * Functional: helix_core strives to be a functional set of utilities usable throughout the rest of the editor. * Flexible: use them to build any sort of ad-hoc parser. In the child commit, we'll parse LSP Snippet syntax using these new parser combinators. Why not use an existing parser-combinator crate? Existing popular parser-combinator crates have histories of making breaking changes (for example nom and combine). > Implementation note: I tried to not introduce a new trait since the > types can be expressed in terms of `impl Fn`s. The trait is necessary > to build `seq` implementations without a proc macro though, and also > allows us to use `&'static str`s very conveniently: see the trait > implementation for `&'static str`.

commitc8e6857affdd286a5aff1e8f72fc428ea216076e authorMichael Davis <mcarsondavis@gmail.com> dateTue 7 Feb 2023, 09:18 (4 years ago) parentf976c00
4 files changed+582
Cargo.lock+7 Cargo.toml+1 helix-parsec/Cargo.toml+14 helix-parsec/src/lib.rs+560
Cargo.lock+7
@@ -1149,6 +1149,13 @@ dependencies = [
11491149 "which",
11501150]
11511151
1152+[[package]]
1153+name = "helix-parsec"
1154+version = "0.6.0"
1155+dependencies = [
1156+ "regex",
1157+]
1158+
11521159[[package]]
11531160name = "helix-term"
11541161version = "0.6.0"
Cargo.toml+1
@@ -8,6 +8,7 @@ members = [
88 "helix-dap",
99 "helix-loader",
1010 "helix-vcs",
11+ "helix-parsec",
1112 "xtask",
1213]
1314
helix-parsec/Cargo.tomlnew file+14
@@ -0,0 +1,14 @@
1+[package]
2+name = "helix-parsec"
3+version = "0.6.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2021"
6+license = "MPL-2.0"
7+description = "Parser combinators for Helix"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
11+include = ["src/**/*", "README.md"]
12+
13+[dependencies]
14+regex = "1"
helix-parsec/src/lib.rsnew file+560
@@ -0,0 +1,560 @@
1+//! Parser-combinator functions
2+//!
3+//! This module provides parsers and parser combinators which can be used
4+//! together to build parsers by functional composition.
5+
6+use regex::Regex;
7+
8+// This module implements parser combinators following https://bodil.lol/parser-combinators/.
9+// `sym` (trait implementation for `&'static str`), `map`, `pred` (filter), `one_or_more`,
10+// `zero_or_more`, as well as the `Parser` trait originate mostly from that post.
11+// The remaining parsers and parser combinators are either based on
12+// https://github.com/archseer/snippets.nvim/blob/a583da6ef130d2a4888510afd8c4e5ffd62d0dce/lua/snippet/parser.lua#L5-L138
13+// or are novel.
14+
15+// When a parser matches the input successfully, it returns `Ok((next_input, some_value))`
16+// where the type of the returned value depends on the parser. If the parser fails to match,
17+// it returns `Err(input)`.
18+type ParseResult<'a, Output> = Result<(&'a str, Output), &'a str>;
19+
20+/// A parser or parser-combinator.
21+///
22+/// Parser-combinators compose multiple parsers together to parse input.
23+/// For example, two basic parsers (`&'static str`s) may be combined with
24+/// a parser-combinator like [or] to produce a new parser.
25+///
26+/// ```
27+/// use helix_parsec::{or, Parser};
28+/// let foo = "foo"; // matches "foo" literally
29+/// let bar = "bar"; // matches "bar" literally
30+/// let foo_or_bar = or(foo, bar); // matches either "foo" or "bar"
31+/// assert_eq!(Ok(("", "foo")), foo_or_bar.parse("foo"));
32+/// assert_eq!(Ok(("", "bar")), foo_or_bar.parse("bar"));
33+/// assert_eq!(Err("baz"), foo_or_bar.parse("baz"));
34+/// ```
35+pub trait Parser<'a> {
36+ type Output;
37+
38+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output>;
39+}
40+
41+// Most parser-combinators are written as higher-order functions which take some
42+// parser(s) as input and return a new parser: a function that takes input and returns
43+// a parse result. The underlying implementation of [Parser::parse] for these functions
44+// is simply application.
45+#[doc(hidden)]
46+impl<'a, F, T> Parser<'a> for F
47+where
48+ F: Fn(&'a str) -> ParseResult<T>,
49+{
50+ type Output = T;
51+
52+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
53+ self(input)
54+ }
55+}
56+
57+/// A parser which matches the string literal exactly.
58+///
59+/// This parser succeeds if the next characters in the input are equal to the given
60+/// string literal.
61+///
62+/// Note that [str::parse] interferes with calling [Parser::parse] on string literals
63+/// directly; this trait implementation works when used within any parser combinator
64+/// but does not work on its own. To call [Parser::parse] on a parser for a string
65+/// literal, use the [token] parser.
66+///
67+/// # Examples
68+///
69+/// ```
70+/// use helix_parsec::{or, Parser};
71+/// let parser = or("foo", "bar");
72+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
73+/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
74+/// assert_eq!(Err("baz"), parser.parse("baz"));
75+/// ```
76+impl<'a> Parser<'a> for &'static str {
77+ type Output = &'a str;
78+
79+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
80+ match input.get(0..self.len()) {
81+ Some(actual) if actual == *self => Ok((&input[self.len()..], &input[0..self.len()])),
82+ _ => Err(input),
83+ }
84+ }
85+}
86+
87+// Parsers
88+
89+/// A parser which matches the given string literally.
90+///
91+/// This function is a convenience for interpreting string literals as parsers
92+/// and is only necessary to avoid conflict with [str::parse]. See the documentation
93+/// for the `&'static str` implementation of [Parser].
94+///
95+/// # Examples
96+///
97+/// ```
98+/// use helix_parsec::{token, Parser};
99+/// let parser = token("foo");
100+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
101+/// assert_eq!(Err("bar"), parser.parse("bar"));
102+/// ```
103+pub fn token<'a>(literal: &'static str) -> impl Parser<'a, Output = &'a str> {
104+ literal
105+}
106+
107+/// A parser which matches the pattern described by the given regular expression.
108+///
109+/// The pattern must match from the beginning of the input as if the regular expression
110+/// included the `^` anchor. Using a `^` anchor in the regular expression is
111+/// recommended in order to reduce any work done by the regex on non-matching input.
112+///
113+/// # Examples
114+///
115+/// ```
116+/// use helix_parsec::{pattern, Parser};
117+/// use regex::Regex;
118+/// let regex = Regex::new(r"Hello, \w+!").unwrap();
119+/// let parser = pattern(&regex);
120+/// assert_eq!(Ok(("", "Hello, world!")), parser.parse("Hello, world!"));
121+/// assert_eq!(Err("Hey, you!"), parser.parse("Hey, you!"));
122+/// assert_eq!(Err("Oh Hello, world!"), parser.parse("Oh Hello, world!"));
123+/// ```
124+pub fn pattern<'a>(regex: &'a Regex) -> impl Parser<'a, Output = &'a str> {
125+ move |input: &'a str| match regex.find(input) {
126+ Some(match_) if match_.start() == 0 => {
127+ Ok((&input[match_.end()..], &input[0..match_.end()]))
128+ }
129+ _ => Err(input),
130+ }
131+}
132+
133+/// A parser which matches all values until the specified pattern is found.
134+///
135+/// If the pattern is not found, this parser does not match. The input up to the
136+/// character which returns `true` is returned but not that character itself.
137+///
138+/// If the pattern function returns true on the first input character, this
139+/// parser fails.
140+///
141+/// # Examples
142+///
143+/// ```
144+/// use helix_parsec::{take_until, Parser};
145+/// let parser = take_until(|c| c == '.');
146+/// assert_eq!(Ok((".bar", "foo")), parser.parse("foo.bar"));
147+/// assert_eq!(Err(".foo"), parser.parse(".foo"));
148+/// assert_eq!(Err("foo"), parser.parse("foo"));
149+/// ```
150+pub fn take_until<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str>
151+where
152+ F: Fn(char) -> bool,
153+{
154+ move |input: &'a str| match input.find(&pattern) {
155+ Some(index) if index != 0 => Ok((&input[index..], &input[0..index])),
156+ _ => Err(input),
157+ }
158+}
159+
160+// Variadic parser combinators
161+
162+/// A parser combinator which matches a sequence of parsers in an all-or-nothing fashion.
163+///
164+/// The returned value is a tuple containing the outputs of all parsers in order. Each
165+/// parser in the sequence may be typed differently.
166+///
167+/// # Examples
168+///
169+/// ```
170+/// use helix_parsec::{seq, Parser};
171+/// let parser = seq!("<", "a", ">");
172+/// assert_eq!(Ok(("", ("<", "a", ">"))), parser.parse("<a>"));
173+/// assert_eq!(Err("<b>"), parser.parse("<b>"));
174+/// ```
175+#[macro_export]
176+macro_rules! seq {
177+ ($($parsers: expr),+ $(,)?) => {
178+ ($($parsers),+)
179+ }
180+}
181+
182+// Seq is implemented using trait-implementations of Parser for various size tuples.
183+// This allows sequences to be typed heterogeneously.
184+macro_rules! seq_impl {
185+ ($($parser:ident),+) => {
186+ #[allow(non_snake_case)]
187+ impl<'a, $($parser),+> Parser<'a> for ($($parser),+)
188+ where
189+ $($parser: Parser<'a>),+
190+ {
191+ type Output = ($($parser::Output),+);
192+
193+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
194+ let ($($parser),+) = self;
195+ seq_body_impl!(input, input, $($parser),+ ; )
196+ }
197+ }
198+ }
199+}
200+
201+macro_rules! seq_body_impl {
202+ ($input:expr, $next_input:expr, $head:ident, $($tail:ident),+ ; $(,)? $($acc:ident),*) => {
203+ match $head.parse($next_input) {
204+ Ok((next_input, $head)) => seq_body_impl!($input, next_input, $($tail),+ ; $($acc),*, $head),
205+ Err(_) => Err($input),
206+ }
207+ };
208+ ($input:expr, $next_input:expr, $last:ident ; $(,)? $($acc:ident),*) => {
209+ match $last.parse($next_input) {
210+ Ok((next_input, last)) => Ok((next_input, ($($acc),+, last))),
211+ Err(_) => Err($input),
212+ }
213+ }
214+}
215+
216+seq_impl!(A, B);
217+seq_impl!(A, B, C);
218+seq_impl!(A, B, C, D);
219+seq_impl!(A, B, C, D, E);
220+seq_impl!(A, B, C, D, E, F);
221+seq_impl!(A, B, C, D, E, F, G);
222+seq_impl!(A, B, C, D, E, F, G, H);
223+seq_impl!(A, B, C, D, E, F, G, H, I);
224+seq_impl!(A, B, C, D, E, F, G, H, I, J);
225+
226+/// A parser combinator which chooses the first of the input parsers which matches
227+/// successfully.
228+///
229+/// All input parsers must have the same output type. This is a variadic form for [or].
230+///
231+/// # Examples
232+///
233+/// ```
234+/// use helix_parsec::{choice, or, Parser};
235+/// let parser = choice!("foo", "bar", "baz");
236+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
237+/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
238+/// assert_eq!(Err("quiz"), parser.parse("quiz"));
239+/// ```
240+#[macro_export]
241+macro_rules! choice {
242+ ($parser: expr $(,)?) => {
243+ $parser
244+ };
245+ ($parser: expr, $($rest: expr),+ $(,)?) => {
246+ or($parser, choice!($($rest),+))
247+ }
248+}
249+
250+// Ordinary parser combinators
251+
252+/// A parser combinator which takes a parser as input and maps the output using the
253+/// given transformation function.
254+///
255+/// This corresponds to [Result::map]. The value is only mapped if the input parser
256+/// matches against input.
257+///
258+/// # Examples
259+///
260+/// ```
261+/// use helix_parsec::{map, Parser};
262+/// let parser = map("123", |s| s.parse::<i32>().unwrap());
263+/// assert_eq!(Ok(("", 123)), parser.parse("123"));
264+/// assert_eq!(Err("abc"), parser.parse("abc"));
265+/// ```
266+pub fn map<'a, P, F, T>(parser: P, map_fn: F) -> impl Parser<'a, Output = T>
267+where
268+ P: Parser<'a>,
269+ F: Fn(P::Output) -> T,
270+{
271+ move |input| {
272+ parser
273+ .parse(input)
274+ .map(|(next_input, result)| (next_input, map_fn(result)))
275+ }
276+}
277+
278+/// A parser combinator which succeeds if the given parser matches the input and
279+/// the given `filter_map_fn` returns `Some`.
280+///
281+/// # Examples
282+///
283+/// ```
284+/// use helix_parsec::{filter_map, take_until, Parser};
285+/// let parser = filter_map(take_until(|c| c == '.'), |s| s.parse::<i32>().ok());
286+/// assert_eq!(Ok((".456", 123)), parser.parse("123.456"));
287+/// assert_eq!(Err("abc.def"), parser.parse("abc.def"));
288+/// ```
289+pub fn filter_map<'a, P, F, T>(parser: P, filter_map_fn: F) -> impl Parser<'a, Output = T>
290+where
291+ P: Parser<'a>,
292+ F: Fn(P::Output) -> Option<T>,
293+{
294+ move |input| match parser.parse(input) {
295+ Ok((next_input, value)) => match filter_map_fn(value) {
296+ Some(value) => Ok((next_input, value)),
297+ None => Err(input),
298+ },
299+ Err(_) => Err(input),
300+ }
301+}
302+
303+/// A parser combinator which succeeds if the first given parser matches the input and
304+/// the second given parse also matches.
305+///
306+/// # Examples
307+///
308+/// ```
309+/// use helix_parsec::{reparse_as, take_until, one_or_more, Parser};
310+/// let parser = reparse_as(take_until(|c| c == '/'), one_or_more("a"));
311+/// assert_eq!(Ok(("/bb", vec!["a", "a"])), parser.parse("aa/bb"));
312+/// ```
313+pub fn reparse_as<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T>
314+where
315+ P1: Parser<'a, Output = &'a str>,
316+ P2: Parser<'a, Output = T>,
317+{
318+ filter_map(parser1, move |str| {
319+ parser2.parse(str).map(|(_, value)| value).ok()
320+ })
321+}
322+
323+/// A parser combinator which only matches the input when the predicate function
324+/// returns true.
325+///
326+/// # Examples
327+///
328+/// ```
329+/// use helix_parsec::{filter, take_until, Parser};
330+/// let parser = filter(take_until(|c| c == '.'), |s| s == &"123");
331+/// assert_eq!(Ok((".456", "123")), parser.parse("123.456"));
332+/// assert_eq!(Err("456.123"), parser.parse("456.123"));
333+/// ```
334+pub fn filter<'a, P, F, T>(parser: P, pred_fn: F) -> impl Parser<'a, Output = T>
335+where
336+ P: Parser<'a, Output = T>,
337+ F: Fn(&P::Output) -> bool,
338+{
339+ move |input| {
340+ if let Ok((next_input, value)) = parser.parse(input) {
341+ if pred_fn(&value) {
342+ return Ok((next_input, value));
343+ }
344+ }
345+ Err(input)
346+ }
347+}
348+
349+/// A parser combinator which matches either of the input parsers.
350+///
351+/// Both parsers must have the same output type. For a variadic form which
352+/// can take any number of parsers, use `choice!`.
353+///
354+/// # Examples
355+///
356+/// ```
357+/// use helix_parsec::{or, Parser};
358+/// let parser = or("foo", "bar");
359+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
360+/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
361+/// assert_eq!(Err("baz"), parser.parse("baz"));
362+/// ```
363+pub fn or<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T>
364+where
365+ P1: Parser<'a, Output = T>,
366+ P2: Parser<'a, Output = T>,
367+{
368+ move |input| match parser1.parse(input) {
369+ ok @ Ok(_) => ok,
370+ Err(_) => parser2.parse(input),
371+ }
372+}
373+
374+/// A parser combinator which attempts to match the given parser, returning a
375+/// `None` output value if the parser does not match.
376+///
377+/// The parser produced with this combinator always succeeds. If the given parser
378+/// succeeds, `Some(value)` is returned where `value` is the output of the given
379+/// parser. Otherwise, `None`.
380+///
381+/// # Examples
382+///
383+/// ```
384+/// use helix_parsec::{optional, Parser};
385+/// let parser = optional("foo");
386+/// assert_eq!(Ok(("bar", Some("foo"))), parser.parse("foobar"));
387+/// assert_eq!(Ok(("bar", None)), parser.parse("bar"));
388+/// ```
389+pub fn optional<'a, P, T>(parser: P) -> impl Parser<'a, Output = Option<T>>
390+where
391+ P: Parser<'a, Output = T>,
392+{
393+ move |input| match parser.parse(input) {
394+ Ok((next_input, value)) => Ok((next_input, Some(value))),
395+ Err(_) => Ok((input, None)),
396+ }
397+}
398+
399+/// A parser combinator which runs the given parsers in sequence and returns the
400+/// value of `left` if both are matched.
401+///
402+/// This is useful for two-element sequences in which you only want the output
403+/// value of the `left` parser.
404+///
405+/// # Examples
406+///
407+/// ```
408+/// use helix_parsec::{left, Parser};
409+/// let parser = left("foo", "bar");
410+/// assert_eq!(Ok(("", "foo")), parser.parse("foobar"));
411+/// ```
412+pub fn left<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T>
413+where
414+ L: Parser<'a, Output = T>,
415+ R: Parser<'a>,
416+{
417+ map(seq!(left, right), |(left_value, _)| left_value)
418+}
419+
420+/// A parser combinator which runs the given parsers in sequence and returns the
421+/// value of `right` if both are matched.
422+///
423+/// This is useful for two-element sequences in which you only want the output
424+/// value of the `right` parser.
425+///
426+/// # Examples
427+///
428+/// ```
429+/// use helix_parsec::{right, Parser};
430+/// let parser = right("foo", "bar");
431+/// assert_eq!(Ok(("", "bar")), parser.parse("foobar"));
432+/// ```
433+pub fn right<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T>
434+where
435+ L: Parser<'a>,
436+ R: Parser<'a, Output = T>,
437+{
438+ map(seq!(left, right), |(_, right_value)| right_value)
439+}
440+
441+/// A parser combinator which matches the given parser against the input zero or
442+/// more times.
443+///
444+/// This parser always succeeds and returns the empty Vec when it matched zero
445+/// times.
446+///
447+/// # Examples
448+///
449+/// ```
450+/// use helix_parsec::{zero_or_more, Parser};
451+/// let parser = zero_or_more("a");
452+/// assert_eq!(Ok(("", vec![])), parser.parse(""));
453+/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a"));
454+/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa"));
455+/// assert_eq!(Ok(("bb", vec![])), parser.parse("bb"));
456+/// ```
457+pub fn zero_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
458+where
459+ P: Parser<'a, Output = T>,
460+{
461+ move |mut input| {
462+ let mut values = Vec::new();
463+
464+ while let Ok((next_input, value)) = parser.parse(input) {
465+ input = next_input;
466+ values.push(value);
467+ }
468+
469+ Ok((input, values))
470+ }
471+}
472+
473+/// A parser combinator which matches the given parser against the input one or
474+/// more times.
475+///
476+/// This parser combinator acts the same as [zero_or_more] but must match at
477+/// least once.
478+///
479+/// # Examples
480+///
481+/// ```
482+/// use helix_parsec::{one_or_more, Parser};
483+/// let parser = one_or_more("a");
484+/// assert_eq!(Err(""), parser.parse(""));
485+/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a"));
486+/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa"));
487+/// assert_eq!(Err("bb"), parser.parse("bb"));
488+/// ```
489+pub fn one_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
490+where
491+ P: Parser<'a, Output = T>,
492+{
493+ move |mut input| {
494+ let mut values = Vec::new();
495+
496+ match parser.parse(input) {
497+ Ok((next_input, value)) => {
498+ input = next_input;
499+ values.push(value);
500+ }
501+ Err(err) => return Err(err),
502+ }
503+
504+ while let Ok((next_input, value)) = parser.parse(input) {
505+ input = next_input;
506+ values.push(value);
507+ }
508+
509+ Ok((input, values))
510+ }
511+}
512+
513+/// A parser combinator which matches one or more instances of the given parser
514+/// interspersed with the separator parser.
515+///
516+/// Output values of the separator parser are discarded.
517+///
518+/// This is typically used to parse function arguments or list items.
519+///
520+/// # Examples
521+///
522+/// ```rust
523+/// use helix_parsec::{sep, Parser};
524+/// let parser = sep("a", ",");
525+/// assert_eq!(Ok(("", vec!["a", "a", "a"])), parser.parse("a,a,a"));
526+/// ```
527+pub fn sep<'a, P, S, T>(parser: P, separator: S) -> impl Parser<'a, Output = Vec<T>>
528+where
529+ P: Parser<'a, Output = T>,
530+ S: Parser<'a>,
531+{
532+ move |mut input| {
533+ let mut values = Vec::new();
534+
535+ match parser.parse(input) {
536+ Ok((next_input, value)) => {
537+ input = next_input;
538+ values.push(value);
539+ }
540+ Err(err) => return Err(err),
541+ }
542+
543+ loop {
544+ match separator.parse(input) {
545+ Ok((next_input, _)) => input = next_input,
546+ Err(_) => break,
547+ }
548+
549+ match parser.parse(input) {
550+ Ok((next_input, value)) => {
551+ input = next_input;
552+ values.push(value);
553+ }
554+ Err(_) => break,
555+ }
556+ }
557+
558+ Ok((input, values))
559+ }
560+}