use super::super::utils::{ARG_ANY_ONE, ARG_ANY_TWO, coerce_num};
use crate::args::ArgSchema;
use crate::function::Function;
use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
use formualizer_macros::func_caps;
fn scalar_like_value(arg: &ArgumentHandle<'_, '_>) -> Result<LiteralValue, ExcelError> {
Ok(match arg.value()? {
CalcValue::Scalar(v) => v,
CalcValue::Range(rv) => rv.get_cell(0, 0),
CalcValue::Callable(_) => LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
),
})
}
#[derive(Debug)]
pub struct CharFn;
impl Function for CharFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"CHAR"
}
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>],
_: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
let v = scalar_like_value(&args[0])?;
let n = match v {
LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
other => coerce_num(&other)?,
};
let code = n.trunc() as i32;
if !(1..=255).contains(&code) {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let unicode_char = match code as u8 {
0x80 => '\u{20AC}', 0x82 => '\u{201A}', 0x83 => '\u{0192}', 0x84 => '\u{201E}', 0x85 => '\u{2026}', 0x86 => '\u{2020}', 0x87 => '\u{2021}', 0x88 => '\u{02C6}', 0x89 => '\u{2030}', 0x8A => '\u{0160}', 0x8B => '\u{2039}', 0x8C => '\u{0152}', 0x8E => '\u{017D}', 0x91 => '\u{2018}', 0x92 => '\u{2019}', 0x93 => '\u{201C}', 0x94 => '\u{201D}', 0x95 => '\u{2022}', 0x96 => '\u{2013}', 0x97 => '\u{2014}', 0x98 => '\u{02DC}', 0x99 => '\u{2122}', 0x9A => '\u{0161}', 0x9B => '\u{203A}', 0x9C => '\u{0153}', 0x9E => '\u{017E}', 0x9F => '\u{0178}', 0x81 | 0x8D | 0x8F | 0x90 | 0x9D => {
'\u{FFFD}'
}
c => char::from(c),
};
Ok(CalcValue::Scalar(LiteralValue::Text(
unicode_char.to_string(),
)))
}
}
#[derive(Debug)]
pub struct CodeFn;
impl Function for CodeFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"CODE"
}
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>],
_: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
let v = scalar_like_value(&args[0])?;
let s = match v {
LiteralValue::Text(t) => t,
LiteralValue::Empty => {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
other => other.to_string(),
};
if s.is_empty() {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let first_char = s.chars().next().unwrap();
let code = match first_char {
'\u{20AC}' => 0x80, '\u{201A}' => 0x82, '\u{0192}' => 0x83, '\u{201E}' => 0x84, '\u{2026}' => 0x85, '\u{2020}' => 0x86, '\u{2021}' => 0x87, '\u{02C6}' => 0x88, '\u{2030}' => 0x89, '\u{0160}' => 0x8A, '\u{2039}' => 0x8B, '\u{0152}' => 0x8C, '\u{017D}' => 0x8E, '\u{2018}' => 0x91, '\u{2019}' => 0x92, '\u{201C}' => 0x93, '\u{201D}' => 0x94, '\u{2022}' => 0x95, '\u{2013}' => 0x96, '\u{2014}' => 0x97, '\u{02DC}' => 0x98, '\u{2122}' => 0x99, '\u{0161}' => 0x9A, '\u{203A}' => 0x9B, '\u{0153}' => 0x9C, '\u{017E}' => 0x9E, '\u{0178}' => 0x9F, c if (c as u32) < 256 => c as i64,
c => c as i64, };
Ok(CalcValue::Scalar(LiteralValue::Int(code)))
}
}
fn asc_convert(text: &str) -> String {
text.chars()
.map(|c| {
let cp = c as u32;
if cp == 0x3000 {
' '
} else if (0xFF01..=0xFF5E).contains(&cp) {
char::from_u32(cp - 0xFF01 + 0x21).unwrap_or(c)
} else {
c
}
})
.collect()
}
#[derive(Debug)]
pub struct AscFn;
impl Function for AscFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ASC"
}
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>],
_: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let v = scalar_like_value(&args[0])?;
let s = match v {
LiteralValue::Text(t) => t,
LiteralValue::Empty => String::new(),
LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
other => other.to_string(),
};
Ok(CalcValue::Scalar(LiteralValue::Text(asc_convert(&s))))
}
}
#[derive(Debug)]
pub struct ReptFn;
impl Function for ReptFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"REPT"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
let text_val = scalar_like_value(&args[0])?;
let count_val = scalar_like_value(&args[1])?;
let text = match text_val {
LiteralValue::Text(t) => t,
LiteralValue::Empty => String::new(),
LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
other => other.to_string(),
};
let count = match count_val {
LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
other => coerce_num(&other)?,
};
let count = count.trunc() as i64;
if count < 0 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let max_result_len = 32767;
let result_len = text.len() * (count as usize);
if result_len > max_result_len {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let result = text.repeat(count as usize);
Ok(CalcValue::Scalar(LiteralValue::Text(result)))
}
}
pub fn register_builtins() {
use std::sync::Arc;
crate::function_registry::register_function(Arc::new(CharFn));
crate::function_registry::register_function(Arc::new(CodeFn));
crate::function_registry::register_function(Arc::new(AscFn));
crate::function_registry::register_function(Arc::new(ReptFn));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_workbook::TestWorkbook;
use crate::traits::ArgumentHandle;
use formualizer_parse::parser::{ASTNode, ASTNodeType};
fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
wb.interpreter()
}
fn lit(v: LiteralValue) -> ASTNode {
ASTNode::new(ASTNodeType::Literal(v), None)
}
#[test]
fn char_basic() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CharFn));
let ctx = interp(&wb);
let n = lit(LiteralValue::Number(65.0));
let f = ctx.context.get_function("", "CHAR").unwrap();
assert_eq!(
f.dispatch(
&[ArgumentHandle::new(&n, &ctx)],
&ctx.function_context(None)
)
.unwrap()
.into_literal(),
LiteralValue::Text("A".to_string())
);
}
#[test]
fn code_basic() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CodeFn));
let ctx = interp(&wb);
let s = lit(LiteralValue::Text("A".to_string()));
let f = ctx.context.get_function("", "CODE").unwrap();
assert_eq!(
f.dispatch(
&[ArgumentHandle::new(&s, &ctx)],
&ctx.function_context(None)
)
.unwrap()
.into_literal(),
LiteralValue::Int(65)
);
}
#[test]
fn asc_converts_full_width_ascii_and_space() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AscFn));
let ctx = interp(&wb);
let s = lit(LiteralValue::Text("ABC123! x".to_string()));
let f = ctx.context.get_function("", "ASC").unwrap();
assert_eq!(
f.dispatch(
&[ArgumentHandle::new(&s, &ctx)],
&ctx.function_context(None)
)
.unwrap()
.into_literal(),
LiteralValue::Text("ABC123! x".to_string())
);
}
#[test]
fn rept_basic() {
let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ReptFn));
let ctx = interp(&wb);
let s = lit(LiteralValue::Text("ab".to_string()));
let n = lit(LiteralValue::Number(3.0));
let f = ctx.context.get_function("", "REPT").unwrap();
assert_eq!(
f.dispatch(
&[ArgumentHandle::new(&s, &ctx), ArgumentHandle::new(&n, &ctx)],
&ctx.function_context(None)
)
.unwrap()
.into_literal(),
LiteralValue::Text("ababab".to_string())
);
}
}