yash_syntax/parser/lex/
raw_param.rs1use super::core::Lexer;
20use crate::parser::core::Result;
21use crate::syntax::Param;
22use crate::syntax::ParamType;
23use crate::syntax::SpecialParam;
24use crate::syntax::TextUnit;
25
26pub const fn is_portable_name_char(c: char) -> bool {
35 matches!(c, '0'..='9' | 'A'..='Z' | '_' | 'a'..='z')
36}
37
38#[inline(always)]
45#[must_use]
46pub fn is_portable_name(s: &str) -> bool {
47 yash_env::variable::is_portable_variable_name(s)
48}
49
50pub const fn is_special_parameter_char(c: char) -> bool {
54 SpecialParam::from_char(c).is_some()
55}
56
57pub const fn is_single_char_name(c: char) -> bool {
65 c.is_ascii_digit() || is_special_parameter_char(c)
66}
67
68impl Lexer<'_> {
69 pub async fn raw_param(&mut self, start_index: usize) -> Result<Option<TextUnit>> {
80 let param = if let Some(c) = self.consume_char_if(is_special_parameter_char).await? {
81 Param {
82 id: c.value.to_string(),
83 r#type: SpecialParam::from_char(c.value).unwrap().into(),
84 }
85 } else if let Some(c) = self.consume_char_if(|c| c.is_ascii_digit()).await? {
86 Param {
87 id: c.value.to_string(),
88 r#type: ParamType::Positional(c.value.to_digit(10).unwrap() as usize),
89 }
90 } else if let Some(c) = self.consume_char_if(is_portable_name_char).await? {
91 let mut name = c.value.to_string();
92 while let Some(c) = self.consume_char_if(is_portable_name_char).await? {
93 name.push(c.value);
94 }
95 Param::variable(name)
96 } else {
97 return Ok(None);
98 };
99
100 let location = self.location_range(start_index..self.index());
101
102 Ok(Some(TextUnit::RawParam { param, location }))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::source::Source;
110 use crate::syntax::Param;
111 use assert_matches::assert_matches;
112 use futures_util::FutureExt as _;
113
114 #[test]
115 fn test_is_portable_name() {
116 assert!(!is_portable_name(""));
117 assert!(is_portable_name("valid_name"));
118 assert!(!is_portable_name("1invalid_name"));
119 assert!(is_portable_name("valid_name_123"));
120 assert!(is_portable_name("_VALID_NAME"));
121 }
122
123 #[test]
124 fn lexer_raw_param_special_parameter() {
125 let mut lexer = Lexer::with_code("$@;");
126 lexer.peek_char().now_or_never().unwrap().unwrap();
127 lexer.consume_char();
128
129 let result = lexer.raw_param(0).now_or_never().unwrap().unwrap().unwrap();
130 assert_matches!(result, TextUnit::RawParam { param, location } => {
131 assert_eq!(param, Param::from(SpecialParam::At));
132 assert_eq!(*location.code.value.borrow(), "$@;");
133 assert_eq!(location.code.start_line_number.get(), 1);
134 assert_eq!(*location.code.source, Source::Unknown);
135 assert_eq!(location.range, 0..2);
136 });
137
138 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(';')));
139 }
140
141 #[test]
142 fn lexer_raw_param_digit() {
143 let mut lexer = Lexer::with_code("$12");
144 lexer.peek_char().now_or_never().unwrap().unwrap();
145 lexer.consume_char();
146
147 let result = lexer.raw_param(0).now_or_never().unwrap().unwrap().unwrap();
148 assert_matches!(result, TextUnit::RawParam { param, location } => {
149 assert_eq!(param, Param::from(1));
150 assert_eq!(*location.code.value.borrow(), "$12");
151 assert_eq!(location.code.start_line_number.get(), 1);
152 assert_eq!(*location.code.source, Source::Unknown);
153 assert_eq!(location.range, 0..2);
154 });
155
156 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('2')));
157 }
158
159 #[test]
160 fn lexer_raw_param_posix_name() {
161 let mut lexer = Lexer::with_code("$az_AZ_019<");
162 lexer.peek_char().now_or_never().unwrap().unwrap();
163 lexer.consume_char();
164
165 let result = lexer.raw_param(0).now_or_never().unwrap().unwrap().unwrap();
166 assert_matches!(result, TextUnit::RawParam { param, location } => {
167 assert_eq!(param, Param::variable("az_AZ_019"));
168 assert_eq!(*location.code.value.borrow(), "$az_AZ_019<");
169 assert_eq!(location.code.start_line_number.get(), 1);
170 assert_eq!(*location.code.source, Source::Unknown);
171 assert_eq!(location.range, 0..10);
172 });
173
174 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
175 }
176
177 #[test]
178 fn lexer_raw_param_posix_name_line_continuations() {
179 let mut lexer = Lexer::with_code("$a\\\n\\\nb\\\n\\\nc\\\n>");
180 lexer.peek_char().now_or_never().unwrap().unwrap();
181 lexer.consume_char();
182
183 let result = lexer.raw_param(0).now_or_never().unwrap().unwrap().unwrap();
184 assert_matches!(result, TextUnit::RawParam { param, location } => {
185 assert_eq!(param, Param::variable("abc"));
186 assert_eq!(*location.code.value.borrow(), "$a\\\n\\\nb\\\n\\\nc\\\n>");
187 assert_eq!(location.code.start_line_number.get(), 1);
188 assert_eq!(*location.code.source, Source::Unknown);
189 assert_eq!(location.range, 0..14);
190 });
191
192 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('>')));
193 }
194
195 #[test]
196 fn lexer_raw_param_not_parameter() {
197 let mut lexer = Lexer::with_code("$;");
198 lexer.peek_char().now_or_never().unwrap().unwrap();
199 lexer.consume_char();
200 assert_eq!(lexer.raw_param(0).now_or_never().unwrap(), Ok(None));
201 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(';')));
202 }
203}