From b8ab245fa35143ec6c1884d7aa685f25a5213940 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Mon, 10 Jan 2022 03:33:43 -0500 Subject: parse alternations --- src/parse/ast.rs | 19 ++++++++++++++--- src/parse/test_ast.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/shell.pest | 14 ++++++++++++- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 053f07f..2f4ae86 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -158,7 +158,10 @@ pub struct Word { impl Word { fn build_ast(pair: pest::iterators::Pair) -> Self { - assert!(matches!(pair.as_rule(), Rule::word)); + assert!(matches!( + pair.as_rule(), + Rule::word | Rule::alternation_word + )); Self { parts: pair.into_inner().flat_map(WordPart::build_ast).collect(), } @@ -175,6 +178,7 @@ impl Word { #[derive(Debug, Clone, PartialEq, Eq)] enum WordPart { + Alternation(Vec), Var(String), Bareword(String), DoubleQuoted(String), @@ -186,7 +190,10 @@ impl WordPart { fn build_ast( pair: pest::iterators::Pair, ) -> impl Iterator + '_ { - assert!(matches!(pair.as_rule(), Rule::word_part)); + assert!(matches!( + pair.as_rule(), + Rule::word_part | Rule::alternation_word_part + )); pair.into_inner().map(|pair| match pair.as_rule() { Rule::var => { let s = pair.as_str(); @@ -200,19 +207,25 @@ impl WordPart { .to_string(), ) } - Rule::bareword => Self::Bareword(strip_escape(pair.as_str())), + Rule::bareword | Rule::alternation_bareword => { + Self::Bareword(strip_escape(pair.as_str())) + } Rule::double_string => { Self::DoubleQuoted(strip_escape(pair.as_str())) } Rule::single_string => { Self::SingleQuoted(strip_basic_escape(pair.as_str())) } + Rule::alternation => Self::Alternation( + pair.into_inner().map(Word::build_ast).collect(), + ), _ => unreachable!(), }) } fn eval(self, env: &Env) -> String { match self { + Self::Alternation(_) => todo!(), Self::Var(name) => env.var(&name), Self::Bareword(s) | Self::DoubleQuoted(s) diff --git a/src/parse/test_ast.rs b/src/parse/test_ast.rs index b16eba4..19bf7af 100644 --- a/src/parse/test_ast.rs +++ b/src/parse/test_ast.rs @@ -79,6 +79,12 @@ macro_rules! w { } } +macro_rules! wpa { + ($($word:expr),*) => { + WordPart::Alternation(vec![$($word),*]) + } +} + macro_rules! wpv { ($var:literal) => { WordPart::Var($var.to_string()) @@ -231,3 +237,53 @@ fn test_parts() { cs!(p!(e!(w!("perl"), w!(wpb!("-E"), wps!("say \"foo\""))))) ); } + +#[test] +fn test_alternation() { + parse_eq!( + "echo {foo,bar}", + cs!(p!(e!(w!("echo"), w!(wpa!(w!("foo"), w!("bar")))))) + ); + parse_eq!( + "echo {foo,bar}.rs", + cs!(p!(e!( + w!("echo"), + w!(wpa!(w!("foo"), w!("bar")), wpb!(".rs")) + ))) + ); + parse_eq!( + "echo {foo,bar,baz}.rs", + cs!(p!(e!( + w!("echo"), + w!(wpa!(w!("foo"), w!("bar"), w!("baz")), wpb!(".rs")) + ))) + ); + parse_eq!( + "echo {foo,}.rs", + cs!(p!(e!(w!("echo"), w!(wpa!(w!("foo"), w!()), wpb!(".rs"))))) + ); + parse_eq!("echo {foo}", cs!(p!(e!(w!("echo"), w!(wpa!(w!("foo"))))))); + parse_eq!("echo {}", cs!(p!(e!(w!("echo"), w!(wpa!(w!())))))); + parse_eq!( + "echo {foo,bar}.{rs,c}", + cs!(p!(e!( + w!("echo"), + w!( + wpa!(w!("foo"), w!("bar")), + wpb!("."), + wpa!(w!("rs"), w!("c")) + ) + ))) + ); + parse_eq!( + "echo {$foo,\"${HOME}/bin\"}.{'r'\"s\",c}", + cs!(p!(e!( + w!("echo"), + w!( + wpa!(w!(wpv!("foo")), w!(wpv!("HOME"), wpd!("/bin"))), + wpb!("."), + wpa!(w!(wps!("r"), wpd!("s")), w!("c")) + ) + ))) + ); +} diff --git a/src/shell.pest b/src/shell.pest index 393e60e..d09ccbc 100644 --- a/src/shell.pest +++ b/src/shell.pest @@ -3,7 +3,7 @@ escape_char = @{ "\\" ~ ANY } bareword_char = @{ escape_char | - !("|" | ";" | "\"" | "'" | "$" | WHITESPACE | COMMENT) ~ ANY + !("|" | ";" | "\"" | "'" | "$" | "{" | WHITESPACE | COMMENT) ~ ANY } single_string_char = @{ basic_escape_char | (!"'" ~ ANY) } double_string_char = @{ escape_char | (!"\"" ~ ANY) } @@ -21,7 +21,19 @@ bareword = @{ bareword_char+ } single_string = @{ single_string_char+ } double_string = @{ double_string_char+ } +alternation_bareword_char = @{ !("," | "}") ~ bareword_char } +alternation_bareword = @{ alternation_bareword_char+ } +alternation_word_part = ${ + var | + alternation_bareword | + "'" ~ single_string ~ "'" | + "\"" ~ (var | double_string)+ ~ "\"" +} +alternation_word = ${ alternation_word_part* } +alternation = ${ "{" ~ alternation_word ~ ("," ~ alternation_word)* ~ "}" } + word_part = ${ + alternation | var | bareword | "'" ~ single_string ~ "'" | -- cgit v1.2.3-54-g00ecf