189 lines
7.1 KiB
Rust
189 lines
7.1 KiB
Rust
// Grammar:
|
|
// structured references -> table_name "[" arguments "]"
|
|
// arguments -> table_reference | "["specifier"]" "," table_reference
|
|
// specifier > "#All" |
|
|
// "#This Row" |
|
|
// "#Data" |
|
|
// "#Headers" |
|
|
// "#Totals"
|
|
// table_reference -> column_reference | range_reference
|
|
// column reference -> column_name | "["column_name"]"
|
|
// range_reference -> column_reference":"column_reference
|
|
|
|
use crate::expressions::token::TokenType;
|
|
use crate::expressions::token::{TableReference, TableSpecifier};
|
|
|
|
use super::Result;
|
|
use super::{Lexer, LexerError};
|
|
|
|
impl Lexer {
|
|
fn consume_table_specifier(&mut self) -> Result<Option<TableSpecifier>> {
|
|
if self.peek_char() == Some('#') {
|
|
// It's a specifier
|
|
// TODO(TD): There are better ways of doing this :)
|
|
let rest_of_formula: String = self.chars[self.position..self.len].iter().collect();
|
|
let specifier = if rest_of_formula.starts_with("#This Row]") {
|
|
self.position += "#This Row]".bytes().len();
|
|
TableSpecifier::ThisRow
|
|
} else if rest_of_formula.starts_with("#All]") {
|
|
self.position += "#All]".bytes().len();
|
|
TableSpecifier::All
|
|
} else if rest_of_formula.starts_with("#Data]") {
|
|
self.position += "#Data]".bytes().len();
|
|
TableSpecifier::Data
|
|
} else if rest_of_formula.starts_with("#Headers]") {
|
|
self.position += "#Headers]".bytes().len();
|
|
TableSpecifier::Headers
|
|
} else if rest_of_formula.starts_with("#Totals]") {
|
|
self.position += "#Totals]".bytes().len();
|
|
TableSpecifier::Totals
|
|
} else {
|
|
return Err(LexerError {
|
|
position: self.position,
|
|
message: "Invalid structured reference".to_string(),
|
|
});
|
|
};
|
|
Ok(Some(specifier))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn consume_column_reference(&mut self) -> Result<String> {
|
|
self.consume_whitespace();
|
|
let end_char = if self.peek_char() == Some('[') {
|
|
self.position += 1;
|
|
']'
|
|
} else {
|
|
')'
|
|
};
|
|
|
|
let mut position = self.position;
|
|
while position < self.len {
|
|
let next_char = self.chars[position];
|
|
if next_char != end_char {
|
|
position += 1;
|
|
if next_char == '\'' {
|
|
if position == self.len {
|
|
return Err(LexerError {
|
|
position: self.position,
|
|
message: "Invalid column name".to_string(),
|
|
});
|
|
}
|
|
// skip next char
|
|
position += 1
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
let chars: String = self.chars[self.position..position].iter().collect();
|
|
if end_char == ']' {
|
|
position += 1;
|
|
}
|
|
self.position = position;
|
|
Ok(chars
|
|
.replace("'[", "[")
|
|
.replace("']", "]")
|
|
.replace("'#", "#")
|
|
.replace("'@", "@")
|
|
.replace("''", "'"))
|
|
}
|
|
|
|
// Possibilities:
|
|
// 1. MyTable[#Totals] or MyTable[#This Row]
|
|
// 2. MyTable[MyColumn]
|
|
// 3. MyTable[[My Column]]
|
|
// 4. MyTable[[#This Row], [My Column]]
|
|
// 5. MyTable[[#Totals], [MyColumn]]
|
|
// 6. MyTable[[#This Row], [Jan]:[Dec]]
|
|
// 7. MyTable[]
|
|
//
|
|
// Multiple specifiers are not supported yet:
|
|
// 1. MyTable[[#Data], [#Totals], [MyColumn]]
|
|
//
|
|
// In particular note that names of columns are escaped only when they are in the first argument
|
|
// We use '[' and ']'
|
|
// When there is only a specifier but not a reference the specifier is not in brackets
|
|
//
|
|
// Invalid:
|
|
// * MyTable[#Totals, [Jan]:[March]] => MyTable[[#Totals], [Jan]:[March]]
|
|
//
|
|
// NOTES:
|
|
// * MyTable[[#Totals]] is translated into MyTable[#Totals]
|
|
// * Excel shows '@' instead of '#This Row':
|
|
// MyTable[[#This Row], [Jan]:[Dec]] => MyTable[@[Jan]:[Dec]]
|
|
// But this is only a UI thing that we will ignore for now.
|
|
pub(crate) fn consume_structured_reference(&mut self, table_name: &str) -> Result<TokenType> {
|
|
self.expect(TokenType::LeftBracket)?;
|
|
let peek_char = self.peek_char();
|
|
if peek_char == Some(']') {
|
|
// This is just a reference to the full table
|
|
self.expect(TokenType::RightBracket)?;
|
|
return Ok(TokenType::Ident(table_name.to_string()));
|
|
}
|
|
if peek_char == Some('#') {
|
|
// Expecting MyTable[#Totals]
|
|
if let Some(specifier) = self.consume_table_specifier()? {
|
|
return Ok(TokenType::StructuredReference {
|
|
table_name: table_name.to_string(),
|
|
specifier: Some(specifier),
|
|
table_reference: None,
|
|
});
|
|
} else {
|
|
return Err(LexerError {
|
|
position: self.position,
|
|
message: "Invalid structured reference".to_string(),
|
|
});
|
|
}
|
|
} else if peek_char != Some('[') {
|
|
// Expecting MyTable[MyColumn]
|
|
self.position -= 1;
|
|
let column_name = self.consume_column_reference()?;
|
|
return Ok(TokenType::StructuredReference {
|
|
table_name: table_name.to_string(),
|
|
specifier: None,
|
|
table_reference: Some(TableReference::ColumnReference(column_name)),
|
|
});
|
|
}
|
|
self.expect(TokenType::LeftBracket)?;
|
|
let specifier = self.consume_table_specifier()?;
|
|
if specifier.is_some() {
|
|
let peek_token = self.peek_token();
|
|
if peek_token == TokenType::Comma {
|
|
self.advance_token();
|
|
self.expect(TokenType::LeftBracket)?;
|
|
} else if peek_token == TokenType::RightBracket {
|
|
return Ok(TokenType::StructuredReference {
|
|
table_name: table_name.to_string(),
|
|
specifier,
|
|
table_reference: None,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Now it's either:
|
|
// [Column Name]
|
|
// [Column Name]:[Column Name]
|
|
self.position -= 1;
|
|
let column_reference = self.consume_column_reference()?;
|
|
let table_reference = if self.peek_char() == Some(':') {
|
|
self.position += 1;
|
|
let column_reference_right = self.consume_column_reference()?;
|
|
self.expect(TokenType::RightBracket)?;
|
|
Some(TableReference::RangeReference((
|
|
column_reference,
|
|
column_reference_right,
|
|
)))
|
|
} else {
|
|
self.expect(TokenType::RightBracket)?;
|
|
Some(TableReference::ColumnReference(column_reference))
|
|
};
|
|
Ok(TokenType::StructuredReference {
|
|
table_name: table_name.to_string(),
|
|
specifier,
|
|
table_reference,
|
|
})
|
|
}
|
|
}
|