use crate::ast::{
Ast, AstReferences, BuiltinFunction, BuiltinVariable, Functions, Node, VariableValue, Variables,
};
use crate::error::{EvalError, EvalResult};
use crate::parser::code_section_parser::{CodeSection, CodeSectionParser};
use crate::{Cell, Result, Row, Runtime, Spreadsheet};
use a1_notation::{Address, A1};
use std::cell;
use std::collections;
mod display;
mod template_at_rest;
#[derive(Debug)]
pub struct Template<'a> {
pub functions: Functions,
pub spreadsheet: cell::RefCell<Spreadsheet>,
pub variables: Variables,
csv_line_number: usize,
runtime: &'a Runtime,
}
impl<'a> Template<'a> {
pub fn compile(runtime: &'a Runtime) -> Result<Self> {
let spreadsheet = Spreadsheet::parse(runtime)?;
let code_section = if let Some(code_section_source) = &runtime.source_code.code_section {
Some(CodeSectionParser::parse(code_section_source, runtime)?)
} else {
None
};
let compiled_template = Self::new(spreadsheet, code_section, runtime)
.eval()
.map_err(|e| runtime.source_code.eval_error(&e.message, e.position))?;
runtime.info(&compiled_template);
Ok(compiled_template)
}
pub fn new(
spreadsheet: Spreadsheet,
code_section: Option<CodeSection>,
runtime: &'a Runtime,
) -> Self {
let cli_vars = &runtime.options.key_values;
let spreadsheet_vars = spreadsheet.variables();
let (code_section_vars, code_section_fns) = if let Some(cs) = code_section {
(cs.variables, cs.functions)
} else {
(collections::HashMap::new(), collections::HashMap::new())
};
Self {
runtime,
csv_line_number: runtime.source_code.length_of_code_section + 1,
spreadsheet: cell::RefCell::new(spreadsheet),
functions: code_section_fns,
variables: code_section_vars
.into_iter()
.chain(spreadsheet_vars)
.chain(cli_vars.clone())
.collect(),
}
}
fn eval(self) -> EvalResult<Self> {
self.runtime.progress("Evaluating all cells");
self.eval_expands().eval_cells()
}
fn eval_expands(self) -> Self {
let mut new_spreadsheet = Spreadsheet::default();
let s = self.spreadsheet.borrow_mut();
let mut row_num = 0;
for row in s.rows.iter() {
if let Some(e) = row.modifier.expand {
for _ in 0..e.expand_amount(row_num) {
new_spreadsheet.rows.push(row.clone_to_row(row_num.into()));
row_num += 1;
}
} else {
new_spreadsheet.rows.push(row.clone_to_row(row_num.into()));
row_num += 1;
}
}
Self {
spreadsheet: cell::RefCell::new(new_spreadsheet),
..self
}
}
fn eval_cells(&self) -> EvalResult<Self> {
let spreadsheet = self.spreadsheet.borrow();
let mut evaled_rows = vec![];
for row in spreadsheet.rows.iter() {
evaled_rows.push(self.eval_row(row)?);
}
Ok(Self {
csv_line_number: self.csv_line_number,
functions: self.functions.clone(),
runtime: self.runtime,
spreadsheet: cell::RefCell::new(Spreadsheet { rows: evaled_rows }),
variables: self.variables.clone(),
})
}
fn eval_ast(&self, ast: &Ast, position: Address) -> EvalResult<Ast> {
let mut evaled_ast = *ast.clone();
let mut last_round_refs = AstReferences::default();
loop {
let refs = evaled_ast.extract_references(self);
if refs.is_empty() || refs == last_round_refs {
break;
}
last_round_refs = refs.clone();
evaled_ast = evaled_ast
.eval_variables(self.resolve_variables(&refs.variables, position)?)?
.eval_functions(&refs.functions, |fn_id, args| {
if let Some(function) = self.functions.get(fn_id) {
Ok(function.clone())
} else if let Some(BuiltinFunction { eval, .. }) =
self.runtime.builtin_functions.get(fn_id)
{
Ok(Box::new(eval(position, args)?))
} else {
Err(EvalError::new(position, "Undefined function: {fn_id}"))
}
})?;
}
Ok(Box::new(evaled_ast))
}
fn eval_row(&self, row: &Row) -> EvalResult<Row> {
let mut cells = vec![];
for cell in row.cells.iter() {
let evaled_ast = if let Some(ast) = &cell.ast {
Some(self.eval_ast(ast, cell.position)?)
} else {
None
};
cells.push(Cell {
ast: evaled_ast,
position: cell.position,
modifier: cell.modifier.clone(),
value: cell.value.clone(),
});
}
Ok(Row {
cells,
row: row.row,
modifier: row.modifier.clone(),
})
}
pub fn is_function_defined(&self, fn_name: &str) -> bool {
self.functions.contains_key(fn_name) || self.runtime.builtin_functions.contains_key(fn_name)
}
pub fn is_variable_defined(&self, var_name: &str) -> bool {
self.variables.contains_key(var_name)
|| self.runtime.builtin_variables.contains_key(var_name)
}
fn resolve_variables(
&self,
var_names: &[String],
position: Address,
) -> EvalResult<collections::HashMap<String, Ast>> {
let mut resolved_vars = collections::HashMap::new();
for var_name in var_names {
if let Some(val) = self.resolve_variable(var_name, position)? {
resolved_vars.insert(var_name.to_string(), val);
}
}
Ok(resolved_vars)
}
fn resolve_variable(&self, var_name: &str, position: Address) -> EvalResult<Option<Ast>> {
Ok(if let Some(value) = self.variables.get(var_name) {
let value_from_var = match &**value {
Node::Variable { value, .. } => {
match value {
VariableValue::Absolute(address) => (*address).into(),
VariableValue::Ast(ast) => *ast.clone(),
VariableValue::ColumnRelative { scope, column } => {
let scope_a1: A1 = (*scope).into();
if scope_a1.contains(&position.into()) {
position.with_x(column.x).into()
} else {
let row_range: A1 = (*scope).into();
row_range.with_x(column.x).into()
}
}
VariableValue::Row(row) => {
let a1: a1_notation::A1 = (*row).into();
a1.into()
}
VariableValue::RowRelative { scope, .. } => {
let scope_a1: A1 = (*scope).into();
if scope_a1.contains(&position.into()) {
let row_a1: A1 = position.row.into();
row_a1.into()
} else {
let row_range: A1 = (*scope).into();
row_range.into()
}
}
}
}
n => n.clone(),
};
Some(Box::new(value_from_var))
} else if let Some(BuiltinVariable { eval, .. }) =
self.runtime.builtin_variables.get(var_name)
{
Some(Box::new(eval(position)?))
} else {
None
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::TestFile;
use std::cell;
fn build_template(runtime: &Runtime) -> Template {
Template {
csv_line_number: 5,
functions: collections::HashMap::new(),
variables: collections::HashMap::new(),
runtime,
spreadsheet: cell::RefCell::new(Spreadsheet::default()),
}
}
#[test]
fn compile_empty() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let template = Template::compile(&runtime);
assert!(template.is_ok());
}
#[test]
fn compile_simple() {
let test_file = TestFile::new("csv", "---\nfoo,bar,baz\n1,2,3");
let runtime = test_file.into();
let template = Template::compile(&runtime).unwrap();
assert_eq!(template.spreadsheet.borrow().rows.len(), 2);
}
#[test]
fn compile_with_expand_finite() {
let test_file = TestFile::new("xlsx", "![[expand=10]]foo,bar,baz");
let runtime = test_file.into();
let template = Template::compile(&runtime).unwrap();
assert_eq!(template.spreadsheet.borrow().rows.len(), 10);
}
#[test]
fn compile_with_expand_infinite() {
let test_file = TestFile::new("xlsx", "![[expand]]foo,bar,baz");
let runtime = test_file.into();
let template = Template::compile(&runtime).unwrap();
assert_eq!(template.spreadsheet.borrow().rows.len(), 1000);
}
#[test]
fn compile_with_expand_multiple() {
let test_file = TestFile::new("xlsx", "![[e=10]]foo,bar,baz\n![[e]]1,2,3");
let runtime = test_file.into();
let template = Template::compile(&runtime).unwrap();
assert_eq!(template.spreadsheet.borrow().rows.len(), 1000);
}
#[test]
fn compile_with_expand_and_rows() {
let test_file = TestFile::new("xlsx", "foo,bar,baz\n![[e=2]]foo,bar,baz\none,last,row\n");
let runtime = test_file.into();
let template = Template::compile(&runtime).unwrap();
let spreadsheet = template.spreadsheet.borrow();
assert_eq!(spreadsheet.rows[0].row, 0.into());
assert_eq!(spreadsheet.rows[0].cells[0].position.row, 0.into());
assert_eq!(spreadsheet.rows[1].row, 1.into());
assert_eq!(spreadsheet.rows[1].cells[0].position.row, 1.into());
assert_eq!(spreadsheet.rows[2].row, 2.into());
assert_eq!(spreadsheet.rows[2].cells[0].position.row, 2.into());
assert_eq!(spreadsheet.rows[3].row, 3.into());
assert_eq!(spreadsheet.rows[3].cells[0].position.row, 3.into());
}
#[test]
fn is_function_defined_true() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut template = build_template(&runtime);
template
.functions
.insert("foo".to_string(), Box::new(42.into()));
assert!(template.is_function_defined("foo"));
}
#[test]
fn is_function_defined_builtin_true() {
let test_file = TestFile::new("csv", "");
let mut runtime: Runtime = test_file.into();
runtime.builtin_functions.insert(
"foo".to_string(),
BuiltinFunction {
name: "foo".to_owned(),
eval: Box::new(|_a1, _args| Ok(42.into())),
},
);
let template = build_template(&runtime);
assert!(template.is_function_defined("foo"));
}
#[test]
fn is_variable_defined_true() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut template = build_template(&runtime);
template
.variables
.insert("foo".to_string(), Box::new(42.into()));
assert!(template.is_variable_defined("foo"));
}
#[test]
fn is_variable_defined_builtin_true() {
let test_file = TestFile::new("csv", "");
let mut runtime: Runtime = test_file.into();
runtime.builtin_variables.insert(
"foo".to_string(),
BuiltinVariable {
name: "foo".to_owned(),
eval: Box::new(|_a1| Ok(42.into())),
},
);
let template = build_template(&runtime);
assert!(template.is_variable_defined("foo"));
}
#[test]
fn new_with_code_section() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut functions = collections::HashMap::new();
functions.insert("foo".to_string(), Box::new(1.into()));
let mut variables = collections::HashMap::new();
variables.insert("bar".to_string(), Box::new(2.into()));
let code_section = CodeSection {
functions,
variables,
};
let template = Template::new(Spreadsheet::default(), Some(code_section), &runtime);
assert!(template.functions.contains_key("foo"));
assert!(template.variables.contains_key("bar"));
}
#[test]
fn new_without_code_section() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let template = Template::new(Spreadsheet::default(), None, &runtime);
assert!(template.functions.is_empty());
assert!(template.variables.is_empty());
}
}