Skip to content

Commit

Permalink
Merge #97: Perform assignments in simple command invoking function
Browse files Browse the repository at this point in the history
  • Loading branch information
magicant committed Oct 15, 2021
2 parents dfce608 + 89a1d6e commit c516eaf
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 20 deletions.
55 changes: 40 additions & 15 deletions yash-semantics/src/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,51 @@
//! Assignment.

use crate::expansion::expand_value;
use yash_env::variable::Scope;
use yash_env::variable::Variable;

#[doc(no_inline)]
pub use crate::expansion::{Env, Error, ErrorCause, Result};
#[doc(no_inline)]
pub use yash_env::variable::Scope;
#[doc(no_inline)]
pub use yash_syntax::syntax::Assign;

// TODO Export or not?
// TODO Specifying the scope of assignment
/// Performs an assignment.
///
/// This function [expands the value](expand_value) and then
/// [assigns](yash_env::variable::VariableSet::assign) it to the environment.
pub async fn perform_assignment<E: Env>(env: &mut E, assign: &Assign) -> Result {
pub async fn perform_assignment<E: Env>(
env: &mut E,
assign: &Assign,
scope: Scope,
export: bool,
) -> Result {
let name = assign.name.clone();
let value = expand_value(env, &assign.value).await?;
let value = Variable {
value,
last_assigned_location: Some(assign.location.clone()),
is_exported: false,
is_exported: export,
read_only_location: None,
};
env.assign_variable(Scope::Global, name, value)
.map_err(|e| Error {
cause: ErrorCause::AssignReadOnly(e),
location: assign.location.clone(),
})?;
env.assign_variable(scope, name, value).map_err(|e| Error {
cause: ErrorCause::AssignReadOnly(e),
location: assign.location.clone(),
})?;
Ok(())
}

/// Performs assignments.
///
/// This function calls [`perform_assignment`] for each [`Assign`].
pub async fn perform_assignments<E: Env>(env: &mut E, assigns: &[Assign]) -> Result {
pub async fn perform_assignments<E: Env>(
env: &mut E,
assigns: &[Assign],
scope: Scope,
export: bool,
) -> Result {
for assign in assigns {
perform_assignment(env, assign).await?;
perform_assignment(env, assign, scope, export).await?;
}
Ok(())
}
Expand All @@ -63,7 +71,6 @@ mod tests {
use super::*;
use assert_matches::assert_matches;
use futures_executor::block_on;
use yash_env::variable::Scope;
use yash_env::variable::Value;
use yash_env::Env;
use yash_syntax::source::Location;
Expand All @@ -72,7 +79,7 @@ mod tests {
fn perform_assignment_new_value() {
let mut env = Env::new_virtual();
let a: Assign = "foo=bar".parse().unwrap();
block_on(perform_assignment(&mut env, &a)).unwrap();
block_on(perform_assignment(&mut env, &a, Scope::Global, false)).unwrap();
assert_eq!(
env.variables.get("foo").unwrap(),
&Variable {
Expand All @@ -84,6 +91,24 @@ mod tests {
);
}

#[test]
fn perform_assignment_overwriting() {
let mut env = Env::new_virtual();
let a: Assign = "foo=bar".parse().unwrap();
block_on(perform_assignment(&mut env, &a, Scope::Global, false)).unwrap();
let a: Assign = "foo=baz".parse().unwrap();
block_on(perform_assignment(&mut env, &a, Scope::Global, true)).unwrap();
assert_eq!(
env.variables.get("foo").unwrap(),
&Variable {
value: Value::Scalar("baz".to_string()),
last_assigned_location: Some(a.location),
is_exported: true,
read_only_location: None,
}
);
}

#[test]
fn perform_assignment_read_only() {
let mut env = Env::new_virtual();
Expand All @@ -98,7 +123,7 @@ mod tests {
.assign(Scope::Global, "v".to_string(), v)
.unwrap();
let a: Assign = "v=new".parse().unwrap();
let e = block_on(perform_assignment(&mut env, &a)).unwrap_err();
let e = block_on(perform_assignment(&mut env, &a, Scope::Global, false)).unwrap_err();
assert_matches!(e.cause, ErrorCause::AssignReadOnly(roe) => {
assert_eq!(roe.name, "v");
assert_eq!(roe.read_only_location, location);
Expand Down
35 changes: 30 additions & 5 deletions yash-semantics/src/command_impl/simple_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ use crate::Command;
use async_trait::async_trait;
use std::ffi::CString;
use std::ops::ControlFlow::Continue;
use std::ops::DerefMut;
use std::rc::Rc;
use yash_env::builtin::Builtin;
use yash_env::exec::ExitStatus;
use yash_env::exec::Result;
use yash_env::expansion::Field;
use yash_env::function::Function;
use yash_env::system::Errno;
#[cfg(doc)]
use yash_env::variable::Scope;
use yash_env::Env;
use yash_env::System;
Expand Down Expand Up @@ -166,7 +166,7 @@ impl Command for syntax::SimpleCommand {
if let Some(name) = fields.get(0) {
match search(env, &name.value) {
Some(Builtin(builtin)) => execute_builtin(env, builtin, fields).await,
Some(Function(function)) => execute_function(env, function).await,
Some(Function(function)) => execute_function(env, function, &self.assigns).await,
Some(External { path }) => {
execute_external_utility(env, path, fields, Rc::clone(&self.redirs)).await
}
Expand All @@ -190,7 +190,7 @@ async fn execute_absent_target(env: &mut Env, assigns: &[Assign]) -> Result {
// TODO open redirections

// TODO Apply last command substitution exit status
match perform_assignments(env, assigns).await {
match perform_assignments(env, assigns, Scope::Global, false).await {
Ok(()) => Continue(()),
Err(error) => error.handle(env).await,
}
Expand All @@ -204,10 +204,13 @@ async fn execute_builtin(env: &mut Env, builtin: Builtin, fields: Vec<Field>) ->
abort
}

async fn execute_function(env: &mut Env, function: Rc<Function>) -> Result {
async fn execute_function(env: &mut Env, function: Rc<Function>, assigns: &[Assign]) -> Result {
// TODO open redirections
let mut scope = env.push_variable_context();
// TODO expand and perform assignments
match perform_assignments(scope.deref_mut(), assigns, Scope::Local, true).await {
Ok(()) => (),
Err(error) => return error.handle(&mut scope).await,
}
// TODO Apply positional parameters
// TODO Update control flow stack
function.body.execute(&mut scope).await?;
Expand Down Expand Up @@ -411,6 +414,28 @@ mod tests {
assert_eq!(stdout.content, "42\n".as_bytes());
}

#[test]
fn simple_command_performs_assignment_locally() {
use yash_env::function::HashEntry;
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Box::new(system));
env.builtins.insert("echo", echo_builtin());
env.functions.insert(HashEntry(Rc::new(Function {
name: "foo".to_string(),
body: Rc::new("{ echo $x; }".parse().unwrap()),
origin: Location::dummy("dummy"),
is_read_only: false,
})));
let command: syntax::SimpleCommand = "x=hello foo".parse().unwrap();
block_on(command.execute(&mut env));
assert_eq!(env.variables.get("x"), None);

let state = state.borrow();
let stdout = state.file_system.get("/dev/stdout").unwrap().borrow();
assert_eq!(stdout.content, "hello\n".as_bytes());
}

#[test]
fn simple_command_calls_execve_with_correct_arguments() {
in_virtual_system(|mut env, _pid, state| async move {
Expand Down

0 comments on commit c516eaf

Please sign in to comment.