1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// This file is part of yash, an extended POSIX shell.
// Copyright (C) 2021 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 <https://www.gnu.org/licenses/>.

//! Module that defines the main `Variable` type.

use super::Expansion;
use super::Quirk;
use super::Value;
use std::ops::Deref;
use thiserror::Error;
use yash_syntax::source::Location;

/// Definition of a variable.
///
/// The methods of `Variable` are designed to be used in a method chain,
/// but you usually don't create a `Variable` instance directly.
/// Instead, use [`VariableSet::get_or_new`](super::VariableSet::get_or_new) or
/// [`Env::get_or_create_variable`](crate::Env::get_or_create_variable) to
/// create a variable in a variable set and obtain a mutable reference to it
/// ([`VariableRefMut`]), which allows you to modify the variable.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Variable {
    /// Value of the variable.
    ///
    /// The value is `None` if the variable has been declared without
    /// assignment.
    pub value: Option<Value>,

    /// Optional location where this variable was assigned.
    ///
    /// If the current variable value originates from an assignment performed in
    /// the shell session, `last_assigned_location` is the location of the
    /// assignment.  Otherwise, `last_assigned_location` is `None`.
    pub last_assigned_location: Option<Location>,

    /// Whether this variable is exported or not.
    ///
    /// An exported variable is also referred to as an _environment variable_.
    pub is_exported: bool,

    /// Optional location where this variable was made read-only.
    ///
    /// If this variable is not read-only, `read_only_location` is `None`.
    /// Otherwise, `read_only_location` is the location of the simple command
    /// that executed the `readonly` built-in that made this variable read-only.
    pub read_only_location: Option<Location>,

    /// Special characteristics of the variable
    ///
    /// See [`Quirk`] and [`expand`](Self::expand) for details.
    pub quirk: Option<Quirk>,
}

impl Variable {
    /// Creates a new scalar variable from a string.
    ///
    /// The returned variable's `last_assigned_location` and
    /// `read_only_location` are `None` and `is_exported` is false.
    #[must_use]
    pub fn new<S: Into<String>>(value: S) -> Self {
        Variable {
            value: Some(Value::scalar(value)),
            ..Default::default()
        }
    }

    /// Creates a new array variable from a string.
    ///
    /// The returned variable's `last_assigned_location` and
    /// `read_only_location` are `None` and `is_exported` is false.
    #[must_use]
    pub fn new_array<I, S>(values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        Variable {
            value: Some(Value::array(values)),
            ..Default::default()
        }
    }

    /// Creates a new empty array variable.
    ///
    /// The returned variable's `last_assigned_location` and
    /// `read_only_location` are `None` and `is_exported` is false.
    #[must_use]
    pub fn new_empty_array() -> Self {
        Self::new_array([] as [&str; 0])
    }

    /// Sets the last assigned location.
    ///
    /// This is a convenience function for doing
    /// `self.last_assigned_location = Some(location)` in a method chain.
    #[inline]
    #[must_use]
    pub fn set_assigned_location(mut self, location: Location) -> Self {
        self.last_assigned_location = Some(location);
        self
    }

    /// Sets the `is_exported` flag.
    ///
    /// This is a convenience function for doing `self.is_exported = true` in a
    /// method chain.
    #[inline]
    #[must_use]
    pub fn export(mut self) -> Self {
        self.is_exported = true;
        self
    }

    /// Makes the variable read-only.
    ///
    /// This is a convenience function for doing
    /// `self.read_only_location = Some(location)` in a method chain.
    #[inline]
    #[must_use]
    pub fn make_read_only(mut self, location: Location) -> Self {
        self.read_only_location = Some(location);
        self
    }

    /// Whether this variable is read-only or not.
    #[must_use]
    pub const fn is_read_only(&self) -> bool {
        self.read_only_location.is_some()
    }

    /// Returns the value of this variable, applying any quirk.
    ///
    /// If this variable has no [`Quirk`], this function just returns
    /// `self.value` converted to [`Expansion`]. Otherwise, the effect of the
    /// quirk is applied to the value and the result is returned.
    ///
    /// This function requires the location of the parameter expanding this
    /// variable, so that `Quirk::LineNumber` can yield the line number of the
    /// location.
    #[inline]
    pub fn expand(&self, location: &Location) -> Expansion {
        super::quirk::expand(self, location)
    }
}

/// Managed mutable reference to a variable.
///
/// This type allows you to mutate a variable in a variable set while
/// maintaining the invariants of the variable set.
/// To obtain an instance of `VariableRefMut`, use
/// [`VariableSet::get_or_new`](super::VariableSet::get_or_new).
#[derive(Debug, Eq, PartialEq)]
pub struct VariableRefMut<'a>(&'a mut Variable);

/// Error that occurs when assigning a value to a read-only variable.
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("cannot assign to read-only variable")]
pub struct AssignError {
    /// Value that was being assigned.
    pub new_value: Value,
    /// Location of the failed assignment.
    pub assigned_location: Option<Location>,
    /// Location where the variable was made read-only.
    pub read_only_location: Location,
}

impl<'a> From<&'a mut Variable> for VariableRefMut<'a> {
    fn from(variable: &'a mut Variable) -> Self {
        VariableRefMut(variable)
    }
}

impl Deref for VariableRefMut<'_> {
    type Target = Variable;

    fn deref(&self) -> &Variable {
        self.0
    }
}

impl<'a> VariableRefMut<'a> {
    /// Assigns a value to this variable.
    ///
    /// The `value` and `location` operands are set to the `value` and
    /// `last_assigned_location` fields of this variable, respectively.
    /// If successful, this function returns the previous value and location.
    ///
    /// This function fails if this variable is read-only. In that case, the
    /// error contains the given operands as well as the location where this
    /// variable was made read-only.
    #[inline]
    pub fn assign<V: Into<Value>, L: Into<Option<Location>>>(
        &mut self,
        value: V,
        location: L,
    ) -> Result<(Option<Value>, Option<Location>), AssignError> {
        self.assign_impl(value.into(), location.into())
    }

    fn assign_impl(
        &mut self,
        value: Value,
        location: Option<Location>,
    ) -> Result<(Option<Value>, Option<Location>), AssignError> {
        if let Some(read_only_location) = self.0.read_only_location.clone() {
            return Err(AssignError {
                new_value: value,
                assigned_location: location,
                read_only_location,
            });
        }

        let old_value = std::mem::replace(&mut self.0.value, Some(value));
        let old_location = std::mem::replace(&mut self.0.last_assigned_location, location);
        Ok((old_value, old_location))
        // TODO Apply quirk
    }

    /// Sets whether this variable is exported or not.
    pub fn export(&mut self, is_exported: bool) {
        self.0.is_exported = is_exported;
    }

    /// Makes this variable read-only.
    ///
    /// The `location` operand is set to the `read_only_location` field of this
    /// variable unless this variable is already read-only.
    pub fn make_read_only(&mut self, location: Location) {
        self.0.read_only_location.get_or_insert(location);
    }

    /// Sets the quirk of this variable.
    ///
    /// This function overwrites any existing quirk of this variable.
    pub fn set_quirk(&mut self, quirk: Option<Quirk>) {
        self.0.quirk = quirk;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn assigning_values() {
        let mut var = Variable::default();
        let mut var = VariableRefMut::from(&mut var);
        let result = var.assign(Value::scalar("foo value"), None);
        assert_eq!(result, Ok((None, None)));
        assert_eq!(*var, Variable::new("foo value"));

        let location = Location::dummy("bar location");
        let result = var.assign(Value::scalar("bar value"), location.clone());
        assert_eq!(result, Ok((Some(Value::scalar("foo value")), None)));
        assert_eq!(var.value, Some(Value::scalar("bar value")));
        assert_eq!(var.last_assigned_location.as_ref(), Some(&location));

        assert_eq!(
            var.assign(Value::array(["a", "b", "c"]), None),
            Ok((Some(Value::scalar("bar value")), Some(location))),
        );
        assert_eq!(var.value, Some(Value::array(["a", "b", "c"])));
    }

    #[test]
    fn exporting() {
        let mut var = Variable::default();
        let mut var = VariableRefMut::from(&mut var);
        assert!(!var.is_exported);
        var.export(true);
        assert!(var.is_exported);
        var.export(false);
        assert!(!var.is_exported);
    }

    #[test]
    fn making_variables_read_only() {
        let mut var = Variable::default();
        let mut var = VariableRefMut::from(&mut var);
        let location = Location::dummy("read-only location");
        var.make_read_only(location.clone());
        assert_eq!(var.read_only_location.as_ref(), Some(&location));

        var.make_read_only(Location::dummy("ignored location"));
        assert_eq!(var.read_only_location.as_ref(), Some(&location));
    }

    #[test]
    fn assigning_to_readonly_variable() {
        let mut var = Variable::default();
        let mut var = VariableRefMut::from(&mut var);
        let assigned_location = Some(Location::dummy("assigned location"));
        let read_only_location = Location::dummy("read-only location");
        var.make_read_only(read_only_location.clone());
        assert_eq!(
            var.assign(Value::scalar("foo value"), assigned_location.clone()),
            Err(AssignError {
                new_value: Value::scalar("foo value"),
                assigned_location,
                read_only_location,
            })
        )
    }
}