refactoring builtins

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

@ -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<Self> {
match name {
"echo" => Some(Self::Echo),
_ => None,
impl Call for Builtin {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
match self {
Builtin::Echo => Echo.call(ctx, args),
}
}
}
pub fn call(&self, _: &mut State, args: &Vec<Value>) -> Result<Value, ExecError> {
match self {
Builtin::Echo => {
let args: Result<Vec<String>, 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<Value, ExecError> {
let args = args
.into_iter()
.map(|v| v.try_as_str())
.collect::<Result<Vec<_>, _>>()?;
_ = write!(ctx.stdout, "{}\n", args.join(" "));
Ok(Value::None)
}
}
pub fn all() -> HashMap<&'static str, Builtin> {
use Builtin::*;
HashMap::from([("echo", Echo)])
}

@ -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)?;
}
}

@ -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;

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

@ -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<Value, ExecError>;
fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError>;
}
/// An individual value at runtime.
@ -23,19 +23,13 @@ pub enum Value {
impl Value {
pub fn try_to_string(self) -> Result<String, ExecError> {
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<String, ExecError> {
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<Value, ExecError> {
fn eval(&self, _: &mut Context) -> Result<Value, ExecError> {
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<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},
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<Value, ExecError> {
fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
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<Value, ExecError> {
fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
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<Vec<Value>, ExecError> {
self.args.iter().map(|elem| elem.eval(ctx)).collect()
fn exec_builtin(&self, ctx: &mut Context, builtin: Builtin) -> Result<Value, ExecError> {
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> {
let mut proc = process::Command::new(&name);
fn exec_command(&self, ctx: &mut Context, name: &str) -> Result<Value, ExecError> {
let args = self
.args
.iter()
.map(|elem| elem.eval(ctx)?.try_to_string())
.collect::<Result<Vec<_>, _>>()?;
if self.args.len() > 0 {
let args = self
.args
.iter()
.map(|elem| elem.eval(ctx)?.try_to_string())
.collect::<Result<Vec<String>, 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<Value, ExecError> {
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<Value, ExecError> {
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),
}
}
}

Loading…
Cancel
Save