yash_syntax/parser/lex/
modifier.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Part of the lexer that parses suffix modifiers
18
19use super::core::Lexer;
20use super::core::WordContext;
21use super::core::WordLexer;
22use crate::parser::core::Result;
23use crate::parser::error::Error;
24use crate::parser::error::SyntaxError;
25use crate::syntax::Modifier;
26use crate::syntax::Switch;
27use crate::syntax::SwitchAction;
28use crate::syntax::SwitchCondition;
29use crate::syntax::Trim;
30use crate::syntax::TrimLength;
31use crate::syntax::TrimSide;
32
33impl Lexer<'_> {
34    /// Returns an invalid modifier error.
35    ///
36    /// The `start_index` must be the index of the first character of the modifier.
37    fn invalid_modifier(&mut self, start_index: usize) -> Result<Modifier> {
38        let cause = SyntaxError::InvalidModifier.into();
39        let location = self.location_range(start_index..self.index());
40        Err(Error { cause, location })
41    }
42
43    fn suffix_modifier_not_found(&mut self, start_index: usize, colon: bool) -> Result<Modifier> {
44        if colon {
45            self.invalid_modifier(start_index)
46        } else {
47            Ok(Modifier::None)
48        }
49    }
50
51    /// Parses a [trim](Trim).
52    ///
53    /// This function blindly consumes the current character, which must be
54    /// `symbol`.
55    async fn trim(&mut self, start_index: usize, colon: bool, symbol: char) -> Result<Modifier> {
56        self.consume_char();
57        if colon {
58            return self.invalid_modifier(start_index);
59        }
60
61        let side = match symbol {
62            '#' => TrimSide::Prefix,
63            '%' => TrimSide::Suffix,
64            _ => unreachable!(),
65        };
66
67        let length = if self.skip_if(|c| c == symbol).await? {
68            TrimLength::Longest
69        } else {
70            TrimLength::Shortest
71        };
72
73        let mut lexer = WordLexer {
74            lexer: self,
75            context: WordContext::Word,
76        };
77        // Boxing needed for recursion
78        let mut pattern = Box::pin(lexer.word(|c| c == '}')).await?;
79        pattern.parse_tilde_front();
80
81        Ok(Modifier::Trim(Trim {
82            side,
83            length,
84            pattern,
85        }))
86    }
87}
88
89impl WordLexer<'_, '_> {
90    /// Parses a [switch](Switch), except the optional initial colon.
91    ///
92    /// This function blindly consumes the current character, which must be
93    /// `symbol`.
94    async fn switch(&mut self, colon: bool, symbol: char) -> Result<Modifier> {
95        self.consume_char();
96        let action = match symbol {
97            '+' => SwitchAction::Alter,
98            '-' => SwitchAction::Default,
99            '=' => SwitchAction::Assign,
100            '?' => SwitchAction::Error,
101            _ => unreachable!(),
102        };
103
104        let condition = if colon {
105            SwitchCondition::UnsetOrEmpty
106        } else {
107            SwitchCondition::Unset
108        };
109
110        // Boxing needed for recursion
111        let mut word = Box::pin(self.word(|c| c == '}')).await?;
112        match self.context {
113            WordContext::Text => (),
114            WordContext::Word => word.parse_tilde_front(),
115        }
116
117        Ok(Modifier::Switch(Switch {
118            action,
119            condition,
120            word,
121        }))
122    }
123
124    /// Parses a suffix modifier, i.e., a modifier other than the length prefix.
125    ///
126    /// If there is a [switch](Switch), [`self.context`](Self::context) affects
127    /// how the word of the switch is parsed: If the context is `Word`, a tilde
128    /// expansion is recognized at the beginning of the word and any character
129    /// can be escaped by a backslash. If the context is `Text`, only `$`, `"`,
130    /// `` ` ``, `\` and `}` can be escaped and single quotes are not recognized
131    /// in the word.
132    pub async fn suffix_modifier(&mut self) -> Result<Modifier> {
133        let start_index = self.index();
134        let colon = self.skip_if(|c| c == ':').await?;
135
136        if let Some(symbol) = self.peek_char().await? {
137            match symbol {
138                '+' | '-' | '=' | '?' => self.switch(colon, symbol).await,
139                '#' | '%' => self.trim(start_index, colon, symbol).await,
140                _ => self.suffix_modifier_not_found(start_index, colon),
141            }
142        } else {
143            self.suffix_modifier_not_found(start_index, colon)
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::parser::error::ErrorCause;
152    use crate::syntax::Text;
153    use crate::syntax::TextUnit;
154    use crate::syntax::WordUnit;
155    use assert_matches::assert_matches;
156    use futures_util::FutureExt;
157
158    #[test]
159    fn lexer_suffix_modifier_eof() {
160        let mut lexer = Lexer::with_code("");
161        let mut lexer = WordLexer {
162            lexer: &mut lexer,
163            context: WordContext::Word,
164        };
165
166        let result = lexer.suffix_modifier().now_or_never().unwrap();
167        assert_eq!(result, Ok(Modifier::None));
168    }
169
170    #[test]
171    fn lexer_suffix_modifier_none() {
172        let mut lexer = Lexer::with_code("}");
173        let mut lexer = WordLexer {
174            lexer: &mut lexer,
175            context: WordContext::Word,
176        };
177
178        let result = lexer.suffix_modifier().now_or_never().unwrap();
179        assert_eq!(result, Ok(Modifier::None));
180
181        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
182    }
183
184    #[test]
185    fn lexer_suffix_modifier_alter_empty() {
186        let mut lexer = Lexer::with_code("+}");
187        let mut lexer = WordLexer {
188            lexer: &mut lexer,
189            context: WordContext::Word,
190        };
191
192        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
193        assert_matches!(result, Modifier::Switch(switch) => {
194            assert_eq!(switch.action, SwitchAction::Alter);
195            assert_eq!(switch.condition, SwitchCondition::Unset);
196            assert_eq!(switch.word.units, []);
197            assert_eq!(*switch.word.location.code.value.borrow(), "+}");
198            assert_eq!(switch.word.location.range, 1..1);
199        });
200
201        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
202    }
203
204    #[test]
205    fn lexer_suffix_modifier_alter_word() {
206        let mut lexer = Lexer::with_code(r"+a  z}");
207        let mut lexer = WordLexer {
208            lexer: &mut lexer,
209            context: WordContext::Word,
210        };
211
212        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
213        assert_matches!(result, Modifier::Switch(switch) => {
214            assert_eq!(switch.action, SwitchAction::Alter);
215            assert_eq!(switch.condition, SwitchCondition::Unset);
216            assert_eq!(
217                switch.word.units,
218                [
219                    WordUnit::Unquoted(TextUnit::Literal('a')),
220                    WordUnit::Unquoted(TextUnit::Literal(' ')),
221                    WordUnit::Unquoted(TextUnit::Literal(' ')),
222                    WordUnit::Unquoted(TextUnit::Literal('z')),
223                ]
224            );
225            assert_eq!(*switch.word.location.code.value.borrow(), "+a  z}");
226            assert_eq!(switch.word.location.range, 1..5);
227        });
228
229        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
230    }
231
232    #[test]
233    fn lexer_suffix_modifier_colon_alter_empty() {
234        let mut lexer = Lexer::with_code(":+}");
235        let mut lexer = WordLexer {
236            lexer: &mut lexer,
237            context: WordContext::Word,
238        };
239
240        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
241        assert_matches!(result, Modifier::Switch(switch) => {
242            assert_eq!(switch.action, SwitchAction::Alter);
243            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
244            assert_eq!(switch.word.units, []);
245            assert_eq!(*switch.word.location.code.value.borrow(), ":+}");
246            assert_eq!(switch.word.location.range, 2..2);
247        });
248
249        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
250    }
251
252    #[test]
253    fn lexer_suffix_modifier_default_empty() {
254        let mut lexer = Lexer::with_code("-}");
255        let mut lexer = WordLexer {
256            lexer: &mut lexer,
257            context: WordContext::Word,
258        };
259
260        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
261        assert_matches!(result, Modifier::Switch(switch) => {
262            assert_eq!(switch.action, SwitchAction::Default);
263            assert_eq!(switch.condition, SwitchCondition::Unset);
264            assert_eq!(switch.word.units, []);
265            assert_eq!(*switch.word.location.code.value.borrow(), "-}");
266            assert_eq!(switch.word.location.range, 1..1);
267        });
268
269        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
270    }
271
272    #[test]
273    fn lexer_suffix_modifier_colon_default_word() {
274        let mut lexer = Lexer::with_code(r":-cool}");
275        let mut lexer = WordLexer {
276            lexer: &mut lexer,
277            context: WordContext::Word,
278        };
279
280        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
281        assert_matches!(result, Modifier::Switch(switch) => {
282            assert_eq!(switch.action, SwitchAction::Default);
283            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
284            assert_eq!(
285                switch.word.units,
286                [
287                    WordUnit::Unquoted(TextUnit::Literal('c')),
288                    WordUnit::Unquoted(TextUnit::Literal('o')),
289                    WordUnit::Unquoted(TextUnit::Literal('o')),
290                    WordUnit::Unquoted(TextUnit::Literal('l')),
291                ]
292            );
293            assert_eq!(*switch.word.location.code.value.borrow(), ":-cool}");
294            assert_eq!(switch.word.location.range, 2..6);
295        });
296
297        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
298    }
299
300    #[test]
301    fn lexer_suffix_modifier_colon_assign_empty() {
302        let mut lexer = Lexer::with_code(":=}");
303        let mut lexer = WordLexer {
304            lexer: &mut lexer,
305            context: WordContext::Word,
306        };
307
308        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
309        assert_matches!(result, Modifier::Switch(switch) => {
310            assert_eq!(switch.action, SwitchAction::Assign);
311            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
312            assert_eq!(switch.word.units, []);
313            assert_eq!(*switch.word.location.code.value.borrow(), ":=}");
314            assert_eq!(switch.word.location.range, 2..2);
315        });
316
317        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
318    }
319
320    #[test]
321    fn lexer_suffix_modifier_assign_word() {
322        let mut lexer = Lexer::with_code(r"=Yes}");
323        let mut lexer = WordLexer {
324            lexer: &mut lexer,
325            context: WordContext::Word,
326        };
327
328        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
329        assert_matches!(result, Modifier::Switch(switch) => {
330            assert_eq!(switch.action, SwitchAction::Assign);
331            assert_eq!(switch.condition, SwitchCondition::Unset);
332            assert_eq!(
333                switch.word.units,
334                [
335                    WordUnit::Unquoted(TextUnit::Literal('Y')),
336                    WordUnit::Unquoted(TextUnit::Literal('e')),
337                    WordUnit::Unquoted(TextUnit::Literal('s')),
338                ]
339            );
340            assert_eq!(*switch.word.location.code.value.borrow(), "=Yes}");
341            assert_eq!(switch.word.location.range, 1..4);
342        });
343
344        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
345    }
346
347    #[test]
348    fn lexer_suffix_modifier_error_empty() {
349        let mut lexer = Lexer::with_code("?}");
350        let mut lexer = WordLexer {
351            lexer: &mut lexer,
352            context: WordContext::Word,
353        };
354
355        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
356        assert_matches!(result, Modifier::Switch(switch) => {
357            assert_eq!(switch.action, SwitchAction::Error);
358            assert_eq!(switch.condition, SwitchCondition::Unset);
359            assert_eq!(switch.word.units, []);
360            assert_eq!(*switch.word.location.code.value.borrow(), "?}");
361            assert_eq!(switch.word.location.range, 1..1);
362        });
363
364        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
365    }
366
367    #[test]
368    fn lexer_suffix_modifier_colon_error_word() {
369        let mut lexer = Lexer::with_code(r":?No}");
370        let mut lexer = WordLexer {
371            lexer: &mut lexer,
372            context: WordContext::Word,
373        };
374
375        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
376        assert_matches!(result, Modifier::Switch(switch) => {
377            assert_eq!(switch.action, SwitchAction::Error);
378            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
379            assert_eq!(
380                switch.word.units,
381                [
382                    WordUnit::Unquoted(TextUnit::Literal('N')),
383                    WordUnit::Unquoted(TextUnit::Literal('o')),
384                ]
385            );
386            assert_eq!(*switch.word.location.code.value.borrow(), ":?No}");
387            assert_eq!(switch.word.location.range, 2..4);
388        });
389
390        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
391    }
392
393    #[test]
394    fn lexer_suffix_modifier_tilde_expansion_in_switch_word_in_word_context() {
395        let mut lexer = Lexer::with_code(r"-~}");
396        let mut lexer = WordLexer {
397            lexer: &mut lexer,
398            context: WordContext::Word,
399        };
400
401        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
402        assert_matches!(result, Modifier::Switch(switch) => {
403            assert_eq!(
404                switch.word.units,
405                [WordUnit::Tilde {
406                    name: "".to_string(),
407                    followed_by_slash: false
408                }]
409            );
410        });
411    }
412
413    #[test]
414    fn lexer_suffix_modifier_tilde_expansion_in_switch_word_in_text_context() {
415        let mut lexer = Lexer::with_code(r"-~}");
416        let mut lexer = WordLexer {
417            lexer: &mut lexer,
418            context: WordContext::Text,
419        };
420
421        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
422        assert_matches!(result, Modifier::Switch(switch) => {
423            assert_eq!(
424                switch.word.units,
425                [WordUnit::Unquoted(TextUnit::Literal('~'))]
426            );
427        });
428    }
429
430    #[test]
431    fn lexer_suffix_modifier_trim_shortest_prefix_in_word_context() {
432        let mut lexer = Lexer::with_code("#'*'}");
433        let mut lexer = WordLexer {
434            lexer: &mut lexer,
435            context: WordContext::Word,
436        };
437
438        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
439        assert_matches!(result, Modifier::Trim(trim) => {
440            assert_eq!(trim.side, TrimSide::Prefix);
441            assert_eq!(trim.length, TrimLength::Shortest);
442            assert_eq!(trim.pattern.units, [WordUnit::SingleQuote("*".to_string())]);
443            assert_eq!(*trim.pattern.location.code.value.borrow(), "#'*'}");
444            assert_eq!(trim.pattern.location.range, 1..4);
445        });
446
447        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
448    }
449
450    #[test]
451    fn lexer_suffix_modifier_trim_shortest_prefix_in_text_context() {
452        let mut lexer = Lexer::with_code("#'*'}");
453        let mut lexer = WordLexer {
454            lexer: &mut lexer,
455            context: WordContext::Text,
456        };
457
458        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
459        assert_matches!(result, Modifier::Trim(trim) => {
460            assert_eq!(trim.side, TrimSide::Prefix);
461            assert_eq!(trim.length, TrimLength::Shortest);
462            assert_eq!(trim.pattern.units, [WordUnit::SingleQuote("*".to_string())]);
463            assert_eq!(*trim.pattern.location.code.value.borrow(), "#'*'}");
464            assert_eq!(trim.pattern.location.range, 1..4);
465        });
466
467        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
468    }
469
470    #[test]
471    fn lexer_suffix_modifier_trim_longest_prefix() {
472        let mut lexer = Lexer::with_code(r#"##"?"}"#);
473        let mut lexer = WordLexer {
474            lexer: &mut lexer,
475            context: WordContext::Word,
476        };
477
478        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
479        assert_matches!(result, Modifier::Trim(trim) => {
480            assert_eq!(trim.side, TrimSide::Prefix);
481            assert_eq!(trim.length, TrimLength::Longest);
482            assert_eq!(trim.pattern.units.len(), 1, "{:?}", trim.pattern);
483            assert_matches!(&trim.pattern.units[0], WordUnit::DoubleQuote(Text(units)) => {
484                assert_eq!(units[..], [TextUnit::Literal('?')]);
485            });
486            assert_eq!(*trim.pattern.location.code.value.borrow(), r#"##"?"}"#);
487            assert_eq!(trim.pattern.location.range, 2..5);
488        });
489
490        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
491    }
492
493    #[test]
494    fn lexer_suffix_modifier_trim_shortest_suffix() {
495        let mut lexer = Lexer::with_code(r"%\%}");
496        let mut lexer = WordLexer {
497            lexer: &mut lexer,
498            context: WordContext::Word,
499        };
500
501        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
502        assert_matches!(result, Modifier::Trim(trim) => {
503            assert_eq!(trim.side, TrimSide::Suffix);
504            assert_eq!(trim.length, TrimLength::Shortest);
505            assert_eq!(
506                trim.pattern.units,
507                [WordUnit::Unquoted(TextUnit::Backslashed('%'))]
508            );
509            assert_eq!(*trim.pattern.location.code.value.borrow(), r"%\%}");
510            assert_eq!(trim.pattern.location.range, 1..3);
511        });
512
513        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
514    }
515
516    #[test]
517    fn lexer_suffix_modifier_trim_longest_suffix() {
518        let mut lexer = Lexer::with_code("%%%}");
519        let mut lexer = WordLexer {
520            lexer: &mut lexer,
521            context: WordContext::Word,
522        };
523
524        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
525        assert_matches!(result, Modifier::Trim(trim) => {
526            assert_eq!(trim.side, TrimSide::Suffix);
527            assert_eq!(trim.length, TrimLength::Longest);
528            assert_eq!(
529                trim.pattern.units,
530                [WordUnit::Unquoted(TextUnit::Literal('%'))]
531            );
532            assert_eq!(*trim.pattern.location.code.value.borrow(), "%%%}");
533            assert_eq!(trim.pattern.location.range, 2..3);
534        });
535
536        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
537    }
538
539    #[test]
540    fn lexer_suffix_modifier_tilde_expansion_in_trim_word() {
541        let mut lexer = Lexer::with_code(r"#~}");
542        let mut lexer = WordLexer {
543            lexer: &mut lexer,
544            context: WordContext::Word,
545        };
546
547        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
548        assert_matches!(result, Modifier::Trim(trim) => {
549            assert_eq!(
550                trim.pattern.units,
551                [WordUnit::Tilde {
552                    name: "".to_string(),
553                    followed_by_slash: false
554                }]
555            );
556        });
557    }
558
559    #[test]
560    fn lexer_suffix_modifier_orphan_colon_eof() {
561        let mut lexer = Lexer::with_code(r":");
562        let mut lexer = WordLexer {
563            lexer: &mut lexer,
564            context: WordContext::Word,
565        };
566
567        let e = lexer.suffix_modifier().now_or_never().unwrap().unwrap_err();
568        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidModifier));
569        assert_eq!(*e.location.code.value.borrow(), ":");
570        assert_eq!(e.location.range, 0..1);
571    }
572
573    #[test]
574    fn lexer_suffix_modifier_orphan_colon_followed_by_letter() {
575        let mut lexer = Lexer::with_code(r":x}");
576        let mut lexer = WordLexer {
577            lexer: &mut lexer,
578            context: WordContext::Word,
579        };
580
581        let e = lexer.suffix_modifier().now_or_never().unwrap().unwrap_err();
582        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidModifier));
583        assert_eq!(*e.location.code.value.borrow(), ":x}");
584        assert_eq!(e.location.range, 0..1);
585    }
586
587    #[test]
588    fn lexer_suffix_modifier_orphan_colon_followed_by_symbol() {
589        let mut lexer = Lexer::with_code(r":#}");
590        let mut lexer = WordLexer {
591            lexer: &mut lexer,
592            context: WordContext::Word,
593        };
594
595        let e = lexer.suffix_modifier().now_or_never().unwrap().unwrap_err();
596        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidModifier));
597        assert_eq!(*e.location.code.value.borrow(), ":#}");
598        assert_eq!(e.location.range, 0..2);
599    }
600}