diff --git a/src/main.rs b/src/main.rs index c0025929a8c639487a50f01babf7f18afa5ba92e..85ed88f4e616e27b0120230fec1150a68e671d7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ mod shell; mod ascii_helpers; use builtins::Builtin; -use shell::{Shell, signals}; +use shell::{Shell, Binary, signals}; use std::sync::mpsc; use std::thread; @@ -38,7 +38,7 @@ fn inner_main(sigint_rx : mpsc::Receiver<i32>) { let builtins = Builtin::map(); let mut shell = Shell::new(&builtins, sigint_rx); shell.evaluate_init_file(); - shell.execute(); + shell.main(); } #[cfg(not(target_os = "redox"))] diff --git a/src/shell/binary.rs b/src/shell/binary.rs new file mode 100644 index 0000000000000000000000000000000000000000..a33fce0a9a3e9e23338aa246dce27117483f3cdc --- /dev/null +++ b/src/shell/binary.rs @@ -0,0 +1,239 @@ +//! Contains the binary logic of Ion. + +use liner::{Buffer, Context}; +use smallvec::SmallVec; +use std::env; +use std::fs::File; +use std::io::{self, Write, Read, ErrorKind}; +use std::iter::{self, FromIterator}; +use std::path::Path; +use super::flow_control::Statement; +use super::status::*; +use super::{Shell, FlowLogic, JobControl, ShellHistory}; +use parser::QuoteTerminator; + +pub trait Binary { + /// Launches the shell, parses arguments, and then diverges into one of the `execution` paths. + fn main(self); + /// Parses and executes the arguments that were supplied to the shell. + fn execute_arguments<A: Iterator<Item = String>>(&mut self, args: A); + /// Creates an interactive session that reads from a prompt provided by Liner. + fn execute_interactive(self); + /// Executes all of the statements contained within a given script. + fn execute_script<P: AsRef<Path>>(&mut self, path: P); + /// Ensures that read statements from a script are terminated. + fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, lines: I); + /// Ensures that read statements from the interactive prompt is terminated. + fn terminate_quotes(&mut self, command: String) -> Result<String, ()>; +} + +impl<'a> Binary for Shell<'a> { + fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, mut lines: I) { + while let Some(command) = lines.next() { + let mut buffer = QuoteTerminator::new(command); + while !buffer.check_termination() { + loop { + if let Some(command) = lines.next() { + buffer.append(command); + break + } else { + let stderr = io::stderr(); + let _ = writeln!(stderr.lock(), "ion: unterminated quote in script"); + self.exit(FAILURE); + } + } + } + self.on_command(&buffer.consume()); + } + // The flow control level being non zero means that we have a statement that has + // only been partially parsed. + if self.flow_control.level != 0 { + eprintln!("ion: unexpected end of script: expected end block for `{}`", + self.flow_control.current_statement.short()); + } + } + + fn terminate_quotes(&mut self, command: String) -> Result<String, ()> { + let mut buffer = QuoteTerminator::new(command); + self.flow_control.level += 1; + while !buffer.check_termination() { + loop { + if let Some(command) = self.readln() { + buffer.append(command); + break + } else { + return Err(()); + } + } + } + self.flow_control.level -= 1; + Ok(buffer.consume()) + } + + fn execute_arguments<A: Iterator<Item = String>>(&mut self, mut args: A) { + if let Some(mut arg) = args.next() { + for argument in args { + arg.push(' '); + if argument == "" { + arg.push_str("''"); + } else { + arg.push_str(&argument); + } + } + self.on_command(&arg); + } else { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: -c requires an argument"); + self.exit(FAILURE); + } + } + + fn execute_interactive(mut self) { + self.context = Some({ + let mut context = Context::new(); + context.word_divider_fn = Box::new(word_divide); + if "1" == self.variables.get_var_or_empty("HISTORY_FILE_ENABLED") { + let path = self.variables.get_var("HISTORY_FILE").expect("shell didn't set history_file"); + context.history.set_file_name(Some(path.clone())); + if !Path::new(path.as_str()).exists() { + eprintln!("ion: creating history file at \"{}\"", path); + if let Err(why) = File::create(path) { + eprintln!("ion: could not create history file: {}", why); + } + } + match context.history.load_history() { + Ok(()) => { + // pass + } + Err(ref err) if err.kind() == ErrorKind::NotFound => { + let history_filename = self.variables.get_var_or_empty("HISTORY_FILE"); + eprintln!("ion: failed to find history file {}: {}", history_filename, err); + }, + Err(err) => { + eprintln!("ion: failed to load history: {}", err); + } + } + } + context + }); + + self.variables.set_array ( + "args", + iter::once(env::args().next().unwrap()).collect(), + ); + + loop { + if let Some(command) = self.readln() { + if ! command.is_empty() { + if let Ok(command) = self.terminate_quotes(command) { + // Parse and potentially execute the command. + self.on_command(command.trim()); + + // Mark the command in the context history if it was a success. + if self.previous_status != NO_SUCH_COMMAND || self.flow_control.level > 0 { + self.set_context_history_from_vars(); + if let Err(err) = self.context.as_mut().unwrap().history.push(command.into()) { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: {}", err); + } + } + } else { + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + self.flow_control.current_statement = Statement::Default; + } + } + self.update_variables(); + } else { + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + self.flow_control.current_statement = Statement::Default; + } + } + } + + fn main(mut self) { + let mut args = env::args().skip(1); + if let Some(path) = args.next() { + if path == "-c" { + self.execute_arguments(args); + } else { + let mut array = SmallVec::from_iter( + Some(path.clone().into()) + ); + for arg in args { array.push(arg.into()); } + self.variables.set_array("args", array); + self.execute_script(&path); + } + + self.wait_for_background(); + let previous_status = self.previous_status; + self.exit(previous_status); + } else { + self.execute_interactive(); + } + } + + fn execute_script<P: AsRef<Path>>(&mut self, path: P) { + let path = path.as_ref(); + match File::open(path) { + Ok(mut file) => { + let capacity = file.metadata().ok().map_or(0, |x| x.len()); + let mut command_list = String::with_capacity(capacity as usize); + match file.read_to_string(&mut command_list) { + Ok(_) => self.terminate_script_quotes(command_list.lines().map(|x| x.to_owned())), + Err(err) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: failed to read {:?}: {}", path, err); + } + } + }, + Err(err) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: failed to open {:?}: {}", path, err); + } + } + } +} + +fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> { + let mut res = Vec::new(); + let mut word_start = None; + + macro_rules! check_boundary { + ($c:expr, $index:expr, $escaped:expr) => {{ + if let Some(start) = word_start { + if $c == ' ' && !$escaped { + res.push((start, $index)); + word_start = None; + } + } else { + if $c != ' ' { + word_start = Some($index); + } + } + }} + } + + let mut iter = buf.chars().enumerate(); + while let Some((i, &c)) = iter.next() { + match c { + '\\' => { + if let Some((_, &cnext)) = iter.next() { + // We use `i` in order to include the backslash as part of the word + check_boundary!(cnext, i, true); + } + } + c => check_boundary!(c, i, false), + } + } + if let Some(start) = word_start { + // When start has been set, that means we have encountered a full word. + res.push((start, buf.num_chars())); + } + res +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 313904b3f21a98c9c7cc06a27dc3888252d382fd..b2d471b5c54b98aeaa28c7effac4b8e7b302dd5d 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,15 +1,16 @@ mod assignments; +mod binary; mod completer; +mod flow; +mod history; +mod job; +mod pipe; pub mod directory_stack; pub mod flags; pub mod flow_control; pub mod foreground; -mod flow; pub mod fork; -mod history; pub mod job_control; -mod job; -mod pipe; pub mod signals; pub mod status; pub mod variables; @@ -17,81 +18,36 @@ pub mod variables; pub use self::history::ShellHistory; pub use self::job::{Job, JobKind}; pub use self::flow::FlowLogic; +pub use self::binary::Binary; -use std::fs::File; -use std::io::{self, ErrorKind, Read, Write}; -use std::env; -use std::mem; -use std::path::{PathBuf, Path}; -use std::process; -use std::time::SystemTime; -use std::iter::FromIterator; -use std::sync::{Arc, Mutex}; -use std::sync::mpsc::Receiver; -use smallvec::SmallVec; use app_dirs::{AppDataType, AppInfo, app_root}; use builtins::*; use fnv::FnvHashMap; -use liner::{Context, CursorPosition, Event, EventKind, BasicCompleter, Buffer}; -use types::*; -use smallstring::SmallString; +use liner::{Context, CursorPosition, Event, EventKind, BasicCompleter}; +use parser::*; +use parser::peg::Pipeline; use self::completer::{MultiCompleter, IonFileCompleter}; use self::directory_stack::DirectoryStack; use self::flags::*; -use self::flow_control::{FlowControl, Function, FunctionArgument, Statement, Type}; +use self::flow_control::{FlowControl, Function, FunctionArgument, Type}; use self::foreground::ForegroundSignals; use self::job_control::{JobControl, BackgroundProcess}; -use self::variables::Variables; -use self::status::*; use self::pipe::PipelineExecution; -use parser::{ - expand_string, - ArgumentSplitter, - QuoteTerminator, - ExpanderFunctions, - Select, -}; - -use parser::peg::Pipeline; - -fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> { - let mut res = Vec::new(); - let mut word_start = None; - - macro_rules! check_boundary { - ($c:expr, $index:expr, $escaped:expr) => {{ - if let Some(start) = word_start { - if $c == ' ' && !$escaped { - res.push((start, $index)); - word_start = None; - } - } else { - if $c != ' ' { - word_start = Some($index); - } - } - }} - } - - let mut iter = buf.chars().enumerate(); - while let Some((i, &c)) = iter.next() { - match c { - '\\' => { - if let Some((_, &cnext)) = iter.next() { - // We use `i` in order to include the backslash as part of the word - check_boundary!(cnext, i, true); - } - } - c => check_boundary!(c, i, false), - } - } - if let Some(start) = word_start { - // When start has been set, that means we have encountered a full word. - res.push((start, buf.num_chars())); - } - res -} +use self::status::*; +use self::variables::Variables; +use smallstring::SmallString; +use smallvec::SmallVec; +use std::env; +use std::fs::File; +use std::io::{self, ErrorKind, Write}; +use std::mem; +use std::path::{PathBuf, Path}; +use std::process; +use std::sync::mpsc::Receiver; +use std::sync::{Arc, Mutex}; +use std::time::SystemTime; +use types::*; /// The shell structure is a megastructure that manages all of the state of the shell throughout the entirety of the /// program. It is initialized at the beginning of the program, and lives until the end of the program. @@ -293,173 +249,6 @@ impl<'a> Shell<'a> { process::exit(status); } - pub fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, mut lines: I) { - while let Some(command) = lines.next() { - let mut buffer = QuoteTerminator::new(command); - while !buffer.check_termination() { - loop { - if let Some(command) = lines.next() { - buffer.append(command); - break - } else { - let stderr = io::stderr(); - let _ = writeln!(stderr.lock(), "ion: unterminated quote in script"); - self.exit(FAILURE); - } - } - } - self.on_command(&buffer.consume()); - } - // The flow control level being non zero means that we have a statement that has - // only been partially parsed. - if self.flow_control.level != 0 { - eprintln!("ion: unexpected end of script: expected end block for `{}`", - self.flow_control.current_statement.short()); - } - } - - pub fn terminate_quotes(&mut self, command: String) -> Result<String, ()> { - let mut buffer = QuoteTerminator::new(command); - self.flow_control.level += 1; - while !buffer.check_termination() { - loop { - if let Some(command) = self.readln() { - buffer.append(command); - break - } else { - return Err(()); - } - } - } - self.flow_control.level -= 1; - Ok(buffer.consume()) - } - - pub fn execute_script<P: AsRef<Path>>(&mut self, path: P) { - let path = path.as_ref(); - match File::open(path) { - Ok(mut file) => { - let capacity = file.metadata().ok().map_or(0, |x| x.len()); - let mut command_list = String::with_capacity(capacity as usize); - match file.read_to_string(&mut command_list) { - Ok(_) => self.terminate_script_quotes(command_list.lines().map(|x| x.to_owned())), - Err(err) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: failed to read {:?}: {}", path, err); - } - } - }, - Err(err) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: failed to open {:?}: {}", path, err); - } - } - } - - pub fn execute(&mut self) { - use std::iter; - - let mut args = env::args().skip(1); - - if let Some(path) = args.next() { - if path == "-c" { - if let Some(mut arg) = args.next() { - for argument in args { - arg.push(' '); - if argument == "" { - arg.push_str("''"); - } else { - arg.push_str(&argument); - } - } - self.on_command(&arg); - } else { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: -c requires an argument"); - self.exit(FAILURE); - } - } else { - let mut array = SmallVec::from_iter( - Some(path.clone().into()) - ); - for arg in args { array.push(arg.into()); } - self.variables.set_array("args", array); - self.execute_script(&path); - } - - self.wait_for_background(); - let previous_status = self.previous_status; - self.exit(previous_status); - } - - // Only interactive sessions will have a context. - self.context = Some({ - let mut context = Context::new(); - context.word_divider_fn = Box::new(word_divide); - if "1" == self.variables.get_var_or_empty("HISTORY_FILE_ENABLED") { - let path = self.variables.get_var("HISTORY_FILE").expect("shell didn't set history_file"); - context.history.set_file_name(Some(path.clone())); - if !Path::new(path.as_str()).exists() { - eprintln!("ion: creating history file at \"{}\"", path); - if let Err(why) = File::create(path) { - eprintln!("ion: could not create history file: {}", why); - } - } - match context.history.load_history() { - Ok(()) => { - // pass - } - Err(ref err) if err.kind() == ErrorKind::NotFound => { - let history_filename = self.variables.get_var_or_empty("HISTORY_FILE"); - eprintln!("ion: failed to find history file {}: {}", history_filename, err); - }, - Err(err) => { - eprintln!("ion: failed to load history: {}", err); - } - } - } - context - }); - - self.variables.set_array ( - "args", - iter::once(env::args().next().unwrap()).collect(), - ); - - loop { - if let Some(command) = self.readln() { - if ! command.is_empty() { - if let Ok(command) = self.terminate_quotes(command) { - // Parse and potentially execute the command. - self.on_command(command.trim()); - - // Mark the command in the context history if it was a success. - if self.previous_status != NO_SUCH_COMMAND || self.flow_control.level > 0 { - self.set_context_history_from_vars(); - if let Err(err) = self.context.as_mut().unwrap().history.push(command.into()) { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: {}", err); - } - } - } else { - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; - self.flow_control.current_statement = Statement::Default; - } - } - self.update_variables(); - } else { - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; - self.flow_control.current_statement = Statement::Default; - } - } - } - /// This function updates variables that need to be kept consistent with each iteration /// of the prompt. For example, the PWD variable needs to be updated to reflect changes to the /// the current working directory.