diff --git a/yash-builtin/src/set.rs b/yash-builtin/src/set.rs index 728b55bb..974659bf 100644 --- a/yash-builtin/src/set.rs +++ b/yash-builtin/src/set.rs @@ -136,7 +136,6 @@ use yash_env::option::State; use yash_env::option::{Interactive, Monitor}; use yash_env::semantics::ExitStatus; use yash_env::semantics::Field; -use yash_env::variable::Array; use yash_env::variable::Scope::Global; use yash_env::Env; @@ -167,10 +166,9 @@ fn modify( // Modify positional parameters if let Some(fields) = positional_params { - let location = env.stack.current_builtin().map(|b| b.name.origin.clone()); let params = env.variables.positional_params_mut(); - params.value = Some(Array(fields.into_iter().map(|f| f.value).collect())); - params.last_assigned_location = location; + params.values = fields.into_iter().map(|f| f.value).collect(); + params.last_modified_location = env.stack.current_builtin().map(|b| b.name.origin.clone()); } } @@ -366,10 +364,12 @@ xtrace off let result = main(&mut env, args).now_or_never().unwrap(); assert_eq!(result, Result::new(ExitStatus::SUCCESS)); - let v = env.variables.positional_params(); - assert_eq!(v.value, Some(Value::array(["a", "b", "z"]))); - assert_eq!(v.read_only_location, None); - assert_eq!(v.last_assigned_location.as_ref().unwrap(), &location); + let params = env.variables.positional_params(); + assert_eq!( + params.values, + ["a".to_string(), "b".to_string(), "z".to_string()], + ); + assert_eq!(params.last_modified_location, Some(location)); } #[test] diff --git a/yash-builtin/src/shift.rs b/yash-builtin/src/shift.rs index 9765a491..6ba5a415 100644 --- a/yash-builtin/src/shift.rs +++ b/yash-builtin/src/shift.rs @@ -67,7 +67,6 @@ use std::borrow::Cow; use yash_env::builtin::Result; use yash_env::semantics::ExitStatus; use yash_env::semantics::Field; -use yash_env::variable::Value; use yash_env::Env; use yash_syntax::source::pretty::Annotation; use yash_syntax::source::pretty::AnnotationType; @@ -99,13 +98,8 @@ pub async fn main(env: &mut Env, args: Vec) -> Result { }; let params = env.variables.positional_params_mut(); - let values = match params.value.as_mut() { - None => panic!("positional parameters are undefined"), - Some(Value::Scalar(value)) => panic!("positional parameters are not an array: {value:?}"), - Some(Value::Array(params)) => params, - }; - - if values.len() < count { + let len = params.values.len(); + if len < count { // Failure: cannot shift so many positional parameters let (label, location) = match operand_location { None => ( @@ -119,14 +113,14 @@ pub async fn main(env: &mut Env, args: Vec) -> Result { format!( "requested to shift {} but there {} only {}", count, - if values.len() == 1 { "is" } else { "are" }, - values.len(), + if len == 1 { "is" } else { "are" }, + len, ) .into(), Cow::Borrowed(location), ), }; - let last_location = params.last_assigned_location.clone(); + let last_location = params.last_modified_location.clone(); let mut annotations = vec![Annotation::new(AnnotationType::Error, label, &location)]; if let Some(last_location) = &last_location { annotations.push(Annotation::new( @@ -145,8 +139,8 @@ pub async fn main(env: &mut Env, args: Vec) -> Result { return Result::with_exit_status_and_divert(ExitStatus::FAILURE, divert); } - values.drain(..count); - params.last_assigned_location = env.stack.current_builtin().map(|b| b.name.origin.clone()); + params.values.drain(..count); + params.last_modified_location = env.stack.current_builtin().map(|b| b.name.origin.clone()); Result::default() } @@ -172,28 +166,26 @@ mod tests { name: Field::dummy("shift"), is_special: true, })); - env.variables.positional_params_mut().value = Some(Value::array(["1", "2", "3"])); + env.variables.positional_params_mut().values = + vec!["1".to_string(), "2".to_string(), "3".to_string()]; let result = main(&mut env, vec![]).now_or_never().unwrap(); assert_eq!(result, Result::default()); assert_eq!( - env.variables.positional_params().value, - Some(Value::array(["2", "3"])), + env.variables.positional_params().values, + ["2".to_string(), "3".to_string()], ); let result = main(&mut env, vec![]).now_or_never().unwrap(); assert_eq!(result, Result::default()); - assert_eq!( - env.variables.positional_params().value, - Some(Value::array(["3"])), - ); + assert_eq!(env.variables.positional_params().values, ["3".to_string()],); let result = main(&mut env, vec![]).now_or_never().unwrap(); assert_eq!(result, Result::default()); let params = env.variables.positional_params(); - assert_eq!(params.value, Some(Value::Array(vec![]))); + assert_eq!(params.values, [] as [String; 0]); assert_eq!( - params.last_assigned_location, + params.last_modified_location, Some(Location::dummy("shift")), ); } @@ -205,32 +197,37 @@ mod tests { name: Field::dummy("shift"), is_special: true, })); - env.variables.positional_params_mut().value = - Some(Value::array(["1", "2", "3", "4", "5", "6", "7"])); + env.variables.positional_params_mut().values = ["1", "2", "3", "4", "5", "6", "7"] + .into_iter() + .map(Into::into) + .collect(); let args = Field::dummies(["2"]); let result = main(&mut env, args).now_or_never().unwrap(); assert_eq!(result, Result::default()); assert_eq!( - env.variables.positional_params().value, - Some(Value::array(["3", "4", "5", "6", "7"])), + env.variables.positional_params().values, + [ + "3".to_string(), + "4".to_string(), + "5".to_string(), + "6".to_string(), + "7".to_string(), + ], ); let args = Field::dummies(["3"]); let result = main(&mut env, args).now_or_never().unwrap(); assert_eq!(result, Result::default()); assert_eq!( - env.variables.positional_params().value, - Some(Value::array(["6", "7"])), + env.variables.positional_params().values, + ["6".to_string(), "7".to_string(),], ); let args = Field::dummies(["2"]); let result = main(&mut env, args).now_or_never().unwrap(); assert_eq!(result, Result::default()); - assert_eq!( - env.variables.positional_params().value, - Some(Value::Array(vec![])), - ); + assert_eq!(env.variables.positional_params().values, [] as [String; 0]); } #[test] @@ -266,7 +263,8 @@ mod tests { name: Field::dummy("shift"), is_special: true, })); - env.variables.positional_params_mut().value = Some(Value::array(["1", "2", "3"])); + env.variables.positional_params_mut().values = + vec!["1".to_string(), "2".to_string(), "3".to_string()]; let args = Field::dummies(["4"]); let actual_result = main(&mut env, args).now_or_never().unwrap(); diff --git a/yash-env/src/variable.rs b/yash-env/src/variable.rs index 63f204ec..6627e921 100644 --- a/yash-env/src/variable.rs +++ b/yash-env/src/variable.rs @@ -81,17 +81,30 @@ struct VariableInContext { context_index: usize, } -/// Type of a context. +/// Positional parameters +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PositionalParams { + /// Values of positional parameters + pub values: Vec, + /// Location of the last modification of positional parameters + pub last_modified_location: Option, +} + +/// Variable context /// -/// The context type affects the behavior of variable -/// [assignment](VariableRefMut::assign). -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ContextType { +/// This enum defines the type of a context. The context type affects the +/// behavior of variable [assignment](VariableRefMut::assign). A regular context +/// is the default context type and may have positional parameters. A volatile +/// context is used for holding temporary variables when executing a built-in or +/// function. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Context { /// Context for normal assignments. /// - /// The base context is a regular context. The context for a function's - /// local assignment is also regular. - Regular, + /// The base context is a regular context. Every function invocation also + /// creates a regular context for local assignments and positional + /// parameters. + Regular { positional_params: PositionalParams }, /// Context for temporary assignments. /// @@ -100,29 +113,10 @@ pub enum ContextType { Volatile, } -/// Variable context. -/// -/// Variables defined in the context are not stored in this struct. -/// See `VariableSet::all_variables`. -#[derive(Clone, Debug, Eq, PartialEq)] -struct Context { - /// Context type. - r#type: ContextType, - - /// Positional parameters. - /// - /// This variable is very special: - /// - /// - Its value is always an `Array`. - /// - It is never exported nor read-only. - positional_params: Variable, -} - -impl Context { - fn new(r#type: ContextType) -> Self { - Context { - r#type, - positional_params: Variable::new_empty_array(), +impl Default for Context { + fn default() -> Self { + Context::Regular { + positional_params: Default::default(), } } } @@ -153,7 +147,7 @@ impl Default for VariableSet { fn default() -> Self { VariableSet { all_variables: Default::default(), - contexts: vec![Context::new(ContextType::Regular)], + contexts: vec![Context::default()], } } } @@ -217,7 +211,7 @@ impl VariableSet { fn index_of_topmost_regular_context(contexts: &[Context]) -> usize { contexts .iter() - .rposition(|context| context.r#type == ContextType::Regular) + .rposition(|context| matches!(context, Context::Regular { .. })) .expect("base context has gone") } @@ -274,8 +268,8 @@ impl VariableSet { /// yourself, or use [`Env::get_or_create_variable`] to get the option /// applied automatically. /// - /// [regular]: ContextType::Regular - /// [volatile]: ContextType::Volatile + /// [regular]: Context::Regular + /// [volatile]: Context::Volatile #[inline] pub fn get_or_new>(&mut self, name: S, scope: Scope) -> VariableRefMut { self.get_or_new_impl(name.into(), scope) @@ -303,14 +297,14 @@ impl VariableSet { if var.context_index < context_index { break; } - match self.contexts[var.context_index].r#type { - ContextType::Regular => { + match self.contexts[var.context_index] { + Context::Regular { .. } => { if let Some(removed_volatile_variable) = removed_volatile_variable { var.variable = removed_volatile_variable; } break 'branch; } - ContextType::Volatile => { + Context::Volatile => { removed_volatile_variable.get_or_insert(stack.pop().unwrap().variable); } } @@ -324,9 +318,9 @@ impl VariableSet { Scope::Volatile => { assert_eq!( - self.contexts[context_index].r#type, - ContextType::Volatile, - "no volatile context to store the variable" + self.contexts[context_index], + Context::Volatile, + "no volatile context to store the variable", ); if let Some(var) = stack.last() { if var.context_index != context_index { @@ -388,8 +382,8 @@ impl VariableSet { /// You cannot modify positional parameters using this function. /// See [`positional_params_mut`](Self::positional_params_mut). /// - /// [regular]: ContextType::Regular - /// [volatile]: ContextType::Volatile + /// [regular]: Context::Regular + /// [volatile]: Context::Volatile pub fn unset<'a>( &'a mut self, scope: Scope, @@ -436,14 +430,14 @@ impl VariableSet { /// The `scope` parameter chooses variables returned by the iterator: /// /// - `Global`: all variables - /// - `Local`: variables in the topmost [regular](ContextType::Regular) - /// context or above. - /// - `Volatile`: variables above the topmost - /// [regular](ContextType::Regular) context + /// - `Local`: variables in the topmost [regular] context or above. + /// - `Volatile`: variables above the topmost [regular] context /// /// In all cases, the iterator ignores variables hidden by another. /// /// The order of iterated variables is unspecified. + /// + /// [regular]: Context::Regular pub fn iter(&self, scope: Scope) -> Iter { Iter { inner: self.all_variables.iter(), @@ -538,42 +532,43 @@ impl VariableSet { /// /// See also [`positional_params_mut`](Self::positional_params_mut). #[must_use] - pub fn positional_params(&self) -> &Variable { - let index = Self::index_of_topmost_regular_context(&self.contexts); - &self.contexts[index].positional_params + pub fn positional_params(&self) -> &PositionalParams { + self.contexts + .iter() + .rev() + .find_map(|context| match context { + Context::Regular { positional_params } => Some(positional_params), + Context::Volatile => None, + }) + .expect("base context has gone") } /// Returns a mutable reference to the positional parameters. /// - /// Although positional parameters are not considered a variable in the - /// POSIX standard, we implement them as an anonymous array variable. It is - /// the caller's responsibility to keep the variable in a correct state: - /// - /// - The variable value should be an array. Not a scalar. - /// - The variable should not be exported nor made read-only. - /// - /// The `VariableSet` does not check if these rules are maintained. - /// /// Every regular context starts with an empty array of positional /// parameters, and volatile contexts cannot have positional parameters. /// This function returns a reference to the positional parameters of the /// topmost regular context. #[must_use] - pub fn positional_params_mut(&mut self) -> &mut Variable { - let index = Self::index_of_topmost_regular_context(&self.contexts); - &mut self.contexts[index].positional_params + pub fn positional_params_mut(&mut self) -> &mut PositionalParams { + self.contexts + .iter_mut() + .rev() + .find_map(|context| match context { + Context::Regular { positional_params } => Some(positional_params), + Context::Volatile => None, + }) + .expect("base context has gone") } - fn push_context_impl(&mut self, context_type: ContextType) { - self.contexts.push(Context::new(context_type)); + fn push_context_impl(&mut self, context: Context) { + self.contexts.push(context); } fn pop_context_impl(&mut self) { debug_assert!(!self.contexts.is_empty()); assert_ne!(self.contexts.len(), 1, "cannot pop the base context"); self.contexts.pop(); - // TODO Use complementary stack of hash tables to avoid scanning the - // whole `self.all_variables` self.all_variables.retain(|_, stack| { if let Some(vic) = stack.last() { if vic.context_index >= self.contexts.len() { @@ -614,13 +609,12 @@ pub use self::guard::{ContextGuard, EnvContextGuard}; #[cfg(test)] mod tests { use super::*; - use assert_matches::assert_matches; #[test] fn new_variable_in_global_scope() { let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Global); @@ -638,8 +632,8 @@ mod tests { let mut set = VariableSet::new(); let mut var = set.get_or_new("foo", Scope::Global); var.assign("ONE", None).unwrap(); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Global); @@ -661,15 +655,15 @@ mod tests { fn new_variable_in_local_scope() { // This test case creates two local variables in separate contexts. let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Regular); + set.push_context_impl(Context::default()); let mut var = set.get_or_new("foo", Scope::Local); assert_eq!(*var, Variable::default()); var.assign("OUTER", None).unwrap(); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Local); @@ -687,7 +681,7 @@ mod tests { #[test] fn existing_variable_in_local_scope() { let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Regular); + set.push_context_impl(Context::default()); let mut var = set.get_or_new("foo", Scope::Local); var.assign("OLD", None).unwrap(); @@ -704,8 +698,8 @@ mod tests { #[test] fn new_variable_in_volatile_scope() { let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Volatile); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); @@ -724,8 +718,8 @@ mod tests { var.assign("VALUE", None).unwrap(); var.make_read_only(Location::dummy("read-only location")); let save_var = var.clone(); - set.push_context_impl(ContextType::Volatile); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); @@ -742,7 +736,7 @@ mod tests { #[test] fn existing_variable_in_volatile_scope() { let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("INITIAL", None).unwrap(); @@ -763,11 +757,11 @@ mod tests { #[test] fn lowering_volatile_variable_to_base_context() { let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("DUMMY", None).unwrap(); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("VOLATILE", Location::dummy("anywhere")).unwrap(); var.export(true); @@ -801,11 +795,11 @@ mod tests { let mut set = VariableSet::new(); let mut var = set.get_or_new("foo", Scope::Local); var.assign("ONE", None).unwrap(); - set.push_context_impl(ContextType::Regular); + set.push_context_impl(Context::default()); let mut var = set.get_or_new("foo", Scope::Local); var.assign("TWO", None).unwrap(); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("VOLATILE", Location::dummy("anywhere")).unwrap(); var.export(true); @@ -839,12 +833,12 @@ mod tests { #[test] fn lowering_volatile_variable_to_topmost_regular_context_without_existing_variable() { let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("DUMMY", None).unwrap(); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("VOLATILE", Location::dummy("anywhere")).unwrap(); var.export(true); @@ -875,18 +869,18 @@ mod tests { #[test] fn lowering_volatile_variable_to_topmost_regular_context_overwriting_existing_variable() { let mut set = VariableSet::new(); - set.push_context_impl(ContextType::Regular); - set.push_context_impl(ContextType::Regular); + set.push_context_impl(Context::default()); + set.push_context_impl(Context::default()); let mut var = set.get_or_new("foo", Scope::Local); var.assign("OLD", None).unwrap(); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("DUMMY", None).unwrap(); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Volatile); var.assign("VOLATILE", Location::dummy("first")).unwrap(); var.export(true); - set.push_context_impl(ContextType::Volatile); + set.push_context_impl(Context::Volatile); let mut var = set.get_or_new("foo", Scope::Local); @@ -940,12 +934,12 @@ mod tests { .get_or_new("foo", Scope::Global) .assign("X", None) .unwrap(); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); variables .get_or_new("foo", Scope::Local) .assign("Y", None) .unwrap(); - variables.push_context_impl(ContextType::Volatile); + variables.push_context_impl(Context::Volatile); variables .get_or_new("foo", Scope::Volatile) .assign("Z", None) @@ -963,18 +957,18 @@ mod tests { .get_or_new("foo", Scope::Global) .assign("A", None) .unwrap(); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); // Non-local read-only variable does not prevent unsetting let mut readonly_foo = variables.get_or_new("foo", Scope::Local); readonly_foo.assign("B", None).unwrap(); readonly_foo.make_read_only(Location::dummy("dummy")); let readonly_foo = readonly_foo.clone(); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); variables .get_or_new("foo", Scope::Local) .assign("C", None) .unwrap(); - variables.push_context_impl(ContextType::Volatile); + variables.push_context_impl(Context::Volatile); variables .get_or_new("foo", Scope::Volatile) .assign("D", None) @@ -992,7 +986,7 @@ mod tests { .get_or_new("foo", Scope::Global) .assign("A", None) .unwrap(); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); let result = variables.unset(Scope::Local, "foo").unwrap(); assert_eq!(result, None); @@ -1006,17 +1000,17 @@ mod tests { .get_or_new("foo", Scope::Global) .assign("A", None) .unwrap(); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); variables .get_or_new("foo", Scope::Local) .assign("B", None) .unwrap(); - variables.push_context_impl(ContextType::Volatile); + variables.push_context_impl(Context::Volatile); variables .get_or_new("foo", Scope::Volatile) .assign("C", None) .unwrap(); - variables.push_context_impl(ContextType::Volatile); + variables.push_context_impl(Context::Volatile); variables .get_or_new("foo", Scope::Volatile) .assign("D", None) @@ -1034,7 +1028,7 @@ mod tests { .get_or_new("foo", Scope::Global) .assign("A", None) .unwrap(); - variables.push_context_impl(ContextType::Volatile); + variables.push_context_impl(Context::Volatile); let result = variables.unset(Scope::Volatile, "foo").unwrap(); assert_eq!(result, None); @@ -1047,15 +1041,15 @@ mod tests { let mut variables = VariableSet::new(); let mut foo = variables.get_or_new("foo", Scope::Global); foo.assign("A", None).unwrap(); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); let mut foo = variables.get_or_new("foo", Scope::Local); foo.assign("B", None).unwrap(); foo.make_read_only(Location::dummy("dummy")); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); let mut foo = variables.get_or_new("foo", Scope::Local); foo.assign("C", None).unwrap(); foo.make_read_only(read_only_location.clone()); - variables.push_context_impl(ContextType::Regular); + variables.push_context_impl(Context::default()); let mut foo = variables.get_or_new("foo", Scope::Local); foo.assign("D", None).unwrap(); @@ -1086,14 +1080,14 @@ mod tests { let mut var = set.get_or_new("local", Scope::Global); var.assign("hidden value", None).unwrap(); - let mut set = set.push_context(ContextType::Regular); + let mut set = set.push_context(Context::default()); let mut var = set.get_or_new("local", Scope::Local); var.assign("visible value", None).unwrap(); let mut var = set.get_or_new("volatile", Scope::Local); var.assign("hidden value", None).unwrap(); - let mut set = set.push_context(ContextType::Volatile); + let mut set = set.push_context(Context::Volatile); let mut var = set.get_or_new("volatile", Scope::Volatile); var.assign("volatile value", None).unwrap(); @@ -1209,65 +1203,55 @@ mod tests { #[test] fn positional_params_in_base_context() { let mut variables = VariableSet::new(); - assert_eq!(variables.positional_params().value, Some(Array(vec![]))); + assert_eq!(variables.positional_params().values, [] as [String; 0]); - let v = variables.positional_params_mut(); - assert_matches!(&mut v.value, Some(Array(values)) => { - values.push("foo".to_string()); - values.push("bar".to_string()); - }); + let params = variables.positional_params_mut(); + params.values.push("foo".to_string()); + params.values.push("bar".to_string()); - assert_matches!(&variables.positional_params().value, Some(Array(values)) => { - assert_eq!(values.as_ref(), ["foo".to_string(), "bar".to_string()]); - }); + assert_eq!( + variables.positional_params().values, + ["foo".to_string(), "bar".to_string()], + ); } #[test] fn positional_params_in_second_regular_context() { let mut variables = VariableSet::new(); - variables.push_context_impl(ContextType::Regular); - assert_eq!(variables.positional_params().value, Some(Array(vec![]))); + variables.push_context_impl(Context::default()); + assert_eq!(variables.positional_params().values, [] as [String; 0]); - let v = variables.positional_params_mut(); - assert_matches!(&mut v.value, Some(Array(values)) => { - values.push("1".to_string()); - }); + let params = variables.positional_params_mut(); + params.values.push("1".to_string()); - assert_matches!(&variables.positional_params().value, Some(Array(values)) => { - assert_eq!(values.as_ref(), ["1".to_string()]); - }); + assert_eq!(variables.positional_params().values, ["1".to_string()]); } #[test] fn getting_positional_params_in_volatile_context() { let mut variables = VariableSet::new(); - let v = variables.positional_params_mut(); - assert_matches!(&mut v.value, Some(Array(values)) => { - values.push("a".to_string()); - values.push("b".to_string()); - values.push("c".to_string()); - }); - - variables.push_context_impl(ContextType::Volatile); - assert_matches!(&variables.positional_params().value, Some(Array(values)) => { - assert_eq!(values.as_ref(), ["a".to_string(), "b".to_string(), "c".to_string()]); - }); + let params = variables.positional_params_mut(); + params.values.push("a".to_string()); + params.values.push("b".to_string()); + params.values.push("c".to_string()); + + variables.push_context_impl(Context::Volatile); + assert_eq!( + variables.positional_params().values, + ["a".to_string(), "b".to_string(), "c".to_string()], + ); } #[test] fn setting_positional_params_in_volatile_context() { let mut variables = VariableSet::new(); - variables.push_context_impl(ContextType::Volatile); + variables.push_context_impl(Context::Volatile); - let v = variables.positional_params_mut(); - assert_matches!(&mut v.value, Some(Array(values)) => { - values.push("x".to_string()); - }); + let params = variables.positional_params_mut(); + params.values.push("x".to_string()); variables.pop_context_impl(); - assert_matches!(&variables.positional_params().value, Some(Array(values)) => { - assert_eq!(values.as_ref(), ["x".to_string()]); - }); + assert_eq!(variables.positional_params().values, ["x".to_string()]); } } diff --git a/yash-env/src/variable/guard.rs b/yash-env/src/variable/guard.rs index 10bcb27f..b43fccc3 100644 --- a/yash-env/src/variable/guard.rs +++ b/yash-env/src/variable/guard.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::ContextType; +use super::Context; use super::VariableSet; use crate::Env; use std::ops::Deref; @@ -30,12 +30,12 @@ pub struct ContextGuard<'a> { } impl VariableSet { - /// Pushes a new empty context to this variable set. + /// Pushes a new context to this variable set. /// /// This function returns a scope guard that will pop the context when dropped. #[inline] - pub fn push_context(&mut self, context_type: ContextType) -> ContextGuard<'_> { - self.push_context_impl(context_type); + pub fn push_context(&mut self, context: Context) -> ContextGuard<'_> { + self.push_context_impl(context); ContextGuard { stack: self } } @@ -86,8 +86,8 @@ impl Env { /// `self.variables.push_context(context_type)`, but returns an /// `EnvContextGuard` that allows re-borrowing the `Env`. #[inline] - pub fn push_context(&mut self, context_type: ContextType) -> EnvContextGuard<'_> { - self.variables.push_context_impl(context_type); + pub fn push_context(&mut self, context: Context) -> EnvContextGuard<'_> { + self.variables.push_context_impl(context); EnvContextGuard { env: self } } @@ -131,7 +131,7 @@ mod tests { #[test] fn scope_guard() { let mut env = Env::new_virtual(); - let mut guard = env.variables.push_context(ContextType::Regular); + let mut guard = env.variables.push_context(Context::default()); guard .get_or_new("foo", Scope::Global) .assign("", None) @@ -150,7 +150,7 @@ mod tests { #[test] fn env_scope_guard() { let mut env = Env::new_virtual(); - let mut guard = env.push_context(ContextType::Regular); + let mut guard = env.push_context(Context::default()); guard .variables .get_or_new("foo", Scope::Global) diff --git a/yash-semantics/src/command/compound_command/for_loop.rs b/yash-semantics/src/command/compound_command/for_loop.rs index c88d9e01..2483002b 100644 --- a/yash-semantics/src/command/compound_command/for_loop.rs +++ b/yash-semantics/src/command/compound_command/for_loop.rs @@ -33,7 +33,6 @@ use yash_env::semantics::Field; use yash_env::semantics::Result; use yash_env::stack::Frame; use yash_env::variable::Scope; -use yash_env::variable::Value::{Array, Scalar}; use yash_env::Env; use yash_quote::quoted; use yash_syntax::syntax::List; @@ -57,20 +56,15 @@ pub async fn execute( Err(error) => return apply_errexit(error.handle(env).await, env), } } else { - match env.variables.positional_params().value { - None => vec![], - Some(Scalar(ref value)) => vec![Field { + env.variables + .positional_params() + .values + .iter() + .map(|value| Field { value: value.clone(), origin: name.origin.clone(), - }], - Some(Array(ref values)) => values - .iter() - .map(|value| Field { - value: value.clone(), - origin: name.origin.clone(), - }) - .collect(), - } + }) + .collect() }; trace_values(env, &name, &values).await; @@ -126,7 +120,6 @@ mod tests { use yash_env::option::Option::ErrExit; use yash_env::option::State::On; use yash_env::semantics::ExitStatus; - use yash_env::variable::Value; use yash_env::VirtualSystem; use yash_syntax::source::Location; use yash_syntax::syntax::CompoundCommand; @@ -147,7 +140,7 @@ mod tests { let state = Rc::clone(&system.state); let mut env = Env::with_system(Box::new(system)); env.builtins.insert("echo", echo_builtin()); - env.variables.positional_params_mut().value = Some(Value::array(["foo"])); + env.variables.positional_params_mut().values = vec!["foo".to_string()]; let command: CompoundCommand = "for v do echo :$v:; done".parse().unwrap(); let result = command.execute(&mut env).now_or_never().unwrap(); @@ -162,7 +155,8 @@ mod tests { let state = Rc::clone(&system.state); let mut env = Env::with_system(Box::new(system)); env.builtins.insert("echo", echo_builtin()); - env.variables.positional_params_mut().value = Some(Value::array(["1", "2", "three"])); + env.variables.positional_params_mut().values = + vec!["1".to_string(), "2".to_string(), "three".to_string()]; let command: CompoundCommand = "for foo do echo :$foo:; done".parse().unwrap(); let result = command.execute(&mut env).now_or_never().unwrap(); diff --git a/yash-semantics/src/command/simple_command.rs b/yash-semantics/src/command/simple_command.rs index 89553bb4..03ffd4bd 100644 --- a/yash-semantics/src/command/simple_command.rs +++ b/yash-semantics/src/command/simple_command.rs @@ -36,7 +36,7 @@ use yash_env::semantics::ExitStatus; use yash_env::semantics::Field; use yash_env::semantics::Result; #[cfg(doc)] -use yash_env::variable::ContextType; +use yash_env::variable::Context; use yash_env::variable::Scope; use yash_env::Env; use yash_syntax::syntax; @@ -85,8 +85,8 @@ use yash_syntax::syntax::Assign; /// /// If the target is a function, redirections are performed in the same way as a /// regular built-in. Then, assignments are performed in a -/// [volatile](ContextType::Volatile) variable context and exported. Next, a -/// [regular](ContextType::Regular) context is +/// [volatile](Context::Volatile) variable context and exported. Next, a +/// [regular](Context::Regular) context is /// [pushed](yash_env::variable::VariableSet::push_context) to allow local /// variable assignment during the function execution. The remaining fields not /// used in the command search become positional parameters in the new context. diff --git a/yash-semantics/src/command/simple_command/builtin.rs b/yash-semantics/src/command/simple_command/builtin.rs index 400efbe0..1489f38a 100644 --- a/yash-semantics/src/command/simple_command/builtin.rs +++ b/yash-semantics/src/command/simple_command/builtin.rs @@ -31,7 +31,7 @@ use yash_env::semantics::Field; use yash_env::semantics::Result; use yash_env::stack::Builtin as FrameBuiltin; use yash_env::stack::Frame; -use yash_env::variable::ContextType; +use yash_env::variable::Context; use yash_env::Env; use yash_syntax::syntax::Assign; use yash_syntax::syntax::Redir; @@ -78,7 +78,7 @@ pub async fn execute_builtin( } // TODO Reject elective and extension built-ins in POSIX mode Mandatory | Elective | Extension | Substitutive => { - let mut env = env.push_context(ContextType::Volatile); + let mut env = env.push_context(Context::Volatile); perform_assignments(&mut env, assigns, true, xtrace.as_mut()).await?; trace_and_execute(&mut env, fields, builtin.execute, xtrace).await } diff --git a/yash-semantics/src/command/simple_command/external.rs b/yash-semantics/src/command/simple_command/external.rs index 65052b6a..dbe8f1ae 100644 --- a/yash-semantics/src/command/simple_command/external.rs +++ b/yash-semantics/src/command/simple_command/external.rs @@ -35,7 +35,7 @@ use yash_env::semantics::Result; use yash_env::subshell::JobControl; use yash_env::subshell::Subshell; use yash_env::system::Errno; -use yash_env::variable::ContextType; +use yash_env::variable::Context; use yash_env::Env; use yash_env::System; use yash_syntax::source::Location; @@ -59,7 +59,7 @@ pub async fn execute_external_utility( return e.handle(env).await; }; - let mut env = env.push_context(ContextType::Volatile); + let mut env = env.push_context(Context::Volatile); perform_assignments(&mut env, assigns, true, xtrace.as_mut()).await?; trace_fields(xtrace.as_mut(), &fields); diff --git a/yash-semantics/src/command/simple_command/function.rs b/yash-semantics/src/command/simple_command/function.rs index e3c4163a..1e4f914e 100644 --- a/yash-semantics/src/command/simple_command/function.rs +++ b/yash-semantics/src/command/simple_command/function.rs @@ -29,8 +29,8 @@ use yash_env::function::Function; use yash_env::semantics::Divert; use yash_env::semantics::Field; use yash_env::semantics::Result; -use yash_env::variable::ContextType; -use yash_env::variable::Value; +use yash_env::variable::Context; +use yash_env::variable::PositionalParams; use yash_env::Env; use yash_syntax::syntax::Assign; use yash_syntax::syntax::Redir; @@ -48,20 +48,22 @@ pub async fn execute_function( return e.handle(env).await; }; - let mut outer = env.push_context(ContextType::Volatile); + let mut outer = env.push_context(Context::Volatile); perform_assignments(&mut outer, assigns, true, xtrace.as_mut()).await?; trace_fields(xtrace.as_mut(), &fields); print(&mut outer, xtrace).await; - let mut inner = outer.push_context(ContextType::Regular); - - // Apply positional parameters - let params = inner.variables.positional_params_mut(); + // Prepare positional parameters let mut i = fields.into_iter(); - let field = i.next().unwrap(); - params.last_assigned_location = Some(field.origin); - params.value = Some(Value::array(i.map(|f| f.value))); + let last_modified_location = Some(i.next().unwrap().origin); + let values = i.map(|f| f.value).collect(); + let positional_params = PositionalParams { + values, + last_modified_location, + }; + + let mut inner = outer.push_context(Context::Regular { positional_params }); // TODO Update control flow stack let result = function.body.execute(&mut inner).await; diff --git a/yash-semantics/src/expansion/initial/param.rs b/yash-semantics/src/expansion/initial/param.rs index 1db58e0e..e72a27fd 100644 --- a/yash-semantics/src/expansion/initial/param.rs +++ b/yash-semantics/src/expansion/initial/param.rs @@ -156,7 +156,7 @@ pub mod tests { pub fn env_with_positional_params_and_ifs() -> yash_env::Env { let mut env = yash_env::Env::new_virtual(); - env.variables.positional_params_mut().value = Some(Value::array(["a", "c"])); + env.variables.positional_params_mut().values = vec!["a".to_string(), "c".to_string()]; env.variables .get_or_new("IFS", Scope::Global) .assign("&?!", None) diff --git a/yash-semantics/src/expansion/initial/param/resolve.rs b/yash-semantics/src/expansion/initial/param/resolve.rs index 913f0c4f..24d481cc 100644 --- a/yash-semantics/src/expansion/initial/param/resolve.rs +++ b/yash-semantics/src/expansion/initial/param/resolve.rs @@ -18,7 +18,6 @@ use super::name::Name; use yash_env::variable::Expansion; -use yash_env::variable::Value; use yash_env::Env; use yash_syntax::source::Location; @@ -41,13 +40,13 @@ pub fn resolve<'a>(name: Name<'_>, env: &'a Env, location: &Location) -> Expansi } value.into() } - fn positional(env: &Env) -> Expansion { - env.variables.positional_params().value.as_ref().into() + fn positional(env: &Env) -> &[String] { + &env.variables.positional_params().values } match name { Name::Variable(name) => variable(env, name, location), - Name::Special('@' | '*') => positional(env), + Name::Special('@' | '*') => positional(env).into(), Name::Special('#') => positional(env).len().to_string().into(), Name::Special('?') => env.exit_status.to_string().into(), Name::Special('-') => options(env), @@ -56,10 +55,7 @@ pub fn resolve<'a>(name: Name<'_>, env: &'a Env, location: &Location) -> Expansi Name::Special('0') => env.arg0.as_str().into(), Name::Special(_) => Expansion::Unset, Name::Positional(0) => Expansion::Unset, - Name::Positional(index) => match &env.variables.positional_params().value { - Some(Value::Array(params)) => params.get(index - 1).into(), - _ => Expansion::Unset, - }, + Name::Positional(index) => positional(env).get(index - 1).into(), } } @@ -68,7 +64,7 @@ mod tests { use super::*; use yash_env::job::Pid; use yash_env::variable::Scope; - use yash_env::variable::Variable; + use yash_env::variable::Value; use yash_syntax::source::Location; #[test] @@ -128,7 +124,7 @@ mod tests { assert_eq!(result, Expansion::Array([].as_slice().into())); let params = vec!["a".to_string(), "foo bar".to_string(), "9".to_string()]; - env.variables.positional_params_mut().value = Some(Value::Array(params.clone())); + env.variables.positional_params_mut().values = params.clone(); let result = resolve(Name::Special('@'), &env, &loc); assert_eq!(result, Expansion::Array(params.into())); } @@ -141,7 +137,7 @@ mod tests { assert_eq!(result, Expansion::Array([].as_slice().into())); let params = vec!["a".to_string(), "foo bar".to_string(), "9".to_string()]; - env.variables.positional_params_mut().value = Some(Value::Array(params.clone())); + env.variables.positional_params_mut().values = params.clone(); let result = resolve(Name::Special('*'), &env, &loc); assert_eq!(result, Expansion::Array(params.into())); } @@ -154,7 +150,7 @@ mod tests { assert_eq!(result, Expansion::Scalar("0".into())); let params = vec!["a".to_string(), "foo bar".to_string(), "9".to_string()]; - env.variables.positional_params_mut().value = Some(Value::Array(params)); + env.variables.positional_params_mut().values = params; let result = resolve(Name::Special('#'), &env, &loc); assert_eq!(result, Expansion::Scalar("3".into())); } @@ -240,7 +236,7 @@ mod tests { #[test] fn positional_set() { let mut env = Env::new_virtual(); - *env.variables.positional_params_mut() = Variable::new_array(["a", "b"]); + env.variables.positional_params_mut().values = vec!["a".to_string(), "b".to_string()]; let loc = Location::dummy(""); assert_eq!(resolve(Name::Positional(0), &env, &loc), Expansion::Unset); diff --git a/yash-semantics/src/expansion/initial/param/switch.rs b/yash-semantics/src/expansion/initial/param/switch.rs index bd0d7c87..3d6cb520 100644 --- a/yash-semantics/src/expansion/initial/param/switch.rs +++ b/yash-semantics/src/expansion/initial/param/switch.rs @@ -436,7 +436,8 @@ mod tests { #[test] fn assign_array_word() { let mut env = yash_env::Env::new_virtual(); - env.variables.positional_params_mut().value = Some(Value::array(["1", "2 2", "3"])); + env.variables.positional_params_mut().values = + vec!["1".to_string(), "2 2".to_string(), "3".to_string()]; env.variables .get_or_new("IFS", Scope::Global) .assign("~", None) diff --git a/yash/src/lib.rs b/yash/src/lib.rs index 2fd27bfd..f1ccc171 100644 --- a/yash/src/lib.rs +++ b/yash/src/lib.rs @@ -37,7 +37,6 @@ async fn print_version(env: &mut env::Env) -> i32 { async fn parse_and_print(mut env: env::Env) -> i32 { use env::option::Option::{Interactive, Monitor}; use env::option::State::On; - use env::variable::Value::Array; use semantics::trap::run_exit_trap; use semantics::Divert; use semantics::ExitStatus; @@ -70,7 +69,7 @@ async fn parse_and_print(mut env: env::Env) -> i32 { } env.arg0 = run.arg0; - env.variables.positional_params_mut().value = Some(Array(run.positional_params)); + env.variables.positional_params_mut().values = run.positional_params; if env.options.get(Interactive) == On { env.traps.enable_terminator_handlers(&mut env.system).ok();