use crate::ast::{Ast, Node, VariableValue, Variables};
use crate::cell_options::ROW_MAX;
use crate::{ArcSourceCode, Error, EvalError, EvalResult, Result, Row};
use log::trace;
use serde::{Deserialize, Serialize};
use std::collections;
mod display;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Spreadsheet {
pub rows: Vec<Row>,
pub(crate) fills_expanded: bool,
}
impl Spreadsheet {
pub(crate) fn eval_fills(self) -> EvalResult<Self> {
if self.fills_expanded {
return Ok(self);
} else if self.has_multiple_infinite_expands() {
return Err(EvalError::new(
"![[fill]]",
"Multiple infinite fills - \
you can only have one without a fill amount and all others must supply an \
amount (i.e., `![[fill=10]]` rather than `![[fill]]`)",
));
}
let mut rows = Vec::with_capacity(self.rows.len());
let mut row_num = 0;
let rows_left_over = self.rows_left_over();
for row in self.rows {
if let Some(f) = row.fill {
let new_fill = f.clone_to_row(row_num);
let fill_amount = new_fill.amount.unwrap_or(rows_left_over);
for _ in 0..fill_amount {
rows.push(Row {
fill: Some(new_fill),
..row.clone()
});
row_num += 1;
}
} else {
rows.push(row);
row_num += 1;
}
}
Ok(Self {
rows,
fills_expanded: true,
})
}
pub(crate) fn parse(source_code: &ArcSourceCode) -> Result<Spreadsheet> {
let csvp_config = csvp::Config {
lines_above: source_code.length_of_code_section,
..csvp::Config::default()
};
let records = csvp::parser::parse(&source_code.csv_section, &csvp_config).map_err(|e| {
Error::CsvParseError {
filename: source_code.filename.clone(),
parse_error: Box::new(source_code.csv_error_to_parse_error(e)),
}
})?;
let mut rows: Vec<Row> = vec![];
for (row_index, row) in records.into_iter().enumerate() {
trace!("Parsing row {row_index}");
rows.push(Row::parse(&row, source_code)?);
}
Ok(Spreadsheet {
rows,
fills_expanded: false,
})
}
pub(crate) fn variables(&self) -> Variables {
let mut vars = collections::HashMap::new();
for (row_index, row) in self.rows.iter().enumerate() {
let row_a1: a1::Row = row_index.into();
if let Some(var_id) = &row.var {
let reference = if let Some(fill) = row.fill {
Node::Variable {
name: var_id.clone(),
value: VariableValue::RowRelative { fill, row: row_a1 },
}
} else {
Node::Variable {
name: var_id.clone(),
value: VariableValue::Row(row_a1),
}
};
vars.insert(var_id.to_owned(), Ast::new(reference));
};
for (cell_index, cell) in row.cells.iter().enumerate() {
let cell_a1 = a1::Address::new(cell_index, row_index);
if let Some(var_id) = &cell.var {
let reference = if let Some(fill) = row.fill {
Node::Variable {
name: var_id.clone(),
value: VariableValue::ColumnRelative {
fill,
column: cell_a1.column,
},
}
} else {
Node::Variable {
name: var_id.clone(),
value: VariableValue::Absolute(cell_a1),
}
};
vars.insert(var_id.to_owned(), Ast::new(reference));
}
}
}
vars
}
pub(crate) fn widest_row(&self) -> usize {
self.rows
.iter()
.map(|row| row.cells.len())
.max()
.unwrap_or(0)
}
fn has_multiple_infinite_expands(&self) -> bool {
let mut saw_infinite = false;
for row in &self.rows {
if let Some(fill) = row.fill {
if fill.amount.is_none() {
if saw_infinite {
return true;
}
saw_infinite = true;
}
}
}
false
}
fn rows_left_over(&self) -> usize {
let total_rows_minus_infinite = self.rows.iter().fold(0, |acc, row| {
acc + row.fill.map_or(1, |f| f.amount.unwrap_or(0))
});
ROW_MAX.saturating_sub(total_rows_minus_infinite)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cell_options::*;
use crate::test_utils::*;
use crate::*;
fn build_source_code(input: &str) -> ArcSourceCode {
ArcSourceCode::new((&TestSourceCode::new("csv", input)).into())
}
fn build_cell_with_var<S: Into<String>>(var_name: S) -> Cell {
let mut cell = Cell::new(build_field("", (0, 0)));
cell.var = Some(var_name.into());
cell
}
#[test]
fn eval_fills_already_expanded() {
let spreadsheet = Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, Some(10))),
..Default::default()
},
Row {
fill: Some(Fill::new(10, Some(30))),
..Default::default()
},
],
fills_expanded: true,
}
.eval_fills()
.unwrap();
assert_eq!(spreadsheet.rows.len(), 2);
}
#[test]
fn eval_fills_finite() {
let spreadsheet = Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, Some(10))),
..Default::default()
},
Row {
fill: Some(Fill::new(10, Some(30))),
..Default::default()
},
],
fills_expanded: false,
}
.eval_fills()
.unwrap();
assert_eq!(spreadsheet.rows.len(), 40);
assert_eq!(spreadsheet.rows[0].fill.unwrap().start_row, 0.into());
assert_eq!(spreadsheet.rows[9].fill.unwrap().start_row, 0.into());
assert_eq!(spreadsheet.rows[10].fill.unwrap().start_row, 10.into());
assert_eq!(spreadsheet.rows[39].fill.unwrap().start_row, 10.into());
}
#[test]
fn eval_fills_infinite() {
let spreadsheet = Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, Some(10))),
..Default::default()
},
Row {
fill: Some(Fill::new(10, None)),
..Default::default()
},
],
fills_expanded: false,
}
.eval_fills()
.unwrap();
assert_eq!(spreadsheet.rows.len(), 1000);
assert_eq!(spreadsheet.rows[0].fill.unwrap().start_row, 0.into());
assert_eq!(spreadsheet.rows[9].fill.unwrap().start_row, 0.into());
assert_eq!(spreadsheet.rows[10].fill.unwrap().start_row, 10.into());
assert_eq!(spreadsheet.rows[999].fill.unwrap().start_row, 10.into());
}
#[test]
fn eval_fills_multiple_and_infinite() {
let spreadsheet = Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, Some(10))),
..Default::default()
},
Row {
fill: Some(Fill::new(10, None)),
..Default::default()
},
Row {
fill: Some(Fill::new(990, Some(10))),
..Default::default()
},
],
fills_expanded: false,
}
.eval_fills()
.unwrap();
assert_eq!(spreadsheet.rows.len(), 1000);
}
#[test]
fn eval_fills_multiple_infinite() {
assert!(Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, None)),
..Default::default()
},
Row {
fill: Some(Fill::new(10, None)),
..Default::default()
},
],
fills_expanded: false,
}
.eval_fills()
.is_err());
}
#[test]
fn parse_simple() {
let source_code = build_source_code("foo,bar,baz\n1,2,3\n");
let spreadsheet = Spreadsheet::parse(&source_code).unwrap();
assert_eq!(spreadsheet.rows.len(), 2);
assert_eq!(spreadsheet.rows[0].cells.len(), 3);
assert_eq!(spreadsheet.rows[1].cells.len(), 3);
assert_eq!(spreadsheet.rows[0].cells[0].field.value, "foo");
assert_eq!(spreadsheet.rows[0].cells[1].field.value, "bar");
assert_eq!(spreadsheet.rows[0].cells[2].field.value, "baz");
assert_eq!(spreadsheet.rows[1].cells[0].field.value, "1");
assert_eq!(spreadsheet.rows[1].cells[1].field.value, "2");
assert_eq!(spreadsheet.rows[1].cells[2].field.value, "3");
assert!(spreadsheet.rows[0].cells[0].ast.is_none());
assert!(spreadsheet.rows[0].cells[1].ast.is_none());
assert!(spreadsheet.rows[0].cells[2].ast.is_none());
assert!(spreadsheet.rows[1].cells[0].ast.is_none());
assert!(spreadsheet.rows[1].cells[1].ast.is_none());
assert!(spreadsheet.rows[1].cells[2].ast.is_none());
}
#[test]
fn parse_with_asts() {
let source_code = build_source_code("=1,=2 * 3,=foo\n");
let spreadsheet = Spreadsheet::parse(&source_code).unwrap();
assert!(spreadsheet.rows[0].cells[0].ast.is_some());
assert!(spreadsheet.rows[0].cells[1].ast.is_some());
assert!(spreadsheet.rows[0].cells[2].ast.is_some());
}
#[test]
fn parse_trim_spaces() {
let source_code = build_source_code(" foo , bar\n");
let spreadsheet = Spreadsheet::parse(&source_code).unwrap();
assert_eq!(spreadsheet.rows[0].cells[0].field.value, "foo");
assert_eq!(spreadsheet.rows[0].cells[1].field.value, "bar");
}
#[test]
fn parse_with_options() {
let source_code = build_source_code("[[t=b / fs=20]]foo");
let spreadsheet = Spreadsheet::parse(&source_code).unwrap();
assert!(spreadsheet.rows[0].cells[0]
.text_formats
.contains(&TextFormat::Bold));
assert_eq!(spreadsheet.rows[0].cells[0].font_size, Some(20));
}
#[test]
fn parse_with_row_option() {
let source_code = build_source_code("![[t=b]]foo,bar,baz");
let spreadsheet = Spreadsheet::parse(&source_code).unwrap();
assert!(spreadsheet.rows[0].cells[0]
.text_formats
.contains(&TextFormat::Bold));
assert!(spreadsheet.rows[0].cells[1]
.text_formats
.contains(&TextFormat::Bold));
assert!(spreadsheet.rows[0].cells[2]
.text_formats
.contains(&TextFormat::Bold));
}
#[test]
fn variables_unscoped() {
let spreadsheet = Spreadsheet {
rows: vec![Row {
cells: vec![build_cell_with_var("foo"), build_cell_with_var("bar")],
..Default::default()
}],
..Default::default()
};
let variables = spreadsheet.variables();
assert_eq!(
**variables.get("foo").unwrap(),
Node::var("foo", VariableValue::Absolute((0, 0).into()))
);
assert_eq!(
**variables.get("bar").unwrap(),
Node::var("bar", VariableValue::Absolute((1, 0).into()))
);
}
#[test]
fn variables_with_scope() {
let spreadsheet = Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, Some(10))),
cells: vec![build_cell_with_var("foo")],
..Default::default()
},
Row {
fill: Some(Fill::new(10, Some(100))),
var: Some("bar".to_string()),
..Default::default()
},
],
..Default::default()
};
let variables = spreadsheet.variables();
assert_eq!(
**variables.get("foo").unwrap(),
Node::var(
"foo",
VariableValue::ColumnRelative {
fill: Fill {
amount: Some(10),
start_row: 0.into()
},
column: 0.into(),
}
)
);
assert_eq!(
**variables.get("bar").unwrap(),
Node::var(
"bar",
VariableValue::RowRelative {
fill: Fill {
amount: Some(100),
start_row: 10.into()
},
row: 1.into(),
}
)
);
}
#[test]
fn widest_row() {
let cell = Cell::new(build_field("foo", (0, 0)));
let spreadsheet = Spreadsheet {
rows: vec![
Row {
cells: vec![cell.clone()],
..Default::default()
},
Row {
cells: vec![cell.clone(), cell.clone()],
..Default::default()
},
Row {
cells: vec![cell.clone(), cell.clone(), cell.clone()],
..Default::default()
},
],
..Default::default()
};
assert_eq!(spreadsheet.widest_row(), 3);
}
}