From 3cb990d3081285fba2c26a8b8ed4aff4d7264d1a Mon Sep 17 00:00:00 2001 From: mlcui Date: Fri, 21 Jun 2024 10:56:04 +1000 Subject: [PATCH] Resolve fully-uppercase builtin identifiers Some users try to use uppercase function names like LOG as they are used to Excel functions, which don't work in fend. Resolve fully-uppercase builtin identifiers if they are not parsed as units. This ensures that if, for example, "I" becomes a unit in the future, it will be resolved as the unit - not the imaginary number. --- core/src/ast.rs | 54 +++++++++++++++++++++++++++------ core/tests/integration_tests.rs | 11 +++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/core/src/ast.rs b/core/src/ast.rs index 33ab59fc..7619e938 100644 --- a/core/src/ast.rs +++ b/core/src/ast.rs @@ -678,6 +678,50 @@ pub(crate) fn resolve_identifier( attrs: Attrs, context: &mut crate::Context, int: &I, +) -> FResult { + let cloned_scope = scope.clone(); + if let Some(ref scope) = cloned_scope { + if let Some(val) = scope.get(ident, attrs, context, int)? { + return Ok(val); + } + } + if let Some(val) = context.variables.get(ident.as_str()) { + return Ok(val.clone()); + } + + let builtin_result = resolve_builtin_identifier(ident, cloned_scope, attrs, context, int); + if !matches!(builtin_result, Err(FendError::IdentifierNotFound(_))) { + return builtin_result; + } + let unit_result = crate::units::query_unit(ident.as_str(), attrs, context, int); + if !matches!(unit_result, Err(FendError::IdentifierNotFound(_))) { + return unit_result; + } + + if !ident + .as_str() + .bytes() + .all(|b| b.is_ascii_digit() || b.is_ascii_uppercase()) + { + return unit_result; + } + let lowercase_builtin_result = resolve_builtin_identifier( + &ident.as_str().to_ascii_lowercase().into(), + scope, + attrs, + context, + int, + ); + // "Unknown identifier" errors should use the uppercase ident. + lowercase_builtin_result.or(unit_result) +} + +fn resolve_builtin_identifier( + ident: &Ident, + scope: Option>, + attrs: Attrs, + context: &mut crate::Context, + int: &I, ) -> FResult { macro_rules! eval_box { ($input:expr) => { @@ -690,14 +734,6 @@ pub(crate) fn resolve_identifier( )?) }; } - if let Some(scope) = scope.clone() { - if let Some(val) = scope.get(ident, attrs, context, int)? { - return Ok(val); - } - } - if let Some(val) = context.variables.get(ident.as_str()) { - return Ok(val.clone()); - } Ok(match ident.as_str() { "pi" | "\u{3c0}" => Value::Num(Box::new(Number::pi())), "tau" | "\u{3c4}" => Value::Num(Box::new(Number::pi().mul(2.into(), int)?)), @@ -774,7 +810,7 @@ pub(crate) fn resolve_identifier( "tomorrow" => Value::Date(crate::date::Date::today(context)?.next()), "yesterday" => Value::Date(crate::date::Date::today(context)?.prev()), "trans" => Value::String(Cow::Borrowed("🏳️‍⚧️")), - _ => return crate::units::query_unit(ident.as_str(), attrs, context, int), + _ => return Err(FendError::IdentifierNotFound(ident.clone())), }) } diff --git a/core/tests/integration_tests.rs b/core/tests/integration_tests.rs index 86f4870b..cfe28701 100644 --- a/core/tests/integration_tests.rs +++ b/core/tests/integration_tests.rs @@ -5994,3 +5994,14 @@ fn fibonacci() { test_eval("fib 10", "55"); test_eval("fib 11", "89"); } + +#[test] +fn uppercase_identifiers() { + test_eval("SIN PI", "0"); + test_eval("COS TAU", "1"); + test_eval("LOG 1", "approx. 0"); + test_eval("LOG10 1", "approx. 0"); + test_eval("EXP 0", "approx. 1"); + + expect_error("foo = 1; FOO", Some("unknown identifier 'FOO'")); +}