1use crate::{lite_parser::LiteCommand, parser::parse_call};
2use nu_protocol::{
3 DeclId, ParseError, Span, Type,
4 ast::{Expr, Expression, Pipeline},
5 engine::StateWorkingSet,
6};
7
8pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
10 b"if",
11 b"match",
12 b"try",
13 b"overlay",
14 b"overlay hide",
15 b"overlay new",
16 b"overlay use",
17];
18
19pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
21 b"alias",
22 b"const",
23 b"def",
24 b"extern",
25 b"module",
26 b"use",
27 b"export",
28 b"export alias",
29 b"export const",
30 b"export def",
31 b"export extern",
32 b"export module",
33 b"export use",
34 b"for",
35 b"loop",
36 b"while",
37 b"return",
38 b"break",
39 b"continue",
40 b"let",
41 b"mut",
42 b"hide",
43 b"export-env",
44 b"source-env",
45 b"source",
46 b"run",
47 b"where",
48 b"plugin use",
49];
50
51pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Span]) -> bool {
53 if let (Some(&span1), Some(&span2)) = (spans.first(), spans.get(1)) {
55 let cmd_name = working_set.get_span_contents(Span::append(span1, span2));
56 return UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name);
57 }
58
59 if let Some(&span1) = spans.first() {
61 let cmd_name = working_set.get_span_contents(span1);
62 UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name)
63 } else {
64 false
65 }
66}
67
68pub fn is_parser_keyword(name: &[u8]) -> bool {
83 ALIASABLE_PARSER_KEYWORDS.contains(&name) || UNALIASABLE_PARSER_KEYWORDS.contains(&name)
84}
85
86pub fn single_word_parser_keywords() -> impl Iterator<Item = &'static str> {
91 ALIASABLE_PARSER_KEYWORDS
92 .iter()
93 .chain(UNALIASABLE_PARSER_KEYWORDS.iter())
94 .filter_map(|bytes| {
95 let name = std::str::from_utf8(bytes).ok()?;
96 (!name.contains(' ')).then_some(name)
97 })
98}
99
100pub fn reject_parser_keyword_name(
105 working_set: &mut StateWorkingSet,
106 name: &str,
107 kind: &str,
108 span: Span,
109) -> bool {
110 if is_parser_keyword(name.as_bytes()) {
111 working_set.error(ParseError::NameIsKeyword(
112 name.to_owned(),
113 kind.to_owned(),
114 span,
115 ));
116 true
117 } else {
118 false
119 }
120}
121
122pub(crate) fn find_keyword_decl(working_set: &StateWorkingSet, name: &[u8]) -> Option<DeclId> {
128 (0..working_set.num_decls())
129 .map(DeclId::new)
130 .find(|decl_id| {
131 let decl = working_set.get_decl(*decl_id);
132 decl.name().as_bytes() == name && decl.is_keyword()
133 })
134}
135
136pub fn parse_keyword(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
139 let orig_parse_errors_len = working_set.parse_errors.len();
140
141 let call_expr = parse_call(
142 working_set,
143 &lite_command.parts,
144 lite_command.parts[0],
145 None,
146 );
147
148 if working_set.parse_errors.len() > orig_parse_errors_len {
150 return Pipeline::from_vec(vec![call_expr]);
151 }
152
153 if let Expression {
154 expr: Expr::Call(call),
155 ..
156 } = call_expr.clone()
157 {
158 let cmd = working_set.get_decl(call.decl_id);
160 if call.named_iter().any(|(flag, _, _)| flag.item == "help") {
162 let call_span = call.span();
163 return Pipeline::from_vec(vec![Expression::new(
164 working_set,
165 Expr::Call(call),
166 call_span,
167 Type::Any,
168 )]);
169 }
170
171 match cmd.name() {
172 "overlay hide" => crate::parse_module::parse_overlay_hide(working_set, call),
173 "overlay new" => crate::parse_module::parse_overlay_new(working_set, call),
174 "overlay use" => crate::parse_module::parse_overlay_use(working_set, call),
175 #[cfg(feature = "plugin")]
176 "plugin use" => crate::parse_source::parse_plugin_use(working_set, call),
177 _ => Pipeline::from_vec(vec![call_expr]),
178 }
179 } else {
180 Pipeline::from_vec(vec![call_expr])
181 }
182}
183
184pub use crate::parse_alias::parse_alias;
186pub use crate::parse_bindings::{parse_const, parse_let, parse_mut};
187pub use crate::parse_def::{
188 parse_attribute_block, parse_def, parse_def_predecl, parse_extern, parse_for,
189};
190pub use crate::parse_module::{
191 parse_export_env, parse_export_in_block, parse_export_in_module, parse_hide, parse_module,
192 parse_module_block, parse_module_file_or_dir, parse_overlay_hide, parse_overlay_new,
193 parse_overlay_use, parse_use,
194};
195pub use crate::parse_source::{
196 LIB_DIRS_VAR, find_dirs_var, find_in_dirs, find_main_block_id_in_script, parse_run,
197 parse_run_expr, parse_source, parse_where, parse_where_expr,
198};
199#[cfg(feature = "plugin")]
200pub use crate::parse_source::{PLUGIN_DIRS_VAR, parse_plugin_use};
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn is_parser_keyword_matches_single_word_keywords() {
208 assert!(is_parser_keyword(b"def"));
209 assert!(is_parser_keyword(b"let"));
210 assert!(is_parser_keyword(b"if")); assert!(is_parser_keyword(b"overlay")); assert!(is_parser_keyword(b"where"));
213 }
214
215 #[test]
216 fn is_parser_keyword_rejects_ordinary_command_names() {
217 assert!(!is_parser_keyword(b"ls"));
218 assert!(!is_parser_keyword(b"my-command"));
219 assert!(!is_parser_keyword(b""));
220 }
221
222 #[test]
223 fn is_parser_keyword_includes_multi_word_entries() {
224 assert!(is_parser_keyword(b"export def"));
227 assert!(is_parser_keyword(b"overlay use"));
228 }
229
230 #[test]
231 fn single_word_parser_keywords_excludes_multi_word_and_matches_is_parser_keyword() {
232 let names: Vec<_> = single_word_parser_keywords().collect();
233 assert!(names.contains(&"def"));
234 assert!(names.contains(&"if"));
235 assert!(!names.iter().any(|n| n.contains(' ')));
236 for name in &names {
237 assert!(
238 is_parser_keyword(name.as_bytes()),
239 "{name} should be a parser keyword"
240 );
241 }
242 }
243}