Shared Rust kernels
catalogue/conditional
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
conditional · 18 lines · calc-func/src/catalogue/conditional.rs on GitHub/// An eager named call taking `min` to `max` arguments.
const fn conditional(
id: FunctionId,
spelling: &'static str,
(min, max): (usize, usize),
parameters: &'static [Parameter],
kernel: EagerKernel,
) -> CatalogueEntry {
CatalogueEntry {
id,
spelling,
notation: Notation::Call,
arity: CallableArityShape::range(min, max),
parameters,
demand: ArgumentDemand::Eager,
kernel: Kernel::Eager(kernel),
}
}
Used by 8 functions.
total · 8 lines · calc-func/src/catalogue/conditional.rs on GitHubfn total(site: &Site<'_>, plan: &Plan, services: &mut EvalServices<'_>) -> Step<CalcValue> {
let mut total = 0.0;
plan.each_matched(site, services, &mut |n| {
total = site.add(total, n)?;
Ok(())
})?;
site.number(total)
}
Used by 4 functions.
mean · 10 lines · calc-func/src/catalogue/conditional.rs on GitHubfn mean(site: &Site<'_>, plan: &Plan, services: &mut EvalServices<'_>) -> Step<CalcValue> {
let (mut total, mut counted) = (0.0, 0u64);
plan.each_matched(site, services, &mut |n| {
total = site.add(total, n)?;
counted += 1;
Ok(())
})?;
// A count is below 2^53, so it converts to binary64 exactly.
site.number(site.divide(total, counted as f64)?)
}
Used by 2 functions.
extreme · 15 lines · calc-func/src/catalogue/conditional.rs on GitHubfn extreme(
site: &Site<'_>,
plan: &Plan,
services: &mut EvalServices<'_>,
replaces: fn(f64, f64) -> bool,
) -> Step<CalcValue> {
let mut best: Option<f64> = None;
plan.each_matched(site, services, &mut |n| {
if best.is_none_or(|best| replaces(n, best)) {
best = Some(n);
}
Ok(())
})?;
site.number(best.unwrap_or(0.0))
}
Used by 2 functions.
Range::shape · 3 lines · calc-func/src/catalogue/conditional.rs on GitHubfn shape(self) -> (u32, u32) {
(self.area.row_count(), self.area.column_count())
}
Used by 8 functions.
range · 26 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The reference argument at `index` as a range of one area.
fn range(
site: &Site<'_>,
services: &mut EvalServices<'_>,
arguments: &[CalcValue],
index: usize,
) -> Step<Range> {
let CoreRef::Reference(reference) = arguments[index].core_ref() else {
return Err(Exit::Fault(FunctionFault::ReferenceRequired {
id: site.id(),
index,
}));
};
match reference.areas() {
// dc-ylr: Excel gives the conditional family #VALUE! for a range of a closed workbook.
[area]
if services
.host()
.is_some_and(|host| host.is_external_sheet(area.sheet())) =>
{
Err(Exit::Error(WorksheetErrorCode::Value))
}
[area] => Ok(Range { index, area: *area }),
_ => Err(Exit::Error(WorksheetErrorCode::Value)),
}
}
Used by 20 functions.
single · 36 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The plan of COUNTIF (no target), SUMIF and AVERAGEIF: the range, its criterion, and the
/// target, which is the range when the third argument is omitted and otherwise the third
/// argument's top-left cell extended to the range's size, cut at the sheet's last row; one cut at
/// the last column is not modelled (see the module documentation).
fn single(
site: &Site<'_>,
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
with_target: bool,
) -> Step<Plan> {
let criteria_range = range(site, services, arguments, 0)?;
let target = match (with_target, arguments.get(2)) {
(false, _) => None,
(true, None) => Some(criteria_range),
(true, Some(_)) => {
let given = range(site, services, arguments, 2)?;
let (rows, columns) = criteria_range.shape();
let first = given.area.first();
let last_column = u64::from(first.column()) + u64::from(columns) - 1;
if last_column > u64::from(MAX_COLUMNS) {
return Err(site.not_modelled(Some(2)));
}
let last_row = (u64::from(first.row()) + u64::from(rows) - 1).min(u64::from(MAX_ROWS));
let last = CellKey::new(first.sheet(), last_row as u32, last_column as u32)
.expect("a cell on the sheet");
let area = GridArea::new(first, last).expect("an area from the given first cell");
Some(Range { index: 2, area })
}
};
let criterion = criterion(site, services, 1, &arguments[1])?;
Ok(Plan {
conditions: vec![(criteria_range, criterion)],
target,
size: criteria_range.area.cell_count(),
})
}
Used by 3 functions.
pairs · 40 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The plan of an *IFS function: its pairs from argument 1 behind the target range (argument 0)
/// when `with_target`, from argument 0 otherwise. A count of arguments that does not form pairs
/// is an arity the entry does not admit. Every range must have the first range's shape.
fn pairs(
site: &Site<'_>,
arguments: &[CalcValue],
services: &mut EvalServices<'_>,
with_target: bool,
) -> Step<Plan> {
let first_pair = usize::from(with_target);
if !(arguments.len() - first_pair).is_multiple_of(2) {
return Err(Exit::Fault(FunctionFault::ArityMismatch {
id: site.id(),
supplied: arguments.len(),
}));
}
let target = if with_target {
Some(range(site, services, arguments, 0)?)
} else {
None
};
let mut ranges = Vec::with_capacity((arguments.len() - first_pair) / 2);
for index in (first_pair..arguments.len()).step_by(2) {
ranges.push(range(site, services, arguments, index)?);
}
let shape = target.unwrap_or(ranges[0]).shape();
if ranges.iter().any(|range| range.shape() != shape) {
return Err(Exit::Error(WorksheetErrorCode::Value));
}
let mut conditions = Vec::with_capacity(ranges.len());
for range in ranges {
let index = range.index + 1;
conditions.push((range, criterion(site, services, index, &arguments[index])?));
}
Ok(Plan {
conditions,
target,
size: u64::from(shape.0) * u64::from(shape.1),
})
}
Used by 5 functions.
Plan::read_conditions · 12 lines · calc-func/src/catalogue/conditional.rs on GitHub/// Reads every criteria range, in argument order, into its verdicts.
fn read_conditions(
&self,
site: &Site<'_>,
services: &mut EvalServices<'_>,
) -> Step<Vec<Verdicts>> {
let mut all = Vec::with_capacity(self.conditions.len());
for (range, criterion) in &self.conditions {
all.push(verdicts(site, services, *range, criterion, None)?);
}
Ok(all)
}
Used by 2 functions.
Plan::each_matched · 42 lines · calc-func/src/catalogue/conditional.rs on GitHub/// Calls `take` with every number of the target in a row that every criterion matches, in
/// row-major order; the first error in such a row is the result.
fn each_matched(
&self,
site: &Site<'_>,
services: &mut EvalServices<'_>,
take: &mut dyn FnMut(f64) -> Step<()>,
) -> Step<()> {
let target = self.target.expect("a function with a target range");
let mut used: Option<Vec<(u64, Used)>> = None;
let mut conditions = Vec::with_capacity(self.conditions.len());
for (range, criterion) in &self.conditions {
let keep = used.is_none() && range.area == target.area;
let mut kept = Vec::new();
conditions.push(verdicts(
site,
services,
*range,
criterion,
keep.then_some(&mut kept),
)?);
if keep {
used = Some(kept);
}
}
let used = match used {
Some(used) => used,
None => target_cells(site, services, target)?,
};
let mut cursors = vec![0usize; conditions.len()];
for (offset, cell) in used {
match row(&conditions, &mut cursors, offset) {
Row::Yes => match cell {
Used::Number(n) => take(n)?,
Used::Error(code) => return Err(Exit::Error(code)),
},
Row::No => {}
Row::Unknown(index) => return Err(site.not_modelled(Some(index))),
}
}
Ok(())
}
Used by 6 functions.
row · 30 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The verdict of the row at `offset`, at or past every cursor's position: each range's verdict
/// at a cell it holds there, else its verdict on a blank. Moves each cursor to its first held
/// cell at or past `offset`.
fn row(conditions: &[Verdicts], cursors: &mut [usize], offset: u64) -> Row {
let mut unknown = None;
for (verdicts, cursor) in conditions.iter().zip(cursors.iter_mut()) {
while verdicts
.held
.get(*cursor)
.is_some_and(|&(at, _)| at < offset)
{
*cursor += 1;
}
let verdict = match verdicts.held.get(*cursor) {
Some(&(at, verdict)) if at == offset => verdict,
_ => verdicts.blank,
};
match verdict {
Match::No => return Row::No,
Match::Unknown => {
unknown.get_or_insert(verdicts.index);
}
Match::Yes => {}
}
}
match unknown {
Some(index) => Row::Unknown(index),
None => Row::Yes,
}
}
Used by 20 functions.
count · 37 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The positions, among `size`, at which every range's verdict is yes. The rows where some range
/// holds a value are taken one by one, in offset order; the rest are blank in every range and are
/// counted at once.
fn count(site: &Site<'_>, conditions: &[Verdicts], size: u64) -> Step<CalcValue> {
let mut cursors = vec![0usize; conditions.len()];
let (mut counted, mut held) = (0u64, 0u64);
while let Some(offset) = conditions
.iter()
.zip(&cursors)
.filter_map(|(verdicts, &cursor)| verdicts.held.get(cursor).map(|&(at, _)| at))
.min()
{
held += 1;
match row(conditions, &mut cursors, offset) {
Row::Yes => counted += 1,
Row::No => {}
Row::Unknown(index) => return Err(site.not_modelled(Some(index))),
}
for (verdicts, cursor) in conditions.iter().zip(cursors.iter_mut()) {
if verdicts
.held
.get(*cursor)
.is_some_and(|&(at, _)| at == offset)
{
*cursor += 1;
}
}
}
if conditions
.iter()
.all(|verdicts| verdicts.blank == Match::Yes)
{
counted += size - held;
}
// At most 2^34 cells, which binary64 holds exactly.
site.number(counted as f64)
}
Used by 2 functions.
offset · 6 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The row-major offset of `key` in `area`.
fn offset(area: GridArea, key: CellKey) -> u64 {
let first = area.first();
u64::from(key.row() - first.row()) * u64::from(area.column_count())
+ u64::from(key.column() - first.column())
}
Used by 20 functions.
read_range · 33 lines · calc-func/src/catalogue/conditional.rs on GitHub/// Reads `range` through the cell reader, calling `visit` with the offset and value of each cell
/// visited with a value, in increasing offset order. A reader that visits a cell outside the area
/// or out of row-major order cannot supply it.
fn read_range(
site: &Site<'_>,
services: &mut EvalServices<'_>,
range: Range,
visit: &mut dyn FnMut(u64, &CalcValue) -> Step<()>,
) -> Step<()> {
let cells = reader(site, services, range.index)?;
let reference = ReferenceLike::area(range.area);
let mut last: Option<u64> = None;
read_reference(
site,
cells,
&reference,
range.index,
&mut |area, key, value| {
if matches!(value.core_ref(), CoreRef::Empty | CoreRef::Missing) {
return Ok(());
}
let at = offset(area, key);
if last.is_some_and(|last| last >= at) {
return Err(Exit::Fault(FunctionFault::ReferenceUnreadable {
id: site.id(),
index: range.index,
}));
}
last = Some(at);
visit(at, value)
},
)
}
Used by 20 functions.
verdicts · 25 lines · calc-func/src/catalogue/conditional.rs on GitHub/// Reads a criteria range into its verdicts under `criterion`; with `kept`, also keeps the cells
/// a target uses, when the target is this range.
fn verdicts(
site: &Site<'_>,
services: &mut EvalServices<'_>,
range: Range,
criterion: &Criterion,
mut kept: Option<&mut Vec<(u64, Used)>>,
) -> Step<Verdicts> {
let mut held = Vec::new();
read_range(site, services, range, &mut |at, value| {
held.push((at, criterion.verdict(site, range.index, value)?));
if let Some(kept) = kept.as_deref_mut()
&& let Some(cell) = used(site, range.index, value)?
{
kept.push((at, cell));
}
Ok(())
})?;
Ok(Verdicts {
index: range.index,
held,
blank: criterion.verdict(site, range.index, &CalcValue::empty())?,
})
}
Used by 20 functions.
target_cells · 15 lines · calc-func/src/catalogue/conditional.rs on GitHub/// Reads the target range: the cells a matched row would use, by offset.
fn target_cells(
site: &Site<'_>,
services: &mut EvalServices<'_>,
target: Range,
) -> Step<Vec<(u64, Used)>> {
let mut kept = Vec::new();
read_range(site, services, target, &mut |at, value| {
if let Some(cell) = used(site, target.index, value)? {
kept.push((at, cell));
}
Ok(())
})?;
Ok(kept)
}
Used by 6 functions.
used · 14 lines · calc-func/src/catalogue/conditional.rs on GitHub/// What a target cell gives a matched row: a number, an error, or nothing (text, logicals and
/// blank cells are skipped).
fn used(site: &Site<'_>, index: usize, value: &CalcValue) -> Step<Option<Used>> {
match value.core_ref() {
CoreRef::Number(n) => Ok(Some(Used::Number(
site.argument(index, coerce::operand(n))?,
))),
CoreRef::Error(code) => Ok(Some(Used::Error(code))),
CoreRef::Text(_) | CoreRef::Logical(_) | CoreRef::Empty | CoreRef::Missing => {
Ok(None)
}
CoreRef::Array(_) | CoreRef::Reference(_) => Err(site.not_modelled(Some(index))),
}
}
Used by 20 functions.
Match::of · 3 lines · calc-func/src/catalogue/conditional.rs on GitHubfn of(holds: bool) -> Self {
if holds { Match::Yes } else { Match::No }
}
Used by 20 functions.
Match::negated · 7 lines · calc-func/src/catalogue/conditional.rs on GitHubfn negated(self) -> Self {
match self {
Match::Yes => Match::No,
Match::No => Match::Yes,
Match::Unknown => Match::Unknown,
}
}
Used by 20 functions.
Op::holds · 10 lines · calc-func/src/catalogue/conditional.rs on GitHubfn holds(self, ordering: Ordering) -> bool {
match self {
Op::Equal => ordering.is_eq(),
Op::NotEqual => ordering.is_ne(),
Op::Less => ordering.is_lt(),
Op::LessOrEqual => ordering.is_le(),
Op::Greater => ordering.is_gt(),
Op::GreaterOrEqual => ordering.is_ge(),
}
}
Used by 20 functions.
Op::is_equality · 3 lines · calc-func/src/catalogue/conditional.rs on GitHubfn is_equality(self) -> bool {
matches!(self, Op::Equal | Op::NotEqual)
}
Used by 20 functions.
spells · 6 lines · calc-func/src/catalogue/conditional.rs on GitHubfn spells(units: &[u16], word: &str) -> bool {
units.len() == word.len()
&& units.iter().zip(word.bytes()).all(|(&unit, byte)| {
u8::try_from(unit).is_ok_and(|unit| unit.to_ascii_uppercase() == byte)
})
}
Used by 20 functions.
criterion · 45 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The criterion argument at `index`, dereferenced when it is a reference to one cell.
fn criterion(
site: &Site<'_>,
services: &mut EvalServices<'_>,
index: usize,
argument: &CalcValue,
) -> Step<Criterion> {
let dereferenced;
let value = match argument.core_ref() {
CoreRef::Reference(reference) => {
let [area] = reference.areas() else {
return Err(site.not_modelled(Some(index)));
};
if area.cell_count() != 1 {
// A criterion of several cells lifts the function over them: not modelled.
return Err(site.not_modelled(Some(index)));
}
let mut found = CalcValue::empty();
read_range(
site,
services,
Range { index, area: *area },
&mut |_, value| {
found = value.clone();
Ok(())
},
)?;
dereferenced = found;
&dereferenced
}
_ => argument,
};
match value.core_ref() {
CoreRef::Number(n) => Ok(Criterion::Number(
Op::Equal,
site.argument(index, coerce::operand(n))?,
)),
CoreRef::Logical(b) => Ok(Criterion::Logical(Op::Equal, b)),
CoreRef::Error(code) => Ok(Criterion::Error(Op::Equal, code)),
// An empty cell given as the criterion, and an omitted criterion, are the value 0.
CoreRef::Empty | CoreRef::Missing => Ok(Criterion::Number(Op::Equal, 0.0)),
CoreRef::Text(text) => criterion_text(site, index, text.utf16_code_units()),
CoreRef::Array(_) | CoreRef::Reference(_) => Err(site.not_modelled(Some(index))),
}
}
Used by 20 functions.
database_criterion · 40 lines · calc-func/src/catalogue/conditional.rs on GitHub/// A criteria cell of a database function (DSUM and the others): a blank cell sets no condition
/// (`None`); text with no operator that is neither a number nor a logical matches the cells
/// whose text begins with it, as Microsoft's database function pages state; anything else is read
/// as a criterion of the *IFS functions.
pub(super) fn database_criterion(
site: &Site<'_>,
index: usize,
value: &CalcValue,
) -> Step<Option<Criterion>> {
match value.core_ref() {
CoreRef::Empty | CoreRef::Missing => Ok(None),
CoreRef::Text(text) if text.len_utf16_code_units() == 0 => Ok(None),
CoreRef::Text(text) => {
let units = text.utf16_code_units();
let written = [b'<', b'>', b'='].iter().any(|&b| units[0] == u16::from(b));
let criterion = criterion_text(site, index, units)?;
Ok(Some(match criterion {
Criterion::Text(Op::Equal, literal) if !written => {
let mut tokens: Vec<Token> = literal.into_iter().map(Token::Unit).collect();
tokens.push(Token::Run);
Criterion::Pattern(Op::Equal, tokens)
}
Criterion::Pattern(Op::Equal, mut tokens) if !written => {
if tokens.last() != Some(&Token::Run) {
tokens.push(Token::Run);
}
Criterion::Pattern(Op::Equal, tokens)
}
other => other,
}))
}
CoreRef::Number(n) => Ok(Some(Criterion::Number(
Op::Equal,
site.argument(index, coerce::operand(n))?,
))),
CoreRef::Logical(b) => Ok(Some(Criterion::Logical(Op::Equal, b))),
CoreRef::Error(code) => Err(Exit::Error(code)),
CoreRef::Array(_) | CoreRef::Reference(_) => Err(site.not_modelled(Some(index))),
}
}
Used by 12 functions.
rank · 4 lines · calc-func/src/catalogue/conditional.rs on GitHub/// An error's place in the order of [`ERROR_CRITERIA`], when it has a literal there.
fn rank(code: WorksheetErrorCode) -> Option<usize> {
ERROR_CRITERIA.iter().position(|(_, known)| *known == code)
}
Used by 20 functions.
criterion_text · 102 lines · calc-func/src/catalogue/conditional.rs on GitHub/// A criterion written as text; see the module documentation.
fn criterion_text(site: &Site<'_>, index: usize, units: &[u16]) -> Step<Criterion> {
if units.len() > LONGEST_TEXT {
return Err(Exit::Error(WorksheetErrorCode::Value));
}
let starts = |prefix: &str| {
units.len() >= prefix.len()
&& units
.iter()
.zip(prefix.bytes())
.all(|(&u, b)| u == u16::from(b))
};
let (op, written, rest) = if starts("<=") {
(Op::LessOrEqual, true, &units[2..])
} else if starts(">=") {
(Op::GreaterOrEqual, true, &units[2..])
} else if starts("<>") {
(Op::NotEqual, true, &units[2..])
} else if starts("<") {
(Op::Less, true, &units[1..])
} else if starts(">") {
(Op::Greater, true, &units[1..])
} else if starts("=") {
(Op::Equal, true, &units[1..])
} else {
(Op::Equal, false, units)
};
if rest.is_empty() {
return Ok(match (op, written) {
(Op::Equal, false) => Criterion::BlankOrEmptyText,
(Op::Equal, true) => Criterion::Blank,
(Op::NotEqual, _) => Criterion::NotBlank,
_ => Criterion::Never,
});
}
if let Some(&(_, code)) = ERROR_CRITERIA
.iter()
.find(|(spelling, _)| spells(rest, spelling))
{
return Ok(Criterion::Error(op, code));
}
if OTHER_ERRORS.iter().any(|spelling| spells(rest, spelling)) {
return Err(site.not_modelled(Some(index)));
}
let special = |unit: &u16| [b'*', b'?', b'~'].iter().any(|&b| *unit == u16::from(b));
if op.is_equality() && rest.iter().any(special) {
let mut tokens = Vec::with_capacity(rest.len());
let mut at = 0;
while at < rest.len() {
let unit = rest[at];
tokens.push(if unit == u16::from(b'~') {
match rest.get(at + 1) {
Some(&next) if special(&next) => {
at += 1;
Token::Unit(next)
}
// Before any other character, or at the end, ~ is itself.
_ => Token::Unit(unit),
}
} else if unit == u16::from(b'*') {
Token::Run
} else if unit == u16::from(b'?') {
Token::One
} else {
Token::Unit(unit)
});
at += 1;
}
if tokens.iter().all(|token| matches!(token, Token::Unit(_))) {
let literal = tokens
.into_iter()
.map(|token| match token {
Token::Unit(unit) => unit,
Token::One | Token::Run => unreachable!("only literal units remain"),
})
.collect();
return Ok(Criterion::Text(op, literal));
}
return Ok(Criterion::Pattern(op, tokens));
}
let operand = CalcValue::text(ExcelText::from_utf16_code_units(rest.to_vec()));
match site.number_argument(index, &operand) {
Ok(n) => Ok(Criterion::Number(op, n)),
// Text that reads as no number: no digit, out of the range Excel reads, or a word outside
// the words coercion's measured list lets number text hold (such as the label 2016-17
// Winter).
Err(Exit::Error(_)) => {
let logical = if spells(rest, TRUE_TEXT) {
Some(true)
} else if spells(rest, FALSE_TEXT) {
Some(false)
} else {
None
};
Ok(match logical {
Some(b) => Criterion::Logical(op, b),
None => Criterion::Text(op, rest.to_vec()),
})
}
Err(exit) => Err(exit),
}
}
Used by 20 functions.
decided · 9 lines · calc-func/src/catalogue/conditional.rs on GitHub/// A comparison's step as a verdict: a refusal as not modelled is an unknown verdict, and any
/// other fault (ExactUnavailable) stops the call.
fn decided(step: Step<bool>) -> Step<Match> {
match step {
Ok(holds) => Ok(Match::of(holds)),
Err(Exit::Fault(FunctionFault::NotImplemented { .. })) => Ok(Match::Unknown),
Err(exit) => Err(exit),
}
}
Used by 20 functions.
Criterion::verdict · 115 lines · calc-func/src/catalogue/conditional.rs on GitHub/// The verdict of a cell of the range at argument `index` holding `value` (Empty for a blank
/// cell).
pub(super) fn verdict(&self, site: &Site<'_>, index: usize, value: &CalcValue) -> Step<Match> {
if let CoreRef::Number(n) = value.core_ref() {
site.argument(index, coerce::operand(n))?;
}
let blank = matches!(value.core_ref(), CoreRef::Empty | CoreRef::Missing);
match self {
Criterion::BlankOrEmptyText => Ok(Match::of(
blank
|| value
.as_text()
.is_some_and(|t| t.len_utf16_code_units() == 0),
)),
Criterion::Blank => Ok(Match::of(blank)),
Criterion::NotBlank => Ok(Match::of(!blank)),
Criterion::Never => Ok(Match::No),
Criterion::Number(op, operand) => {
let cell = match value.core_ref() {
CoreRef::Number(n) => Some(n),
// Text is never a number to an ordering or to <>; only = reads it as one.
CoreRef::Text(_) if *op != Op::Equal => None,
CoreRef::Text(_) => match site.number_argument(index, value) {
Ok(n) => Some(n),
// Text that reads as no number (see the criterion's operand above).
Err(Exit::Error(_)) => None,
Err(exit) => return decided(Err(exit)),
},
_ => None,
};
let verdict = match cell {
Some(n) => decided(
comparison::numbers(site, n, *operand).map(|ordering| op.holds(ordering)),
)?,
None => Match::of(*op == Op::NotEqual),
};
Ok(verdict)
}
Criterion::Logical(op, operand) => Ok(match value.core_ref() {
CoreRef::Logical(b) => Match::of(op.holds(b.cmp(operand))),
CoreRef::Text(text)
if spells(text.utf16_code_units(), TRUE_TEXT)
|| spells(text.utf16_code_units(), FALSE_TEXT) =>
{
Match::Unknown
}
_ => Match::of(*op == Op::NotEqual),
}),
Criterion::Error(op, operand) => Ok(match value.core_ref() {
CoreRef::Error(code) => {
let ordering = if code == *operand {
Some(Ordering::Equal)
} else if op.is_equality() {
Some(Ordering::Less)
} else {
rank(code)
.zip(rank(*operand))
.map(|(code, operand)| code.cmp(&operand))
};
ordering.map_or(Match::Unknown, |ordering| Match::of(op.holds(ordering)))
}
CoreRef::Text(text)
if ERROR_CRITERIA.iter().any(|(spelling, code)| {
(code == operand || !op.is_equality())
&& spells(text.utf16_code_units(), spelling)
}) =>
{
Match::Unknown
}
_ => Match::of(*op == Op::NotEqual),
}),
Criterion::Text(op, operand) => {
let Some(text) = value.as_text() else {
return Ok(Match::of(*op == Op::NotEqual));
};
let units = text.utf16_code_units();
if units.len() > LONGEST_TEXT {
return Ok(Match::Unknown);
}
let want = if op.is_equality() {
Want::Equality
} else {
Want::Order
};
Ok(match comparison::texts(units, operand, want) {
Ok(ordering) => Match::of(op.holds(ordering)),
Err(_) => Match::Unknown,
})
}
Criterion::Pattern(op, tokens) => {
let equal = match value.as_text() {
None => Match::No,
Some(text) => {
let units = text.utf16_code_units();
if units.len() > LONGEST_TEXT {
Match::Unknown
} else if tokens.iter().all(|token| *token == Token::Run) {
Match::Yes
} else if units.iter().any(|&unit| is_surrogate(unit))
|| tokens.iter().any(
|token| matches!(token, Token::Unit(unit) if is_surrogate(*unit)),
)
{
// A pattern over text holding a character beyond the BMP: whether ?
// takes one character or one code unit there is not modelled.
Match::Unknown
} else {
Match::of(wildcard(tokens, units))
}
}
};
Ok(equality(*op, equal))
}
}
}
Used by 20 functions.
equality · 8 lines · calc-func/src/catalogue/conditional.rs on GitHub/// An equality verdict under `op`, which is = or <>.
fn equality(op: Op, equal: Match) -> Match {
if op == Op::NotEqual {
equal.negated()
} else {
equal
}
}
Used by 20 functions.
is_surrogate · 3 lines · calc-func/src/catalogue/conditional.rs on GitHubfn is_surrogate(unit: u16) -> bool {
(0xD800..=0xDFFF).contains(&unit)
}
Used by 20 functions.
wildcard · 38 lines · calc-func/src/catalogue/conditional.rs on GitHub/// Whether `text` (no surrogates) matches `pattern`, case ignored character by character through
/// the simple lower-case mapping: Excel 16.0.20430 matched é* to Élan, ασ* to ΑΣ and dž* to Džemal,
/// and matched neither *SSE to Straße nor nai* to naïve (read ad hoc on this machine, dc-ip6).
fn wildcard(pattern: &[Token], text: &[u16]) -> bool {
let fold = crate::ported::text_kernels::fold_unit;
let (mut p, mut t) = (0, 0);
// The last * met: its position in the pattern and the text position its run ends at.
let mut star: Option<(usize, usize)> = None;
while t < text.len() {
match pattern.get(p) {
Some(Token::Run) => {
star = Some((p, t));
p += 1;
continue;
}
Some(Token::One) => {
p += 1;
t += 1;
continue;
}
Some(Token::Unit(unit)) if fold(*unit) == fold(text[t]) => {
p += 1;
t += 1;
continue;
}
_ => {}
}
match star {
Some((at, end)) => {
p = at + 1;
t = end + 1;
star = Some((at, end + 1));
}
None => return false,
}
}
pattern[p..].iter().all(|token| *token == Token::Run)
}
Used by 20 functions.