Skip to content

Commit

Permalink
Resolve fully-uppercase builtin identifiers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mlcui-corp committed Jun 21, 2024
1 parent 431b6c2 commit 3cb990d
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 9 deletions.
54 changes: 45 additions & 9 deletions core/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,50 @@ pub(crate) fn resolve_identifier<I: Interrupt>(
attrs: Attrs,
context: &mut crate::Context,
int: &I,
) -> FResult<Value> {
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<I: Interrupt>(
ident: &Ident,
scope: Option<Arc<Scope>>,
attrs: Attrs,
context: &mut crate::Context,
int: &I,
) -> FResult<Value> {
macro_rules! eval_box {
($input:expr) => {
Expand All @@ -690,14 +734,6 @@ pub(crate) fn resolve_identifier<I: Interrupt>(
)?)
};
}
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)?)),
Expand Down Expand Up @@ -774,7 +810,7 @@ pub(crate) fn resolve_identifier<I: Interrupt>(
"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())),
})
}

Expand Down
11 changes: 11 additions & 0 deletions core/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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'"));
}

0 comments on commit 3cb990d

Please sign in to comment.