1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
//! Parsing and handling program syntax(es) of the shell. //! //! Both, commands entered to the shell interactively through STDIN, and read //! from a file, are *programs*. Our shell provides multiple discrete languages //! to write programs in, each with a corresponding implementation of this //! module's `Program` trait. //! //! ### POSIX Shell Language //! //! The basic, portable POSIX shell language. For detailed information read the //! [`posix`](crate::program::posix) module docs. //! //! ```sh //! for ((i=0; i<10; i++)); do echo $i; done //! ``` //! //! ### Modern Shell Language //! //! A more modern and ergonomic language. For more detailed information read //! the [`modern`](crate::program::modern) module docs. //! //! ```sh //! # WIP //! for i in (0..10) { echo $i } //! ``` //! //! ### Default Syntax //! //! Our shell has a `PrimaryProgram` which is in charge of parsing programs //! which are not passed in via a `{#}` language block. This can be configured //! to your preference. This does not effect the shell when launched in //! POSIX compatibility mode, or when a specific default language is passed //! as a flag. //! //! ### `{#}` Language Blocks //! //! Both the [`posix`](crate::program::posix) and //! [`modern`](crate::program::modern) languages have support for a special //! expression which treats the body as a program from another language. This //! forms the basis of oursh's modern features, and backwards compatibility. //! While the primary goal of this syntax is to be able to mix both POSIX and //! non-POSIX shell scripts, the feature is much more powerful. Any //! interperator can be used, just like with `#!`. //! //! ```sh //! date # Call `date` in the primary syntax. //! {# date} # Specifies the alternate syntax. //! //! # Use ruby, why not... //! {#!ruby //! require 'date' //! puts Date.today //! } //! ``` //! //! Strict POSIX compatibility can be enabled by removing this feature alone. //! //! - TODO #5: Parse sequence of programs from stream. //! - TODO #5: Partial parses for readline-ish / syntax highlighting. use std::{ result, ffi::CString, fmt::Debug, io::BufRead, }; use nix::{ unistd::Pid, sys::wait::WaitStatus, }; use crate::{ process::retain_alive_jobs, }; /// Convenience type for results with program errors. pub type Result<T> = result::Result<T, Error>; /// A comprehensive error type for the operation of programs. #[derive(Debug)] pub enum Error { /// A general issue reading the program. // TODO: Wrap an io error? Read, /// An error within the lexer or parser. // TODO: Wrap both our lex::Error and ParseError. Parse, /// An error encountered during the evaluation of a program. // TODO: Propagate status. // TODO: Just wrap an Wait/ExitStatus? Runtime, } pub trait Run { fn run(&self, runtime: &mut Runtime) -> Result<WaitStatus>; } /// A program is as large as a file or as small as a line. /// /// Each program is to be treated as a complete single language entity, with /// the explicit exception of `{#}` blocks, which act as bridges between /// programs. /// /// ### Working Thoughts /// /// - Is simply iterating a collection of `commands` really the correct /// semantics for all the types of programs we want? /// - What language information do we still need to store? pub trait Program: Sized + Debug + Run { /// The type of each of this program's commands. type Command: Command; /// Parse a whole program from the given `reader`. fn parse<R: BufRead>(reader: R) -> Result<Self>; /// Return a list of all the commands in this program. fn commands(&self) -> &[Self::Command]; } impl<P: Program> Run for P { fn run(&self, runtime: &mut Runtime) -> Result<WaitStatus> { let mut last = WaitStatus::Exited(Pid::this(), 0); for command in self.commands().iter() { last = command.run(runtime)?; } Ok(last) } } /// A command is a task given by the user as part of a [`Program`](Program). /// /// Each command is handled by a [`Process`](crate::process::Process), and a /// single command may be run multiple times each as a new `Process`. Each time /// a command is run, the conditions within the control of the shell are /// reproduced; IO redirection, working directory, and even the environment are /// each faithfully preserved. pub trait Command: Sized + Debug + Run { /// Return the name of this command. /// /// This name *may* not be the same as the name given to the process by /// the running [`Process`](crate::process::Process). // TODO: Ids? fn name(&self) -> CString { CString::new(format!("{:?}", self)) .expect("error in UTF-8 of format") } } /// The primary program type, used for unannotated blocks. // TODO: This should be `ModernProgram`. pub type PrimaryProgram = PosixProgram; /// TODO: alt explain // TODO: This should be `PosixProgram`. pub type AlternateProgram = BasicProgram; /// Parse a program of the primary type. /// /// # Examples /// /// ``` /// use oursh::program::parse_primary; /// /// parse_primary(b"ls | wc" as &[u8]); /// ``` pub fn parse_primary<R: BufRead>(reader: R) -> Result<PrimaryProgram> { PrimaryProgram::parse(reader) } /// Parse a program of the alternate type. /// /// # Examples /// /// ``` /// use oursh::program::parse_alternate; /// /// parse_alternate(b"ls" as &[u8]); /// ``` pub fn parse_alternate<R: BufRead>(reader: R) -> Result<AlternateProgram> { AlternateProgram::parse(reader) } /// Parse a program of the given type. /// /// # Examples /// /// ``` /// use oursh::program::{parse, PosixProgram, BasicProgram}; /// /// let program = b"sleep 1; date & date"; /// assert!(parse::<PosixProgram, &[u8]>(program).is_ok()); /// ``` pub fn parse<P: Program, R: BufRead>(reader: R) -> Result<P> { P::parse(reader) } // The various program grammars. // // If reading this code were like sking, you'd now be hitting blues. ASTs and // language semantics are somewhat tricky subjects. pub mod runtime; pub use self::runtime::Runtime; pub mod basic; pub use self::basic::Program as BasicProgram; pub mod posix; pub use self::posix::Program as PosixProgram; pub mod modern; pub use self::modern::Program as ModernProgram; // TODO: Replace program::Result pub fn parse_and_run(text: &str, runtime: &mut Runtime) -> crate::program::Result<WaitStatus> { let result = if text.is_empty() { Ok(WaitStatus::Exited(Pid::this(), 0)) } else { // Parse with the primary grammar and run each command in order. let program = match parse_primary(text.as_bytes()) { Ok(program) => program, Err(e) => { eprintln!("{:?}: {:#?}", e, text); return Err(e); } }; if let Some(editor) = &mut runtime.rl { editor.add_history_entry(text); } // Print the program if the flag is given. if runtime.args.get_bool("--ast") { eprintln!("{:#?}", program); } // Run it! program.run(runtime) }; retain_alive_jobs(runtime.jobs); result }