diff --git a/Cargo.lock b/Cargo.lock index 140a33270..4d3e8eb37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2100,6 +2100,7 @@ dependencies = [ "crossterm_winapi", "ctrlc", "hamcrest2", + "is_executable", "itertools", "log", "miette", diff --git a/Cargo.toml b/Cargo.toml index 57ba6898b..7085066cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,8 @@ nu-term-grid = { path = "./crates/nu-term-grid", version = "0.59.0" } pretty_env_logger = "0.4.0" rayon = "1.5.1" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +is_executable = "1.0.1" + # mimalloc = { version = "*", default-features = false } # Plugins diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 13d1732c6..245a17abc 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -13,7 +13,9 @@ pub use known_external::KnownExternal; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parser::{parse, parse_block, parse_external_call, trim_quotes, Import}; +pub use parser::{ + is_math_expression_like, parse, parse_block, parse_external_call, trim_quotes, Import, +}; #[cfg(feature = "plugin")] pub use parse_keywords::parse_register; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index ade875518..f7a2f8ae0 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -41,7 +41,13 @@ fn is_identifier_byte(b: u8) -> bool { b != b'.' && b != b'[' && b != b'(' && b != b'{' } -fn is_math_expression_byte(b: u8) -> bool { +pub fn is_math_expression_like(bytes: &[u8]) -> bool { + if bytes.is_empty() { + return false; + } + + let b = bytes[0]; + b == b'0' || b == b'1' || b == b'2' @@ -826,7 +832,13 @@ pub fn parse_call( // Find the longest group of words that could form a command let bytes = working_set.get_span_contents(*word_span); - if is_math_expression_byte(bytes[0]) { + if is_math_expression_like(bytes) + && !working_set + .permanent_state + .external_exceptions + .iter() + .any(|x| x == bytes) + { break; } @@ -3446,7 +3458,13 @@ pub fn parse_expression( let bytes = working_set.get_span_contents(spans[pos]); - let (output, err) = if is_math_expression_byte(bytes[0]) { + let (output, err) = if is_math_expression_like(bytes) + && !working_set + .permanent_state + .external_exceptions + .iter() + .any(|x| x == bytes) + { parse_math_expression(working_set, &spans[pos..], None) } else { // For now, check for special parses of certain keywords diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 08b3be252..581764b63 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -169,6 +169,9 @@ pub struct EngineState { pub env_vars: im::HashMap, #[cfg(feature = "plugin")] pub plugin_signatures: Option, + + // A list of external commands that look like math expressions + pub external_exceptions: Vec>, } pub const NU_VARIABLE_ID: usize = 0; @@ -199,6 +202,7 @@ impl EngineState { env_vars: im::HashMap::new(), #[cfg(feature = "plugin")] plugin_signatures: None, + external_exceptions: vec![], } } @@ -633,6 +637,7 @@ impl Default for EngineState { pub struct StateWorkingSet<'a> { pub permanent_state: &'a EngineState, pub delta: StateDelta, + pub external_commands: Vec>, } /// A delta (or change set) between the current global state and a possible future global state. Deltas @@ -707,6 +712,7 @@ impl<'a> StateWorkingSet<'a> { Self { delta: StateDelta::new(), permanent_state, + external_commands: vec![], } } diff --git a/src/main.rs b/src/main.rs index 259589594..5a1237e4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,10 @@ fn main() -> Result<()> { }; let _ = engine_state.merge_delta(delta, None, &init_cwd); + // Make a note of the exceptions we see for externals that look like math expressions + let exceptions = crate::utils::external_exceptions(); + engine_state.external_exceptions = exceptions; + // TODO: make this conditional in the future // Ctrl-c protection section let ctrlc = Arc::new(AtomicBool::new(false)); diff --git a/src/utils.rs b/src/utils.rs index 95880dd73..0454d4a45 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -254,6 +254,32 @@ pub(crate) fn eval_source( true } +/// Finds externals that have names that look like math expressions +pub fn external_exceptions() -> Vec> { + let mut executables = vec![]; + + if let Ok(path) = std::env::var("PATH") { + for path in std::env::split_paths(&path) { + let path = path.to_string_lossy().to_string(); + + if let Ok(mut contents) = std::fs::read_dir(path) { + while let Some(Ok(item)) = contents.next() { + if is_executable::is_executable(&item.path()) { + if let Ok(name) = item.file_name().into_string() { + let name = name.as_bytes().to_vec(); + if nu_parser::is_math_expression_like(&name) { + executables.push(name); + } + } + } + } + } + } + } + + executables +} + #[cfg(windows)] pub fn enable_vt_processing() -> Result<(), ShellError> { use crossterm_winapi::{ConsoleMode, Handle};