diff --git a/yash-builtin/src/getopts.rs b/yash-builtin/src/getopts.rs index 46895a8aa..1666c2ae3 100644 --- a/yash-builtin/src/getopts.rs +++ b/yash-builtin/src/getopts.rs @@ -180,9 +180,9 @@ //! //! # Implementation notes //! -//! This implementation uses the `getopts_state` field in the [`Env`] to check -//! if the built-in is invoked with the same arguments and `$OPTIND` as the -//! previous invocation. +//! This implementation uses the `any` field in the [`Env`] to check if the +//! built-in is invoked with the same arguments and `$OPTIND` as the previous +//! invocation. use crate::common::report_error; use crate::common::report_simple_error; @@ -190,7 +190,6 @@ use crate::common::syntax::parse_arguments; use crate::common::syntax::Mode; use either::Either::{Left, Right}; use std::num::NonZeroUsize; -use yash_env::builtin::getopts::Origin; use yash_env::semantics::ExitStatus; use yash_env::semantics::Field; use yash_env::variable::OPTIND; @@ -239,11 +238,11 @@ pub async fn main(env: &mut Env, args: Vec) -> crate::Result { // Get the arguments to parse let (args, arg_origin) = if operands.len() > 2 { let iter = operands[2..].iter().map(|f| f.value.as_str()); - (Left(iter), Origin::DirectArgs) + (Left(iter), verify::Origin::DirectArgs) } else { let params = &env.variables.positional_params().values; let iter = params.iter().map(|v| v.as_str()); - (Right(iter), Origin::PositionalParams) + (Right(iter), verify::Origin::PositionalParams) }; // Get the `$OPTIND` value @@ -256,10 +255,10 @@ pub async fn main(env: &mut Env, args: Vec) -> crate::Result { origin: arg_origin, optind, }; - if let Some(previous) = &env.getopts_state { - match current.verify(previous) { + if let Some(state) = env.any.get_mut::() { + match current.verify(&*state) { Ok(None) => {} - Ok(Some(current)) => env.getopts_state = Some(current.into_state()), + Ok(Some(current)) => *state = current.into_state(), Err(e) => return report_simple_error(env, &e.to_string()).await, } } else { @@ -267,7 +266,7 @@ pub async fn main(env: &mut Env, args: Vec) -> crate::Result { let message = format!("unexpected $OPTIND value `{optind}`"); return report_simple_error(env, &message).await; } - env.getopts_state = Some(current.into_state()); + env.any.insert(Box::new(current.into_state())); } // Parse the next option diff --git a/yash-builtin/src/getopts/report.rs b/yash-builtin/src/getopts/report.rs index ed402e74b..b34ef9366 100644 --- a/yash-builtin/src/getopts/report.rs +++ b/yash-builtin/src/getopts/report.rs @@ -18,6 +18,7 @@ use super::indexes_to_optind; use super::model; +use super::verify::GetoptsState; use thiserror::Error; use yash_env::semantics::Field; use yash_env::variable::AssignError; @@ -205,7 +206,7 @@ impl model::Result { .assign(optind.clone(), location) .map_err(|e| Error::with_name_and_assign_error(OPTIND.to_string(), e))?; - if let Some(state) = &mut env.getopts_state { + if let Some(state) = env.any.get_mut::() { state.optind = optind; } @@ -215,11 +216,10 @@ impl model::Result { #[cfg(test)] mod tests { + use super::super::verify::Origin; use super::*; use assert_matches::assert_matches; use std::num::NonZeroUsize; - use yash_env::builtin::getopts::GetoptsState; - use yash_env::builtin::getopts::Origin; use yash_env::stack::Builtin; use yash_env::stack::Frame; use yash_env::variable::Value; @@ -355,11 +355,11 @@ mod tests { #[test] fn report_updates_optind_of_getopts_state() { let mut env = env_with_dummy_arg0_and_optarg(); - env.getopts_state = Some(GetoptsState { + env.any.insert(Box::new(GetoptsState { args: vec!["-a".to_string(), "-b".to_string()], origin: Origin::DirectArgs, optind: "1".to_string(), - }); + })); let result = model::Result { option: Some(model::OptionOccurrence { option: 'a', @@ -372,7 +372,7 @@ mod tests { _ = result.report(&mut env, false, Field::dummy("opt_var")); - assert_eq!(env.getopts_state.unwrap().optind, "2"); + assert_eq!(env.any.get::().unwrap().optind, "2"); } #[test] diff --git a/yash-builtin/src/getopts/verify.rs b/yash-builtin/src/getopts/verify.rs index 9116e3b0e..ba84fea4a 100644 --- a/yash-builtin/src/getopts/verify.rs +++ b/yash-builtin/src/getopts/verify.rs @@ -22,8 +22,6 @@ //! arguments and `$OPTIND` value in subsequent calls. use thiserror::Error; -use yash_env::builtin::getopts::GetoptsState; -use yash_env::builtin::getopts::Origin; /// Type of error returned by [`GetoptsStateRef::verify`] #[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)] @@ -36,6 +34,33 @@ pub enum Error { DifferentOptind, } +/// Origin of the arguments parsed by the getopts built-in +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Origin { + /// The arguments are passed directly to the built-in. + DirectArgs, + /// No arguments are passed to the built-in, so the built-in parses the + /// positional parameters. + PositionalParams, +} + +/// State shared between getopts built-in invocations +/// +/// The getopts built-in is designed to be called multiple times with the same +/// arguments, assuming that the `$OPTIND` variable isn't altered externally. +/// The built-in stores the arguments and the `$OPTIND` value in this data, and +/// verifies if it receives the same arguments and `$OPTIND` value in subsequent +/// calls. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct GetoptsState { + /// Expected arguments to parse + pub args: Vec, + /// Expected origin of the arguments + pub origin: Origin, + /// Expected value of `$OPTIND` + pub optind: String, +} + /// Borrowed version of [`GetoptsState`] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct GetoptsStateRef<'a, I> { diff --git a/yash-env/CHANGELOG.md b/yash-env/CHANGELOG.md index ee79dd05d..27fa127fe 100644 --- a/yash-env/CHANGELOG.md +++ b/yash-env/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - The `Env` struct now implements `yash_syntax::decl_util::Glossary`. +- The `Env` struct now contains the `any` field of type `DataSet`. + - The `DataSet` struct is defined in the newly added `any` module. + It can be used to store arbitrary data. - The `builtin::Builtin` struct now has the `is_declaration_utility` field. - The `builtin::Builtin` struct now can be constructed with the associated function `new`. @@ -29,7 +32,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - The implementation of `From` for converting `errno::Errno` to and from - `nix::errno::Errno`. + `nix::errno::Errno` +- The `getopts_state` field from the `Env` struct +- The `builtin::getopts` module and its contents + (the `GetoptsState` struct and the `Origin` enum) - Internal dependencies: - nix 0.29.0 diff --git a/yash-env/src/any.rs b/yash-env/src/any.rs new file mode 100644 index 000000000..de3e92d62 --- /dev/null +++ b/yash-env/src/any.rs @@ -0,0 +1,197 @@ +// This file is part of yash, an extended POSIX shell. +// Copyright (C) 2025 WATANABE Yuki +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Types for storing arbitrary data in the environment +//! +//! This module provides [`DataSet`] for storing arbitrary data in [`Env`]. +//! It internally uses [`Any`] to store data of arbitrary types. +//! +//! [`Env`]: crate::Env + +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::Debug; + +/// Entry in the [`DataSet`] +#[derive(Debug)] +struct Entry { + data: Box, + clone: fn(&dyn Any) -> Box, +} +// TODO: When dyn upcasting coercion[1] is stabilized, we will be able to define +// `trait Data: Any + Clone {}` and insert `Box` into `DataSet` +// without the need to store the `clone` function. +// [1]: https://github.com/rust-lang/rust/issues/65991 + +impl Clone for Entry { + fn clone(&self) -> Self { + Self { + data: (self.clone)(&*self.data), + clone: self.clone, + } + } +} + +/// Clones data of the specified type +/// +/// This function is used to clone data stored in the [`DataSet`]. +#[must_use] +fn clone(data: &dyn Any) -> Box { + Box::new(data.downcast_ref::().unwrap().clone()) +} + +/// Collection of arbitrary data +/// +/// This struct is used to store arbitrary data in the environment. +/// Data stored in this struct are identified by their [`TypeId`], so you cannot +/// store multiple data instances of the same type. +#[derive(Clone, Debug, Default)] +pub struct DataSet { + inner: HashMap, +} + +impl DataSet { + /// Creates a new empty `DataSet`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Inserts a new data into the `DataSet`. + /// + /// If data of the same type is already stored in `self`, it is replaced. + pub fn insert(&mut self, data: Box) -> Option> { + let clone = clone::; + let entry = Entry { data, clone }; + self.inner + .insert(TypeId::of::(), entry) + .map(|old| old.data.downcast().unwrap()) + } + + /// Obtains a reference to the data of the specified type. + #[must_use] + pub fn get(&self) -> Option<&T> { + self.inner + .get(&TypeId::of::()) + .map(|entry| entry.data.downcast_ref().unwrap()) + } + + /// Obtains a mutable reference to the data of the specified type. + #[must_use] + pub fn get_mut(&mut self) -> Option<&mut T> { + self.inner + .get_mut(&TypeId::of::()) + .map(|entry| entry.data.downcast_mut().unwrap()) + } + + /// Obtains a reference to the data of the specified type, or inserts a new + /// data if it does not exist. + /// + /// If the data does not exist, the data is created by calling the provided + /// closure and inserted into the `DataSet`, and a reference to the data is + /// returned. If the data already exists, a reference to the existing data + /// is returned and the closure is not called. + pub fn get_or_insert_with(&mut self, f: F) -> &mut T + where + T: Clone + 'static, + F: FnOnce() -> Box, + { + self.inner + .entry(TypeId::of::()) + .or_insert_with(|| { + let data = f(); + let clone = clone::; + Entry { data, clone } + }) + .data + .downcast_mut() + .unwrap() + } + + /// Removes the data of the specified type from the `DataSet`. + /// + /// Returns the data if it exists. + pub fn remove(&mut self) -> Option> { + self.inner + .remove(&TypeId::of::()) + .map(|entry| entry.data.downcast().unwrap()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn insert_and_get() { + let mut data_set = DataSet::new(); + let old = data_set.insert(Box::new(42i32)); + assert_eq!(old, None); + assert_eq!(data_set.get::(), Some(&42)); + } + + #[test] + fn insert_again() { + let mut data_set = DataSet::new(); + data_set.insert(Box::new(42i32)); + let old = data_set.insert(Box::new(43i32)); + assert_eq!(old, Some(Box::new(42))); + assert_eq!(data_set.get::(), Some(&43)); + } + + #[test] + fn get_mut() { + let mut data_set = DataSet::new(); + data_set.insert(Box::new(42i32)); + let data = data_set.get_mut::().unwrap(); + assert_eq!(data, &42); + *data = 43; + assert_eq!(data_set.get::(), Some(&43)); + } + + #[test] + fn get_or_insert_with() { + let mut data_set = DataSet::new(); + let data = data_set.get_or_insert_with(|| Box::new(0i8)); + assert_eq!(data, &0); + *data = 1; + let data = data_set.get_or_insert_with::(|| unreachable!()); + assert_eq!(data, &1); + } + + #[test] + fn remove_existing() { + let mut data_set = DataSet::new(); + data_set.insert(Box::new(42i32)); + let data = data_set.remove::().unwrap(); + assert_eq!(*data, 42); + } + + #[test] + fn remove_nonexisting() { + let mut data_set = DataSet::new(); + let data = data_set.remove::(); + assert_eq!(data, None); + } + + #[test] + fn clone() { + let mut data_set = DataSet::new(); + data_set.insert::(Box::new(42)); + let clone = data_set.clone(); + assert_eq!(clone.get::(), Some(&42)); + } +} diff --git a/yash-env/src/builtin.rs b/yash-env/src/builtin.rs index f4a84797b..d43a3f14a 100644 --- a/yash-env/src/builtin.rs +++ b/yash-env/src/builtin.rs @@ -31,8 +31,6 @@ use std::fmt::Debug; use std::future::Future; use std::pin::Pin; -pub mod getopts; - /// Types of built-in utilities #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Type { diff --git a/yash-env/src/builtin/getopts.rs b/yash-env/src/builtin/getopts.rs deleted file mode 100644 index 3292fc080..000000000 --- a/yash-env/src/builtin/getopts.rs +++ /dev/null @@ -1,44 +0,0 @@ -// This file is part of yash, an extended POSIX shell. -// Copyright (C) 2023 WATANABE Yuki -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Definition of [`GetoptsState`] - -/// Origin of the arguments parsed by the `getopts` built-in -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Origin { - /// The arguments are passed directly to the built-in. - DirectArgs, - /// No arguments are passed to the built-in, so the built-in parses the - /// positional parameters. - PositionalParams, -} - -/// State of the `getopts` built-in -/// -/// This data is specifically designed for use by the `getopts` built-in. The -/// built-in is designed to be called multiple times with the same arguments, -/// assuming that the `$OPTIND` variable isn't altered externally. The built-in -/// stores the arguments and the `$OPTIND` value in this data, and verifies if -/// it receives the same arguments and `$OPTIND` value in subsequent calls. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct GetoptsState { - /// Expected arguments to parse - pub args: Vec, - /// Expected origin of the arguments - pub origin: Origin, - /// Expected value of `$OPTIND` - pub optind: String, -} diff --git a/yash-env/src/lib.rs b/yash-env/src/lib.rs index 18f58d980..8c43a50b7 100644 --- a/yash-env/src/lib.rs +++ b/yash-env/src/lib.rs @@ -31,7 +31,7 @@ //! the underlying system. [`VirtualSystem`] is a dummy for simulating the //! system's behavior without affecting the actual system. -use self::builtin::getopts::GetoptsState; +use self::any::DataSet; use self::builtin::Builtin; use self::function::FunctionSet; use self::io::Fd; @@ -102,9 +102,6 @@ pub struct Env { /// Functions defined in the environment pub functions: FunctionSet, - /// State of the previous invocation of the `getopts` built-in - pub getopts_state: Option, - /// Jobs managed in the environment pub jobs: JobList, @@ -134,6 +131,9 @@ pub struct Env { /// Variables and positional parameters defined in the environment pub variables: VariableSet, + /// Abstract container that can store any type-erased data + pub any: DataSet, + /// Interface to the system-managed parts of the environment pub system: SharedSystem, } @@ -152,7 +152,6 @@ impl Env { builtins: Default::default(), exit_status: Default::default(), functions: Default::default(), - getopts_state: Default::default(), jobs: Default::default(), main_pgid: system.getpgrp(), main_pid: system.getpid(), @@ -161,6 +160,7 @@ impl Env { traps: Default::default(), tty: Default::default(), variables: Default::default(), + any: Default::default(), system: SharedSystem::new(system), } } @@ -184,7 +184,6 @@ impl Env { builtins: self.builtins.clone(), exit_status: self.exit_status, functions: self.functions.clone(), - getopts_state: self.getopts_state.clone(), jobs: self.jobs.clone(), main_pgid: self.main_pgid, main_pid: self.main_pid, @@ -193,6 +192,7 @@ impl Env { traps: self.traps.clone(), tty: self.tty, variables: self.variables.clone(), + any: self.any.clone(), system: SharedSystem::new(system), } } @@ -464,6 +464,7 @@ impl Env { } mod alias; +pub mod any; pub mod builtin; mod decl_util; pub mod function;