anticipate_runner/
parser.rs

1use crate::{
2    error::LexError, interpreter::ScriptSource, resolve_path, Error, Result,
3};
4use logos::{Lexer, Logos};
5use std::{
6    borrow::Cow,
7    ops::Range,
8    path::{Path, PathBuf},
9};
10
11fn pragma(lex: &mut Lexer<Token>) -> Option<String> {
12    let slice = lex.slice();
13    let value = &slice[2..];
14    Some(value.to_owned())
15}
16
17fn integer(lex: &mut Lexer<Token>) -> Option<u64> {
18    let slice = lex.slice();
19    if let Some(num) = slice.split(' ').last() {
20        num.parse().ok()
21    } else {
22        None
23    }
24}
25
26#[derive(Logos, Debug, PartialEq, Clone)]
27#[logos(error = LexError)]
28enum Token {
29    #[regex("#![^\n]+", callback = pragma)]
30    Pragma(String),
31    #[regex("#[$]\\s+sendline\\s")]
32    SendLine,
33    #[regex("#[$]\\s+sendcontrol\\s")]
34    SendControl,
35    #[regex("#[$]\\s+expect\\s")]
36    Expect,
37    #[regex("#[$]\\s+regex\\s")]
38    Regex,
39    #[regex("#[$]\\s+sleep\\s+([0-9]+)", callback = integer)]
40    Sleep(u64),
41    #[regex("#[$]\\s+readline\\s*")]
42    ReadLine,
43    #[regex("#[$]\\s+wait\\s*")]
44    Wait,
45    #[regex("#[$]\\s+clear\\s*")]
46    Clear,
47    #[regex("#[$]\\s+send ")]
48    Send,
49    #[regex("#[$]\\s+flush\\s*")]
50    Flush,
51    #[regex("#[$]\\s+include\\s+")]
52    Include,
53    #[regex("#[$].?", priority = 4)]
54    Command,
55    #[regex("\r?\n", priority = 3)]
56    Newline,
57    #[regex("(\t| )*#[^$!]?#*.", priority = 2)]
58    Comment,
59    #[regex("(.|[\t ]+)", priority = 0)]
60    Text,
61}
62
63#[derive(Logos, Debug, PartialEq, Clone)]
64#[logos(error = LexError)]
65enum EnvVars {
66    #[regex("[$][a-zA-Z0-9_]+")]
67    Var,
68    #[regex(".", priority = 0)]
69    Text,
70}
71
72/// Include reference.
73#[derive(Debug)]
74pub struct Include {
75    /// Path to the file.
76    pub path: PathBuf,
77    /// Index in the parent instructions.
78    pub index: usize,
79}
80
81/// Instruction to execute.
82#[derive(Debug)]
83pub enum Instruction<'s> {
84    /// Program to execute.
85    Pragma(String),
86    /// Send a line of text.
87    SendLine(&'s str),
88    /// Send a control character.
89    SendControl(&'s str),
90    /// Expect a string.
91    Expect(&'s str),
92    /// Expect a regex match.
93    Regex(&'s str),
94    /// Sleep a while.
95    Sleep(u64),
96    /// Comment text.
97    Comment(&'s str),
98    /// Read a line of output.
99    ReadLine,
100    /// Wait for the prompt.
101    Wait,
102    /// Clear the screen.
103    Clear,
104    /// Send text, the output stream is not flushed.
105    Send(&'s str),
106    /// Flush the output stream.
107    Flush,
108    /// Include script.
109    Include(ScriptSource),
110}
111
112/// Sequence of commands to execute.
113pub type Instructions<'s> = Vec<Instruction<'s>>;
114
115/// Parser for scripts.
116#[derive(Debug)]
117pub struct ScriptParser;
118
119impl ScriptParser {
120    /// Parse input commands.
121    pub fn parse(source: &str) -> Result<Instructions<'_>> {
122        let (instructions, _) = ScriptParser::parse_file(source, "")?;
123        Ok(instructions)
124    }
125
126    /// Parse input commands relative to a file path.
127    pub fn parse_file(
128        source: &str,
129        base: impl AsRef<Path>,
130    ) -> Result<(Instructions<'_>, Vec<Include>)> {
131        let mut cmd = Vec::new();
132        let mut lex = Token::lexer(source);
133        let mut next_token = lex.next();
134        let mut includes = Vec::new();
135        while let Some(token) = next_token.take() {
136            let token = token?;
137            let span = lex.span();
138            tracing::debug!(token = ?token, "parse");
139            match token {
140                Token::Command => {
141                    let (text, _) = Self::parse_text(&mut lex, source, None)?;
142                    return Err(Error::UnknownInstruction(text.to_owned()));
143                }
144                Token::Comment => {
145                    let (_, finish) =
146                        Self::parse_text(&mut lex, source, None)?;
147                    let text = &source[span.start..finish.end];
148                    cmd.push(Instruction::Comment(text));
149                }
150                Token::Include => {
151                    let (text, _) = Self::parse_text(&mut lex, source, None)?;
152                    let text = text.trim();
153                    match resolve_path(base.as_ref(), text) {
154                        Ok(path) => {
155                            let path: PathBuf = path.as_ref().into();
156                            if !path.try_exists()? {
157                                return Err(Error::Include(
158                                    text.to_owned(),
159                                    path,
160                                ));
161                            }
162                            includes.push(Include {
163                                index: cmd.len(),
164                                path,
165                            });
166                        }
167                        Err(_) => {
168                            return Err(Error::Include(
169                                text.to_owned(),
170                                PathBuf::from(text),
171                            ));
172                        }
173                    }
174                }
175                Token::ReadLine => {
176                    cmd.push(Instruction::ReadLine);
177                }
178                Token::Wait => {
179                    cmd.push(Instruction::Wait);
180                }
181                Token::Clear => {
182                    cmd.push(Instruction::Clear);
183                }
184                Token::Pragma(pragma) => {
185                    if !cmd.is_empty() {
186                        return Err(Error::PragmaFirst);
187                    }
188                    cmd.push(Instruction::Pragma(pragma));
189                }
190                Token::Send => {
191                    let (text, _) = Self::parse_text(&mut lex, source, None)?;
192                    cmd.push(Instruction::Send(text));
193                }
194                Token::Flush => {
195                    cmd.push(Instruction::Flush);
196                }
197                Token::SendLine => {
198                    let (text, _) = Self::parse_text(&mut lex, source, None)?;
199                    cmd.push(Instruction::SendLine(text));
200                }
201                Token::Expect => {
202                    let (text, _) = Self::parse_text(&mut lex, source, None)?;
203                    cmd.push(Instruction::Expect(text));
204                }
205                Token::Regex => {
206                    let (text, _) = Self::parse_text(&mut lex, source, None)?;
207                    cmd.push(Instruction::Regex(text));
208                }
209                Token::SendControl => {
210                    let (text, _) = Self::parse_text(&mut lex, source, None)?;
211                    cmd.push(Instruction::SendControl(text));
212                }
213                Token::Sleep(num) => {
214                    cmd.push(Instruction::Sleep(num));
215                }
216                // Unhandled text is send line
217                Token::Text => {
218                    let (text, _) =
219                        Self::parse_text(&mut lex, source, Some(span))?;
220                    if text.starts_with("#$") {
221                        return Err(Error::UnknownInstruction(
222                            text.to_owned(),
223                        ));
224                    }
225                    cmd.push(Instruction::SendLine(text));
226                }
227                Token::Newline => {}
228            }
229            next_token = lex.next();
230        }
231
232        Ok((cmd, includes))
233    }
234
235    fn parse_text<'s>(
236        lex: &mut Lexer<Token>,
237        source: &'s str,
238        start: Option<Range<usize>>,
239    ) -> Result<(&'s str, Range<usize>)> {
240        let begin = if let Some(start) = start {
241            start
242        } else {
243            lex.span().end..lex.span().end
244        };
245
246        let mut finish: Range<usize> = lex.span();
247        let mut next_token = lex.next();
248        while let Some(token) = next_token.take() {
249            let token = token?;
250            match token {
251                Token::Text => {
252                    finish = lex.span();
253                }
254                _ => break,
255            }
256            next_token = lex.next();
257        }
258        Ok((&source[begin.start..finish.end], finish))
259    }
260
261    pub(crate) fn interpolate(value: &str) -> Result<Cow<str>> {
262        if value.contains('$') {
263            let mut s = String::new();
264            let mut lex = EnvVars::lexer(value);
265            let mut next_token = lex.next();
266            while let Some(token) = next_token.take() {
267                let token = token?;
268                match token {
269                    EnvVars::Var => {
270                        let var = lex.slice();
271                        if let Ok(val) = std::env::var(&var[1..]) {
272                            s.push_str(&val);
273                        } else {
274                            s.push_str(var);
275                        }
276                    }
277                    _ => s.push_str(lex.slice()),
278                }
279
280                next_token = lex.next();
281            }
282            Ok(Cow::Owned(s))
283        } else {
284            Ok(Cow::Borrowed(value))
285        }
286    }
287}