UPDATE: Introducing Arrays
# This PR introduces:
## Parsing arrays:
{1,2,3} and {1;2;3}
Note that array elements can be numbers, booleans and errors (#VALUE!)
## Evaluating arrays in the SUM function
=SUM({1,2,3}) works!
## Evaluating arithmetic operation with arrays
=SUM({1,2,3} * 8) or =SUM({1,2,3}+{2,4,5}) works
This is done with just one function (handle_arithmetic) for most operations
## Some mathematical functions implement arrays
=SUM(SIN({1,2,3})) works
This is done with macros. See fn_single_number
So that implementing new functions that supports array are easy
# Not done in this PR
## Most functions are not supporting arrays
When that happens we either through #N/IMPL! (not implemented error)
or do implicit intersection. Some functions will be rather trivial to "arraify" some will be hard
## The final result in a cell cannot be an array
The formula ={1,2,3} in a cell will result in #N/IMPL!
## Exporting arrays to Excel might not work correctly
Excel uses the cm (cell metadata) for formulas that contain dynamic arrays.
Although the present PR does not introduce dynamic arrays some formulas like =SUM(SIN({1,2,3}))
is considered a dynamic formula
## There are not a lot of tests in this delivery
The bulk of the tests will be added once we start going function by function# This PR introduces:
## Parsing arrays:
{1,2,3} and {1;2;3}
Note that array elements can be numbers, booleans and errors (#VALUE!)
## Evaluating arrays in the SUM function
=SUM({1,2,3}) works!
## Evaluating arithmetic operation with arrays
=SUM({1,2,3} * 8) or =SUM({1,2,3}+{2,4,5}) works
This is done with just one function (handle_arithmetic) for most operations
## Some mathematical functions implement arrays
=SUM(SIN({1,2,3})) works
This is done with macros. See fn_single_number
So that implementing new functions that supports array are easy
# Not done in this PR
## Most functions are not supporting arrays
When that happens we either through #N/IMPL! (not implemented error)
or do implicit intersection. Some functions will be rather trivial to "arraify" some will be hard
## The final result in a cell cannot be an array
The formula ={1,2,3} in a cell will result in #N/IMPL!
## Exporting arrays to Excel might not work correctly
Excel uses the cm (cell metadata) for formulas that contain dynamic arrays.
Although the present PR does not introduce dynamic arrays some formulas like =SUM(SIN({1,2,3}))
is considered a dynamic formula
## There are not a lot of tests in this delivery
The bulk of the tests will be added once we start going function by function
## The array parsing does not respect the locale
Locales that use ',' as a decimal separator need to use something different for arrays
## The might introduce a small performance penalty
We haven't been benchmarking, and having closures for every arithmetic operation and every function
evaluation will introduce a performance hit. Fixing that in he future is not so hard writing tailored
code for the operation
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
e07fdd2091
commit
e5ec75495a
@@ -94,6 +94,14 @@ pub(crate) struct Reference<'a> {
|
||||
column: i32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum ArrayNode {
|
||||
Boolean(bool),
|
||||
Number(f64),
|
||||
String(String),
|
||||
Error(token::Error),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum Node {
|
||||
BooleanKind(bool),
|
||||
@@ -167,7 +175,7 @@ pub enum Node {
|
||||
name: String,
|
||||
args: Vec<Node>,
|
||||
},
|
||||
ArrayKind(Vec<Node>),
|
||||
ArrayKind(Vec<Vec<ArrayNode>>),
|
||||
DefinedNameKind(DefinedNameS),
|
||||
TableNameKind(String),
|
||||
WrongVariableKind(String),
|
||||
@@ -454,6 +462,49 @@ impl Parser {
|
||||
self.parse_primary()
|
||||
}
|
||||
|
||||
fn parse_array_row(&mut self) -> Result<Vec<ArrayNode>, Node> {
|
||||
let mut row = Vec::new();
|
||||
// and array can only have numbers, string or booleans
|
||||
// otherwise it is a syntax error
|
||||
let first_element = match self.parse_expr() {
|
||||
Node::BooleanKind(s) => ArrayNode::Boolean(s),
|
||||
Node::NumberKind(s) => ArrayNode::Number(s),
|
||||
Node::StringKind(s) => ArrayNode::String(s),
|
||||
Node::ErrorKind(kind) => ArrayNode::Error(kind),
|
||||
error @ Node::ParseErrorKind { .. } => return Err(error),
|
||||
_ => {
|
||||
return Err(Node::ParseErrorKind {
|
||||
formula: self.lexer.get_formula(),
|
||||
message: "Invalid value in array".to_string(),
|
||||
position: self.lexer.get_position() as usize,
|
||||
});
|
||||
}
|
||||
};
|
||||
row.push(first_element);
|
||||
let mut next_token = self.lexer.peek_token();
|
||||
// FIXME: this is not respecting the locale
|
||||
while next_token == TokenType::Comma {
|
||||
self.lexer.advance_token();
|
||||
let value = match self.parse_expr() {
|
||||
Node::BooleanKind(s) => ArrayNode::Boolean(s),
|
||||
Node::NumberKind(s) => ArrayNode::Number(s),
|
||||
Node::StringKind(s) => ArrayNode::String(s),
|
||||
Node::ErrorKind(kind) => ArrayNode::Error(kind),
|
||||
error @ Node::ParseErrorKind { .. } => return Err(error),
|
||||
_ => {
|
||||
return Err(Node::ParseErrorKind {
|
||||
formula: self.lexer.get_formula(),
|
||||
message: "Invalid value in array".to_string(),
|
||||
position: self.lexer.get_position() as usize,
|
||||
});
|
||||
}
|
||||
};
|
||||
row.push(value);
|
||||
next_token = self.lexer.peek_token();
|
||||
}
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
fn parse_primary(&mut self) -> Node {
|
||||
let next_token = self.lexer.next_token();
|
||||
match next_token {
|
||||
@@ -475,21 +526,35 @@ impl Parser {
|
||||
TokenType::Number(s) => Node::NumberKind(s),
|
||||
TokenType::String(s) => Node::StringKind(s),
|
||||
TokenType::LeftBrace => {
|
||||
let t = self.parse_expr();
|
||||
if let Node::ParseErrorKind { .. } = t {
|
||||
return t;
|
||||
}
|
||||
// It's an array. It's a collection of rows all of the same dimension
|
||||
|
||||
let first_row = match self.parse_array_row() {
|
||||
Ok(s) => s,
|
||||
Err(error) => return error,
|
||||
};
|
||||
let length = first_row.len();
|
||||
|
||||
let mut matrix = Vec::new();
|
||||
matrix.push(first_row);
|
||||
// FIXME: this is not respecting the locale
|
||||
let mut next_token = self.lexer.peek_token();
|
||||
let mut args: Vec<Node> = vec![t];
|
||||
while next_token == TokenType::Semicolon {
|
||||
self.lexer.advance_token();
|
||||
let p = self.parse_expr();
|
||||
if let Node::ParseErrorKind { .. } = p {
|
||||
return p;
|
||||
}
|
||||
let row = match self.parse_array_row() {
|
||||
Ok(s) => s,
|
||||
Err(error) => return error,
|
||||
};
|
||||
next_token = self.lexer.peek_token();
|
||||
args.push(p);
|
||||
if row.len() != length {
|
||||
return Node::ParseErrorKind {
|
||||
formula: self.lexer.get_formula(),
|
||||
position: self.lexer.get_position() as usize,
|
||||
message: "All rows in an array should be the same length".to_string(),
|
||||
};
|
||||
}
|
||||
matrix.push(row);
|
||||
}
|
||||
|
||||
if let Err(err) = self.lexer.expect(TokenType::RightBrace) {
|
||||
return Node::ParseErrorKind {
|
||||
formula: self.lexer.get_formula(),
|
||||
@@ -497,7 +562,7 @@ impl Parser {
|
||||
message: err.message,
|
||||
};
|
||||
}
|
||||
Node::ArrayKind(args)
|
||||
Node::ArrayKind(matrix)
|
||||
}
|
||||
TokenType::Reference {
|
||||
sheet,
|
||||
|
||||
Reference in New Issue
Block a user