yash_env/parser.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2025 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//! Shell language parser configuration and utilities
18//!
19//! This module contains several items related to the shell language parser.
20//!
21//! - [`Config`] is a struct that holds configuration options for the parser.
22//! - [`IsKeyword`] is a wrapper for a function that checks if a string is a
23//! reserved word.
24//! - [`IsName`] is a wrapper for a function that checks if a string is a valid
25//! variable name.
26//!
27//! Parser implementations are not provided in this crate (`yash-env`). The
28//! standard parser implementation is provided in the `yash-syntax` crate.
29
30use crate::Env;
31use crate::input::InputObject;
32use crate::option::Option::Portable;
33use crate::option::OptionSet;
34use crate::source::Source;
35use derive_more::Debug;
36use std::num::NonZeroU64;
37use std::rc::Rc;
38
39/// Parsing mode derived from shell options
40///
41/// Some shell options change which syntax the parser accepts. This type conveys
42/// the relevant option states from the shell environment to the parser and
43/// lexer, so that the parser does not need to depend on the whole
44/// [`OptionSet`]. The standard parser ([`yash-syntax`](https://crates.io/crates/yash-syntax))
45/// reads it from the [lexer](crate::parser) and adjusts the syntax it accepts
46/// accordingly.
47///
48/// A `Mode` is typically created from the current option set by converting an
49/// [`OptionSet`] with the [`From`] implementation. The [default](Default) mode
50/// permits all syntax (every field is `false`), so the parser behaves as if no
51/// restricting option were set.
52///
53/// This struct is `#[non_exhaustive]` because more fields may be added as more
54/// parsing-affecting options are supported. You can still create one by starting
55/// from [`Default`] or a converted [`OptionSet`] and assigning to individual
56/// fields.
57#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
58#[non_exhaustive]
59pub struct Mode {
60 /// Whether the parser rejects non-portable syntax
61 ///
62 /// This reflects the [`Portable`] shell option. When `true`, the parser
63 /// reports an error on constructs that are valid in yash but not portable
64 /// across POSIX-conforming shells.
65 pub portable: bool,
66}
67
68/// Creates a `Mode` reflecting the given option set.
69impl From<&OptionSet> for Mode {
70 fn from(options: &OptionSet) -> Self {
71 Mode {
72 portable: options.get(Portable).into(),
73 }
74 }
75}
76
77/// Configuration for the parser
78///
79/// This struct holds various configuration options for the parser, including
80/// the input function to read source code and source information.
81///
82/// Parser implementations are not provided in this crate (`yash-env`). The
83/// standard parser implementation is provided in the `yash-syntax` crate.
84/// `Config` is provided here so that other crates can use [`RunReadEvalLoop`]
85/// without depending on `yash-syntax`.
86///
87/// Since this struct is marked as `#[non_exhaustive]`, you cannot construct it
88/// directly. Instead, use the [`with_input`](Self::with_input) function to
89/// create a `Config` instance, and then modify its fields as necessary.
90///
91/// [`RunReadEvalLoop`]: crate::semantics::RunReadEvalLoop
92#[derive(Debug)]
93#[non_exhaustive]
94pub struct Config<'a> {
95 /// Input function to read source code
96 #[debug(skip)]
97 pub input: Box<dyn InputObject + 'a>,
98
99 /// Line number for the first line of the input
100 ///
101 /// The lexer counts lines starting from this number. This affects the
102 /// `start_line_number` field of the [`Code`] instance attached to the
103 /// parsed AST.
104 ///
105 /// The default value is `1`.
106 ///
107 /// [`Code`]: crate::source::Code
108 pub start_line_number: NonZeroU64,
109
110 /// Source information for the input
111 ///
112 /// If provided, this source information is saved in the `source` field of
113 /// the [`Code`] instance attached to the parsed AST.
114 ///
115 /// The default value is `None`, in which case `Source::Unknown` is used.
116 ///
117 /// [`Code`]: crate::source::Code
118 pub source: Option<Rc<Source>>,
119
120 /// Parsing mode derived from shell options
121 ///
122 /// The parser uses this to decide which syntax to accept. The default value
123 /// is [`Mode::default`], which permits all syntax.
124 pub mode: Mode,
125}
126
127impl<'a> Config<'a> {
128 /// Creates a `Config` with the given input function.
129 #[must_use]
130 pub fn with_input(input: Box<dyn InputObject + 'a>) -> Self {
131 Self {
132 input,
133 start_line_number: NonZeroU64::new(1).unwrap(),
134 source: None,
135 mode: Mode::default(),
136 }
137 }
138}
139
140/// Wrapper for a function that checks if a string is a keyword
141///
142/// This struct wraps a function that takes an environment and a string, and
143/// returns `true` if the string is a shell reserved word (keyword) in the given
144/// environment. An implementation of the function should be provided and stored
145/// in the environment's [`any`](Env::any) storage. This allows modules that
146/// need to check for keywords to do so without directly depending on the parser
147/// crate (`yash-syntax`).
148#[derive(Debug)]
149pub struct IsKeyword<S>(pub fn(&Env<S>, &str) -> bool);
150
151// Not derived automatically because S may not implement Clone or Copy.
152impl<S> Clone for IsKeyword<S> {
153 fn clone(&self) -> Self {
154 *self
155 }
156}
157
158impl<S> Copy for IsKeyword<S> {}
159
160/// Wrapper for a function that checks if a string is a valid variable name
161///
162/// This struct wraps a function that takes an environment and a string, and
163/// returns `true` if the string is a valid shell variable name in the given
164/// environment. An implementation of the function should be provided and stored
165/// in the environment's [`any`](Env::any) storage. This allows modules that
166/// need to check for variable names to do so without directly depending on the
167/// parser crate (`yash-syntax`).
168#[derive(Debug)]
169pub struct IsName<S>(pub fn(&Env<S>, &str) -> bool);
170
171// Not derived automatically because S may not implement Clone or Copy.
172impl<S> Clone for IsName<S> {
173 fn clone(&self) -> Self {
174 *self
175 }
176}
177
178impl<S> Copy for IsName<S> {}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::option::Option::PosixlyCorrect;
184 use crate::option::State::On;
185
186 #[test]
187 fn mode_default_permits_all_syntax() {
188 assert_eq!(Mode::default(), Mode { portable: false });
189 }
190
191 #[test]
192 fn mode_from_options_reflects_portable() {
193 let mut options = OptionSet::default();
194 assert!(!Mode::from(&options).portable);
195
196 options.set(Portable, On);
197 assert!(Mode::from(&options).portable);
198 }
199
200 #[test]
201 fn mode_from_options_ignores_unrelated_options() {
202 let mut options = OptionSet::default();
203 options.set(PosixlyCorrect, On);
204 assert!(!Mode::from(&options).portable);
205 }
206
207 #[test]
208 fn config_with_input_defaults_to_permissive_mode() {
209 let config = Config::with_input(Box::new(crate::input::Memory::new("")));
210 assert_eq!(config.mode, Mode::default());
211 }
212}