refactoring builtins

main
Jordan Orelli 11 months ago
parent 71f148139e
commit 621f64d3f1

@ -1,29 +1,36 @@
use crate::{ use crate::{
error::ExecError, error::ExecError,
runtime::{State, Value}, run::{Call, Context, Value},
}; };
use std::{boxed::Box, collections::HashMap, io::Write};
#[derive(Clone, Copy)]
pub enum Builtin { pub enum Builtin {
Echo, Echo,
} }
impl Builtin { impl Call for Builtin {
pub fn lookup(name: &str) -> Option<Self> { fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
match name { match self {
"echo" => Some(Self::Echo), Builtin::Echo => Echo.call(ctx, args),
_ => None, }
} }
} }
pub fn call(&self, _: &mut State, args: &Vec<Value>) -> Result<Value, ExecError> { pub struct Echo;
match self {
Builtin::Echo => { impl Call for Echo {
let args: Result<Vec<String>, ExecError> = fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
args.into_iter().map(|arg| arg.try_as_string()).collect(); let args = args
let args = args?; .into_iter()
println!("{}", args.join(" ")); .map(|v| v.try_as_str())
.collect::<Result<Vec<_>, _>>()?;
_ = write!(ctx.stdout, "{}\n", args.join(" "));
Ok(Value::None) Ok(Value::None)
} }
} }
}
pub fn all() -> HashMap<&'static str, Builtin> {
use Builtin::*;
HashMap::from([("echo", Echo)])
} }

@ -1,8 +1,8 @@
use crate::{ use crate::{
edit, builtins, edit,
log::*, log::*,
output, output,
runtime::{self, Eval}, run::{self, Eval},
syntax::parse, syntax::parse,
}; };
@ -21,7 +21,6 @@ pub struct Session {
pub editor: edit::Editor, pub editor: edit::Editor,
pub stdout: output::Writer, pub stdout: output::Writer,
pub stderr: output::Writer, pub stderr: output::Writer,
pub state: runtime::State,
} }
impl Session { impl Session {
@ -30,11 +29,16 @@ impl Session {
editor: edit::Editor::new()?, editor: edit::Editor::new()?,
stdout: output::Writer::stdout()?, stdout: output::Writer::stdout()?,
stderr: output::Writer::stderr()?, stderr: output::Writer::stderr()?,
state: runtime::State::new(),
}) })
} }
pub fn run(mut self) -> Result<()> { 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 --------"); info!("» shell session start --------");
loop { loop {
self.editor.show_prompt()?; self.editor.show_prompt()?;
@ -49,7 +53,7 @@ impl Session {
continue; continue;
} }
}; };
if let Err(e) = command.eval(&mut self.state) { if let Err(e) = command.eval(&mut ctx) {
self.render_error(e)?; self.render_error(e)?;
} }
} }

@ -34,7 +34,7 @@ mod parse;
mod prompt; mod prompt;
/// the runtime state of the shell process /// the runtime state of the shell process
mod runtime; mod run;
/// syntax and semantic analysis /// syntax and semantic analysis
mod syntax; mod syntax;

@ -94,6 +94,7 @@ fn log_output_mode(mode: Console::CONSOLE_MODE) {
} }
} }
#[derive(Clone)]
pub struct Writer { pub struct Writer {
output: HANDLE, output: HANDLE,
} }

@ -1,10 +1,10 @@
use crate::error::ExecError; use crate::{builtins, error::ExecError, input, output};
use std::{collections::HashMap, process}; use std::{collections::HashMap, process};
/// Eval represents anything that can be evaluated at runtime. /// Eval represents anything that can be evaluated at runtime.
pub trait Eval { pub trait Eval {
/// Evaluates the receiver, given the current runtime state. /// Evaluates the receiver, given the current runtime state.
fn eval(&self, ctx: &mut State) -> Result<Value, ExecError>; fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError>;
} }
/// An individual value at runtime. /// An individual value at runtime.
@ -23,19 +23,13 @@ pub enum Value {
impl Value { impl Value {
pub fn try_to_string(self) -> Result<String, ExecError> { pub fn try_to_string(self) -> Result<String, ExecError> {
match self { Ok(self.try_as_str()?.to_string())
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",
)),
}
} }
pub fn try_as_string(&self) -> Result<String, ExecError> { pub fn try_as_str(&self) -> Result<&str, ExecError> {
match self { match self {
Value::None => Err(ExecError::type_error("expected text value, saw None value")), 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( Value::ExitStatus(_) => Err(ExecError::type_error(
"expected text value, saw ExitStatus value", "expected text value, saw ExitStatus value",
)), )),
@ -44,7 +38,7 @@ impl Value {
} }
impl Eval for Value { impl Eval for Value {
fn eval(&self, _: &mut State) -> Result<Value, ExecError> { fn eval(&self, _: &mut Context) -> Result<Value, ExecError> {
Ok(self.clone()) Ok(self.clone())
} }
} }
@ -56,12 +50,32 @@ impl Eval for Value {
pub struct State { pub struct State {
#[allow(unused)] #[allow(unused)]
variables: HashMap<&'static str, Value>, variables: HashMap<&'static str, Value>,
pub(crate) builtins: HashMap<&'static str, builtins::Builtin>,
} }
impl State { impl State {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
variables: HashMap::new(), 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<builtins::Builtin> {
self.builtins.get(name).copied()
} }
} }
pub trait Call {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError>;
}
pub struct Context<'s> {
pub(crate) stdout: output::Writer,
pub(crate) stderr: output::Writer,
pub(crate) state: &'s mut State,
} }

@ -3,7 +3,7 @@ use crate::{
error::{ExecError, ParseError}, error::{ExecError, ParseError},
lex::{Lexer, Token}, lex::{Lexer, Token},
parse, parse,
runtime::{Eval, State, Value}, run::{Call, Context, Eval, Value},
}; };
use std::{collections::HashSet, process}; use std::{collections::HashSet, process};
@ -15,7 +15,7 @@ pub enum Element {
} }
impl Eval for Element { impl Eval for Element {
fn eval(&self, ctx: &mut State) -> Result<Value, ExecError> { fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
use Element::*; use Element::*;
match self { match self {
Block(block) => block.eval(ctx), Block(block) => block.eval(ctx),
@ -39,7 +39,7 @@ impl Block {
} }
impl Eval for Block { impl Eval for Block {
fn eval(&self, ctx: &mut State) -> Result<Value, ExecError> { fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
let mut v = Value::None; let mut v = Value::None;
for cmd in self.commands.iter() { for cmd in self.commands.iter() {
v = cmd.eval(ctx)?; v = cmd.eval(ctx)?;
@ -55,21 +55,27 @@ pub struct Command {
} }
impl Command { impl Command {
fn eval_args(&self, ctx: &mut State) -> Result<Vec<Value>, ExecError> { fn exec_builtin(&self, ctx: &mut Context, builtin: Builtin) -> Result<Value, ExecError> {
self.args.iter().map(|elem| elem.eval(ctx)).collect() let args = self
.args
.iter()
.map(|elem| elem.eval(ctx))
.collect::<Result<Vec<_>, _>>()?;
builtin.call(ctx, &args)
} }
fn exec_command(&self, ctx: &mut State, name: &str) -> Result<Value, ExecError> { fn exec_command(&self, ctx: &mut Context, name: &str) -> Result<Value, ExecError> {
let mut proc = process::Command::new(&name);
if self.args.len() > 0 {
let args = self let args = self
.args .args
.iter() .iter()
.map(|elem| elem.eval(ctx)?.try_to_string()) .map(|elem| elem.eval(ctx)?.try_to_string())
.collect::<Result<Vec<String>, ExecError>>()?; .collect::<Result<Vec<_>, _>>()?;
let mut proc = process::Command::new(&name);
if args.len() > 0 {
proc.args(args); proc.args(args);
} }
let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError { let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError {
name: name.to_string(), name: name.to_string(),
source: e, source: e,
@ -85,17 +91,12 @@ impl Command {
} }
impl Eval for Command { impl Eval for Command {
fn eval(&self, ctx: &mut State) -> Result<Value, ExecError> { fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
let name = self.name.eval(ctx)?.try_to_string()?; let name = self.name.eval(ctx)?;
let name = name.try_as_str()?;
match Builtin::lookup(&name) { match ctx.state.builtin(name) {
Some(builtin) => { Some(builtin) => self.exec_builtin(ctx, builtin),
let args = self.eval_args(ctx)?; None => self.exec_command(ctx, name),
// let args: Vec<&str> = args.into_iter().map(|arg| arg).collect();
builtin.call(ctx, &args)
}
None => self.exec_command(ctx, &name),
} }
} }
} }

Loading…
Cancel
Save