Skip to content

Refactor expansion #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8aead49
Publicize quote removal in dedicated module
magicant Mar 16, 2022
573e9fb
Move Origin, AttrChar & AttrField to attr
magicant Mar 18, 2022
cb6e090
Implement Phrase
magicant Mar 20, 2022
18fde22
Define initial expansion Env
magicant Mar 20, 2022
2d74283
Define Expand trait
magicant Mar 21, 2022
75bdff3
Implement Expand for slice
magicant Mar 21, 2022
783986c
Expand TextUnit::Literal
magicant Mar 22, 2022
74aa07c
Expand TextUnit::Backslashed
magicant Mar 22, 2022
6f8d1a6
Expand TextUnit::CommandSubst
magicant Mar 22, 2022
50affae
Expand TextUnit::Backquote
magicant Mar 22, 2022
79db2e9
Expand TextUnit::BracedParam & RawParam
magicant Mar 25, 2022
dc493d7
Remove Env::is_quoted
magicant Mar 26, 2022
9370622
Simplify QuickExpand
magicant Mar 26, 2022
e1f2cba
Expand WordUnit::Unquoted & SingleQuote
magicant Mar 26, 2022
0c6c06c
Expand WordUnit::DoubleQuote
magicant Mar 26, 2022
1a2ef49
Phrase::ifs_join
magicant Mar 27, 2022
b341e98
New expansion functions
magicant Mar 27, 2022
75a5ea8
New assignment functions
magicant Mar 27, 2022
9eb1f21
Use new assignment function in simple command
magicant Mar 27, 2022
1a2696c
Use new expansion functions in redirection
magicant Mar 27, 2022
5114233
Use new expansion function in simple command
magicant Mar 27, 2022
1fb57f1
Use new expansion function in function definition
magicant Mar 27, 2022
aa0bca6
Replace functions with new implementation
magicant Mar 27, 2022
5c7f4e0
Remove yash_semantics::redir::Env
magicant Mar 28, 2022
4be00e2
Don't use expansion::Env in assignment
magicant Mar 28, 2022
225acbd
Remove old expansion implementation
magicant Mar 28, 2022
2ca42a6
Remove default type parameter from expansion::Result
magicant Mar 28, 2022
bc160ed
Update doc comments for expansion
magicant Mar 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 80 additions & 21 deletions yash-semantics/src/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
//! Assignment.

use crate::expansion::expand_value;
use yash_env::semantics::ExitStatus;
use yash_env::variable::Variable;
use yash_env::Env;

#[doc(no_inline)]
pub use crate::expansion::{Env, Error, ErrorCause, Result};
pub use crate::expansion::{Error, ErrorCause, Result};
#[doc(no_inline)]
pub use yash_env::variable::Scope;
#[doc(no_inline)]
Expand All @@ -30,56 +32,69 @@ pub use yash_syntax::syntax::Assign;
///
/// 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,
/// The return value is the exit status of the last command substitution
/// performed during the expansion of the assigned value, if any
pub async fn perform_assignment(
env: &mut Env,
assign: &Assign,
scope: Scope,
export: bool,
) -> Result {
) -> Result<Option<ExitStatus>> {
let name = assign.name.clone();
let value = expand_value(env, &assign.value).await?;
let (value, exit_status) = expand_value(env, &assign.value).await?;
let value = Variable {
value,
last_assigned_location: Some(assign.location.clone()),
is_exported: export,
read_only_location: None,
};
env.assign_variable(scope, name, value).map_err(|e| Error {
cause: ErrorCause::AssignReadOnly(e),
location: assign.location.clone(),
})?;
Ok(())
env.variables
.assign(scope, name, value)
.map_err(|e| Error {
cause: ErrorCause::AssignReadOnly(e),
location: assign.location.clone(),
})?;
Ok(exit_status)
}

/// Performs assignments.
///
/// This function calls [`perform_assignment`] for each [`Assign`].
pub async fn perform_assignments<E: Env>(
env: &mut E,
/// The return value is the exit status of the last command substitution
/// performed during the expansion of the assigned values, if any
pub async fn perform_assignments(
env: &mut Env,
assigns: &[Assign],
scope: Scope,
export: bool,
) -> Result {
) -> Result<Option<ExitStatus>> {
let mut exit_status = None;
for assign in assigns {
perform_assignment(env, assign, scope, export).await?;
let new_exit_status = perform_assignment(env, assign, scope, export).await?;
exit_status = new_exit_status.or(exit_status);
}
Ok(())
Ok(exit_status)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::tests::in_virtual_system;
use crate::tests::return_builtin;
use assert_matches::assert_matches;
use futures_executor::block_on;
use futures_util::FutureExt;
use yash_env::variable::Value;
use yash_env::Env;
use yash_syntax::source::Location;

#[test]
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, Scope::Global, false)).unwrap();
let exit_status = perform_assignment(&mut env, &a, Scope::Global, false)
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(exit_status, None);
assert_eq!(
env.variables.get("foo").unwrap(),
&Variable {
Expand All @@ -95,9 +110,17 @@ mod tests {
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 exit_status = perform_assignment(&mut env, &a, Scope::Global, false)
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(exit_status, None);
let a: Assign = "foo=baz".parse().unwrap();
block_on(perform_assignment(&mut env, &a, Scope::Global, true)).unwrap();
let exit_status = perform_assignment(&mut env, &a, Scope::Global, true)
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(exit_status, None);
assert_eq!(
env.variables.get("foo").unwrap(),
&Variable {
Expand All @@ -123,7 +146,10 @@ 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, Scope::Global, false)).unwrap_err();
let e = perform_assignment(&mut env, &a, Scope::Global, false)
.now_or_never()
.unwrap()
.unwrap_err();
assert_matches!(e.cause, ErrorCause::AssignReadOnly(roe) => {
assert_eq!(roe.name, "v");
assert_eq!(roe.read_only_location, location);
Expand All @@ -133,4 +159,37 @@ mod tests {
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.range, 0..5);
}

#[test]
fn perform_assignments_exit_status() {
in_virtual_system(|mut env, _pid, _state| async move {
env.builtins.insert("return", return_builtin());
let assigns = [
"a=A$(return -n 1)".parse().unwrap(),
"b=($(return -n 2))".parse().unwrap(),
];
let exit_status = perform_assignments(&mut env, &assigns, Scope::Global, false)
.await
.unwrap();
assert_eq!(exit_status, Some(ExitStatus(2)));
assert_eq!(
env.variables.get("a").unwrap(),
&Variable {
value: Value::Scalar("A".to_string()),
last_assigned_location: Some(assigns[0].location.clone()),
is_exported: false,
read_only_location: None,
}
);
assert_eq!(
env.variables.get("b").unwrap(),
&Variable {
value: Value::Array(vec!["".to_string()]),
last_assigned_location: Some(assigns[1].location.clone()),
is_exported: false,
read_only_location: None,
}
);
})
}
}
2 changes: 1 addition & 1 deletion yash-semantics/src/command_impl/function_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Command for syntax::FunctionDefinition {
value: name,
origin,
} = match expand_word(env, &self.name).await {
Ok(field) => field,
Ok((field, _exit_status)) => field,
Err(error) => return error.handle(env).await,
};

Expand Down
55 changes: 27 additions & 28 deletions yash-semantics/src/command_impl/simple_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

use crate::command_search::search;
use crate::expansion::expand_words;
use crate::expansion::ExitStatusAdapter;
use crate::redir::RedirGuard;
use crate::Command;
use crate::Handle;
Expand Down Expand Up @@ -164,8 +163,8 @@ impl Command for syntax::SimpleCommand {
/// POSIX leaves many aspects of the simple command execution unspecified.
/// The detail semantics may differ in other shell implementations.
async fn execute(&self, env: &mut Env) -> Result {
let fields = match expand_words(env, &self.words).await {
Ok(fields) => fields,
let (fields, exit_status) = match expand_words(env, &self.words).await {
Ok(result) => result,
Err(error) => return error.handle(env).await,
};

Expand All @@ -187,51 +186,55 @@ impl Command for syntax::SimpleCommand {
}
}
} else {
execute_absent_target(env, &self.assigns, Rc::clone(&self.redirs)).await
let exit_status = exit_status.unwrap_or_default();
execute_absent_target(env, &self.assigns, Rc::clone(&self.redirs), exit_status).await
}
}
}

async fn perform_redirs(
env: &mut RedirGuard<'_, ExitStatusAdapter<'_, Env>>,
redirs: &[Redir],
) -> Result {
async fn perform_redirs(env: &mut RedirGuard<'_>, redirs: &[Redir]) -> Result<Option<ExitStatus>> {
match env.perform_redirs(&*redirs).await {
Ok(()) => Continue(()),
Err(e) => e.handle(env).await,
Ok(exit_status) => Continue(exit_status),
Err(e) => {
e.handle(env).await?;
Continue(None)
}
}
}

async fn perform_assignments(
env: &mut ExitStatusAdapter<'_, Env>,
env: &mut Env,
assigns: &[Assign],
export: bool,
) -> Result {
) -> Result<Option<ExitStatus>> {
let scope = if export {
Scope::Volatile
} else {
Scope::Global
};
match crate::assign::perform_assignments(env, assigns, scope, export).await {
Ok(()) => Continue(()),
Err(error) => error.handle(env).await,
Ok(exit_status) => Continue(exit_status),
Err(error) => {
error.handle(env).await?;
Continue(None)
}
}
}

async fn execute_absent_target(
env: &mut Env,
assigns: &[Assign],
redirs: Rc<Vec<Redir>>,
exit_status: ExitStatus,
) -> Result {
// Perform redirections in a subshell
let exit_status = if let Some(redir) = redirs.first() {
let redir_exit_status = if let Some(redir) = redirs.first() {
let first_redir_location = redir.body.operand().location.clone();
let redir_results = env.run_in_subshell(move |env| {
Box::pin(async move {
let env = &mut ExitStatusAdapter::new(env);
let env = &mut RedirGuard::new(env);
perform_redirs(env, &*redirs).await?;
env.exit_status = env.last_command_subst_exit_status().unwrap_or_default();
let redir_exit_status = perform_redirs(env, &*redirs).await?;
env.exit_status = redir_exit_status.unwrap_or(exit_status);
Continue(())
})
});
Expand All @@ -249,12 +252,11 @@ async fn execute_absent_target(
}
}
} else {
ExitStatus::SUCCESS
exit_status
};

let env = &mut ExitStatusAdapter::new(env);
perform_assignments(env, assigns, false).await?;
env.exit_status = env.last_command_subst_exit_status().unwrap_or(exit_status);
let assignment_exit_status = perform_assignments(env, assigns, false).await?;
env.exit_status = assignment_exit_status.unwrap_or(redir_exit_status);
Continue(())
}

Expand All @@ -267,7 +269,6 @@ async fn execute_builtin(
) -> Result {
let name = fields.remove(0);
let env = &mut env.push_frame(Frame::Builtin { name });
let env = &mut ExitStatusAdapter::new(&mut **env);
let env = &mut RedirGuard::new(env);
perform_redirs(env, redirs).await?;

Expand Down Expand Up @@ -295,14 +296,13 @@ async fn execute_function(
fields: Vec<Field>,
redirs: &[Redir],
) -> Result {
let env = &mut ExitStatusAdapter::new(env);
let env = &mut RedirGuard::new(env);
perform_redirs(env, redirs).await?;

let mut outer = ScopeGuard::push_context(&mut **env, ContextType::Volatile);
perform_assignments(&mut *outer, assigns, true).await?;

let mut inner = ScopeGuard::push_context(&mut **outer, ContextType::Regular);
let mut inner = ScopeGuard::push_context(&mut *outer, ContextType::Regular);

// Apply positional parameters
let mut params = inner.variables.positional_params_mut();
Expand Down Expand Up @@ -331,7 +331,6 @@ async fn execute_external_utility(
let location = name.origin.clone();
let args = to_c_strings(fields);

let env = &mut ExitStatusAdapter::new(env);
let env = &mut RedirGuard::new(env);
perform_redirs(env, redirs).await?;

Expand All @@ -340,7 +339,7 @@ async fn execute_external_utility(

if path.to_bytes().is_empty() {
print_error(
&mut **env,
&mut *env,
format!("cannot execute external utility {:?}", name.value).into(),
"utility not found".into(),
&name.origin,
Expand Down Expand Up @@ -384,7 +383,7 @@ async fn execute_external_utility(
}
Err(errno) => {
print_error(
&mut **env,
&mut *env,
format!("cannot execute external utility {:?}", name.value).into(),
errno.desc().into(),
&name.origin,
Expand Down
Loading