xref: /NovaShell/src/parser.rs (revision 7bb802ad1ee86687164e3577e7cb403d89b94963)
1 use std::{
2     collections::HashMap,
3     io::ErrorKind,
4     os::fd::{AsRawFd, FromRawFd},
5     process::{Child, ChildStdout, Stdio},
6     sync::{Arc, Mutex},
7 };
8 
9 use regex::Regex;
10 
11 #[derive(Debug)]
12 pub enum Token {
13     Word(String),   // 普通的命令或选项
14     Symbol(String), // 特殊符号
15 }
16 
17 #[derive(Debug, Clone)]
18 pub enum CommandType {
19     Simple, // 简单命令
20     Redirect {
21         target: RedirectTarget,
22         mode: RedirectMode,
23     }, // 重定向命令
24     Pipe,   // 管道命令
25 }
26 
27 #[derive(Debug, Clone, PartialEq, Eq, Copy)]
28 pub enum ConnectType {
29     Simple, // 普通连接
30     And,    // 与连接
31     Or,     // 或连接
32 }
33 
34 #[derive(Debug, Clone)]
35 pub struct Command {
36     name: String,
37     args: Vec<String>,
38     cmd_type: CommandType,
39     conn_type: ConnectType,
40 }
41 
42 impl Command {
43     pub fn new(
44         name: &String,
45         args: &[String],
46         cmd_type: CommandType,
47         conn_type: ConnectType,
48     ) -> Command {
49         Self {
50             name: name.clone(),
51             args: args.to_vec(),
52             cmd_type,
53             conn_type,
54         }
55     }
56 
57     pub fn execute(&self) {}
58 }
59 
60 #[derive(Debug, Clone)]
61 pub enum RedirectTarget {
62     File(String),
63     FileDiscriptor(i32),
64 }
65 
66 impl RedirectTarget {
67     pub fn from_string(str: &String) -> Option<RedirectTarget> {
68         if str.starts_with("&") {
69             if let Ok(fd) = str.split_at(1).1.parse::<i32>() {
70                 Some(RedirectTarget::FileDiscriptor(fd))
71             } else {
72                 None
73             }
74         } else {
75             Some(RedirectTarget::File(str.clone()))
76         }
77     }
78 }
79 
80 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
81 pub enum RedirectMode {
82     Overwrite,
83     Append,
84 }
85 
86 impl RedirectMode {
87     pub fn from_string(str: &String) -> Option<RedirectMode> {
88         match str.as_str() {
89             ">" => Some(RedirectMode::Overwrite),
90             ">>" => Some(RedirectMode::Append),
91             _ => None,
92         }
93     }
94 }
95 
96 #[derive(Debug, Clone)]
97 pub enum ParseError {
98     UnexpectedInput(String),
99     UnsupportedToken(String),
100     UnexpectedToken(String),
101 }
102 
103 impl ParseError {
104     pub fn handle(&self) {
105         match self {
106             ParseError::UnexpectedInput(str) => eprintln!("Unexpected input: \"{str}\""),
107             ParseError::UnsupportedToken(str) => eprintln!("Unsupported token: \"{str}\""),
108             ParseError::UnexpectedToken(str) => eprintln!("Unexpected token: \"{str}\""),
109         }
110     }
111 }
112 
113 pub struct Parser;
114 
115 impl Parser {
116     fn parse_env(str: &str) -> String {
117         std::env::var(str).unwrap_or(String::new())
118     }
119 
120     fn lexer(input: &str) -> Result<Vec<Token>, ParseError> {
121         let mut tokens = Vec::new();
122 
123         // 匹配环境变量的正则表达式
124         let env_token = Regex::new(r#"\$\{(\w[\w\d_]*)\}"#).unwrap();
125 
126         // 使用具体的符号组合来匹配
127         let regex_token =
128             Regex::new(r#"([^'";|&$\s]+)|(["'].*?["'])|(&&|\|\||<<|>>|[<>|&;])"#).unwrap();
129 
130         // 预先替换"${}"包围的环境变量
131         let remaining_input = env_token
132             .replace_all(input, |captures: &regex::Captures| {
133                 Self::parse_env(&captures[1])
134             })
135             .into_owned();
136 
137         let mut remaining_input = remaining_input.trim();
138 
139         while !remaining_input.is_empty() {
140             if let Some(mat) = regex_token.find(remaining_input) {
141                 let token_str = mat.as_str();
142                 if token_str.starts_with('"') || token_str.starts_with('\'') {
143                     tokens.push(Token::Word(token_str[1..token_str.len() - 1].to_string()));
144                 } else if token_str.starts_with('$') {
145                     tokens.push(Token::Word(Self::parse_env(&token_str[1..])));
146                 } else if token_str == ">>"
147                     || token_str == ">"
148                     || token_str == "<<"
149                     || token_str == "<"
150                     || token_str == "|"
151                     || token_str == "&"
152                     || token_str == ";"
153                     || token_str == "&&"
154                     || token_str == "||"
155                 {
156                     if token_str == "<" || token_str == "<<" {
157                         return Err(ParseError::UnsupportedToken(token_str.to_string()));
158                     }
159                     tokens.push(Token::Symbol(token_str.to_string()));
160                 } else {
161                     tokens.push(Token::Word(token_str.to_string()));
162                 }
163 
164                 remaining_input = &remaining_input[mat.end()..].trim();
165             } else {
166                 return Err(ParseError::UnexpectedInput(remaining_input.to_string()));
167             }
168         }
169         Ok(tokens)
170     }
171 
172     fn parser(tokens: Vec<Token>) -> Result<Vec<Pipeline>, ParseError> {
173         let mut commands = Vec::new();
174         let mut current_command: Vec<String> = Vec::new();
175         let mut pipelines = Vec::new();
176         let mut redirect_object: (Option<RedirectMode>, Option<RedirectTarget>) = (None, None);
177 
178         for token in tokens {
179             match token {
180                 Token::Word(ref word) => {
181                     if let (Some(_), None) = redirect_object {
182                         redirect_object.1 = RedirectTarget::from_string(word);
183                     } else {
184                         current_command.push(word.to_string());
185                     }
186                 }
187 
188                 Token::Symbol(symbol) => {
189                     match symbol.as_str() {
190                         ">" | ">>" => {
191                             // 重定向符号不能重复出现
192                             if redirect_object.0.is_some() {
193                                 return Err(ParseError::UnexpectedToken(symbol));
194                             } else {
195                                 redirect_object.0 = RedirectMode::from_string(&symbol);
196                             }
197                         }
198                         "|" | "&" | "||" | "&&" | ";" => {
199                             if let Some((name, args)) = current_command.split_first() {
200                                 let mut cmd_type =
201                                     if let (Some(mode), Some(ref target)) = redirect_object {
202                                         CommandType::Redirect {
203                                             target: target.clone(),
204                                             mode,
205                                         }
206                                     } else {
207                                         CommandType::Simple
208                                     };
209 
210                                 let conn_type = match symbol.as_str() {
211                                     "|" => {
212                                         // 重定向优先级高于管道
213                                         if let CommandType::Simple = cmd_type {
214                                             cmd_type = CommandType::Pipe;
215                                         }
216                                         ConnectType::Simple
217                                     }
218                                     "&" | ";" => ConnectType::Simple,
219                                     "||" => ConnectType::Or,
220                                     "&&" => ConnectType::And,
221                                     _ => todo!(),
222                                 };
223 
224                                 commands.push(Command::new(name, args, cmd_type, conn_type));
225                                 current_command.clear();
226 
227                                 if symbol == "&" {
228                                     pipelines.push(Pipeline::new(&commands, true));
229                                     commands.clear();
230                                 }
231                             } else {
232                                 // 这些符号之前必须有word作为命令被分隔,否则这些符号是没有意义的
233                                 return Err(ParseError::UnexpectedToken(symbol));
234                             }
235                         }
236                         _ => todo!(),
237                     }
238                 }
239             }
240         }
241 
242         // 处理最后一个命令
243         if let Some((name, args)) = current_command.split_first() {
244             commands.push(Command::new(
245                 name,
246                 args,
247                 if let (Some(mode), Some(ref target)) = redirect_object {
248                     CommandType::Redirect {
249                         target: target.clone(),
250                         mode,
251                     }
252                 } else {
253                     CommandType::Simple
254                 },
255                 ConnectType::Simple,
256             ));
257         }
258 
259         if !commands.is_empty() {
260             pipelines.push(Pipeline::new(&commands, false));
261         }
262 
263         Ok(pipelines)
264     }
265 
266     pub fn parse(input: &str) -> Result<Vec<Pipeline>, ParseError> {
267         // 解析输入并生成token列表
268         let tokens = Self::lexer(input)?;
269         // println!("tokens: {tokens:?}");
270 
271         // 解析 tokens 生成命令流水线
272         Self::parser(tokens)
273     }
274 }
275 
276 #[allow(dead_code)]
277 #[derive(Debug)]
278 pub struct ExecuteError {
279     name: String,
280     err_type: ExecuteErrorType,
281 }
282 
283 impl ExecuteError {
284     pub fn handle(&self, prompt: Option<String>) {
285         if let Some(prompt) = prompt {
286             eprint!("{}: ", prompt);
287         }
288         eprint!("{}: ", self.name);
289         match &self.err_type {
290             ExecuteErrorType::CommandNotFound => eprintln!("Command not found"),
291             ExecuteErrorType::FileNotFound(file) => eprintln!("Not a file or directory: {}", file),
292             ExecuteErrorType::NotDir(ref path) => eprintln!("Not a Directory: {path}"),
293             ExecuteErrorType::NotFile(ref path) => eprintln!("Not a File: {path}"),
294             ExecuteErrorType::PermissionDenied(ref file) => eprintln!("File open denied: {file}"),
295             ExecuteErrorType::ExecuteFailed => eprintln!("Command execute failed"),
296             ExecuteErrorType::ExitWithCode(exit_code) => {
297                 eprintln!("Command exit with code: {}", exit_code)
298             }
299             ExecuteErrorType::ProcessTerminated => eprintln!("Process terminated"),
300             ExecuteErrorType::FileOpenFailed(file) => {
301                 eprintln!("File open failed: {}", file.clone())
302             }
303             ExecuteErrorType::TooManyArguments => eprintln!("Too many arguments"),
304             ExecuteErrorType::TooFewArguments => eprintln!("Too few arguments"),
305             ExecuteErrorType::InvalidArgument(arg) => eprintln!("Invalid argument: {}", arg),
306         }
307     }
308 }
309 
310 #[allow(dead_code)]
311 #[derive(Debug, Clone)]
312 pub enum ExecuteErrorType {
313     CommandNotFound,
314     FileNotFound(String),
315     NotDir(String),
316     NotFile(String),
317     PermissionDenied(String),
318     ExecuteFailed,
319     ProcessTerminated,
320     ExitWithCode(i32),
321     FileOpenFailed(String),
322     TooManyArguments,
323     TooFewArguments,
324     InvalidArgument(String),
325 }
326 
327 pub enum RedirectStdout {
328     Stdout(Option<ChildStdout>),
329     RawPipe(i32),
330 }
331 
332 impl RedirectStdout {
333     pub fn as_raw_fd(&mut self) -> i32 {
334         match self {
335             RedirectStdout::Stdout(child_stdout) => child_stdout.take().unwrap().as_raw_fd(),
336             RedirectStdout::RawPipe(fd) => *fd,
337         }
338     }
339 
340     pub fn as_std(&mut self) -> Stdio {
341         match self {
342             RedirectStdout::Stdout(child_stdout) => Stdio::from(child_stdout.take().unwrap()),
343             RedirectStdout::RawPipe(fd) => unsafe { Stdio::from_raw_fd(*fd) },
344         }
345     }
346 }
347 
348 impl From<i32> for RedirectStdout {
349     fn from(value: i32) -> Self {
350         RedirectStdout::RawPipe(value)
351     }
352 }
353 
354 impl From<Option<ChildStdout>> for RedirectStdout {
355     fn from(mut value: Option<ChildStdout>) -> Self {
356         RedirectStdout::Stdout(value.take())
357     }
358 }
359 
360 #[derive(Debug)]
361 pub struct Pipeline {
362     commands: Vec<Command>, // 存储一系列命令
363     backend: bool,
364 }
365 
366 type CommandMap = HashMap<String, fn(&Vec<String>) -> Result<(), ExecuteErrorType>>;
367 
368 impl Pipeline {
369     pub fn new(commands: &Vec<Command>, backend: bool) -> Pipeline {
370         Self {
371             commands: commands.to_vec(),
372             backend,
373         }
374     }
375 
376     pub fn execute(&self, internal_commands: Option<Arc<Mutex<CommandMap>>>) -> Vec<Child> {
377         // 前一个命令是否为管道输出
378         let mut stdout: Option<RedirectStdout> = None;
379         // 提前推断下条命令的布尔值,为None代表下条命令需要运行
380         let mut result_next: Option<bool> = None;
381         let mut children: Vec<Child> = Vec::new();
382         let mut err: Option<ExecuteErrorType> = None;
383 
384         for cmd in self.commands.iter() {
385             if let Some(result) = result_next {
386                 // 如果前面已经推导出本条命令的布尔值,则本条命令不需要执行,并继续推断下条命令
387                 if (result && cmd.conn_type == ConnectType::And)
388                     || (!result && cmd.conn_type == ConnectType::Or)
389                 {
390                     // 如果true遇到||或false遇到&&,则下条命令的布尔值相同
391                     // 如果true遇到&&或false遇到||,继承中断,设为None以执行后续命令
392                     result_next = None;
393                 }
394                 continue;
395             }
396 
397             let mut internal = false;
398             if let Some(ref map) = internal_commands {
399                 let map = map.lock().unwrap();
400                 if let Some(f) = map.get(&cmd.name) {
401                     // 找到内部命令,优先执行,设置标记
402                     internal = true;
403 
404                     // child_fd
405                     let child_fd = if self.backend {
406                         unsafe { libc::fork() }
407                     } else {
408                         0
409                     };
410 
411                     // 为子进程或前台运行
412                     if child_fd == 0 {
413                         let mut old_stdin: Option<i32> = None;
414                         let mut old_stdout: Option<i32> = None;
415 
416                         // 如果上条命令为管道,将标准输入重定向
417                         if let Some(mut redirect_stdout) = stdout {
418                             unsafe {
419                                 old_stdin = Some(libc::dup(libc::STDIN_FILENO));
420                                 libc::dup2(redirect_stdout.as_raw_fd(), libc::STDIN_FILENO);
421                                 stdout = None;
422                             }
423                         }
424 
425                         // 根据命令类型重定向标准输出
426                         match cmd.cmd_type {
427                             CommandType::Simple => {}
428                             CommandType::Pipe => unsafe {
429                                 let mut pipe: [i32; 2] = [0; 2];
430                                 libc::pipe2(pipe.as_mut_ptr(), libc::O_CLOEXEC);
431                                 stdout = Some(RedirectStdout::from(pipe[0]));
432 
433                                 old_stdout = Some(libc::dup(libc::STDOUT_FILENO));
434 
435                                 libc::dup2(pipe[1], libc::STDOUT_FILENO);
436                             },
437                             CommandType::Redirect {
438                                 ref target,
439                                 ref mode,
440                             } => unsafe {
441                                 let mut pipe: [i32; 2] = [0; 2];
442                                 libc::pipe2(pipe.as_mut_ptr(), libc::O_CLOEXEC);
443                                 stdout = Some(RedirectStdout::from(pipe[0]));
444 
445                                 old_stdout = Some(libc::dup(libc::STDOUT_FILENO));
446 
447                                 let append = match mode {
448                                     RedirectMode::Overwrite => false,
449                                     RedirectMode::Append => true,
450                                 };
451 
452                                 match target {
453                                     RedirectTarget::File(file) => {
454                                         match std::fs::OpenOptions::new()
455                                             .write(true)
456                                             .append(append)
457                                             .create(true)
458                                             .open(file)
459                                         {
460                                             Ok(file) => {
461                                                 libc::dup2(file.as_raw_fd(), libc::STDIN_FILENO);
462                                             }
463 
464                                             Err(_) => {
465                                                 err = Some(ExecuteErrorType::FileOpenFailed(
466                                                     file.clone(),
467                                                 ));
468                                             }
469                                         };
470                                     }
471                                     RedirectTarget::FileDiscriptor(fd) => {
472                                         libc::dup2(*fd, libc::STDIN_FILENO);
473                                     }
474                                 }
475                             },
476                         }
477 
478                         // 如果之前没有出错,执行命令
479                         if err.is_none() {
480                             match f(&cmd.args) {
481                                 Ok(_) => err = None,
482                                 Err(err_type) => err = Some(err_type),
483                             }
484                         }
485 
486                         // 还原标准输出
487                         unsafe {
488                             if let Some(old_stdin) = old_stdin {
489                                 libc::dup2(old_stdin, libc::STDIN_FILENO);
490                             }
491 
492                             if let Some(old_stdout) = old_stdout {
493                                 libc::dup2(old_stdout, libc::STDOUT_FILENO);
494                             }
495                         }
496                     } else if child_fd < 0 {
497                         err = Some(ExecuteErrorType::ExecuteFailed)
498                     }
499 
500                     // 后台命令且当前进程为父进程
501                     if self.backend && !child_fd == 0 {
502                         err = match unsafe { libc::waitpid(child_fd, std::ptr::null_mut(), 0) } {
503                             -1 => Some(ExecuteErrorType::ExecuteFailed),
504                             _ => None,
505                         }
506                     }
507                 }
508             };
509 
510             // 没找到执行内部命令的标记,尝试作为外部命令执行
511             if !internal {
512                 let path = if cmd.name.contains('/') {
513                     // 为路径,获取规范的绝对路径
514                     if let Ok(path) = std::fs::canonicalize(&cmd.name) {
515                         if path.is_file() {
516                             Ok(path)
517                         } else {
518                             // 路径不为文件,返回错误
519                             Err(ExecuteErrorType::NotFile(cmd.name.clone()))
520                         }
521                     } else {
522                         Err(ExecuteErrorType::CommandNotFound)
523                     }
524                 } else {
525                     // 不为路径,从环境变量中查找命令
526                     which::which(&cmd.name).map_err(|_| ExecuteErrorType::CommandNotFound)
527                 };
528 
529                 // println!("path: {:?}", path);
530 
531                 match path {
532                     Err(e) => err = Some(e),
533                     Ok(real_path) => {
534                         let mut child_command = std::process::Command::new(real_path);
535                         child_command.args(cmd.args.clone());
536                         child_command.current_dir(std::env::current_dir().unwrap());
537                         if stdout.is_some() {
538                             child_command.stdin(stdout.take().unwrap().as_std());
539                         }
540 
541                         match &cmd.cmd_type {
542                             CommandType::Simple => {}
543                             CommandType::Redirect { target, mode } => {
544                                 let append = match mode {
545                                     RedirectMode::Overwrite => false,
546                                     RedirectMode::Append => true,
547                                 };
548                                 match target {
549                                     RedirectTarget::File(file) => {
550                                         match std::fs::OpenOptions::new()
551                                             .write(true)
552                                             .append(append)
553                                             .create(true)
554                                             .open(file)
555                                         {
556                                             Ok(file) => {
557                                                 child_command.stdout(file);
558                                             }
559                                             Err(_) => {
560                                                 err = Some(ExecuteErrorType::FileOpenFailed(
561                                                     file.clone(),
562                                                 ));
563                                             }
564                                         };
565                                     }
566                                     RedirectTarget::FileDiscriptor(fd) => {
567                                         child_command.stdout(unsafe { Stdio::from_raw_fd(*fd) });
568                                     }
569                                 }
570                             }
571                             CommandType::Pipe => {
572                                 // 标准输出重定向到管道
573                                 child_command.stdout(Stdio::piped());
574                             }
575                         }
576 
577                         if err.is_none() {
578                             match child_command.spawn() {
579                                 Ok(mut child) => {
580                                     // 如果为管道命令,记录下来
581                                     if let CommandType::Pipe = cmd.cmd_type {
582                                         stdout = Some(RedirectStdout::Stdout(child.stdout.take()));
583                                     }
584 
585                                     // println!("exec command: {child_command:#?}");
586 
587                                     match child.wait() {
588                                         Ok(exit_status) => match exit_status.code() {
589                                             Some(exit_code) => {
590                                                 if exit_code != 0 {
591                                                     err = Some(ExecuteErrorType::ExitWithCode(
592                                                         exit_code,
593                                                     ));
594                                                 }
595                                             }
596                                             None => err = Some(ExecuteErrorType::ProcessTerminated),
597                                         },
598                                         Err(_) => err = Some(ExecuteErrorType::ExecuteFailed),
599                                     };
600 
601                                     children.push(child);
602                                 }
603 
604                                 Err(e) => match e.kind() {
605                                     ErrorKind::PermissionDenied => {
606                                         err = Some(ExecuteErrorType::PermissionDenied(
607                                             cmd.name.clone(),
608                                         ))
609                                     }
610                                     _ => eprintln!("Error occurred: {}", e.kind()),
611                                 },
612                             }
613                         }
614                     }
615                 }
616             }
617 
618             // 预计算下条命令的结果
619             result_next = match err {
620                 Some(ref e) => {
621                     ExecuteError {
622                         name: cmd.name.clone(),
623                         err_type: e.clone(),
624                     }
625                     .handle(if internal {
626                         Some("internal command".to_string())
627                     } else {
628                         None
629                     });
630                     if cmd.conn_type == ConnectType::And {
631                         Some(false)
632                     } else {
633                         None
634                     }
635                 }
636                 None => {
637                     if cmd.conn_type == ConnectType::Or {
638                         Some(true)
639                     } else {
640                         None
641                     }
642                 }
643             }
644         }
645 
646         children
647     }
648 
649     pub fn backend(&self) -> bool {
650         self.backend
651     }
652 }
653