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