use crate::args::ArgSchema;
use crate::function::Function;
use crate::function_contract::FunctionDependencyContract;
use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
use formualizer_macros::func_caps;
use super::utils::ARG_ANY_ONE;
fn scalar<'ctx>(value: LiteralValue) -> CalcValue<'ctx> {
CalcValue::Scalar(value)
}
fn error_value<'ctx>(kind: ExcelErrorKind) -> CalcValue<'ctx> {
scalar(LiteralValue::Error(ExcelError::new(kind)))
}
fn arity_error<'ctx>() -> Result<CalcValue<'ctx>, ExcelError> {
Ok(error_value(ExcelErrorKind::Value))
}
fn na_result<'ctx>() -> Result<CalcValue<'ctx>, ExcelError> {
Ok(error_value(ExcelErrorKind::Na))
}
#[derive(Debug)]
pub struct IsNumberFn;
impl Function for IsNumberFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISNUMBER"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
let is_num = matches!(
v,
LiteralValue::Int(_)
| LiteralValue::Number(_)
| LiteralValue::Date(_)
| LiteralValue::DateTime(_)
| LiteralValue::Time(_)
| LiteralValue::Duration(_)
);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
is_num,
)))
}
}
#[derive(Debug)]
pub struct IsTextFn;
impl Function for IsTextFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISTEXT"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
matches!(v, LiteralValue::Text(_)),
)))
}
}
#[derive(Debug)]
pub struct IsLogicalFn;
impl Function for IsLogicalFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISLOGICAL"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
matches!(v, LiteralValue::Boolean(_)),
)))
}
}
#[derive(Debug)]
pub struct IsBlankFn;
impl Function for IsBlankFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISBLANK"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
matches!(v, LiteralValue::Empty),
)))
}
}
#[derive(Debug)]
pub struct IsErrorFn; impl Function for IsErrorFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISERROR"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
matches!(v, LiteralValue::Error(_)),
)))
}
}
#[derive(Debug)]
pub struct IsErrFn; impl Function for IsErrFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISERR"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
let is_err = match v {
LiteralValue::Error(e) => e.kind != ExcelErrorKind::Na,
_ => false,
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
is_err,
)))
}
}
#[derive(Debug)]
pub struct IsNaFn; impl Function for IsNaFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISNA"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
let is_na = matches!(v, LiteralValue::Error(e) if e.kind==ExcelErrorKind::Na);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
is_na,
)))
}
}
#[derive(Debug)]
pub struct IsFormulaFn; impl Function for IsFormulaFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISFORMULA"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
false,
)))
}
}
#[derive(Debug)]
pub struct IsRefFn;
impl Function for IsRefFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISREF"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn dispatch<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
self.eval(args, ctx)
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return arity_error();
}
let Ok(reference) = args[0].as_reference_or_eval() else {
return Ok(scalar(LiteralValue::Boolean(false)));
};
let is_ref = match ctx.inspect_reference(&reference) {
Ok(Some(info)) => info.first_cell.is_some() || info.sheet_count.is_some(),
Ok(None) => true,
Err(_) => false,
};
Ok(scalar(LiteralValue::Boolean(is_ref)))
}
}
#[derive(Debug)]
pub struct FormulaTextFn;
impl Function for FormulaTextFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"FORMULATEXT"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn dispatch<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
self.eval(args, ctx)
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return arity_error();
}
let reference = match args[0].as_reference_or_eval() {
Ok(reference) => reference,
Err(_) => return na_result(),
};
let Some(info) = ctx.inspect_reference(&reference)? else {
return na_result();
};
let Some(cell) = info.first_cell else {
return na_result();
};
match ctx.formula_text_at_cell(cell)? {
Some(text) => Ok(scalar(LiteralValue::Text(text))),
None => na_result(),
}
}
}
#[derive(Debug)]
pub struct SheetFn;
impl Function for SheetFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"SHEET"
}
fn min_args(&self) -> usize {
0
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn dispatch<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
self.eval(args, ctx)
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() > 1 {
return arity_error();
}
if args.is_empty() {
return ctx
.current_sheet_index()
.map(|idx| scalar(LiteralValue::Int(idx as i64)))
.map(Ok)
.unwrap_or_else(na_result);
}
if let Ok(reference) = args[0].as_reference_or_eval() {
let Some(info) = ctx.inspect_reference(&reference)? else {
return na_result();
};
return info
.first_sheet_index
.map(|idx| scalar(LiteralValue::Int(idx as i64)))
.map(Ok)
.unwrap_or_else(na_result);
}
match args[0].value()?.into_literal() {
LiteralValue::Text(name) => ctx
.sheet_index_by_name(name.as_ref())
.map(|idx| scalar(LiteralValue::Int(idx as i64)))
.map(Ok)
.unwrap_or_else(na_result),
LiteralValue::Error(e) => Ok(scalar(LiteralValue::Error(e))),
_ => arity_error(),
}
}
}
#[derive(Debug)]
pub struct SheetsFn;
impl Function for SheetsFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"SHEETS"
}
fn min_args(&self) -> usize {
0
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn dispatch<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
self.eval(args, ctx)
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() > 1 {
return arity_error();
}
if args.is_empty() {
return ctx
.workbook_sheet_count()
.map(|count| scalar(LiteralValue::Int(count as i64)))
.map(Ok)
.unwrap_or_else(na_result);
}
let reference = match args[0].as_reference_or_eval() {
Ok(reference) => reference,
Err(_) => return arity_error(),
};
let Some(info) = ctx.inspect_reference(&reference)? else {
return na_result();
};
info.sheet_count
.map(|count| scalar(LiteralValue::Int(count as i64)))
.map(Ok)
.unwrap_or_else(na_result)
}
}
#[derive(Debug)]
pub struct TypeFn;
impl Function for TypeFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"TYPE"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal(); if let LiteralValue::Error(e) = v {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
let code = match v {
LiteralValue::Int(_)
| LiteralValue::Number(_)
| LiteralValue::Empty
| LiteralValue::Date(_)
| LiteralValue::DateTime(_)
| LiteralValue::Time(_)
| LiteralValue::Duration(_) => 1,
LiteralValue::Text(_) => 2,
LiteralValue::Boolean(_) => 4,
LiteralValue::Array(_) => 64,
LiteralValue::Error(_) => unreachable!(),
LiteralValue::Pending => 1, };
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(code)))
}
}
#[derive(Debug)]
pub struct NaFn; impl Function for NaFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"NA"
}
fn min_args(&self) -> usize {
0
}
fn eval<'a, 'b, 'c>(
&self,
_args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Na),
)))
}
}
#[derive(Debug)]
pub struct NFn; impl Function for NFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"N"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
match v {
LiteralValue::Int(i) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(i))),
LiteralValue::Number(n) => {
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(n)))
}
LiteralValue::Date(_)
| LiteralValue::DateTime(_)
| LiteralValue::Time(_)
| LiteralValue::Duration(_) => {
if let Some(serial) = v.as_serial_number() {
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
serial,
)))
} else {
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0)))
}
}
LiteralValue::Boolean(b) => {
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(if b {
1
} else {
0
})))
}
LiteralValue::Text(_) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
LiteralValue::Empty => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
LiteralValue::Array(_) => {
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0)))
}
LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
LiteralValue::Pending => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
}
}
}
#[derive(Debug)]
pub struct TFn; impl Function for TFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"T"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
match v {
LiteralValue::Text(s) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(s))),
LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
_ => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
String::new(),
))),
}
}
}
#[derive(Debug)]
pub struct IsEvenFn;
impl Function for IsEvenFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISEVEN"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
let n = match v {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
LiteralValue::Int(i) => i as f64,
LiteralValue::Number(n) => n,
LiteralValue::Boolean(b) => {
if b {
1.0
} else {
0.0
}
}
_ => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
};
let n = n.trunc() as i64;
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
n % 2 == 0,
)))
}
}
#[derive(Debug)]
pub struct IsOddFn;
impl Function for IsOddFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISODD"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
let n = match v {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
LiteralValue::Int(i) => i as f64,
LiteralValue::Number(n) => n,
LiteralValue::Boolean(b) => {
if b {
1.0
} else {
0.0
}
}
_ => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
};
let n = n.trunc() as i64;
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
n % 2 != 0,
)))
}
}
#[derive(Debug)]
pub struct ErrorTypeFn;
impl Function for ErrorTypeFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ERROR.TYPE"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
match v {
LiteralValue::Error(e) => {
let code = match e.kind {
ExcelErrorKind::Null => 1,
ExcelErrorKind::Div => 2,
ExcelErrorKind::Value => 3,
ExcelErrorKind::Ref => 4,
ExcelErrorKind::Name => 5,
ExcelErrorKind::Num => 6,
ExcelErrorKind::Na => 7,
ExcelErrorKind::Error => 8,
ExcelErrorKind::NImpl => 9,
ExcelErrorKind::Spill => 10,
ExcelErrorKind::Calc => 11,
ExcelErrorKind::Circ => 12,
ExcelErrorKind::Cancelled => 13,
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(code)))
}
_ => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_na(),
))),
}
}
}
#[derive(Debug)]
pub struct IsNonTextFn;
impl Function for IsNonTextFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ISNONTEXT"
}
fn min_args(&self) -> usize {
1
}
fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
FunctionDependencyContract::static_scalar_all_args(arity)
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = args[0].value()?.into_literal();
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
!matches!(v, LiteralValue::Text(_)),
)))
}
}
pub fn register_builtins() {
use std::sync::Arc;
crate::function_registry::register_function(Arc::new(IsNumberFn));
crate::function_registry::register_function(Arc::new(IsTextFn));
crate::function_registry::register_function(Arc::new(IsNonTextFn));
crate::function_registry::register_function(Arc::new(IsLogicalFn));
crate::function_registry::register_function(Arc::new(IsBlankFn));
crate::function_registry::register_function(Arc::new(IsErrorFn));
crate::function_registry::register_function(Arc::new(IsErrFn));
crate::function_registry::register_function(Arc::new(IsNaFn));
crate::function_registry::register_function(Arc::new(IsFormulaFn));
crate::function_registry::register_function(Arc::new(IsRefFn));
crate::function_registry::register_function(Arc::new(FormulaTextFn));
crate::function_registry::register_function(Arc::new(SheetFn));
crate::function_registry::register_function(Arc::new(SheetsFn));
crate::function_registry::register_function(Arc::new(IsEvenFn));
crate::function_registry::register_function(Arc::new(IsOddFn));
crate::function_registry::register_function(Arc::new(ErrorTypeFn));
crate::function_registry::register_function(Arc::new(TypeFn));
crate::function_registry::register_function(Arc::new(NaFn));
crate::function_registry::register_function(Arc::new(NFn));
crate::function_registry::register_function(Arc::new(TFn));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_workbook::TestWorkbook;
use formualizer_parse::parser::{ASTNode, ASTNodeType};
fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
wb.interpreter()
}
#[test]
fn isnumber_numeric_and_date() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsNumberFn));
let ctx = interp(&wb);
let f = ctx.context.get_function("", "ISNUMBER").unwrap();
let num = ASTNode::new(
ASTNodeType::Literal(LiteralValue::Number(std::f64::consts::PI)),
None,
);
let date = ASTNode::new(
ASTNodeType::Literal(LiteralValue::Date(
chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
)),
None,
);
let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("x".into())), None);
let args_num = vec![crate::traits::ArgumentHandle::new(&num, &ctx)];
let args_date = vec![crate::traits::ArgumentHandle::new(&date, &ctx)];
let args_txt = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
assert_eq!(
f.dispatch(&args_num, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(true)
);
assert_eq!(
f.dispatch(&args_date, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(true)
);
assert_eq!(
f.dispatch(&args_txt, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(false)
);
}
#[test]
fn istest_and_isblank() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsTextFn));
let ctx = interp(&wb);
let f = ctx.context.get_function("", "ISTEXT").unwrap();
let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
let n = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
let args_t = vec![crate::traits::ArgumentHandle::new(&t, &ctx)];
let args_n = vec![crate::traits::ArgumentHandle::new(&n, &ctx)];
assert_eq!(
f.dispatch(&args_t, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(true)
);
assert_eq!(
f.dispatch(&args_n, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(false)
);
let wb2 = TestWorkbook::new().with_function(std::sync::Arc::new(IsBlankFn));
let ctx2 = interp(&wb2);
let f2 = ctx2.context.get_function("", "ISBLANK").unwrap();
let blank = ASTNode::new(ASTNodeType::Literal(LiteralValue::Empty), None);
let blank_args = vec![crate::traits::ArgumentHandle::new(&blank, &ctx2)];
assert_eq!(
f2.dispatch(&blank_args, &ctx2.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(true)
);
}
#[test]
fn iserror_variants() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsErrorFn));
let ctx = interp(&wb);
let f = ctx.context.get_function("", "ISERROR").unwrap();
let err = ASTNode::new(
ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div))),
None,
);
let ok = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
let a_err = vec![crate::traits::ArgumentHandle::new(&err, &ctx)];
let a_ok = vec![crate::traits::ArgumentHandle::new(&ok, &ctx)];
assert_eq!(
f.dispatch(&a_err, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(true)
);
assert_eq!(
f.dispatch(&a_ok, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Boolean(false)
);
}
#[test]
fn type_codes_basic() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TypeFn));
let ctx = interp(&wb);
let f = ctx.context.get_function("", "TYPE").unwrap();
let v_num = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
let v_txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("hi".into())), None);
let v_bool = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
let v_err = ASTNode::new(
ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
None,
);
let v_arr = ASTNode::new(
ASTNodeType::Literal(LiteralValue::Array(vec![vec![LiteralValue::Int(1)]])),
None,
);
let a_num = vec![crate::traits::ArgumentHandle::new(&v_num, &ctx)];
let a_txt = vec![crate::traits::ArgumentHandle::new(&v_txt, &ctx)];
let a_bool = vec![crate::traits::ArgumentHandle::new(&v_bool, &ctx)];
let a_err = vec![crate::traits::ArgumentHandle::new(&v_err, &ctx)];
let a_arr = vec![crate::traits::ArgumentHandle::new(&v_arr, &ctx)];
assert_eq!(
f.dispatch(&a_num, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Int(1)
);
assert_eq!(
f.dispatch(&a_txt, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Int(2)
);
assert_eq!(
f.dispatch(&a_bool, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Int(4)
);
match f
.dispatch(&a_err, &ctx.function_context(None))
.unwrap()
.into_literal()
{
LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
_ => panic!(),
}
assert_eq!(
f.dispatch(&a_arr, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Int(64)
);
}
#[test]
fn na_and_n_and_t() {
let wb = TestWorkbook::new()
.with_function(std::sync::Arc::new(NaFn))
.with_function(std::sync::Arc::new(NFn))
.with_function(std::sync::Arc::new(TFn));
let ctx = wb.interpreter();
let na_fn = ctx.context.get_function("", "NA").unwrap();
match na_fn
.eval(&[], &ctx.function_context(None))
.unwrap()
.into_literal()
{
LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
_ => panic!(),
}
let n_fn = ctx.context.get_function("", "N").unwrap();
let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
let args = vec![crate::traits::ArgumentHandle::new(&val, &ctx)];
assert_eq!(
n_fn.dispatch(&args, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Int(1)
);
let t_fn = ctx.context.get_function("", "T").unwrap();
let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
let args_t = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
assert_eq!(
t_fn.dispatch(&args_t, &ctx.function_context(None))
.unwrap()
.into_literal(),
LiteralValue::Text("abc".into())
);
}
}