Source: core/formula/formula-syntax.js

/*
 * Copyright 2026 The Ray Optics Simulation authors and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export const CONSTANTS = new Set(["pi", "e"]);
export const UNARY_FUNCTIONS = new Set([
  "sqrt",
  "sin",
  "cos",
  "tan",
  "sec",
  "csc",
  "cot",
  "sinh",
  "cosh",
  "tanh",
  "log",
  "exp",
  "asin",
  "acos",
  "atan",
  "asinh",
  "acosh",
  "atanh",
  "floor",
  "round",
  "ceil",
  "fix",
  "abs",
  "sign",
]);
export const BINARY_FUNCTIONS = new Set([
  "atan2",
  "guardNonzero",
  "guardNonNegative",
  "guardNotInteger",
  "guardValid",
  "fallback",
]);
export const VARIADIC_FUNCTIONS = new Set(["max", "min"]);
export const RESERVED_NAMES = new Set([
  ...CONSTANTS,
  ...UNARY_FUNCTIONS,
  ...BINARY_FUNCTIONS,
  ...VARIADIC_FUNCTIONS,
]);

export function isFormulaIdentifierStart(char) {
  return char !== undefined && /[$_\p{ID_Start}]/u.test(char);
}

export function isFormulaIdentifierContinue(char) {
  return char !== undefined && /[$_\u200c\u200d\p{ID_Continue}]/u.test(char);
}

/**
 * Return whether a string can be used as a formula parameter or assignment name.
 *
 * Formula names must be JavaScript-style identifiers and must not collide with
 * constants or supported function names.
 *
 * @param {unknown} name - Candidate formula name.
 * @returns {boolean} True when `name` is valid in formula syntax.
 */
export function isValidFormulaParameterName(name) {
  if (typeof name !== "string" || name.length === 0) return false;
  if (RESERVED_NAMES.has(name)) return false;
  if (!isFormulaIdentifierStart(name[0])) return false;

  for (let index = 1; index < name.length; index += 1) {
    if (!isFormulaIdentifierContinue(name[index])) return false;
  }
  return true;
}