Commit 5f7c0923 authored by Christopher Silva's avatar Christopher Silva

Use decimal/add functions/add basic formula support

Use decimal for numbers instead of having int and float cell types
Add avg, count, max, min functions and improve the func macro
Add a basic formula parser that just recognizes '=FORMULA(RANGE)'
Add formula cell type that stores the formula and result cell
Add write_formula method to sheet that parses the formula, executes it,
and stores it
parent 58313362
This diff is collapsed.
......@@ -7,3 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rust_decimal = "1.0.3"
anyhow = "1.0.25"
pest = "2.1.2"
pest_derive = "2.1.0"
use std::fmt;
use rust_decimal::Decimal;
#[derive(Clone, Debug)]
pub enum Cell {
Int(i32),
Float(f32),
Number(Decimal),
Text(String),
Formula(String, Box<Cell>),
}
use Cell::*;
impl fmt::Display for Cell {
fn fmt(&self, fm: &mut fmt::Formatter) -> Result<(), fmt::Error>{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
match self {
Cell::Int(i) => write!(fm, "{}", i),
Cell::Float(f) => write!(fm, "{}", f),
Cell::Text(s) => write!(fm, "{}", s),
Number(n) => write!(f, "{}", n),
Text(s) => write!(f, "{}", s),
Formula(formula, result) => write!(f, "{}{}", result, formula),
}
}
}
use anyhow::Result;
use pest::Parser;
use pest::iterators::Pair;
#[derive(Parser)]
#[grammar = "grammar.pest"]
pub struct FormulaParser;
#[derive(Clone, Debug, Default)]
pub struct FunctionCall {
pub function: String,
pub r: usize,
pub c: usize,
pub h: usize,
pub w: usize,
}
fn process(pair: Pair<Rule>) -> FunctionCall {
let mut formula_rules = pair.into_inner();
let mut func_rules = formula_rules.next().unwrap().into_inner();
let name = func_rules.next().unwrap().as_str();
let mut range_rules = func_rules.next().unwrap().into_inner();
let mut start_rules = range_rules.next().unwrap().into_inner();
let start_r = start_rules.next().unwrap().as_str();
let start_c = start_rules.next().unwrap().as_str();
let mut end_rules = range_rules.next().unwrap().into_inner();
let end_r = end_rules.next().unwrap().as_str();
let end_c = end_rules.next().unwrap().as_str();
let function = name.to_string();
let r = start_r.parse::<usize>().expect("failed to parse start_r");
let c = start_c.parse::<usize>().expect("failed to parse start_c");
let h = end_r.parse::<usize>().expect("failed to parse end_r") - r + 1;
let w = end_c.parse::<usize>().expect("failed to parse end_c") - c + 1;
FunctionCall { function, r, c, h, w }
}
pub fn parse(input: String) -> Result<FunctionCall> {
let mut parsed = FormulaParser::parse(Rule::formula, &input)?;
Ok(process(parsed.next().unwrap()))
}
use std::default::Default;
use rust_decimal::prelude::*;
use rust_decimal::Decimal;
use crate::cell::Cell;
use Cell::*;
use crate::sheet::Range;
struct Acc {
int: i32,
float: f32,
flag: bool,
}
impl Default for Acc {
fn default() -> Self {
Acc {
int: 0,
float: 0.0,
flag: false,
}
}
}
impl Acc {
fn finish(&self) -> Cell {
match (self.int, self.float, self.flag) {
(i, _, false) => Cell::Int(i),
(_, f, true) => Cell::Float(f),
}
pub fn call(func: String, cells: Range) -> Cell {
match &func[..] {
"sum" => sum(cells),
"avg" => avg(cells),
"count" => count(cells),
"max" => max(cells),
"min" => min(cells),
_ => unimplemented!{},
}
}
macro_rules! func {
($name:ident, "r->c", $closure:expr) => {
($name:ident, "r->c", $initializer:expr, $closure:expr) => {
pub fn $name(cells: Range) -> Cell {
Number(cells.iter()
.flatten()
.fold($initializer, $closure))
}
};
($name:ident, "r->c", $initializer:expr, $closure:expr, $finish:expr) => {
pub fn $name(cells: Range) -> Cell {
cells.iter()
$finish(cells.iter()
.flatten()
.fold(Acc::default(), $closure)
.finish()
.fold($initializer, $closure))
}
};
($name:ident, "c->c", $closure:expr) => {
......@@ -45,22 +40,42 @@ macro_rules! func {
};
}
func!(sum, "r->c", |mut acc, cell| {
func!(sum, "r->c", Decimal::zero(), |acc, cell| {
match cell {
Some(Number(n)) => acc + n,
_ => acc,
}
});
func!(avg, "r->c", (Decimal::zero(), 0), |(acc, count), cell| {
match cell {
Some(Number(n)) => (acc + n, count + 1),
_ => (acc, count)
}
}, |(acc, count)| Number(acc / Decimal::new(count, 0)));
func!(count, "r->c", Decimal::zero(), |acc, _cell| {
acc + Decimal::one()
});
func!(max, "r->c", Decimal::min_value(), |acc, cell| {
match cell {
Some(Number(n)) if *n > acc => *n,
_ => acc
}
});
func!(min, "r->c", Decimal::max_value(), |acc, cell| {
match cell {
Some(Int(i)) => acc.int += i,
Some(Float(f)) => {
acc.flag = true;
acc.float += f;
},
_ => {},
Some(Number(n)) if *n < acc => *n,
_ => acc
}
acc
});
func!(trunc, "c->c", |cell| {
match cell {
Some(&Float(f)) => Float(f.trunc()),
_ => Float(0.0),
Some(&Number(n)) => Number(n.trunc()),
_ => Number(Decimal::default()),
}
});
formula = { "=" ~ function }
function = { identifier ~ "(" ~ range ~ ")" }
range = { index ~ ":" ~ index }
index = { "r" ~ uint ~ "c" ~ uint }
uint = { ASCII_DIGIT+ }
identifier = { ASCII_ALPHA+ }
#[macro_use]
extern crate pest_derive;
pub mod cell;
pub mod formula;
pub mod function;
pub mod sheet;
pub mod workbook;
use std::str::FromStr;
use rust_decimal::prelude::*;
use rust_decimal::Decimal;
use spreadsheet::function;
use spreadsheet::workbook::Workbook;
......@@ -23,21 +28,47 @@ fn main() {
for i in 0..10 {
for j in 0..10 {
sheet.write_int(i, j, (100 * i + j) as i32);
sheet.write_number(i, j, format!("{}", (100 * i + j) as i32)).unwrap();
}
}
let range = sheet.range(0, 0, 10, 10);
let sum = function::sum(range);
println!("sum(r0c0:r9c9): {}", sum);
let sum = function::sum(range.clone());
let avg = function::avg(range.clone());
let count = function::count(range.clone());
let max = function::max(range.clone());
let min = function::min(range);
println!("sum,count,max,min of r0c0:r9c9: {} {} {} {} {}", sum, avg, count, max, min);
for i in 0..10 {
for j in 0..10 {
sheet.write_float(i, j, (100 * i + j) as f32);
}
}
let dec = Decimal::from_str("3.1415").unwrap();
let dec2 = Decimal::from_str("18").unwrap();
let dec3 = Decimal::from_str("50.0").unwrap();
let dec4 = Decimal::new(2, 0);
let dec5 = Decimal::new(3, 0);
let dec6 = pow(dec4, dec5);
println!("decimals: {} {} {} {} {} {}", dec, dec2, dec3, dec4, dec5, dec6);
sheet.write_formula(10, 10, "=sum(r0c0:r9c9)".to_string()).unwrap();
println!("range r10c10:r10c10: {:?}", sheet.render_range(10, 10, 1, 1));
sheet.write_formula(10, 10, "=avg(r0c0:r9c9)".to_string()).unwrap();
println!("range r10c10:r10c10: {:?}", sheet.render_range(10, 10, 1, 1));
sheet.write_formula(10, 10, "=count(r0c0:r9c9)".to_string()).unwrap();
println!("range r10c10:r10c10: {:?}", sheet.render_range(10, 10, 1, 1));
sheet.write_formula(10, 10, "=max(r0c0:r9c9)".to_string()).unwrap();
println!("range r10c10:r10c10: {:?}", sheet.render_range(10, 10, 1, 1));
sheet.write_formula(10, 10, "=min(r0c0:r9c9)".to_string()).unwrap();
println!("range r10c10:r10c10: {:?}", sheet.render_range(10, 10, 1, 1));
}
let range = sheet.range(0, 0, 10, 1);
let sum = function::sum(range);
println!("sum(r0c0:r9c0): {}", sum);
fn pow(x: Decimal, y: Decimal) -> Decimal {
if y == Decimal::zero() {
return Decimal::one();
}
let temp = pow(x, (y / Decimal::new(2, 0)).trunc());
if y % Decimal::new(2, 0) == Decimal::zero() {
temp * temp
} else {
x * temp * temp
}
}
use std::collections::HashMap;
use std::str::FromStr;
use anyhow::Result;
use rust_decimal::Decimal;
use crate::cell::Cell;
use Cell::*;
use crate::formula;
use crate::function;
pub type Range<'a> = Vec<Vec<Option<&'a Cell>>>;
pub type RenderedRange = Vec<Vec<String>>;
......@@ -24,18 +30,24 @@ impl Sheet {
self.cells.len()
}
pub fn write_int(&mut self, r: usize, c: usize, value: i32) {
self.cells.insert((r, c), Int(value));
pub fn write_number(&mut self, r: usize, c: usize, value: String) -> Result<()> {
let decimal = Decimal::from_str(&value[..])?;
self.cells.insert((r, c), Number(decimal));
Ok(())
}
pub fn write_float(&mut self, r: usize, c: usize, value: f32) {
self.cells.insert((r, c), Float(value));
}
pub fn write_text(&mut self, r: usize, c: usize, value: String) {
self.cells.insert((r, c), Text(value));
}
pub fn write_formula(&mut self, r: usize, c: usize, value: String) -> Result<()> {
let call = formula::parse(value.clone())?;
let range = self.range(call.r, call.c, call.h, call.w);
let result = function::call(call.function, range);
self.cells.insert((r, c), Formula(value, Box::new(result)));
Ok(())
}
pub fn range(&self, r: usize, c: usize, h: usize, w: usize) -> Range {
(r..r+h).into_iter()
.map(|i| (c..c+w).into_iter()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment