use std::collections::BTreeSet;
use super::ast::{CellRef, Expr, RefBody, Reference, RowRef};
use super::eval::{Engine, EvalContext};
use super::functions::descriptor::DependencyKind;
use super::functions::{function_dependency_kind, function_result_kind};
use super::limits::CalculationLimitKind;
use super::runtime::{CellId, Rect};
use super::scope::DefinedLambdaId;
use super::value::{ErrorKind, Value};
#[derive(Debug, Clone)]
pub(super) struct FormulaCriteria {
origin: FormulaOrigin,
root: Expr,
}
#[derive(Debug, Clone, Copy)]
struct FormulaOrigin {
cell: CellId,
}
impl FormulaOrigin {
fn context<'scope>(self, context: EvalContext<'scope>) -> EvalContext<'scope> {
context
.with_cell(self.cell)
.without_bindings()
.with_defined_name_scope(None)
}
}
impl FormulaCriteria {
pub(super) fn prepare(
engine: &Engine<'_>,
context: EvalContext<'_>,
origin: CellId,
root: &Expr,
database: Rect,
) -> Result<Self, ErrorKind> {
let origin = FormulaOrigin { cell: origin };
let origin_context = origin.context(context);
let mut visited_names = BTreeSet::new();
validate_expr(
engine,
origin_context,
root,
database,
RelativeReferencePolicy::DatabaseRecord,
&mut visited_names,
)?;
Ok(Self {
origin,
root: root.clone(),
})
}
pub(super) fn evaluate(
&self,
engine: &Engine<'_>,
context: EvalContext<'_>,
row_delta: u32,
) -> Result<bool, ErrorKind> {
let mut rebased = self.root.clone();
let origin_context = self.origin.context(context);
shift_expr_rows(engine, origin_context, &mut rebased, row_delta)?;
match engine.eval_scalar(origin_context, &rebased) {
Value::Logical(value) => Ok(value),
Value::Error(kind) => Err(kind),
Value::Blank | Value::Number(_) | Value::Text(_) => Err(ErrorKind::Value),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RelativeReferencePolicy {
DatabaseRecord,
AbsoluteOnly,
}
fn validate_expr(
engine: &Engine<'_>,
context: EvalContext<'_>,
expr: &Expr,
database: Rect,
relative_policy: RelativeReferencePolicy,
visited_names: &mut BTreeSet<DefinedLambdaId>,
) -> Result<(), ErrorKind> {
charge_node(engine, context)?;
match expr {
Expr::Ref(reference) => {
validate_reference(engine, context, reference, database, relative_policy)
}
Expr::Name(name) => {
let (id, named) = engine
.resolve_name_expr_with_id_in_context(context, name)
.ok_or(ErrorKind::Value)?;
if !visited_names.insert(id.clone()) {
return Err(ErrorKind::Value);
}
let result = validate_expr(
engine,
context
.without_bindings()
.with_defined_name_scope(Some(id.scope())),
named,
database,
RelativeReferencePolicy::AbsoluteOnly,
visited_names,
);
visited_names.remove(&id);
result
}
Expr::Call { name, args } => {
if engine
.resolve_name_expr_with_id_in_context(context, name)
.is_some()
|| matches!(
function_dependency_kind(name),
Some(DependencyKind::DynamicReference(_))
)
|| function_result_kind(name).is_some_and(|kind| kind.returns_reference())
|| function_result_kind(name).is_none()
{
return Err(ErrorKind::Value);
}
for arg in args {
validate_expr(
engine,
context,
arg,
database,
relative_policy,
visited_names,
)?;
}
Ok(())
}
Expr::Unary { operand, .. }
| Expr::Paren(operand)
| Expr::ImplicitIntersection(operand) => validate_expr(
engine,
context,
operand,
database,
relative_policy,
visited_names,
),
Expr::Binary { left, right, .. }
| Expr::Range {
start: left,
end: right,
} => {
validate_expr(
engine,
context,
left,
database,
relative_policy,
visited_names,
)?;
validate_expr(
engine,
context,
right,
database,
relative_policy,
visited_names,
)
}
Expr::Array(rows) => {
for row in rows {
for value in row {
validate_expr(
engine,
context,
value,
database,
relative_policy,
visited_names,
)?;
}
}
Ok(())
}
Expr::Number(_) | Expr::Text(_) | Expr::Logical(_) | Expr::ErrorLit(_) | Expr::Missing => {
Ok(())
}
Expr::StructuredRef(_)
| Expr::ReferenceUnion { .. }
| Expr::ReferenceIntersection { .. }
| Expr::SpillRef(_)
| Expr::ExternalReference(_)
| Expr::QualifiedName { .. }
| Expr::BuiltinCallable(_)
| Expr::Invoke { .. } => Err(ErrorKind::Value),
}
}
fn validate_reference(
engine: &Engine<'_>,
context: EvalContext<'_>,
reference: &Reference,
database: Rect,
policy: RelativeReferencePolicy,
) -> Result<(), ErrorKind> {
let target = engine
.resolve_reference_span(context.sheet(), reference)?
.into_rect()
.map_err(|_| ErrorKind::Value)?;
match &reference.body {
RefBody::Cell(cell) => validate_cell_ref(*cell, target.sheet, database, policy),
RefBody::Area(start, end) => {
validate_cell_ref(*start, target.sheet, database, policy)?;
validate_cell_ref(*end, target.sheet, database, policy)
}
RefBody::Columns(start, end) => {
if !start.absolute || !end.absolute {
Err(ErrorKind::Value)
} else {
Ok(())
}
}
RefBody::Rows(start, end) => {
validate_row_ref(*start, policy).and_then(|()| validate_row_ref(*end, policy))
}
}
}
fn validate_cell_ref(
cell: CellRef,
sheet: usize,
database: Rect,
policy: RelativeReferencePolicy,
) -> Result<(), ErrorKind> {
if policy == RelativeReferencePolicy::AbsoluteOnly
&& (!cell.row_absolute || !cell.column_absolute)
{
return Err(ErrorKind::Value);
}
if cell.row_absolute && !cell.column_absolute {
return Err(ErrorKind::Value);
}
if !cell.row_absolute
&& (sheet != database.sheet
|| cell.row != database.row_start.saturating_add(1)
|| cell.column < database.col_start
|| cell.column > database.col_end)
{
return Err(ErrorKind::Value);
}
Ok(())
}
fn validate_row_ref(row: RowRef, _policy: RelativeReferencePolicy) -> Result<(), ErrorKind> {
if !row.absolute {
Err(ErrorKind::Value)
} else {
Ok(())
}
}
fn shift_expr_rows(
engine: &Engine<'_>,
context: EvalContext<'_>,
expr: &mut Expr,
row_delta: u32,
) -> Result<(), ErrorKind> {
charge_node(engine, context)?;
match expr {
Expr::Ref(reference) => shift_reference_rows(reference, row_delta),
Expr::Unary { operand, .. }
| Expr::Paren(operand)
| Expr::ImplicitIntersection(operand) => {
shift_expr_rows(engine, context, operand, row_delta)
}
Expr::Binary { left, right, .. }
| Expr::Range {
start: left,
end: right,
} => {
shift_expr_rows(engine, context, left, row_delta)?;
shift_expr_rows(engine, context, right, row_delta)
}
Expr::Call { args, .. } => {
for arg in args {
shift_expr_rows(engine, context, arg, row_delta)?;
}
Ok(())
}
Expr::Array(rows) => {
for row in rows {
for value in row {
shift_expr_rows(engine, context, value, row_delta)?;
}
}
Ok(())
}
Expr::Number(_)
| Expr::Text(_)
| Expr::Logical(_)
| Expr::ErrorLit(_)
| Expr::Name(_)
| Expr::Missing => Ok(()),
Expr::StructuredRef(_)
| Expr::ReferenceUnion { .. }
| Expr::ReferenceIntersection { .. }
| Expr::SpillRef(_)
| Expr::ExternalReference(_)
| Expr::QualifiedName { .. }
| Expr::BuiltinCallable(_)
| Expr::Invoke { .. } => Err(ErrorKind::Value),
}
}
fn shift_reference_rows(reference: &mut Reference, row_delta: u32) -> Result<(), ErrorKind> {
match &mut reference.body {
RefBody::Cell(cell) => shift_cell_row(cell, row_delta),
RefBody::Area(start, end) => {
shift_cell_row(start, row_delta)?;
shift_cell_row(end, row_delta)
}
RefBody::Columns(_, _) => Ok(()),
RefBody::Rows(start, end) => {
shift_row(start, row_delta)?;
shift_row(end, row_delta)
}
}
}
fn shift_cell_row(cell: &mut CellRef, row_delta: u32) -> Result<(), ErrorKind> {
if !cell.row_absolute {
cell.row = cell.row.checked_add(row_delta).ok_or(ErrorKind::Value)?;
if cell.row > super::EXCEL_MAX_ROWS {
return Err(ErrorKind::Value);
}
}
Ok(())
}
fn shift_row(row: &mut RowRef, row_delta: u32) -> Result<(), ErrorKind> {
if !row.absolute {
row.row = row.row.checked_add(row_delta).ok_or(ErrorKind::Value)?;
if row.row > super::EXCEL_MAX_ROWS {
return Err(ErrorKind::Value);
}
}
Ok(())
}
fn charge_node(engine: &Engine<'_>, context: EvalContext<'_>) -> Result<(), ErrorKind> {
if context.is_cancelled() {
return Err(ErrorKind::ResourceLimit(
CalculationLimitKind::FunctionIterations,
));
}
engine.charge_function_iterations(context, 1)
}