yash_env/decl_util.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2024 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//! Defining declaration utilities
18//!
19//! This module contains the [`Glossary`] trait, which is used by the parser to
20//! determine whether a command name is a declaration utility. It also provides
21//! implementations of the `Glossary` trait: [`EmptyGlossary`],
22//! [`PosixGlossary`], and [`Env`].
23//!
24//! This crate (`yash-env`) does not provide a parser itself. The `yash-syntax`
25//! crate provides a parser that uses this module.
26//!
27//! # What are declaration utilities?
28//!
29//! A [declaration utility] is a type of command that causes its argument words
30//! to be expanded in a manner slightly different from other commands. Usually,
31//! command word expansion includes field splitting and pathname expansion. For
32//! declaration utilities, however, those expansions are not performed on the
33//! arguments that have a form of variable assignments.
34//!
35//! [declaration utility]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_100
36//!
37//! Generally, a simple command consists of assignments, redirections, and command
38//! words. The shell syntax allows the redirections to be placed anywhere in the
39//! command, but the assignments must come before the command words. An assignment
40//! token has the form `name=value`, the first token that does not match this
41//! form is considered the command name, and the rest are arguments regardless of
42//! whether they match the form. For example, in the command `a=1 b=2 echo c=3`,
43//! `a=1` and `b=2` are assignments, `echo` is the command name, and `c=3` is an
44//! argument.
45//!
46//! All assignments and command words are expanded when the command is executed,
47//! but the expansions are different. The expansions of assignments are performed
48//! in a way that does not include field splitting and pathname expansion. This
49//! ensures that the values of the assignments are not split or expanded into
50//! multiple fields. The expansions of command words, on the other hand, are
51//! performed in a way that includes field splitting and pathname expansion,
52//! which may expand a single word into multiple fields.
53//!
54//! The assignments specified in a simple command are performed by the shell
55//! before the utility specified by the command name is invoked. However, some
56//! utilities perform their own assignments based on their arguments. For such
57//! a utility, the tokens that specify the assigned variable names and values
58//! are given as arguments to the utility as in the command `export a=1 b=2`.
59//!
60//! By default, such arguments are expanded in the same way as usual command
61//! words, which means that the assignments are subject to field splitting and
62//! pathname expansion even though they are effectively assignments. To prevent
63//! this, the shell recognizes certain command names as declaration utilities
64//! and expands their arguments differently. The shell does not perform field
65//! splitting and pathname expansion on the arguments of declaration utilities
66//! that have the form of variable assignments.
67//!
68//! # Example
69//!
70//! POSIX requires the `export` utility to be recognized as a declaration
71//! utility. In the command `v='1 b=2'; export a=$v`, the word `a=$v` is not
72//! subject to field splitting because `export` is a declaration utility, so the
73//! expanded word `a=1 b=2` is passed to `export` as an argument, so `export`
74//! assigns the value `1 b=2` to the variable `a`. If `export` were not a
75//! declaration utility, the word `a=$v` would be subject to field splitting,
76//! and the expanded word `a=1 b=2` would be split into two fields `a=1` and
77//! `b=2`, so `export` would assign the value `1` to the variable `a` and the
78//! value `2` to the variable `b`.
79//!
80//! # Which command names are declaration utilities?
81//!
82//! The POSIX standard specifies that the following command names are declaration
83//! utilities:
84//!
85//! - `export` and `readonly` are declaration utilities.
86//! - `command` is neutral; it delegates to the next command word to determine
87//! whether it is a declaration utility.
88//!
89//! It is unspecified whether other command names are declaration utilities.
90//!
91//! The syntax parser can use the [`Glossary`] trait to determine whether a
92//! command name is a declaration utility. The parser calls its
93//! [`is_declaration_utility`] method when it encounters a command name, and
94//! changes how the following arguments are parsed based on the result.
95//!
96//! [`is_declaration_utility`]: Glossary::is_declaration_utility
97//!
98//! This module provides three implementations of the `Glossary` trait:
99//!
100//! - [`PosixGlossary`] recognizes the declaration utilities defined by POSIX
101//! (and no others). This is the default glossary used by the parser.
102//! - [`EmptyGlossary`] recognizes no command name as a declaration utility.
103//! The parse result does not conform to POSIX when this glossary is used.
104//! - [`Env`] recognizes declaration utilities based on the built-ins defined
105//! in the environment.
106//!
107//! You can implement the `Glossary` trait for your own glossary if you want to
108//! recognize additional command names as declaration utilities. Such a custom
109//! glossary can only be used when you directly configure the parser.
110
111use crate::Env;
112use std::cell::RefCell;
113use std::fmt::Debug;
114
115/// Interface used by the parser to tell if a command name is a declaration utility
116///
117/// The parser uses this trait to determine whether a command name is a declaration
118/// utility. See the [module-level documentation](self) for details.
119pub trait Glossary: Debug {
120 /// Returns whether the given command name is a declaration utility.
121 ///
122 /// If the command name is a declaration utility, this method should return
123 /// `Some(true)`. If the command name is not a declaration utility, this
124 /// method should return `Some(false)`. If the return value is `None`, this
125 /// method is called again with the next command word in the simple command
126 /// being parsed, effectively delegating the decision to the next command word.
127 ///
128 /// To meet the POSIX standard, the method should return `Some(true)` for the
129 /// command names `export` and `readonly`, and `None` for the command name
130 /// `command`.
131 fn is_declaration_utility(&self, name: &str) -> Option<bool>;
132}
133
134/// Empty glossary that does not recognize any command name as a declaration utility
135///
136/// When this glossary is used, the parser recognizes no command name as a
137/// declaration utility. Note that this does not conform to POSIX.
138#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
139pub struct EmptyGlossary;
140
141impl Glossary for EmptyGlossary {
142 #[inline(always)]
143 fn is_declaration_utility(&self, _name: &str) -> Option<bool> {
144 Some(false)
145 }
146}
147
148/// Glossary that recognizes declaration utilities defined by POSIX
149///
150/// This glossary recognizes the declaration utilities defined by POSIX and no
151/// others. The `is_declaration_utility` method returns `Some(true)` for the
152/// command names `export` and `readonly`, and `None` for the command name
153/// `command`.
154///
155/// This is the minimal glossary that conforms to POSIX, and is the default
156/// glossary used by the parser.
157#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
158pub struct PosixGlossary;
159
160impl Glossary for PosixGlossary {
161 fn is_declaration_utility(&self, name: &str) -> Option<bool> {
162 match name {
163 "export" | "readonly" => Some(true),
164 "command" => None,
165 _ => Some(false),
166 }
167 }
168}
169
170impl<T: Glossary> Glossary for &T {
171 fn is_declaration_utility(&self, name: &str) -> Option<bool> {
172 (**self).is_declaration_utility(name)
173 }
174}
175
176impl<T: Glossary> Glossary for &mut T {
177 fn is_declaration_utility(&self, name: &str) -> Option<bool> {
178 (**self).is_declaration_utility(name)
179 }
180}
181
182/// Allows a glossary to be wrapped in a `RefCell`.
183///
184/// This implementation's methods immutably borrow the inner glossary.
185/// If the inner glossary is mutably borrowed at the same time, it panics.
186impl<T: Glossary> Glossary for RefCell<T> {
187 fn is_declaration_utility(&self, name: &str) -> Option<bool> {
188 self.borrow().is_declaration_utility(name)
189 }
190}
191
192/// Determines whether a command name is a declaration utility.
193///
194/// This implementation looks up the command name in `self.builtins` and returns
195/// the value of `is_declaration_utility` if the built-in is found. Otherwise,
196/// the command is not a declaration utility.
197impl Glossary for Env {
198 fn is_declaration_utility(&self, name: &str) -> Option<bool> {
199 match self.builtins.get(name) {
200 Some(builtin) => builtin.is_declaration_utility,
201 None => Some(false),
202 }
203 }
204}