use super::FormulaExecutor;
use logisheets_base::errors::BasicError;
use logisheets_base::{
BlockId, BlockRange, CellId, EphemeralId, NormalRange, Range, RangeId, RefAbs, SheetId,
};
use logisheets_parser::ast::{self, RangeDisplay};
use logisheets_parser::Parser;
use std::collections::HashSet;
use crate::formula_manager::{ctx::FormulaExecCtx, FormulaManager, Vertex};
pub fn input_formula<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet_idx: usize,
row: usize,
col: usize,
formula: String,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let sheet = ctx
.fetch_sheet_id_by_index(sheet_idx)
.map_err(|l| BasicError::SheetIdxExceed(l))?;
let cell_id = ctx.fetch_cell_id(&sheet, row, col)?;
input(executor, sheet, cell_id, formula, None, ctx)
}
pub fn input_block_formula<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet_idx: usize,
block_id: BlockId,
block_row: usize,
block_col: usize,
formula: String,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let sheet = ctx
.fetch_sheet_id_by_index(sheet_idx)
.map_err(|l| BasicError::SheetIdxExceed(l))?;
let bcid = ctx.fetch_block_cell_id(&sheet, &block_id, block_row, block_col)?;
input(executor, sheet, CellId::BlockCell(bcid), formula, None, ctx)
}
pub fn input_ephemeral_formula<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet_idx: usize,
id: EphemeralId,
formula: String,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let sheet = ctx
.fetch_sheet_id_by_index(sheet_idx)
.map_err(|l| BasicError::SheetIdxExceed(l))?;
if let Some((sheet_id, cell_id)) = ctx.get_cell_id_by_shadow_id(&id) {
let range = match cell_id {
CellId::NormalCell(normal_cell_id) => {
Range::Normal(NormalRange::Single(normal_cell_id))
}
CellId::BlockCell(block_cell_id) => Range::Block(BlockRange::Single(block_cell_id)),
CellId::EphemeralCell(_) => unreachable!(),
};
let range_id = ctx.fetch_range_id(&sheet_id, &range);
let placeholder_node = ast::Node {
pure: ast::PureNode::Reference(ast::CellReference::Mut(RangeDisplay {
range_id,
ref_abs: RefAbs::default(),
sheet_id,
})),
bracket: true,
};
if let CellId::BlockCell(block_cell_id) = cell_id {
if let Some(row_ctx) = ctx.block_cell_row_substitutes(sheet_id, &block_cell_id) {
use std::collections::HashMap;
let mut sib_subs: HashMap<String, ast::Node> = HashMap::new();
for (name, sib_cell) in row_ctx.siblings {
let sib_range = Range::Block(BlockRange::Single(sib_cell));
let sib_range_id = ctx.fetch_range_id(&sheet_id, &sib_range);
sib_subs.insert(
name,
ast::Node {
pure: ast::PureNode::Reference(ast::CellReference::Mut(RangeDisplay {
range_id: sib_range_id,
ref_abs: RefAbs::default(),
sheet_id,
})),
bracket: true,
},
);
}
let key_node = ast::Node {
pure: ast::PureNode::Value(ast::Value::Text(row_ctx.key_value)),
bracket: false,
};
return input_with_resolver(
executor,
sheet,
CellId::EphemeralCell(id),
formula,
placeholder_node,
sib_subs,
key_node,
ctx,
);
}
}
input(
executor,
sheet,
CellId::EphemeralCell(id),
formula,
Some(placeholder_node),
ctx,
)
} else {
let cell_id = CellId::EphemeralCell(id);
input(executor, sheet, cell_id, formula, None, ctx)
}
}
fn input_with_resolver<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet: SheetId,
cell_id: CellId,
formula: String,
placeholder: ast::Node,
sib_subs: std::collections::HashMap<String, ast::Node>,
key_node: ast::Node,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let parser = Parser {};
let ast = parser.parse_with_substitutes(&formula, sheet, ctx, &|kind| match kind {
logisheets_parser::PlaceholderKind::Placeholder => Some(placeholder.clone()),
logisheets_parser::PlaceholderKind::FieldRef(name) => sib_subs.get(name).cloned(),
logisheets_parser::PlaceholderKind::Key => Some(key_node.clone()),
});
let Some(ast) = ast else { return Ok(executor) };
register_parsed_ast(executor, sheet, cell_id, ast, ctx)
}
pub fn remove_formula<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet_idx: usize,
row: usize,
col: usize,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let sheet = ctx
.fetch_sheet_id_by_index(sheet_idx)
.map_err(|l| BasicError::SheetIdxExceed(l))?;
let cell_id = ctx.fetch_cell_id(&sheet, row, col)?;
remove(executor, sheet, cell_id, ctx)
}
pub fn remove_ephemeral_formula<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet_idx: usize,
id: EphemeralId,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let sheet = ctx
.fetch_sheet_id_by_index(sheet_idx)
.map_err(|l| BasicError::SheetIdxExceed(l))?;
remove(executor, sheet, CellId::EphemeralCell(id), ctx)
}
fn remove<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet: SheetId,
cell_id: CellId,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let range = match cell_id {
CellId::NormalCell(normal) => Range::Normal(NormalRange::Single(normal)),
CellId::BlockCell(block) => Range::Block(BlockRange::Single(block)),
CellId::EphemeralCell(v) => Range::Ephemeral(v),
};
let range_id = ctx.fetch_range_id(&sheet, &range);
let this_vertex = Vertex::Range(sheet, range_id);
let FormulaManager {
mut graph,
mut formulas,
names,
} = executor.manager;
if let Some(old_formula_deps) = graph.clone().get_deps(&this_vertex) {
old_formula_deps.iter().for_each(|old_dep| {
graph.remove_dep(&this_vertex, old_dep);
})
};
if let CellId::BlockCell(bcid) = cell_id {
graph.remove_dep(&Vertex::BlockAll(sheet, bcid.block_id), &this_vertex);
}
formulas.remove(&(sheet, cell_id));
Ok(FormulaExecutor {
manager: FormulaManager {
graph,
formulas,
names,
},
dirty_vertices: executor.dirty_vertices,
dirty_ranges: executor.dirty_ranges,
dirty_cubes: executor.dirty_cubes,
dirty_blocks: executor.dirty_blocks,
trigger: executor.trigger,
})
}
fn input<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet: SheetId,
cell_id: CellId,
formula: String,
substitute: Option<ast::Node>,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let parser = Parser {};
let ast = if let Some(node) = substitute {
parser.parse_with_substitude(&formula, &node, sheet, ctx)
} else {
parser.parse(&formula, sheet, ctx)
};
let Some(ast) = ast else { return Ok(executor) };
register_parsed_ast(executor, sheet, cell_id, ast, ctx)
}
fn register_parsed_ast<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet: SheetId,
cell_id: CellId,
ast: ast::Node,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
let range = match cell_id {
CellId::NormalCell(normal) => Range::Normal(NormalRange::Single(normal)),
CellId::BlockCell(block) => Range::Block(BlockRange::Single(block)),
CellId::EphemeralCell(v) => Range::Ephemeral(v),
};
let range_id = ctx.fetch_range_id(&sheet, &range);
let this_vertex = Vertex::Range(sheet, range_id);
if let CellId::BlockCell(bcid) = cell_id {
if let Some(kind) = find_self_block_ref(&ast, sheet, bcid.block_id) {
return Err(BasicError::InvalidFormula(format!(
"{} targeting the cell's own block (sheet={}, block={}) \
is not allowed; use `#FIELD(\"...\")` for same-row \
sibling refs.",
kind, sheet, bcid.block_id
)));
}
}
let mut new_formula_deps = HashSet::<Vertex>::new();
get_all_vertices_from_ast(&ast, &mut new_formula_deps);
let FormulaManager {
mut graph,
mut formulas,
names,
} = executor.manager;
if let Some(old_formula_deps) = graph.clone().get_deps(&this_vertex) {
old_formula_deps.iter().for_each(|old_dep| {
graph.remove_dep(&this_vertex, old_dep);
})
};
new_formula_deps.into_iter().for_each(|new_dep| {
graph.add_dep(this_vertex.clone(), new_dep.clone());
let range_deps = ctx.get_range_deps(&new_dep);
range_deps.into_iter().for_each(|range_dep| {
graph.add_dep(new_dep.clone(), range_dep);
});
});
if let CellId::BlockCell(bcid) = cell_id {
graph.add_dep(Vertex::BlockAll(sheet, bcid.block_id), this_vertex.clone());
}
formulas.insert((sheet, cell_id), ast.clone());
let mut dirty_vertices = executor.dirty_vertices;
dirty_vertices.insert(this_vertex);
Ok(FormulaExecutor {
manager: FormulaManager {
graph,
formulas,
names,
},
dirty_vertices,
dirty_ranges: executor.dirty_ranges,
dirty_cubes: executor.dirty_cubes,
dirty_blocks: executor.dirty_blocks,
trigger: executor.trigger,
})
}
pub fn input_block_cell_template<C: FormulaExecCtx>(
executor: FormulaExecutor,
sheet_idx: usize,
block_id: BlockId,
block_row: usize,
block_col: usize,
ctx: &mut C,
) -> Result<FormulaExecutor, BasicError> {
use std::collections::HashMap;
let sheet = ctx
.fetch_sheet_id_by_index(sheet_idx)
.map_err(|l| BasicError::SheetIdxExceed(l))?;
let bcid = ctx.fetch_block_cell_id(&sheet, &block_id, block_row, block_col)?;
let Some(tpl) = ctx.block_cell_template(sheet, &bcid) else {
return Ok(executor);
};
let mut sib_subs: HashMap<String, ast::Node> = HashMap::new();
for (name, sib_cell) in tpl.siblings {
let range = Range::Block(BlockRange::Single(sib_cell));
let range_id = ctx.fetch_range_id(&sheet, &range);
sib_subs.insert(
name,
ast::Node {
pure: ast::PureNode::Reference(ast::CellReference::Mut(RangeDisplay {
range_id,
ref_abs: RefAbs::default(),
sheet_id: sheet,
})),
bracket: true,
},
);
}
let key_node = ast::Node {
pure: ast::PureNode::Value(ast::Value::Text(tpl.key_value)),
bracket: false,
};
let body = tpl
.template
.strip_prefix('=')
.map(|s| s.to_string())
.unwrap_or(tpl.template);
let parser = Parser {};
let ast = parser.parse_with_substitutes(&body, sheet, ctx, &|kind| match kind {
logisheets_parser::PlaceholderKind::FieldRef(name) => sib_subs.get(name).cloned(),
logisheets_parser::PlaceholderKind::Key => Some(key_node.clone()),
logisheets_parser::PlaceholderKind::Placeholder => None,
});
let Some(ast) = ast else { return Ok(executor) };
register_parsed_ast(executor, sheet, CellId::BlockCell(bcid), ast, ctx)
}
pub fn add_ast_node(
manager: &mut FormulaManager,
sheet_id: SheetId,
cell_id: CellId,
range_id: RangeId,
ast: ast::Node,
) {
let this_vertex = Vertex::Range(sheet_id, range_id);
let mut new_formula_deps = HashSet::<Vertex>::new();
get_all_vertices_from_ast(&ast, &mut new_formula_deps);
manager.formulas.insert((sheet_id, cell_id), ast);
new_formula_deps
.into_iter()
.for_each(|new_dep| manager.graph.add_dep(this_vertex.clone(), new_dep));
}
fn find_self_block_ref(
ast: &ast::Node,
self_sheet: SheetId,
self_block: BlockId,
) -> Option<&'static str> {
match &ast.pure {
ast::PureNode::Func(func) => func
.args
.iter()
.find_map(|n| find_self_block_ref(n, self_sheet, self_block)),
ast::PureNode::Value(_) => None,
ast::PureNode::Reference(_) => None,
ast::PureNode::BlockRef(node) => match node {
ast::BlockRefNode::Single {
sheet_id,
block_id,
key,
..
} => {
if *sheet_id == self_sheet && *block_id == self_block {
Some("BLOCKREF")
} else {
find_self_block_ref(key, self_sheet, self_block)
}
}
ast::BlockRefNode::Multi {
sheet_id,
block_id,
key_condition,
field_condition,
..
} => {
if *sheet_id == self_sheet && *block_id == self_block {
Some("BLOCKREFS")
} else {
find_self_block_ref(key_condition, self_sheet, self_block)
.or_else(|| find_self_block_ref(field_condition, self_sheet, self_block))
}
}
},
}
}
fn get_all_vertices_from_ast(ast: &ast::Node, vertices: &mut HashSet<Vertex>) {
match &ast.pure {
ast::PureNode::Func(func) => {
func.args
.iter()
.for_each(|n| get_all_vertices_from_ast(n, vertices));
}
ast::PureNode::Value(_) => {}
ast::PureNode::Reference(reference) => match reference {
ast::CellReference::Mut(r) => {
let sheet_id = r.sheet_id;
let range_id = r.range_id;
let vertex = Vertex::Range(sheet_id, range_id);
vertices.insert(vertex);
}
ast::CellReference::UnMut(c) => {
let cube_id = c.cube_id;
let vertex = Vertex::Cube(cube_id);
vertices.insert(vertex);
}
ast::CellReference::Ext(ext_ref) => {
let ext_ref_id = ext_ref.ext_ref_id;
let vertex = Vertex::Ext(ext_ref_id);
vertices.insert(vertex);
}
ast::CellReference::Name(name) => {
let vertex = Vertex::Name(*name);
vertices.insert(vertex);
}
ast::CellReference::RefErr => {}
},
ast::PureNode::BlockRef(node) => match node {
ast::BlockRefNode::Single {
sheet_id,
block_id,
field_id,
key,
..
} => {
vertices.insert(Vertex::Block(*sheet_id, *block_id, *field_id));
vertices.insert(Vertex::BlockKey(*sheet_id, *block_id));
vertices.insert(Vertex::BlockAll(*sheet_id, *block_id));
get_all_vertices_from_ast(key, vertices);
}
ast::BlockRefNode::Multi {
sheet_id,
block_id,
key_condition,
field_condition,
..
} => {
vertices.insert(Vertex::BlockAll(*sheet_id, *block_id));
vertices.insert(Vertex::BlockKey(*sheet_id, *block_id));
get_all_vertices_from_ast(key_condition, vertices);
get_all_vertices_from_ast(field_condition, vertices);
}
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_all_vertices_from_ast() {
let sum = ast::Node {
pure: ast::PureNode::Func(ast::Func {
op: ast::Operator::Infix(ast::InfixOperator::Plus),
args: vec![
ast::Node {
pure: ast::PureNode::Value(ast::Value::Number(1.0)),
bracket: false,
},
ast::Node {
pure: ast::PureNode::Reference(ast::CellReference::Mut(
ast::RangeDisplay {
sheet_id: 0,
range_id: 0,
ref_abs: RefAbs::from_addr(false, false),
},
)),
bracket: false,
},
],
}),
bracket: false,
};
let ast = ast::Node {
pure: ast::PureNode::Func(ast::Func {
op: ast::Operator::Infix(ast::InfixOperator::Multiply),
args: vec![
sum,
ast::Node {
pure: ast::PureNode::Reference(ast::CellReference::Mut(
ast::RangeDisplay {
sheet_id: 0,
range_id: 1,
ref_abs: RefAbs::from_addr(false, false),
},
)),
bracket: false,
},
],
}),
bracket: false,
};
let mut vertices = HashSet::<Vertex>::new();
get_all_vertices_from_ast(&ast, &mut vertices);
assert_eq!(vertices.len(), 2);
}
}