use crate::binding::{
BoundPort, ManifestBindings, PortBinding, RangeBinding, RecordBinding, RecordFieldBinding,
ScalarBinding, TableBinding,
};
use crate::context::WorkbookContext;
use crate::error::SheetPortError;
use crate::layout::{resolve_range_layout, resolve_table_layout};
use crate::validation::{ValidationScope, validate_port_value};
use crate::value::{InputSnapshot, InputUpdate, OutputSnapshot, PortValue, TableRow, TableValue};
use crate::{BatchExecutor, BatchOptions};
use formualizer_common::{LiteralValue, RangeAddress};
use formualizer_eval::engine::RecalcPlan;
use formualizer_eval::traits::VolatileLevel;
use formualizer_workbook::Workbook;
use sheetport_spec::{Direction, Manifest};
use std::collections::{BTreeMap, BTreeSet};
struct GridWrite<'a> {
port_id: &'a str,
sheet: &'a str,
start_row: u32,
start_col: u32,
height: u32,
width: u32,
grid: Vec<Vec<LiteralValue>>,
}
pub struct SheetPort<'a> {
workbook: &'a mut Workbook,
bindings: ManifestBindings,
}
#[derive(Debug, Clone)]
pub struct EvalOptions {
pub freeze_volatile: bool,
pub rng_seed: Option<u64>,
pub mode: EvalMode,
pub deterministic_mode: Option<formualizer_eval::engine::DeterministicMode>,
}
impl Default for EvalOptions {
fn default() -> Self {
Self {
freeze_volatile: false,
rng_seed: None,
mode: EvalMode::Full,
deterministic_mode: None,
}
}
}
#[derive(Debug, Clone)]
pub enum EvalMode {
Full,
}
impl<'a> SheetPort<'a> {
pub fn new(workbook: &'a mut Workbook, manifest: Manifest) -> Result<Self, SheetPortError> {
let bindings = ManifestBindings::new(manifest)?;
let ctx = WorkbookContext::new(&*workbook);
ctx.validate(&bindings)?;
Ok(Self { workbook, bindings })
}
pub fn from_bindings(
workbook: &'a mut Workbook,
bindings: ManifestBindings,
) -> Result<Self, SheetPortError> {
let ctx = WorkbookContext::new(&*workbook);
ctx.validate(&bindings)?;
Ok(Self { workbook, bindings })
}
pub fn workbook(&self) -> &Workbook {
&*self.workbook
}
pub fn workbook_mut(&mut self) -> &mut Workbook {
&mut *self.workbook
}
pub fn manifest(&self) -> &Manifest {
self.bindings.manifest()
}
pub fn bindings(&self) -> &[PortBinding] {
self.bindings.bindings()
}
pub fn into_parts(self) -> (&'a mut Workbook, ManifestBindings) {
(self.workbook, self.bindings)
}
pub fn read_inputs(&mut self) -> Result<InputSnapshot, SheetPortError> {
let bindings: Vec<PortBinding> = self
.bindings
.bindings()
.iter()
.filter(|binding| binding.direction == Direction::In)
.cloned()
.collect();
let mut map = BTreeMap::new();
for binding in bindings.iter() {
let value = self.read_port_value(binding)?;
map.insert(binding.id.clone(), value);
}
Ok(InputSnapshot::new(map))
}
pub fn read_outputs(&mut self) -> Result<OutputSnapshot, SheetPortError> {
let bindings: Vec<PortBinding> = self
.bindings
.bindings()
.iter()
.filter(|binding| binding.direction == Direction::Out)
.cloned()
.collect();
let mut map = BTreeMap::new();
for binding in bindings.iter() {
let value = self.read_port_value(binding)?;
map.insert(binding.id.clone(), value);
}
Ok(OutputSnapshot::new(map))
}
pub fn write_inputs(&mut self, update: InputUpdate) -> Result<(), SheetPortError> {
for (port_id, value) in update.into_inner() {
let binding =
self.bindings
.get(&port_id)
.ok_or_else(|| SheetPortError::InvariantViolation {
port: port_id.clone(),
message: "unknown port".to_string(),
})?;
if binding.direction != Direction::In {
return Err(SheetPortError::InvariantViolation {
port: port_id,
message: "cannot write to output port".to_string(),
});
}
let scope = match &binding.kind {
BoundPort::Record(_) => ValidationScope::Partial,
_ => ValidationScope::Full,
};
if let Err(violations) = validate_port_value(binding, &value, scope) {
return Err(SheetPortError::ConstraintViolation { violations });
}
let binding_clone = binding.clone();
self.write_port_value(&binding_clone, value)?;
}
Ok(())
}
pub fn evaluate_once(
&mut self,
options: EvalOptions,
) -> Result<OutputSnapshot, SheetPortError> {
let restore = self.apply_eval_options(&options)?;
let result = (|| -> Result<OutputSnapshot, SheetPortError> {
if self.outputs_require_full_eval() {
self.workbook.prepare_graph_all()?;
self.workbook.evaluate_all()?;
return self.read_outputs();
}
let target_specs = self.collect_output_targets()?;
if target_specs.is_empty() {
self.workbook.prepare_graph_all()?;
self.workbook.evaluate_all()?;
} else {
let mut sheets: BTreeSet<&str> = BTreeSet::new();
for (sheet, _, _) in target_specs.iter() {
sheets.insert(sheet.as_str());
}
self.workbook
.prepare_graph_for_sheets(sheets.iter().copied())?;
let borrowed: Vec<(&str, u32, u32)> = target_specs
.iter()
.map(|(sheet, row, col)| (sheet.as_str(), *row, *col))
.collect();
if self.workbook.evaluate_cells(&borrowed).is_err() {
self.workbook.prepare_graph_all()?;
self.workbook.evaluate_all()?;
}
}
self.read_outputs()
})();
self.restore_eval_options(restore);
result
}
fn outputs_require_full_eval(&self) -> bool {
self.bindings
.bindings()
.iter()
.filter(|binding| binding.direction == Direction::Out)
.any(|binding| match &binding.kind {
BoundPort::Scalar(scalar) => {
matches!(scalar.location, crate::location::ScalarLocation::Name(_))
}
BoundPort::Record(record) => record
.fields
.values()
.any(|field| matches!(field.location, crate::location::FieldLocation::Name(_))),
BoundPort::Range(range) => {
matches!(range.location, crate::location::AreaLocation::Name(_))
}
BoundPort::Table(table) => {
matches!(table.location, crate::location::TableLocation::Table(_))
}
})
}
fn collect_output_targets(&mut self) -> Result<Vec<(String, u32, u32)>, SheetPortError> {
let mut targets: BTreeSet<(String, u32, u32)> = BTreeSet::new();
let output_bindings: Vec<PortBinding> = self
.bindings
.bindings()
.iter()
.filter(|binding| binding.direction == Direction::Out)
.cloned()
.collect();
for binding in output_bindings.iter() {
self.collect_binding_targets(binding, &mut targets)?;
}
Ok(targets.into_iter().collect())
}
fn collect_binding_targets(
&mut self,
binding: &PortBinding,
targets: &mut BTreeSet<(String, u32, u32)>,
) -> Result<(), SheetPortError> {
match &binding.kind {
BoundPort::Scalar(scalar) => match &scalar.location {
crate::location::ScalarLocation::Cell(addr) => {
Self::add_range_cells(targets, addr);
Ok(())
}
crate::location::ScalarLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
if addr.height() != 1 || addr.width() != 1 {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"named range `{name}` must resolve to a single cell for scalar ports"
),
});
}
Self::add_range_cells(targets, &addr);
Ok(())
}
crate::location::ScalarLocation::StructRef(struct_ref) => {
Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!(
"scalar selectors using structured reference `{struct_ref}` \
are not supported yet"
),
})
}
},
BoundPort::Record(record) => {
for (field_name, field_binding) in &record.fields {
match &field_binding.location {
crate::location::FieldLocation::Cell(addr) => {
Self::add_range_cells(targets, addr);
}
crate::location::FieldLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
if addr.height() != 1 || addr.width() != 1 {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"record field `{field_name}` named range `{name}` \
must resolve to a single cell"
),
});
}
Self::add_range_cells(targets, &addr);
}
crate::location::FieldLocation::StructRef(struct_ref) => {
return Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!(
"record field `{field_name}` uses unsupported selector `{struct_ref}`"
),
});
}
}
}
Ok(())
}
BoundPort::Range(range) => match &range.location {
crate::location::AreaLocation::Range(addr) => {
Self::add_range_cells(targets, addr);
Ok(())
}
crate::location::AreaLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
Self::add_range_cells(targets, &addr);
Ok(())
}
crate::location::AreaLocation::Layout(layout) => {
let bounds = resolve_range_layout(&binding.id, self.workbook, layout)?;
let start_row = bounds.start_row;
let end_row = bounds.end_row.max(bounds.start_row);
let start_col = bounds.start_col;
let end_col = bounds.end_col.max(bounds.start_col);
let sheet = bounds.sheet.clone();
let addr =
RangeAddress::new(sheet.clone(), start_row, start_col, end_row, end_col)
.map_err(|msg| SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: msg.to_string(),
})?;
Self::add_range_cells(targets, &addr);
match layout.terminate {
sheetport_spec::LayoutTermination::FirstBlankRow
| sheetport_spec::LayoutTermination::UntilMarker => {
let sentinel_row = if end_row >= start_row {
end_row.saturating_add(1)
} else {
start_row
};
for col in start_col..=end_col {
Self::add_cell(targets, sheet.as_str(), sentinel_row, col);
}
}
sheetport_spec::LayoutTermination::SheetEnd => {}
}
Ok(())
}
other => Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!("unsupported area selector `{other:?}` for range port"),
}),
},
BoundPort::Table(table) => match &table.location {
crate::location::TableLocation::Layout(layout) => {
let column_hints: Vec<Option<String>> = table
.columns
.iter()
.map(|c| c.column_hint.clone())
.collect();
let bounds =
resolve_table_layout(&binding.id, self.workbook, layout, &column_hints)?;
let header_row = layout.header_row;
for &col in &bounds.column_indices {
Self::add_cell(targets, &bounds.sheet, header_row, col);
}
if bounds.data_end_row >= bounds.data_start_row {
for row in bounds.data_start_row..=bounds.data_end_row {
for &col in &bounds.column_indices {
Self::add_cell(targets, &bounds.sheet, row, col);
}
}
}
match layout.terminate {
sheetport_spec::LayoutTermination::FirstBlankRow
| sheetport_spec::LayoutTermination::UntilMarker => {
let sentinel_row = if bounds.data_end_row >= bounds.data_start_row {
bounds.data_end_row.saturating_add(1)
} else {
bounds.data_start_row
};
for &col in &bounds.column_indices {
Self::add_cell(targets, &bounds.sheet, sentinel_row, col);
}
}
sheetport_spec::LayoutTermination::SheetEnd => {}
}
Ok(())
}
crate::location::TableLocation::Table(selector) => {
Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!(
"native table `{}` selectors are not supported yet",
selector.name
),
})
}
},
}
}
fn add_range_cells(targets: &mut BTreeSet<(String, u32, u32)>, addr: &RangeAddress) {
for row in addr.start_row..=addr.end_row {
for col in addr.start_col..=addr.end_col {
Self::add_cell(targets, &addr.sheet, row, col);
}
}
}
fn add_cell(targets: &mut BTreeSet<(String, u32, u32)>, sheet: &str, row: u32, col: u32) {
targets.insert((sheet.to_string(), row, col));
}
pub fn evaluate_with_plan(
&mut self,
plan: &RecalcPlan,
options: EvalOptions,
) -> Result<OutputSnapshot, SheetPortError> {
let restore = self.apply_eval_options(&options)?;
let result = (|| -> Result<OutputSnapshot, SheetPortError> {
self.workbook.evaluate_with_plan(plan)?;
self.read_outputs()
})();
self.restore_eval_options(restore);
result
}
pub fn batch(
&'a mut self,
options: BatchOptions<'a>,
) -> Result<BatchExecutor<'a>, SheetPortError> {
let baseline = self.read_inputs()?;
let baseline_update = baseline.to_update();
let plan = self.workbook().engine().build_recalc_plan()?;
Ok(BatchExecutor::new(self, baseline_update, options, plan))
}
fn read_port_value(&mut self, binding: &PortBinding) -> Result<PortValue, SheetPortError> {
let mut value = match &binding.kind {
BoundPort::Scalar(scalar) => self.read_scalar(binding, scalar),
BoundPort::Record(record) => self.read_record(binding, record),
BoundPort::Range(range) => self.read_range(binding, range),
BoundPort::Table(table) => self.read_table(binding, table),
}?;
value = apply_defaults(binding, value);
if let Err(violations) = validate_port_value(binding, &value, ValidationScope::Full) {
return Err(SheetPortError::ConstraintViolation { violations });
}
Ok(value)
}
fn read_scalar(
&self,
binding: &PortBinding,
scalar: &ScalarBinding,
) -> Result<PortValue, SheetPortError> {
match &scalar.location {
crate::location::ScalarLocation::Cell(addr) => {
let value = self
.workbook
.get_value(&addr.sheet, addr.start_row, addr.start_col)
.unwrap_or(LiteralValue::Empty);
Ok(PortValue::Scalar(value))
}
crate::location::ScalarLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
if addr.height() != 1 || addr.width() != 1 {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"named range `{name}` must resolve to a single cell for scalar ports"
),
});
}
let value = self
.workbook
.get_value(&addr.sheet, addr.start_row, addr.start_col)
.unwrap_or(LiteralValue::Empty);
Ok(PortValue::Scalar(value))
}
_ => Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: "scalar selectors beyond cells or named ranges are not supported yet"
.to_string(),
}),
}
}
fn read_record(
&self,
binding: &PortBinding,
record: &RecordBinding,
) -> Result<PortValue, SheetPortError> {
let mut map = BTreeMap::new();
for (field_name, field_binding) in &record.fields {
let value = self.read_field_value(binding.id.as_str(), field_binding)?;
map.insert(field_name.clone(), value);
}
Ok(PortValue::Record(map))
}
fn read_field_value(
&self,
port_id: &str,
field: &RecordFieldBinding,
) -> Result<LiteralValue, SheetPortError> {
match &field.location {
crate::location::FieldLocation::Cell(addr) => Ok(self
.workbook
.get_value(&addr.sheet, addr.start_row, addr.start_col)
.unwrap_or(LiteralValue::Empty)),
crate::location::FieldLocation::Name(name) => {
let addr = self.named_range_address(port_id, name)?;
if addr.height() != 1 || addr.width() != 1 {
return Err(SheetPortError::InvariantViolation {
port: port_id.to_string(),
message: format!(
"named range `{name}` must resolve to a single cell for record fields"
),
});
}
Ok(self
.workbook
.get_value(&addr.sheet, addr.start_row, addr.start_col)
.unwrap_or(LiteralValue::Empty))
}
crate::location::FieldLocation::StructRef(struct_ref) => {
Err(SheetPortError::UnsupportedSelector {
port: port_id.to_string(),
reason: format!("structured reference `{struct_ref}` is not yet supported"),
})
}
}
}
fn read_range(
&mut self,
binding: &PortBinding,
range: &RangeBinding,
) -> Result<PortValue, SheetPortError> {
let grid = match &range.location {
crate::location::AreaLocation::Range(addr) => self.workbook.read_range(addr),
crate::location::AreaLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
self.workbook.read_range(&addr)
}
crate::location::AreaLocation::Layout(layout) => {
let bounds = resolve_range_layout(&binding.id, self.workbook, layout)?;
let start_row = bounds.start_row;
let end_row = bounds.end_row.max(bounds.start_row);
let start_col = bounds.start_col;
let end_col = bounds.end_col.max(bounds.start_col);
let addr = RangeAddress::new(bounds.sheet, start_row, start_col, end_row, end_col)
.map_err(|msg| SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: msg.to_string(),
})?;
self.workbook.read_range(&addr)
}
other => {
return Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!("unsupported area selector `{other:?}` for range port"),
});
}
};
Ok(PortValue::Range(grid))
}
fn read_table(
&mut self,
binding: &PortBinding,
table: &TableBinding,
) -> Result<PortValue, SheetPortError> {
match &table.location {
crate::location::TableLocation::Layout(layout) => {
let column_hints: Vec<Option<String>> = table
.columns
.iter()
.map(|c| c.column_hint.clone())
.collect();
let bounds =
resolve_table_layout(&binding.id, self.workbook, layout, &column_hints)?;
let mut rows = Vec::new();
if bounds.data_end_row >= bounds.data_start_row {
for row_idx in bounds.data_start_row..=bounds.data_end_row {
let mut values = BTreeMap::new();
for (col_binding, &col_index) in
table.columns.iter().zip(bounds.column_indices.iter())
{
let value = self
.workbook
.get_value(&bounds.sheet, row_idx, col_index)
.unwrap_or(LiteralValue::Empty);
values.insert(col_binding.name.clone(), value);
}
rows.push(TableRow::new(values));
}
}
Ok(PortValue::Table(TableValue::new(rows)))
}
crate::location::TableLocation::Table(table_selector) => {
Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!(
"native table `{}` selectors are not supported yet",
table_selector.name
),
})
}
}
}
fn write_port_value(
&mut self,
binding: &PortBinding,
value: PortValue,
) -> Result<(), SheetPortError> {
match (binding.kind.clone(), value) {
(BoundPort::Scalar(scalar), PortValue::Scalar(val)) => {
self.write_scalar(binding, &scalar, val)
}
(BoundPort::Record(record), PortValue::Record(map)) => {
self.write_record(binding, &record, map)
}
(BoundPort::Range(range), PortValue::Range(grid)) => {
self.write_range(binding, &range, grid)
}
(BoundPort::Table(table), PortValue::Table(rows)) => {
self.write_table(binding, &table, rows)
}
(_, unexpected) => Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"port value did not match expected shape: got {:?}",
unexpected
),
}),
}
}
fn write_scalar(
&mut self,
binding: &PortBinding,
scalar: &ScalarBinding,
value: LiteralValue,
) -> Result<(), SheetPortError> {
match &scalar.location {
crate::location::ScalarLocation::Cell(addr) => self
.workbook
.set_value(&addr.sheet, addr.start_row, addr.start_col, value)
.map_err(SheetPortError::from),
crate::location::ScalarLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
if addr.height() != 1 || addr.width() != 1 {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"named range `{name}` must resolve to a single cell for scalar ports"
),
});
}
self.workbook
.set_value(&addr.sheet, addr.start_row, addr.start_col, value)
.map_err(SheetPortError::from)
}
_ => Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: "scalar selectors beyond cells are not supported yet".to_string(),
}),
}
}
fn write_record(
&mut self,
binding: &PortBinding,
record: &RecordBinding,
update: BTreeMap<String, LiteralValue>,
) -> Result<(), SheetPortError> {
for (field_name, value) in update {
let field_binding = record.fields.get(&field_name).ok_or_else(|| {
SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!("unknown record field `{field_name}`"),
}
})?;
match &field_binding.location {
crate::location::FieldLocation::Cell(addr) => {
self.workbook
.set_value(&addr.sheet, addr.start_row, addr.start_col, value)
.map_err(SheetPortError::from)?;
}
crate::location::FieldLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
if addr.height() != 1 || addr.width() != 1 {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"record field `{field_name}` named range `{name}` must resolve to a single cell"
),
});
}
self.workbook
.set_value(&addr.sheet, addr.start_row, addr.start_col, value)
.map_err(SheetPortError::from)?;
}
_ => {
return Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!("record field `{field_name}` uses unsupported selector"),
});
}
}
}
Ok(())
}
fn write_range(
&mut self,
binding: &PortBinding,
range: &RangeBinding,
grid: Vec<Vec<LiteralValue>>,
) -> Result<(), SheetPortError> {
match &range.location {
crate::location::AreaLocation::Range(addr) => self.write_grid(GridWrite {
port_id: binding.id.as_str(),
sheet: &addr.sheet,
start_row: addr.start_row,
start_col: addr.start_col,
height: addr.height(),
width: addr.width(),
grid,
}),
crate::location::AreaLocation::Layout(layout) => {
let bounds = resolve_range_layout(&binding.id, self.workbook, layout)?;
let expected_width = bounds.columns.len() as u32;
if grid.first().map(|row| row.len() as u32).unwrap_or(0) != expected_width {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: "range update width does not match layout".to_string(),
});
}
let height = grid.len() as u32;
self.write_grid(GridWrite {
port_id: binding.id.as_str(),
sheet: &bounds.sheet,
start_row: bounds.start_row,
start_col: bounds.start_col,
height,
width: expected_width,
grid,
})
}
crate::location::AreaLocation::Name(name) => {
let addr = self.named_range_address(&binding.id, name)?;
let expected_width = addr.width();
if grid.first().map(|row| row.len() as u32).unwrap_or(0) != expected_width {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"range update width does not match named range `{name}` width"
),
});
}
let height = grid.len() as u32;
if height != addr.height() {
return Err(SheetPortError::InvariantViolation {
port: binding.id.clone(),
message: format!(
"range update height does not match named range `{name}` height"
),
});
}
self.write_grid(GridWrite {
port_id: binding.id.as_str(),
sheet: &addr.sheet,
start_row: addr.start_row,
start_col: addr.start_col,
height,
width: expected_width,
grid,
})
}
other => Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!("unsupported area selector `{other:?}` for range port"),
}),
}
}
fn write_grid(&mut self, params: GridWrite<'_>) -> Result<(), SheetPortError> {
let GridWrite {
port_id,
sheet,
start_row,
start_col,
height,
width,
grid,
} = params;
if grid.len() as u32 != height {
return Err(SheetPortError::InvariantViolation {
port: port_id.to_string(),
message: "range update height mismatch".to_string(),
});
}
for (row_offset, row) in grid.into_iter().enumerate() {
if row.len() as u32 != width {
return Err(SheetPortError::InvariantViolation {
port: port_id.to_string(),
message: "range row width mismatch".to_string(),
});
}
let row_idx = start_row + row_offset as u32;
for (col_offset, value) in row.into_iter().enumerate() {
let col_idx = start_col + col_offset as u32;
self.workbook
.set_value(sheet, row_idx, col_idx, value)
.map_err(SheetPortError::from)?;
}
}
Ok(())
}
fn named_range_address(
&self,
port_id: &str,
name: &str,
) -> Result<RangeAddress, SheetPortError> {
self.workbook
.named_range_address(name)
.ok_or_else(|| SheetPortError::InvariantViolation {
port: port_id.to_string(),
message: format!("named range `{name}` was not found in the workbook"),
})
}
fn write_table(
&mut self,
binding: &PortBinding,
table: &TableBinding,
value: TableValue,
) -> Result<(), SheetPortError> {
match &table.location {
crate::location::TableLocation::Layout(layout) => {
let column_hints: Vec<Option<String>> = table
.columns
.iter()
.map(|c| c.column_hint.clone())
.collect();
let bounds =
resolve_table_layout(&binding.id, self.workbook, layout, &column_hints)?;
let existing_row_count = if bounds.data_end_row >= bounds.data_start_row {
bounds.data_end_row - bounds.data_start_row + 1
} else {
0
};
let rows = value.rows;
let new_row_count = rows.len() as u32;
for (row_offset, row) in rows.iter().enumerate() {
let row_idx = bounds.data_start_row + row_offset as u32;
for (col_binding, &col_index) in
table.columns.iter().zip(bounds.column_indices.iter())
{
let cell_value = row
.values
.get(&col_binding.name)
.cloned()
.unwrap_or(LiteralValue::Empty);
self.workbook
.set_value(&bounds.sheet, row_idx, col_index, cell_value)
.map_err(SheetPortError::from)?;
}
}
if new_row_count < existing_row_count {
for row in (bounds.data_start_row + new_row_count)..=bounds.data_end_row {
for &col_index in &bounds.column_indices {
self.workbook
.set_value(&bounds.sheet, row, col_index, LiteralValue::Empty)
.map_err(SheetPortError::from)?;
}
}
}
match layout.terminate {
sheetport_spec::LayoutTermination::FirstBlankRow
| sheetport_spec::LayoutTermination::UntilMarker => {
let blank_row = bounds.data_start_row + new_row_count;
for &col_index in &bounds.column_indices {
self.workbook
.set_value(&bounds.sheet, blank_row, col_index, LiteralValue::Empty)
.map_err(SheetPortError::from)?;
}
}
sheetport_spec::LayoutTermination::SheetEnd => {}
}
Ok(())
}
crate::location::TableLocation::Table(selector) => {
Err(SheetPortError::UnsupportedSelector {
port: binding.id.clone(),
reason: format!(
"native table `{}` selectors are not supported yet",
selector.name
),
})
}
}
}
}
fn apply_defaults(binding: &PortBinding, value: PortValue) -> PortValue {
if let Some(default) = &binding.resolved_default {
normalize_port_value(merge_with_default(value, default))
} else {
normalize_port_value(value)
}
}
fn normalize_literal(lit: LiteralValue) -> LiteralValue {
match lit {
LiteralValue::Int(i) => LiteralValue::Number(i as f64),
other => other,
}
}
fn normalize_port_value(mut value: PortValue) -> PortValue {
match &mut value {
PortValue::Scalar(lit) => {
let old = std::mem::replace(lit, LiteralValue::Empty);
*lit = normalize_literal(old);
}
PortValue::Record(map) => {
for v in map.values_mut() {
let old = std::mem::replace(v, LiteralValue::Empty);
*v = normalize_literal(old);
}
}
PortValue::Range(rows) => {
for row in rows.iter_mut() {
for cell in row.iter_mut() {
let old = std::mem::replace(cell, LiteralValue::Empty);
*cell = normalize_literal(old);
}
}
}
PortValue::Table(table) => {
for row in table.rows.iter_mut() {
for v in row.values.values_mut() {
let old = std::mem::replace(v, LiteralValue::Empty);
*v = normalize_literal(old);
}
}
}
}
value
}
fn merge_with_default(mut current: PortValue, default: &PortValue) -> PortValue {
match (&mut current, default) {
(PortValue::Scalar(current_lit), PortValue::Scalar(default_lit)) => {
if matches!(current_lit, LiteralValue::Empty) {
*current_lit = default_lit.clone();
}
}
(PortValue::Record(current_fields), PortValue::Record(default_fields)) => {
for (field, default_value) in default_fields {
let entry = current_fields
.entry(field.clone())
.or_insert(LiteralValue::Empty);
if matches!(entry, LiteralValue::Empty) {
*entry = default_value.clone();
}
}
}
(PortValue::Range(current_rows), PortValue::Range(default_rows)) => {
let is_empty = current_rows.is_empty()
|| current_rows
.iter()
.all(|row| row.iter().all(|cell| matches!(cell, LiteralValue::Empty)));
if is_empty {
*current_rows = default_rows.clone();
}
}
(PortValue::Table(current_table), PortValue::Table(default_table)) => {
if current_table.is_empty() {
*current_table = default_table.clone();
}
}
_ => {}
}
current
}
struct EvalConfigRestore {
seed: u64,
volatile_level: VolatileLevel,
deterministic_mode: formualizer_eval::engine::DeterministicMode,
seed_overridden: bool,
volatile_overridden: bool,
deterministic_overridden: bool,
}
impl<'a> SheetPort<'a> {
fn apply_eval_options(
&mut self,
options: &EvalOptions,
) -> Result<EvalConfigRestore, SheetPortError> {
let seed = self.workbook.engine().config.workbook_seed;
let volatile_level = self.workbook.engine().config.volatile_level;
let deterministic_mode = self.workbook.engine().config.deterministic_mode.clone();
let mut seed_overridden = false;
let mut volatile_overridden = false;
let mut deterministic_overridden = false;
if let Some(desired_seed) = options.rng_seed
&& desired_seed != seed
{
self.workbook.engine_mut().set_workbook_seed(desired_seed);
seed_overridden = true;
}
if options.freeze_volatile && volatile_level != VolatileLevel::OnOpen {
self.workbook
.engine_mut()
.set_volatile_level(VolatileLevel::OnOpen);
volatile_overridden = true;
}
if let Some(mode) = options.deterministic_mode.clone()
&& mode != deterministic_mode
{
self.workbook
.engine_mut()
.set_deterministic_mode(mode)
.map_err(SheetPortError::from)?;
deterministic_overridden = true;
}
Ok(EvalConfigRestore {
seed,
volatile_level,
deterministic_mode,
seed_overridden,
volatile_overridden,
deterministic_overridden,
})
}
fn restore_eval_options(&mut self, restore: EvalConfigRestore) {
if restore.seed_overridden {
self.workbook.engine_mut().set_workbook_seed(restore.seed);
}
if restore.volatile_overridden {
self.workbook
.engine_mut()
.set_volatile_level(restore.volatile_level);
}
if restore.deterministic_overridden {
self.workbook
.engine_mut()
.set_deterministic_mode(restore.deterministic_mode)
.expect("restore deterministic mode");
}
}
}