225 lines
7.3 KiB
Rust
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)
|
|
}
|