Files
yggdrasil/userspace/tools/shell/src/command/eval.rs
T
2025-07-17 17:47:24 +03:00

225 lines
7.3 KiB
Rust

use std::{io::BufReader, marker::PhantomData, process::ExitCode};
use crate::{
builtin::{self, Envs},
error::Error,
exec::{exec_pipeline, wait_for_pipeline, Execution, InheritStdout, Input, Outcome, Output},
syntax::parse::{BinaryOperator, ConditionalExpression, Expression},
};
use super::{env::Environment, ExpandedPipeline, ExpandedPipelineElement};
pub fn evaluate_pipeline(
pipeline: &ExpandedPipeline,
env: &mut Environment,
) -> Result<(Outcome, Option<ExitCode>), Error> {
let mut executions = vec![];
let mut stdins = vec![];
let mut stdouts = vec![];
let pipeline_stdin = Input::Inherit;
let pipeline_stdout = Output::<InheritStdout>::Inherit(PhantomData);
stdins.push(pipeline_stdin);
for _ in 1..pipeline.elements.len() {
let (read, write) = std::pipe::pipe().unwrap();
stdins.push(Input::Pipe(BufReader::new(read)));
stdouts.push(Output::Pipe(write));
}
stdouts.push(pipeline_stdout);
assert_eq!(stdins.len(), stdouts.len());
assert_eq!(pipeline.elements.len(), stdouts.len());
let io = Iterator::zip(stdins.drain(..), stdouts.drain(..));
for (command, (stdin, stdout)) in pipeline.elements.iter().zip(io) {
let (program, arguments) = command.words.split_first().unwrap();
// let stderr = stderr.try_clone()?;
let envs = command
.envs
.iter()
.map(|(a, b)| (a.clone(), b.clone()))
.collect();
let execution = Execution {
program: program.to_owned(),
arguments: arguments.to_vec(),
envs,
stdin,
stdout,
stderr: Output::Inherit(PhantomData),
};
executions.push(execution);
}
let handles = exec_pipeline(executions.into_iter(), env)?;
let (status, exit) = wait_for_pipeline(handles)?;
Ok((status, exit))
}
fn evaluate_conditional_body(
mut body: &Expression,
env: &mut Environment,
) -> (Outcome, Option<ExitCode>) {
while let Expression::Not(inner) = body {
body = inner;
}
let status = match body {
Expression::Binary(BinaryOperator::Equal, lhs, rhs)
if let (Some(lhs), Some(rhs)) = (lhs.as_word(), rhs.as_word()) =>
{
let lhs = env.expand(lhs);
let rhs = env.expand(rhs);
Some(lhs == rhs)
}
Expression::Binary(BinaryOperator::NotEqual, lhs, rhs)
if let (Some(lhs), Some(rhs)) = (lhs.as_word(), rhs.as_word()) =>
{
let lhs = env.expand(lhs);
let rhs = env.expand(rhs);
Some(lhs != rhs)
}
Expression::Pipeline(pipeline)
if let Some(element) = pipeline
.as_single_command()
.map(|element| ExpandedPipelineElement::from_syntax(env, element)) =>
{
let io = builtin::Args {
stdin: Input::Inherit,
stdout: Output::Inherit(PhantomData),
stderr: Output::Inherit(PhantomData),
};
return (builtin::b_test(io, element.words, Envs::from(vec![])), None);
}
_ => None,
};
match status {
Some(true) => (Outcome::ok(), None),
Some(false) => (Outcome::err(), None),
None => {
eprintln!("test: invalid expression: {:?}", body);
(Outcome::err(), None)
}
}
}
pub fn evaluate_conditional(
conditional: &ConditionalExpression,
env: &mut Environment,
) -> (Outcome, Option<ExitCode>) {
evaluate_conditional_body(&conditional.body, env)
}
pub fn evaluate(expression: &Expression, env: &mut Environment) -> (Outcome, Option<ExitCode>) {
match expression {
Expression::Pipeline(pipeline) => {
let mut expanded = ExpandedPipeline::from_syntax(env, pipeline);
let mut setenvs = vec![];
expanded.elements.retain_mut(|e| {
if e.words.is_empty() {
setenvs.append(&mut e.envs);
}
!e.words.is_empty()
});
for (key, value) in setenvs {
env.insert_current_literal(key, value);
}
if expanded.elements.is_empty() {
return (Outcome::ok(), None);
}
match evaluate_pipeline(&expanded, env) {
Ok(res) => res,
Err(error) => {
eprintln!("{error}");
(Outcome::err(), None)
}
}
}
Expression::Conditional(cond) => evaluate_conditional(cond, env),
// TODO redirects
Expression::If(if_expression) => {
let (condition, exit) = evaluate(&if_expression.condition, env);
if exit.is_some() {
return (condition, exit);
}
if condition.is_success() {
// Execute true branch
evaluate_list(&if_expression.if_true, env)
} else if let Some(if_false) = if_expression.if_false.as_ref() {
evaluate_list(if_false, env)
} else {
(Outcome::ok(), None)
}
}
// TODO redirects
Expression::While(while_expression) => loop {
let (condition, exit) = evaluate(&while_expression.condtion, env);
if exit.is_some() || !condition.is_success() {
return (Outcome::ok(), exit);
}
let (_, exit) = evaluate_list(&while_expression.body, env);
if exit.is_some() {
return (Outcome::ok(), exit);
}
},
// TODO redirects
Expression::For(for_expression) => {
let list = ExpandedPipelineElement::from_syntax(env, &for_expression.list);
env.push_environment();
for word in list.words {
env.insert_current_literal(for_expression.variable, word);
let (_, exit) = evaluate_list(&for_expression.body, env);
if exit.is_some() {
env.pop_environment();
return (Outcome::ok(), exit);
}
}
env.pop_environment();
(Outcome::ok(), None)
}
Expression::Binary(BinaryOperator::And, lhs, rhs) => {
let (lhs, exit) = evaluate(lhs, env);
if exit.is_some() {
return (lhs, exit);
}
if lhs.is_success() {
evaluate(rhs, env)
} else {
(lhs, exit)
}
}
Expression::Binary(BinaryOperator::Or, lhs, rhs) => {
let (lhs, exit) = evaluate(lhs, env);
if exit.is_some() {
return (lhs, exit);
}
if !lhs.is_success() {
evaluate(rhs, env)
} else {
(lhs, exit)
}
}
_ => todo!(),
}
}
pub fn evaluate_list<'a, I: IntoIterator<Item = &'a Expression<'a>>>(
expressions: I,
env: &mut Environment,
) -> (Outcome, Option<ExitCode>) {
let mut status = Outcome::ok();
for expression in expressions {
let (outcome, exit) = evaluate(expression, env);
if exit.is_some() {
return (outcome, exit);
}
status = outcome;
}
(status, None)
}