/*!
# GRAMAR
opComp => '=' | '<' | '>' | '<=' } '>=' | '<>'
opFactor => '*' | '/'
unaryOp => '-' | '+'
expr => concat (opComp concat)*
concat => term ('&' term)*
term => factor (opFactor factor)*
factor => prod (opProd prod)*
prod => power ('^' power)*
power => (unaryOp)* range '%'*
range => primary (':' primary)?
primary => '(' expr ')'
=> number
=> function '(' f_args ')'
=> name
=> string
=> '{' a_args '}'
=> bool
=> bool()
=> error
f_args => e (',' e)*
*/
use std::collections::HashMap;
use crate::functions::Function;
use crate::language::get_language;
use crate::locale::get_locale;
use crate::types::Table;
use super::lexer;
use super::token;
use super::token::OpUnary;
use super::token::TableReference;
use super::token::TokenType;
use super::types::*;
use super::utils::number_to_column;
use token::OpCompare;
pub mod move_formula;
pub mod stringify;
pub mod walk;
#[cfg(test)]
mod tests;
pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> {
let mut lexer = lexer::Lexer::new(
formula,
lexer::LexerMode::A1,
#[allow(clippy::expect_used)]
get_locale("en").expect(""),
#[allow(clippy::expect_used)]
get_language("en").expect(""),
);
if let TokenType::Range {
left,
right,
sheet: _,
} = lexer.next_token()
{
Ok((left.column, left.row, right.column, right.row))
} else {
Err("Not a range".to_string())
}
}
fn get_table_column_by_name(table_column_name: &str, table: &Table) -> Option {
for (index, table_column) in table.columns.iter().enumerate() {
if table_column.name == table_column_name {
return Some(index as i32);
}
}
None
}
pub(crate) struct Reference<'a> {
sheet_name: &'a Option,
sheet_index: u32,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
}
#[derive(PartialEq, Clone, Debug)]
pub enum Node {
BooleanKind(bool),
NumberKind(f64),
StringKind(String),
ReferenceKind {
sheet_name: Option,
sheet_index: u32,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
},
RangeKind {
sheet_name: Option,
sheet_index: u32,
absolute_row1: bool,
absolute_column1: bool,
row1: i32,
column1: i32,
absolute_row2: bool,
absolute_column2: bool,
row2: i32,
column2: i32,
},
WrongReferenceKind {
sheet_name: Option,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
},
WrongRangeKind {
sheet_name: Option,
absolute_row1: bool,
absolute_column1: bool,
row1: i32,
column1: i32,
absolute_row2: bool,
absolute_column2: bool,
row2: i32,
column2: i32,
},
OpRangeKind {
left: Box,
right: Box,
},
OpConcatenateKind {
left: Box,
right: Box,
},
OpSumKind {
kind: token::OpSum,
left: Box,
right: Box,
},
OpProductKind {
kind: token::OpProduct,
left: Box,
right: Box,
},
OpPowerKind {
left: Box,
right: Box,
},
FunctionKind {
kind: Function,
args: Vec,
},
InvalidFunctionKind {
name: String,
args: Vec,
},
ArrayKind(Vec),
DefinedNameKind((String, Option)),
TableNameKind(String),
WrongVariableKind(String),
CompareKind {
kind: OpCompare,
left: Box,
right: Box,
},
UnaryKind {
kind: OpUnary,
right: Box,
},
ErrorKind(token::Error),
ParseErrorKind {
formula: String,
message: String,
position: usize,
},
EmptyArgKind,
}
#[derive(Clone)]
pub struct Parser {
lexer: lexer::Lexer,
worksheets: Vec,
defined_names: Vec<(String, Option)>,
context: CellReferenceRC,
tables: HashMap,
}
impl Parser {
pub fn new(
worksheets: Vec,
defined_names: Vec<(String, Option)>,
tables: HashMap,
) -> Parser {
let lexer = lexer::Lexer::new(
"",
lexer::LexerMode::A1,
#[allow(clippy::expect_used)]
get_locale("en").expect(""),
#[allow(clippy::expect_used)]
get_language("en").expect(""),
);
let context = CellReferenceRC {
sheet: worksheets.first().map_or("", |v| v).to_string(),
column: 1,
row: 1,
};
Parser {
lexer,
worksheets,
defined_names,
context,
tables,
}
}
pub fn set_lexer_mode(&mut self, mode: lexer::LexerMode) {
self.lexer.set_lexer_mode(mode)
}
pub fn set_worksheets_and_names(
&mut self,
worksheets: Vec,
defined_names: Vec<(String, Option)>,
) {
self.worksheets = worksheets;
self.defined_names = defined_names;
}
pub fn parse(&mut self, formula: &str, context: &CellReferenceRC) -> Node {
self.lexer.set_formula(formula);
self.context = context.clone();
self.parse_expr()
}
fn get_sheet_index_by_name(&self, name: &str) -> Option {
let worksheets = &self.worksheets;
for (i, sheet) in worksheets.iter().enumerate() {
if sheet == name {
return Some(i as u32);
}
}
None
}
// Returns:
// * None: If there is no defined name by that name
// * Some(Some(index)): If there is a defined name local to that sheet
// * Some(None): If there is a global defined name
fn get_defined_name(&self, name: &str, sheet: u32) -> Option