1use crate::expansion::AssignReadOnlyError;
20use crate::expansion::expand_value;
21use crate::xtrace::XTrace;
22use std::fmt::Write;
23use yash_env::Env;
24use yash_env::semantics::ExitStatus;
25
26#[doc(no_inline)]
27pub use crate::expansion::{Error, ErrorCause, Result};
28#[doc(no_inline)]
29pub use yash_env::variable::Scope;
30#[doc(no_inline)]
31pub use yash_syntax::syntax::Assign;
32
33pub async fn perform_assignment(
43 env: &mut Env,
44 assign: &Assign,
45 scope: Scope,
46 export: bool,
47 xtrace: Option<&mut XTrace>,
48) -> Result<Option<ExitStatus>> {
49 let name = assign.name.clone();
50 let (value, exit_status) = expand_value(env, &assign.value).await?;
51
52 if let Some(xtrace) = xtrace {
53 write!(
54 xtrace.assigns(),
55 "{}={} ",
56 yash_quote::quoted(&name),
57 value.quote()
58 )
59 .unwrap();
60 }
61
62 let mut variable = env.get_or_create_variable(name, scope);
63 variable
64 .assign(value, assign.location.clone())
65 .map_err(|e| Error {
66 cause: ErrorCause::AssignReadOnly(AssignReadOnlyError {
67 name: assign.name.clone(),
68 new_value: e.new_value,
69 read_only_location: e.read_only_location,
70 vacancy: None,
71 }),
72 location: e.assigned_location.unwrap(),
73 })?;
74 if export {
75 variable.export(true);
76 }
77 Ok(exit_status)
78}
79
80pub async fn perform_assignments(
89 env: &mut Env,
90 assigns: &[Assign],
91 scope: Scope,
92 export: bool,
93 mut xtrace: Option<&mut XTrace>,
94) -> Result<Option<ExitStatus>> {
95 let mut exit_status = None;
96 for assign in assigns {
97 let new_exit_status =
98 perform_assignment(env, assign, scope, export, xtrace.as_deref_mut()).await?;
99 exit_status = new_exit_status.or(exit_status);
100 }
101 Ok(exit_status)
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::tests::return_builtin;
108 use assert_matches::assert_matches;
109 use futures_util::FutureExt;
110 use yash_env::variable::Value;
111 use yash_env::variable::Variable;
112 use yash_env_test_helper::in_virtual_system;
113 use yash_syntax::source::Location;
114
115 #[test]
116 fn perform_assignment_new_value() {
117 let mut env = Env::new_virtual();
118 let a: Assign = "foo=bar".parse().unwrap();
119 let exit_status = perform_assignment(&mut env, &a, Scope::Global, false, None)
120 .now_or_never()
121 .unwrap()
122 .unwrap();
123 assert_eq!(exit_status, None);
124 assert_eq!(
125 env.variables.get("foo").unwrap(),
126 &Variable::new("bar").set_assigned_location(a.location)
127 );
128 }
129
130 #[test]
131 fn perform_assignment_overwriting() {
132 let mut env = Env::new_virtual();
133 let a: Assign = "foo=bar".parse().unwrap();
134 perform_assignment(&mut env, &a, Scope::Global, false, None)
135 .now_or_never()
136 .unwrap()
137 .unwrap();
138
139 let a: Assign = "foo=baz".parse().unwrap();
140 let exit_status = perform_assignment(&mut env, &a, Scope::Global, true, None)
141 .now_or_never()
142 .unwrap()
143 .unwrap();
144 assert_eq!(exit_status, None);
145 assert_eq!(
146 env.variables.get("foo").unwrap(),
147 &Variable::new("baz")
148 .export()
149 .set_assigned_location(a.location)
150 );
151
152 let a: Assign = "foo=foo".parse().unwrap();
153 let exit_status = perform_assignment(&mut env, &a, Scope::Global, false, None)
154 .now_or_never()
155 .unwrap()
156 .unwrap();
157 assert_eq!(exit_status, None);
158 assert_eq!(
160 env.variables.get("foo").unwrap(),
161 &Variable::new("foo")
162 .export()
163 .set_assigned_location(a.location)
164 );
165 }
166
167 #[test]
168 fn perform_assignment_read_only() {
169 let mut env = Env::new_virtual();
170 let location = Location::dummy("read-only location");
171 let mut var = env.variables.get_or_new("v", Scope::Global);
172 var.assign("read-only", None).unwrap();
173 var.make_read_only(location.clone());
174 let a: Assign = "v=new".parse().unwrap();
175 let e = perform_assignment(&mut env, &a, Scope::Global, false, None)
176 .now_or_never()
177 .unwrap()
178 .unwrap_err();
179 assert_matches!(e.cause, ErrorCause::AssignReadOnly(error) => {
180 assert_eq!(error.name, "v");
181 assert_eq!(error.new_value, Value::scalar("new"));
182 assert_eq!(error.read_only_location, location);
183 assert_eq!(error.vacancy, None);
184 });
185 assert_eq!(e.location, Location::dummy("v=new"));
186 }
187
188 #[test]
189 fn perform_assignment_with_xtrace() {
190 let mut xtrace = XTrace::new();
191 let mut env = Env::new_virtual();
192
193 let a: Assign = "foo=bar${unset-&}".parse().unwrap();
194 let _ = perform_assignment(&mut env, &a, Scope::Global, false, Some(&mut xtrace))
195 .now_or_never()
196 .unwrap()
197 .unwrap();
198
199 let a: Assign = "one=1".parse().unwrap();
200 let _ = perform_assignment(&mut env, &a, Scope::Global, false, Some(&mut xtrace))
201 .now_or_never()
202 .unwrap()
203 .unwrap();
204
205 let result = xtrace.finish(&mut env).now_or_never().unwrap();
206 assert_eq!(result, "foo='bar&' one=1\n");
207 }
208
209 #[test]
210 fn perform_assignments_exit_status() {
211 in_virtual_system(|mut env, _state| async move {
212 env.builtins.insert("return", return_builtin());
213 let assigns = [
214 "a=A$(return -n 1)".parse().unwrap(),
215 "b=($(return -n 2))".parse().unwrap(),
216 ];
217 let exit_status = perform_assignments(&mut env, &assigns, Scope::Global, false, None)
218 .await
219 .unwrap();
220 assert_eq!(exit_status, Some(ExitStatus(2)));
221 assert_eq!(
222 env.variables.get("a").unwrap(),
223 &Variable::new("A").set_assigned_location(assigns[0].location.clone())
224 );
225 assert_eq!(
226 env.variables.get("b").unwrap(),
227 &Variable::new_empty_array().set_assigned_location(assigns[1].location.clone())
228 );
229 })
230 }
231}