use crate::ast::Variables;
use crate::{ArcSourceCode, Compiler, ModuleLoader, ModulePath, Result, Row, Scope, Spreadsheet};
use log::{error, info};
use std::fs;
use std::path;
mod display;
mod try_from;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Module {
pub compiler_version: String,
pub module_path: ModulePath,
pub scope: Scope,
pub spreadsheet: Spreadsheet,
pub(crate) required_modules: Vec<ModulePath>,
pub(crate) source_code: ArcSourceCode,
pub(crate) is_dirty: bool,
}
impl Module {
pub(crate) fn new(
source_code: ArcSourceCode,
module_path: ModulePath,
scope: Scope,
spreadsheet: Spreadsheet,
) -> Self {
Self {
compiler_version: env!("CARGO_PKG_VERSION").to_string(),
scope,
module_path,
required_modules: vec![],
spreadsheet,
source_code,
is_dirty: false,
}
}
pub(crate) fn eval_fills(self) -> Self {
let mut new_spreadsheet = Spreadsheet::default();
let s = self.spreadsheet;
let mut row_num = 0;
for row in s.rows.into_iter() {
if let Some(f) = row.fill {
let new_fill = f.clone_to_row(row_num);
for _ in 0..new_fill.fill_amount(row_num) {
new_spreadsheet.rows.push(Row {
fill: Some(new_fill),
..row.clone()
});
row_num += 1;
}
} else {
new_spreadsheet.rows.push(row);
row_num += 1;
}
}
Self {
spreadsheet: new_spreadsheet,
..self
}
}
pub(crate) fn eval_spreadsheet(self, external_vars: Variables) -> Result<Self> {
let spreadsheet = self.spreadsheet;
let scope = self
.scope
.merge_variables(spreadsheet.variables())
.merge_variables(external_vars);
let mut evaled_rows = vec![];
for (row_index, row) in spreadsheet.rows.into_iter().enumerate() {
evaled_rows.push(row.eval(self.source_code.clone(), &scope, row_index.into())?);
}
Ok(Self {
scope,
spreadsheet: Spreadsheet { rows: evaled_rows },
..self
})
}
pub(crate) fn load_dependencies<P: Into<path::PathBuf>>(self, relative_to: P) -> Result<Self> {
let module_loader = ModuleLoader::load_dependencies(&self, relative_to)?;
let dependencies = module_loader.into_direct_dependencies()?;
Ok(Self {
scope: self.scope.merge(dependencies),
..self
})
}
pub(crate) fn write_object_file(&self, compiler: &Compiler) -> Result<()> {
if !compiler.options.use_cache {
info!("Not writing object file because --no-cache flag is set");
return Ok(());
}
let object_code_filename = self.source_code.object_code_filename();
info!("Writing object file to {}", object_code_filename.display());
let object_file = fs::File::create(object_code_filename).map_err(|e| {
error!("IO error: {e:?}");
self.source_code
.object_code_error(format!("Error opening object code for writing: {e}"))
})?;
serde_cbor::to_writer(object_file, self).map_err(|e| {
error!("CBOR write error: {e:?}");
self.source_code
.object_code_error(format!("Error serializing object code for writing: {e}"))
})?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::*;
use crate::test_utils::*;
use crate::*;
#[test]
fn eval_fills_finite() {
let module = Module {
spreadsheet: Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, Some(10))),
..Default::default()
},
Row {
fill: Some(Fill::new(10, Some(30))),
..Default::default()
},
],
},
..build_module()
}
.eval_fills();
let spreadsheet = module.spreadsheet;
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 module = Module {
spreadsheet: Spreadsheet {
rows: vec![
Row {
fill: Some(Fill::new(0, Some(10))),
..Default::default()
},
Row {
fill: Some(Fill::new(10, None)),
..Default::default()
},
],
},
..build_module()
}
.eval_fills();
let spreadsheet = module.spreadsheet;
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 load_dependencies_with_scope() {
let mut module = build_module();
module
.scope
.functions
.insert("foo".to_string(), Ast::new(1.into()));
module
.scope
.variables
.insert("bar".to_string(), Ast::new(2.into()));
let module = module.load_dependencies("").unwrap();
assert!(module.scope.functions.contains_key("foo"));
assert!(module.scope.variables.contains_key("bar"));
}
#[test]
fn load_depdencies_without_scope() {
let module = build_module();
assert!(module.scope.functions.is_empty());
assert!(module.scope.variables.is_empty());
}
}