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#[derive(Debug)]
74pub struct Include {
75 pub path: PathBuf,
77 pub index: usize,
79}
80
81#[derive(Debug)]
83pub enum Instruction<'s> {
84 Pragma(String),
86 SendLine(&'s str),
88 SendControl(&'s str),
90 Expect(&'s str),
92 Regex(&'s str),
94 Sleep(u64),
96 Comment(&'s str),
98 ReadLine,
100 Wait,
102 Clear,
104 Send(&'s str),
106 Flush,
108 Include(ScriptSource),
110}
111
112pub type Instructions<'s> = Vec<Instruction<'s>>;
114
115#[derive(Debug)]
117pub struct ScriptParser;
118
119impl ScriptParser {
120 pub fn parse(source: &str) -> Result<Instructions<'_>> {
122 let (instructions, _) = ScriptParser::parse_file(source, "")?;
123 Ok(instructions)
124 }
125
126 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 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}