Skip to main content

yash_builtin/
typeset.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Typeset built-in
18//!
19//! This module implements the [`typeset` built-in], which defines variables or
20//! functions with specified attributes.
21//!
22//! [`typeset` built-in]: https://magicant.github.io/yash-rs/builtins/typeset.html
23//!
24//! # Implementation notes
25//!
26//! The implementation of this built-in is also used by the
27//! [`export`](crate::export) and [`readonly`](crate::readonly) built-ins.
28//! Functions that are common to these built-ins and the typeset built-in are
29//! parameterized to support the different behaviors of the built-ins. By
30//! customizing the contents of [`Command`] and the [`PrintContext`] passed to
31//! [`Command::execute`], you can even implement a new built-in that behaves
32//! differently from all of them.
33
34use self::syntax::OptionSpec;
35use crate::common::output;
36use crate::common::report::{merge_reports, report_error, report_failure};
37use thiserror::Error;
38use yash_env::Env;
39use yash_env::function::Function;
40use yash_env::option::State;
41use yash_env::semantics::Field;
42use yash_env::source::Location;
43use yash_env::source::pretty::{Report, ReportType, Snippet, Span, SpanRole, add_span};
44use yash_env::system::{Fcntl, Isatty, Write};
45use yash_env::variable::{Value, Variable};
46
47mod print_functions;
48mod print_variables;
49mod set_functions;
50mod set_variables;
51pub mod syntax;
52
53/// Attribute that can be set on a variable
54#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
55#[non_exhaustive]
56pub enum VariableAttr {
57    /// The variable is read-only.
58    ReadOnly,
59    /// The variable is exported to the environment.
60    Export,
61}
62
63impl VariableAttr {
64    /// Tests if the attribute is set on a variable
65    #[must_use]
66    pub fn test(&self, var: &Variable) -> State {
67        let is_on = match self {
68            VariableAttr::ReadOnly => var.is_read_only(),
69            VariableAttr::Export => var.is_exported,
70        };
71        State::from(is_on)
72    }
73}
74
75/// Scope in which a variable is defined or selected
76#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
77#[non_exhaustive]
78pub enum Scope {
79    /// Operates on global scope.
80    ///
81    /// When defining variables: If an existing variable is visible in the
82    /// current scope, the variable is updated. Otherwise, a new variable is
83    /// created in the base context.
84    ///
85    /// When printing variables: All visible variables are printed.
86    Global,
87
88    /// Operates on local scope.
89    ///
90    /// When defining variables: The variable is defined in the local context of
91    /// the current function.
92    ///
93    /// When printing variables: Only variables defined in the local context of
94    /// the current function are printed.
95    Local,
96}
97
98/// Set of information to define variables
99#[derive(Clone, Debug, Eq, PartialEq)]
100pub struct SetVariables {
101    /// Names and optional values of the variables to be defined
102    pub variables: Vec<Field>,
103    /// Attributes to be set on the variables
104    pub attrs: Vec<(VariableAttr, State)>,
105    /// Scope in which the variables are defined
106    pub scope: Scope,
107}
108
109/// Set of information to print variables
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct PrintVariables {
112    /// Names of the variables to be printed
113    ///
114    /// If empty, all variables are printed.
115    pub variables: Vec<Field>,
116    /// Attributes to select the variables to be printed
117    pub attrs: Vec<(VariableAttr, State)>,
118    /// Scope in which the variables are printed
119    pub scope: Scope,
120}
121
122/// Attribute that can be set on a function
123#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
124#[non_exhaustive]
125pub enum FunctionAttr {
126    /// The function is read-only.
127    ReadOnly,
128}
129
130impl FunctionAttr {
131    /// Tests if the attribute is set on a function.
132    #[must_use]
133    fn test<S>(&self, function: &Function<S>) -> State {
134        let is_on = match self {
135            Self::ReadOnly => function.is_read_only(),
136        };
137        State::from(is_on)
138    }
139}
140
141/// Set of information to modify functions
142#[derive(Clone, Debug, Eq, PartialEq)]
143pub struct SetFunctions {
144    /// Names of the functions to be modified
145    pub functions: Vec<Field>,
146    /// Attributes to be set on the functions
147    pub attrs: Vec<(FunctionAttr, State)>,
148}
149
150/// Set of information to print functions
151#[derive(Clone, Debug, Eq, PartialEq)]
152pub struct PrintFunctions {
153    /// Names of the functions to be printed
154    ///
155    /// If empty, all functions are printed.
156    pub functions: Vec<Field>,
157    /// Attributes to select the functions to be printed
158    pub attrs: Vec<(FunctionAttr, State)>,
159}
160
161/// Set of information used when printing variables or functions
162///
163/// [`PrintVariables::execute`] and [`PrintFunctions::execute`] print a list of
164/// commands that invoke a built-in to recreate variables and functions,
165/// respectively. This context is used to control the details of the commands.
166#[derive(Clone, Debug, Eq, PartialEq)]
167pub struct PrintContext<'a> {
168    /// Name of the built-in printed as part of the commands to recreate the
169    /// variables or functions
170    pub builtin_name: &'a str,
171
172    /// Whether the command that invokes the built-in should always be printed
173    ///
174    /// The typeset built-in does not itself modify the attributes of variables
175    /// or functions when invoked simply with a name operand. If a separate
176    /// array assignment or function definition command is sufficient to
177    /// reproduce an array variable or function, the command that invokes the
178    /// typeset built-in may be omitted. This field indicates whether the
179    /// command should always be printed regardless of the attributes of the
180    /// variables or functions.
181    ///
182    /// This field should be false for the typeset built-in to allow omitting,
183    /// but it should be true for the export and readonly built-ins to force
184    /// printing as they always modify the attributes.
185    pub builtin_is_significant: bool,
186
187    /// Options that may be printed for the built-in
188    ///
189    /// When printing a command that invokes the built-in, the command may
190    /// include options that appear in this slice to re-set the attributes of
191    /// the variables or functions.
192    pub options_allowed: &'a [OptionSpec<'a>],
193}
194
195/// Printing context for the typeset built-in
196pub const PRINT_CONTEXT: PrintContext<'static> = PrintContext {
197    builtin_name: "typeset",
198    builtin_is_significant: false,
199    options_allowed: self::syntax::ALL_OPTIONS,
200};
201
202/// Set of information that defines the behavior of a single invocation of the
203/// typeset built-in
204///
205/// The [`syntax::interpret`] function returns a value of this type after
206/// parsing the arguments. Call the [`execute`](Self::execute) method to perform
207/// the actual operation.
208#[derive(Clone, Debug, Eq, PartialEq)]
209pub enum Command {
210    SetVariables(SetVariables),
211    PrintVariables(PrintVariables),
212    SetFunctions(SetFunctions),
213    PrintFunctions(PrintFunctions),
214}
215
216impl From<SetVariables> for Command {
217    fn from(v: SetVariables) -> Self {
218        Self::SetVariables(v)
219    }
220}
221
222impl From<PrintVariables> for Command {
223    fn from(v: PrintVariables) -> Self {
224        Self::PrintVariables(v)
225    }
226}
227
228impl From<SetFunctions> for Command {
229    fn from(v: SetFunctions) -> Self {
230        Self::SetFunctions(v)
231    }
232}
233
234impl From<PrintFunctions> for Command {
235    fn from(v: PrintFunctions) -> Self {
236        Self::PrintFunctions(v)
237    }
238}
239
240impl Command {
241    /// Executes the command (except for actual printing).
242    ///
243    /// This method updates the shell environment according to the command.
244    /// If there are no errors, the method returns a string that should be
245    /// printed to the standard output.
246    /// Otherwise, the method returns a non-empty vector of errors.
247    pub fn execute<S>(
248        self,
249        env: &mut Env<S>,
250        print_context: &PrintContext,
251    ) -> Result<String, Vec<ExecuteError>> {
252        match self {
253            Self::SetVariables(command) => command.execute(env),
254            Self::PrintVariables(command) => command.execute(&env.variables, print_context),
255            Self::SetFunctions(command) => command.execute(&mut env.functions),
256            Self::PrintFunctions(command) => command.execute(&env.functions, print_context),
257        }
258    }
259}
260
261/// Error returned on assigning to a read-only variable
262#[derive(Clone, Debug, Eq, Error, PartialEq)]
263#[error("cannot assign to read-only variable {name:?}")]
264pub struct AssignReadOnlyError {
265    /// Name of the read-only variable
266    pub name: String,
267    /// Value that was being assigned
268    pub new_value: Value,
269    /// Location where the variable was tried to be assigned
270    pub assigned_location: Location,
271    /// Location where the variable was made read-only
272    pub read_only_location: Location,
273}
274
275impl From<AssignReadOnlyError> for yash_env::variable::AssignError {
276    fn from(e: AssignReadOnlyError) -> Self {
277        Self {
278            new_value: e.new_value,
279            assigned_location: Some(e.assigned_location),
280            read_only_location: e.read_only_location,
281        }
282    }
283}
284
285/// This conversion is available only when the optional `yash-semantics`
286/// feature is enabled.
287#[cfg(feature = "yash-semantics")]
288impl From<AssignReadOnlyError> for yash_semantics::expansion::AssignReadOnlyError {
289    fn from(e: AssignReadOnlyError) -> Self {
290        Self {
291            name: e.name,
292            new_value: e.new_value,
293            read_only_location: e.read_only_location,
294            vacancy: None,
295        }
296    }
297}
298
299impl AssignReadOnlyError {
300    /// Converts the error to a report.
301    #[must_use]
302    pub fn to_report(&self) -> Report<'_> {
303        let mut report = Report::new();
304        report.r#type = ReportType::Error;
305        report.title = "error assigning to variable".into();
306        report.snippets =
307            Snippet::with_primary_span(&self.assigned_location, self.to_string().into());
308        add_span(
309            &self.read_only_location.code,
310            Span {
311                range: self.read_only_location.byte_range(),
312                role: SpanRole::Supplementary {
313                    label: "the variable was made read-only here".into(),
314                },
315            },
316            &mut report.snippets,
317        );
318        report
319    }
320}
321
322impl<'a> From<&'a AssignReadOnlyError> for Report<'a> {
323    #[inline]
324    fn from(error: &'a AssignReadOnlyError) -> Self {
325        error.to_report()
326    }
327}
328
329/// Error that occurs when trying to cancel the read-only attribute of a
330/// variable or function
331#[derive(Clone, Debug, Error, Eq, PartialEq)]
332#[error("cannot cancel read-only-ness of {name}")]
333pub struct UndoReadOnlyError {
334    /// Name of the variable or function
335    pub name: Field,
336    /// Location where the variable or function was made read-only
337    pub read_only_location: Location,
338}
339
340/// Error that can occur during the execution of the typeset built-in
341#[derive(Clone, Debug, Error, Eq, PartialEq)]
342pub enum ExecuteError {
343    /// Assigning to a read-only variable
344    AssignReadOnlyVariable(#[from] AssignReadOnlyError),
345    /// Cancelling the read-only attribute of a variable
346    UndoReadOnlyVariable(UndoReadOnlyError),
347    /// Cancelling the read-only attribute of a function
348    UndoReadOnlyFunction(UndoReadOnlyError),
349    /// Modifying a non-existing function
350    ModifyUnsetFunction(Field),
351    /// Printing a non-existing variable
352    PrintUnsetVariable(Field),
353    /// Printing a non-existing function
354    PrintUnsetFunction(Field),
355}
356
357impl std::fmt::Display for ExecuteError {
358    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359        match self {
360            Self::AssignReadOnlyVariable(e) => e.fmt(f),
361            Self::UndoReadOnlyVariable(e) => {
362                write!(f, "cannot cancel read-only-ness of variable `{}`", e.name)
363            }
364            Self::UndoReadOnlyFunction(e) => {
365                write!(f, "cannot cancel read-only-ness of function `{}`", e.name)
366            }
367            Self::ModifyUnsetFunction(field) => {
368                write!(f, "cannot modify non-existing function `{}`", field)
369            }
370            Self::PrintUnsetVariable(field) => {
371                write!(f, "cannot print non-existing variable `{}`", field)
372            }
373            Self::PrintUnsetFunction(field) => {
374                write!(f, "cannot print non-existing function `{}`", field)
375            }
376        }
377    }
378}
379
380impl ExecuteError {
381    /// Converts the error to a report.
382    #[must_use]
383    pub fn to_report(&self) -> Report<'_> {
384        let (title, location, label) = match self {
385            Self::AssignReadOnlyVariable(error) => return error.to_report(),
386            Self::UndoReadOnlyVariable(error) => (
387                "cannot cancel read-only-ness of variable",
388                &error.name.origin,
389                format!("read-only variable `{}`", error.name.value).into(),
390            ),
391            Self::UndoReadOnlyFunction(error) => (
392                "cannot cancel read-only-ness of function",
393                &error.name.origin,
394                format!("read-only function `{}`", error.name.value).into(),
395            ),
396            Self::ModifyUnsetFunction(field) => (
397                "cannot modify non-existing function",
398                &field.origin,
399                format!("non-existing function `{field}`").into(),
400            ),
401            Self::PrintUnsetVariable(field) => (
402                "cannot print non-existing variable",
403                &field.origin,
404                format!("non-existing variable `{field}`").into(),
405            ),
406            Self::PrintUnsetFunction(field) => (
407                "cannot print non-existing function",
408                &field.origin,
409                format!("non-existing function `{field}`").into(),
410            ),
411        };
412
413        let mut report = Report::new();
414        report.r#type = ReportType::Error;
415        report.title = title.into();
416        report.snippets = Snippet::with_primary_span(location, label);
417        match self {
418            Self::UndoReadOnlyVariable(error) => add_span(
419                &error.read_only_location.code,
420                Span {
421                    range: error.read_only_location.byte_range(),
422                    role: SpanRole::Supplementary {
423                        label: "the variable was made read-only here".into(),
424                    },
425                },
426                &mut report.snippets,
427            ),
428            Self::UndoReadOnlyFunction(error) => add_span(
429                &error.read_only_location.code,
430                Span {
431                    range: error.read_only_location.byte_range(),
432                    role: SpanRole::Supplementary {
433                        label: "the function was made read-only here".into(),
434                    },
435                },
436                &mut report.snippets,
437            ),
438            _ => { /* No additional spans */ }
439        }
440        report
441    }
442}
443
444impl<'a> From<&'a ExecuteError> for Report<'a> {
445    #[inline]
446    fn from(error: &'a ExecuteError) -> Self {
447        error.to_report()
448    }
449}
450
451/// Entry point of the typeset built-in
452pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> yash_env::builtin::Result
453where
454    S: Fcntl + Isatty + Write,
455{
456    match syntax::parse(syntax::ALL_OPTIONS, args) {
457        Ok((options, operands)) => match syntax::interpret(options, operands) {
458            Ok(command) => match command.execute(env, &PRINT_CONTEXT) {
459                Ok(result) => output(env, &result).await,
460                Err(errors) => report_failure(env, merge_reports(&errors).unwrap()).await,
461            },
462            Err(error) => report_error(env, &error).await,
463        },
464        Err(error) => report_error(env, &error).await,
465    }
466}