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::SwitchCondition;
28use crate::syntax::SwitchType;
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 r#type = match symbol {
97            '+' => SwitchType::Alter,
98            '-' => SwitchType::Default,
99            '=' => SwitchType::Assign,
100            '?' => SwitchType::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            r#type,
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::source::Source;
153    use crate::syntax::Text;
154    use crate::syntax::TextUnit;
155    use crate::syntax::WordUnit;
156    use assert_matches::assert_matches;
157    use futures_util::FutureExt;
158
159    #[test]
160    fn lexer_suffix_modifier_eof() {
161        let mut lexer = Lexer::from_memory("", Source::Unknown);
162        let mut lexer = WordLexer {
163            lexer: &mut lexer,
164            context: WordContext::Word,
165        };
166
167        let result = lexer.suffix_modifier().now_or_never().unwrap();
168        assert_eq!(result, Ok(Modifier::None));
169    }
170
171    #[test]
172    fn lexer_suffix_modifier_none() {
173        let mut lexer = Lexer::from_memory("}", Source::Unknown);
174        let mut lexer = WordLexer {
175            lexer: &mut lexer,
176            context: WordContext::Word,
177        };
178
179        let result = lexer.suffix_modifier().now_or_never().unwrap();
180        assert_eq!(result, Ok(Modifier::None));
181
182        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
183    }
184
185    #[test]
186    fn lexer_suffix_modifier_alter_empty() {
187        let mut lexer = Lexer::from_memory("+}", Source::Unknown);
188        let mut lexer = WordLexer {
189            lexer: &mut lexer,
190            context: WordContext::Word,
191        };
192
193        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
194        assert_matches!(result, Modifier::Switch(switch) => {
195            assert_eq!(switch.r#type, SwitchType::Alter);
196            assert_eq!(switch.condition, SwitchCondition::Unset);
197            assert_eq!(switch.word.units, []);
198            assert_eq!(*switch.word.location.code.value.borrow(), "+}");
199            assert_eq!(switch.word.location.range, 1..1);
200        });
201
202        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
203    }
204
205    #[test]
206    fn lexer_suffix_modifier_alter_word() {
207        let mut lexer = Lexer::from_memory(r"+a  z}", Source::Unknown);
208        let mut lexer = WordLexer {
209            lexer: &mut lexer,
210            context: WordContext::Word,
211        };
212
213        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
214        assert_matches!(result, Modifier::Switch(switch) => {
215            assert_eq!(switch.r#type, SwitchType::Alter);
216            assert_eq!(switch.condition, SwitchCondition::Unset);
217            assert_eq!(
218                switch.word.units,
219                [
220                    WordUnit::Unquoted(TextUnit::Literal('a')),
221                    WordUnit::Unquoted(TextUnit::Literal(' ')),
222                    WordUnit::Unquoted(TextUnit::Literal(' ')),
223                    WordUnit::Unquoted(TextUnit::Literal('z')),
224                ]
225            );
226            assert_eq!(*switch.word.location.code.value.borrow(), "+a  z}");
227            assert_eq!(switch.word.location.range, 1..5);
228        });
229
230        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
231    }
232
233    #[test]
234    fn lexer_suffix_modifier_colon_alter_empty() {
235        let mut lexer = Lexer::from_memory(":+}", Source::Unknown);
236        let mut lexer = WordLexer {
237            lexer: &mut lexer,
238            context: WordContext::Word,
239        };
240
241        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
242        assert_matches!(result, Modifier::Switch(switch) => {
243            assert_eq!(switch.r#type, SwitchType::Alter);
244            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
245            assert_eq!(switch.word.units, []);
246            assert_eq!(*switch.word.location.code.value.borrow(), ":+}");
247            assert_eq!(switch.word.location.range, 2..2);
248        });
249
250        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
251    }
252
253    #[test]
254    fn lexer_suffix_modifier_default_empty() {
255        let mut lexer = Lexer::from_memory("-}", Source::Unknown);
256        let mut lexer = WordLexer {
257            lexer: &mut lexer,
258            context: WordContext::Word,
259        };
260
261        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
262        assert_matches!(result, Modifier::Switch(switch) => {
263            assert_eq!(switch.r#type, SwitchType::Default);
264            assert_eq!(switch.condition, SwitchCondition::Unset);
265            assert_eq!(switch.word.units, []);
266            assert_eq!(*switch.word.location.code.value.borrow(), "-}");
267            assert_eq!(switch.word.location.range, 1..1);
268        });
269
270        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
271    }
272
273    #[test]
274    fn lexer_suffix_modifier_colon_default_word() {
275        let mut lexer = Lexer::from_memory(r":-cool}", Source::Unknown);
276        let mut lexer = WordLexer {
277            lexer: &mut lexer,
278            context: WordContext::Word,
279        };
280
281        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
282        assert_matches!(result, Modifier::Switch(switch) => {
283            assert_eq!(switch.r#type, SwitchType::Default);
284            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
285            assert_eq!(
286                switch.word.units,
287                [
288                    WordUnit::Unquoted(TextUnit::Literal('c')),
289                    WordUnit::Unquoted(TextUnit::Literal('o')),
290                    WordUnit::Unquoted(TextUnit::Literal('o')),
291                    WordUnit::Unquoted(TextUnit::Literal('l')),
292                ]
293            );
294            assert_eq!(*switch.word.location.code.value.borrow(), ":-cool}");
295            assert_eq!(switch.word.location.range, 2..6);
296        });
297
298        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
299    }
300
301    #[test]
302    fn lexer_suffix_modifier_colon_assign_empty() {
303        let mut lexer = Lexer::from_memory(":=}", Source::Unknown);
304        let mut lexer = WordLexer {
305            lexer: &mut lexer,
306            context: WordContext::Word,
307        };
308
309        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
310        assert_matches!(result, Modifier::Switch(switch) => {
311            assert_eq!(switch.r#type, SwitchType::Assign);
312            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
313            assert_eq!(switch.word.units, []);
314            assert_eq!(*switch.word.location.code.value.borrow(), ":=}");
315            assert_eq!(switch.word.location.range, 2..2);
316        });
317
318        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
319    }
320
321    #[test]
322    fn lexer_suffix_modifier_assign_word() {
323        let mut lexer = Lexer::from_memory(r"=Yes}", Source::Unknown);
324        let mut lexer = WordLexer {
325            lexer: &mut lexer,
326            context: WordContext::Word,
327        };
328
329        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
330        assert_matches!(result, Modifier::Switch(switch) => {
331            assert_eq!(switch.r#type, SwitchType::Assign);
332            assert_eq!(switch.condition, SwitchCondition::Unset);
333            assert_eq!(
334                switch.word.units,
335                [
336                    WordUnit::Unquoted(TextUnit::Literal('Y')),
337                    WordUnit::Unquoted(TextUnit::Literal('e')),
338                    WordUnit::Unquoted(TextUnit::Literal('s')),
339                ]
340            );
341            assert_eq!(*switch.word.location.code.value.borrow(), "=Yes}");
342            assert_eq!(switch.word.location.range, 1..4);
343        });
344
345        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
346    }
347
348    #[test]
349    fn lexer_suffix_modifier_error_empty() {
350        let mut lexer = Lexer::from_memory("?}", Source::Unknown);
351        let mut lexer = WordLexer {
352            lexer: &mut lexer,
353            context: WordContext::Word,
354        };
355
356        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
357        assert_matches!(result, Modifier::Switch(switch) => {
358            assert_eq!(switch.r#type, SwitchType::Error);
359            assert_eq!(switch.condition, SwitchCondition::Unset);
360            assert_eq!(switch.word.units, []);
361            assert_eq!(*switch.word.location.code.value.borrow(), "?}");
362            assert_eq!(switch.word.location.range, 1..1);
363        });
364
365        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
366    }
367
368    #[test]
369    fn lexer_suffix_modifier_colon_error_word() {
370        let mut lexer = Lexer::from_memory(r":?No}", Source::Unknown);
371        let mut lexer = WordLexer {
372            lexer: &mut lexer,
373            context: WordContext::Word,
374        };
375
376        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
377        assert_matches!(result, Modifier::Switch(switch) => {
378            assert_eq!(switch.r#type, SwitchType::Error);
379            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
380            assert_eq!(
381                switch.word.units,
382                [
383                    WordUnit::Unquoted(TextUnit::Literal('N')),
384                    WordUnit::Unquoted(TextUnit::Literal('o')),
385                ]
386            );
387            assert_eq!(*switch.word.location.code.value.borrow(), ":?No}");
388            assert_eq!(switch.word.location.range, 2..4);
389        });
390
391        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
392    }
393
394    #[test]
395    fn lexer_suffix_modifier_tilde_expansion_in_switch_word_in_word_context() {
396        let mut lexer = Lexer::from_memory(r"-~}", Source::Unknown);
397        let mut lexer = WordLexer {
398            lexer: &mut lexer,
399            context: WordContext::Word,
400        };
401
402        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
403        assert_matches!(result, Modifier::Switch(switch) => {
404            assert_eq!(switch.word.units, [WordUnit::Tilde("".to_string())]);
405        });
406    }
407
408    #[test]
409    fn lexer_suffix_modifier_tilde_expansion_in_switch_word_in_text_context() {
410        let mut lexer = Lexer::from_memory(r"-~}", Source::Unknown);
411        let mut lexer = WordLexer {
412            lexer: &mut lexer,
413            context: WordContext::Text,
414        };
415
416        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
417        assert_matches!(result, Modifier::Switch(switch) => {
418            assert_eq!(
419                switch.word.units,
420                [WordUnit::Unquoted(TextUnit::Literal('~'))]
421            );
422        });
423    }
424
425    #[test]
426    fn lexer_suffix_modifier_trim_shortest_prefix_in_word_context() {
427        let mut lexer = Lexer::from_memory("#'*'}", Source::Unknown);
428        let mut lexer = WordLexer {
429            lexer: &mut lexer,
430            context: WordContext::Word,
431        };
432
433        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
434        assert_matches!(result, Modifier::Trim(trim) => {
435            assert_eq!(trim.side, TrimSide::Prefix);
436            assert_eq!(trim.length, TrimLength::Shortest);
437            assert_eq!(trim.pattern.units, [WordUnit::SingleQuote("*".to_string())]);
438            assert_eq!(*trim.pattern.location.code.value.borrow(), "#'*'}");
439            assert_eq!(trim.pattern.location.range, 1..4);
440        });
441
442        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
443    }
444
445    #[test]
446    fn lexer_suffix_modifier_trim_shortest_prefix_in_text_context() {
447        let mut lexer = Lexer::from_memory("#'*'}", Source::Unknown);
448        let mut lexer = WordLexer {
449            lexer: &mut lexer,
450            context: WordContext::Text,
451        };
452
453        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
454        assert_matches!(result, Modifier::Trim(trim) => {
455            assert_eq!(trim.side, TrimSide::Prefix);
456            assert_eq!(trim.length, TrimLength::Shortest);
457            assert_eq!(trim.pattern.units, [WordUnit::SingleQuote("*".to_string())]);
458            assert_eq!(*trim.pattern.location.code.value.borrow(), "#'*'}");
459            assert_eq!(trim.pattern.location.range, 1..4);
460        });
461
462        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
463    }
464
465    #[test]
466    fn lexer_suffix_modifier_trim_longest_prefix() {
467        let mut lexer = Lexer::from_memory(r#"##"?"}"#, Source::Unknown);
468        let mut lexer = WordLexer {
469            lexer: &mut lexer,
470            context: WordContext::Word,
471        };
472
473        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
474        assert_matches!(result, Modifier::Trim(trim) => {
475            assert_eq!(trim.side, TrimSide::Prefix);
476            assert_eq!(trim.length, TrimLength::Longest);
477            assert_eq!(trim.pattern.units.len(), 1, "{:?}", trim.pattern);
478            assert_matches!(&trim.pattern.units[0], WordUnit::DoubleQuote(Text(units)) => {
479                assert_eq!(units[..], [TextUnit::Literal('?')]);
480            });
481            assert_eq!(*trim.pattern.location.code.value.borrow(), r#"##"?"}"#);
482            assert_eq!(trim.pattern.location.range, 2..5);
483        });
484
485        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
486    }
487
488    #[test]
489    fn lexer_suffix_modifier_trim_shortest_suffix() {
490        let mut lexer = Lexer::from_memory(r"%\%}", Source::Unknown);
491        let mut lexer = WordLexer {
492            lexer: &mut lexer,
493            context: WordContext::Word,
494        };
495
496        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
497        assert_matches!(result, Modifier::Trim(trim) => {
498            assert_eq!(trim.side, TrimSide::Suffix);
499            assert_eq!(trim.length, TrimLength::Shortest);
500            assert_eq!(
501                trim.pattern.units,
502                [WordUnit::Unquoted(TextUnit::Backslashed('%'))]
503            );
504            assert_eq!(*trim.pattern.location.code.value.borrow(), r"%\%}");
505            assert_eq!(trim.pattern.location.range, 1..3);
506        });
507
508        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
509    }
510
511    #[test]
512    fn lexer_suffix_modifier_trim_longest_suffix() {
513        let mut lexer = Lexer::from_memory("%%%}", Source::Unknown);
514        let mut lexer = WordLexer {
515            lexer: &mut lexer,
516            context: WordContext::Word,
517        };
518
519        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
520        assert_matches!(result, Modifier::Trim(trim) => {
521            assert_eq!(trim.side, TrimSide::Suffix);
522            assert_eq!(trim.length, TrimLength::Longest);
523            assert_eq!(
524                trim.pattern.units,
525                [WordUnit::Unquoted(TextUnit::Literal('%'))]
526            );
527            assert_eq!(*trim.pattern.location.code.value.borrow(), "%%%}");
528            assert_eq!(trim.pattern.location.range, 2..3);
529        });
530
531        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('}')));
532    }
533
534    #[test]
535    fn lexer_suffix_modifier_tilde_expansion_in_trim_word() {
536        let mut lexer = Lexer::from_memory(r"#~}", Source::Unknown);
537        let mut lexer = WordLexer {
538            lexer: &mut lexer,
539            context: WordContext::Word,
540        };
541
542        let result = lexer.suffix_modifier().now_or_never().unwrap().unwrap();
543        assert_matches!(result, Modifier::Trim(trim) => {
544            assert_eq!(trim.pattern.units, [WordUnit::Tilde("".to_string())]);
545        });
546    }
547
548    #[test]
549    fn lexer_suffix_modifier_orphan_colon_eof() {
550        let mut lexer = Lexer::from_memory(r":", Source::Unknown);
551        let mut lexer = WordLexer {
552            lexer: &mut lexer,
553            context: WordContext::Word,
554        };
555
556        let e = lexer.suffix_modifier().now_or_never().unwrap().unwrap_err();
557        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidModifier));
558        assert_eq!(*e.location.code.value.borrow(), ":");
559        assert_eq!(e.location.range, 0..1);
560    }
561
562    #[test]
563    fn lexer_suffix_modifier_orphan_colon_followed_by_letter() {
564        let mut lexer = Lexer::from_memory(r":x}", Source::Unknown);
565        let mut lexer = WordLexer {
566            lexer: &mut lexer,
567            context: WordContext::Word,
568        };
569
570        let e = lexer.suffix_modifier().now_or_never().unwrap().unwrap_err();
571        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidModifier));
572        assert_eq!(*e.location.code.value.borrow(), ":x}");
573        assert_eq!(e.location.range, 0..1);
574    }
575
576    #[test]
577    fn lexer_suffix_modifier_orphan_colon_followed_by_symbol() {
578        let mut lexer = Lexer::from_memory(r":#}", Source::Unknown);
579        let mut lexer = WordLexer {
580            lexer: &mut lexer,
581            context: WordContext::Word,
582        };
583
584        let e = lexer.suffix_modifier().now_or_never().unwrap().unwrap_err();
585        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidModifier));
586        assert_eq!(*e.location.code.value.borrow(), ":#}");
587        assert_eq!(e.location.range, 0..2);
588    }
589}