diff --git a/src/builtins.rs b/src/builtins.rs index 1253280..d102293 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -1,29 +1,36 @@ use crate::{ error::ExecError, - runtime::{State, Value}, + run::{Call, Context, Value}, }; +use std::{boxed::Box, collections::HashMap, io::Write}; +#[derive(Clone, Copy)] pub enum Builtin { Echo, } -impl Builtin { - pub fn lookup(name: &str) -> Option { - match name { - "echo" => Some(Self::Echo), - _ => None, +impl Call for Builtin { + fn call(&self, ctx: &mut Context, args: &[Value]) -> Result { + match self { + Builtin::Echo => Echo.call(ctx, args), } } +} - pub fn call(&self, _: &mut State, args: &Vec) -> Result { - match self { - Builtin::Echo => { - let args: Result, ExecError> = - args.into_iter().map(|arg| arg.try_as_string()).collect(); - let args = args?; - println!("{}", args.join(" ")); - Ok(Value::None) - } - } +pub struct Echo; + +impl Call for Echo { + fn call(&self, ctx: &mut Context, args: &[Value]) -> Result { + let args = args + .into_iter() + .map(|v| v.try_as_str()) + .collect::, _>>()?; + _ = write!(ctx.stdout, "{}\n", args.join(" ")); + Ok(Value::None) } } + +pub fn all() -> HashMap<&'static str, Builtin> { + use Builtin::*; + HashMap::from([("echo", Echo)]) +} diff --git a/src/interactive.rs b/src/interactive.rs index fbcf223..68eb91e 100644 --- a/src/interactive.rs +++ b/src/interactive.rs @@ -1,8 +1,8 @@ use crate::{ - edit, + builtins, edit, log::*, output, - runtime::{self, Eval}, + run::{self, Eval}, syntax::parse, }; @@ -21,7 +21,6 @@ pub struct Session { pub editor: edit::Editor, pub stdout: output::Writer, pub stderr: output::Writer, - pub state: runtime::State, } impl Session { @@ -30,11 +29,16 @@ impl Session { editor: edit::Editor::new()?, stdout: output::Writer::stdout()?, stderr: output::Writer::stderr()?, - state: runtime::State::new(), }) } pub fn run(mut self) -> Result<()> { + let mut state = run::State::new(); + let mut ctx = run::Context { + stdout: self.stdout.clone(), + stderr: self.stderr.clone(), + state: &mut state, + }; info!("ยป shell session start --------"); loop { self.editor.show_prompt()?; @@ -49,7 +53,7 @@ impl Session { continue; } }; - if let Err(e) = command.eval(&mut self.state) { + if let Err(e) = command.eval(&mut ctx) { self.render_error(e)?; } } diff --git a/src/main.rs b/src/main.rs index 34f8fa4..ad8f107 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ mod parse; mod prompt; /// the runtime state of the shell process -mod runtime; +mod run; /// syntax and semantic analysis mod syntax; diff --git a/src/output.rs b/src/output.rs index 916c718..97b8723 100644 --- a/src/output.rs +++ b/src/output.rs @@ -94,6 +94,7 @@ fn log_output_mode(mode: Console::CONSOLE_MODE) { } } +#[derive(Clone)] pub struct Writer { output: HANDLE, } diff --git a/src/runtime.rs b/src/run.rs similarity index 62% rename from src/runtime.rs rename to src/run.rs index a119a71..42a6599 100644 --- a/src/runtime.rs +++ b/src/run.rs @@ -1,10 +1,10 @@ -use crate::error::ExecError; +use crate::{builtins, error::ExecError, input, output}; use std::{collections::HashMap, process}; /// Eval represents anything that can be evaluated at runtime. pub trait Eval { /// Evaluates the receiver, given the current runtime state. - fn eval(&self, ctx: &mut State) -> Result; + fn eval(&self, ctx: &mut Context) -> Result; } /// An individual value at runtime. @@ -23,19 +23,13 @@ pub enum Value { impl Value { pub fn try_to_string(self) -> Result { - match self { - Value::None => Err(ExecError::type_error("expected text value, saw None value")), - Value::Text(v) => Ok(v), - Value::ExitStatus(_) => Err(ExecError::type_error( - "expected text value, saw ExitStatus value", - )), - } + Ok(self.try_as_str()?.to_string()) } - pub fn try_as_string(&self) -> Result { + pub fn try_as_str(&self) -> Result<&str, ExecError> { match self { Value::None => Err(ExecError::type_error("expected text value, saw None value")), - Value::Text(v) => Ok(v.clone()), + Value::Text(v) => Ok(&v), Value::ExitStatus(_) => Err(ExecError::type_error( "expected text value, saw ExitStatus value", )), @@ -44,7 +38,7 @@ impl Value { } impl Eval for Value { - fn eval(&self, _: &mut State) -> Result { + fn eval(&self, _: &mut Context) -> Result { Ok(self.clone()) } } @@ -56,12 +50,32 @@ impl Eval for Value { pub struct State { #[allow(unused)] variables: HashMap<&'static str, Value>, + pub(crate) builtins: HashMap<&'static str, builtins::Builtin>, } impl State { pub fn new() -> Self { Self { variables: HashMap::new(), + builtins: builtins::all(), } } + + pub fn has_builtin(&self, name: &str) -> bool { + self.builtins.contains_key(name) + } + + pub fn builtin(&self, name: &str) -> Option { + self.builtins.get(name).copied() + } +} + +pub trait Call { + fn call(&self, ctx: &mut Context, args: &[Value]) -> Result; +} + +pub struct Context<'s> { + pub(crate) stdout: output::Writer, + pub(crate) stderr: output::Writer, + pub(crate) state: &'s mut State, } diff --git a/src/syntax.rs b/src/syntax.rs index 7f9c644..d50e7a8 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -3,7 +3,7 @@ use crate::{ error::{ExecError, ParseError}, lex::{Lexer, Token}, parse, - runtime::{Eval, State, Value}, + run::{Call, Context, Eval, Value}, }; use std::{collections::HashSet, process}; @@ -15,7 +15,7 @@ pub enum Element { } impl Eval for Element { - fn eval(&self, ctx: &mut State) -> Result { + fn eval(&self, ctx: &mut Context) -> Result { use Element::*; match self { Block(block) => block.eval(ctx), @@ -39,7 +39,7 @@ impl Block { } impl Eval for Block { - fn eval(&self, ctx: &mut State) -> Result { + fn eval(&self, ctx: &mut Context) -> Result { let mut v = Value::None; for cmd in self.commands.iter() { v = cmd.eval(ctx)?; @@ -55,21 +55,27 @@ pub struct Command { } impl Command { - fn eval_args(&self, ctx: &mut State) -> Result, ExecError> { - self.args.iter().map(|elem| elem.eval(ctx)).collect() + fn exec_builtin(&self, ctx: &mut Context, builtin: Builtin) -> Result { + let args = self + .args + .iter() + .map(|elem| elem.eval(ctx)) + .collect::, _>>()?; + builtin.call(ctx, &args) } - fn exec_command(&self, ctx: &mut State, name: &str) -> Result { - let mut proc = process::Command::new(&name); + fn exec_command(&self, ctx: &mut Context, name: &str) -> Result { + let args = self + .args + .iter() + .map(|elem| elem.eval(ctx)?.try_to_string()) + .collect::, _>>()?; - if self.args.len() > 0 { - let args = self - .args - .iter() - .map(|elem| elem.eval(ctx)?.try_to_string()) - .collect::, ExecError>>()?; + let mut proc = process::Command::new(&name); + if args.len() > 0 { proc.args(args); } + let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError { name: name.to_string(), source: e, @@ -85,17 +91,12 @@ impl Command { } impl Eval for Command { - fn eval(&self, ctx: &mut State) -> Result { - let name = self.name.eval(ctx)?.try_to_string()?; - - match Builtin::lookup(&name) { - Some(builtin) => { - let args = self.eval_args(ctx)?; - // let args: Vec<&str> = args.into_iter().map(|arg| arg).collect(); - - builtin.call(ctx, &args) - } - None => self.exec_command(ctx, &name), + fn eval(&self, ctx: &mut Context) -> Result { + let name = self.name.eval(ctx)?; + let name = name.try_as_str()?; + match ctx.state.builtin(name) { + Some(builtin) => self.exec_builtin(ctx, builtin), + None => self.exec_command(ctx, name), } } }