use crate::error::CellRefError;
use crate::refs::{CellRange, CellRef, ColRange, RowRange};
use crate::tokens;
use crate::tokens::{space, unquote};
use nom::combinator::{consumed, opt};
use nom::sequence::tuple;
use nom_locate::LocatedSpan;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::num::IntErrorKind;
use std::str::FromStr;
pub type Span<'a> = LocatedSpan<&'a str>;
pub type ParseResult<'s, O> = Result<(Span<'s>, O), CellRefError>;
pub fn lah_cell_ref(i: Span<'_>) -> bool {
tokens::lah_iri(i) || tokens::lah_sheet_name(i) || tokens::lah_dot(i)
}
#[allow(clippy::manual_map)]
pub fn parse_cell_ref(i: Span<'_>) -> ParseResult<'_, (CellRef, Span<'_>)> {
match consumed(tuple((
opt(tokens::iri),
opt(tokens::sheet_name),
tokens::dot, tokens::col,
tokens::row,
)))(i)
{
Ok((rest, (tok, (iri, sheet_name, _dot, col, row)))) => {
let cell_ref = CellRef::new_all(
iri.map(|v| unquote(v)),
match sheet_name {
None => None,
Some((_, v)) => Some(unquote(v)),
},
try_bool_from_abs_flag(row.0),
try_u32_from_rowname(row.1)?,
try_bool_from_abs_flag(col.0),
try_u32_from_colname(col.1)?,
);
Ok((rest, (cell_ref, tok)))
}
Err(e @ nom::Err::Error(_)) => Err(CellRefError::nom_error(i, e)),
Err(e @ nom::Err::Failure(_)) => Err(CellRefError::nom_failure(i, e)),
Err(nom::Err::Incomplete(_)) => unreachable!(),
}
}
pub fn lah_cell_range(i: Span<'_>) -> bool {
tokens::lah_iri(i) || tokens::lah_sheet_name(i) || tokens::lah_dot(i)
}
pub fn parse_cell_range_list(i: Span<'_>) -> ParseResult<'_, Option<Vec<CellRange>>> {
let mut vec = Vec::new();
let mut rest_loop = i;
let rest2 = loop {
let (rest1, (cell_range, _tok)) = parse_cell_range(rest_loop)?;
vec.push(cell_range);
if rest1.is_empty() {
break rest1;
}
let rest1 = match space(rest1) {
Ok((rest1, _sp)) => rest1,
Err(e @ nom::Err::Error(_)) => return Err(CellRefError::nom_error(i, e)),
Err(e @ nom::Err::Failure(_)) => return Err(CellRefError::nom_failure(i, e)),
Err(nom::Err::Incomplete(_)) => unreachable!(),
};
rest_loop = rest1;
};
if vec.is_empty() {
Ok((rest2, None))
} else {
Ok((rest2, Some(vec)))
}
}
#[allow(clippy::manual_map)]
pub fn parse_cell_range(i: Span<'_>) -> ParseResult<'_, (CellRange, Span<'_>)> {
match consumed(tuple((
opt(tokens::iri),
opt(tokens::sheet_name),
tokens::dot,
tokens::col,
tokens::row,
tokens::colon,
opt(tokens::sheet_name),
tokens::dot,
tokens::col,
tokens::row,
)))(i)
{
Ok((
rest,
(
tok,
(
iri,
sheet_name_0,
_dot_0,
col_0,
row_0,
_colon,
sheet_name_1,
_dot_1,
col_1,
row_1,
),
),
)) => {
let cell_range = CellRange::new_all(
iri.map(|v| unquote(v)),
match sheet_name_0 {
None => None,
Some((_, v)) => Some(unquote(v)),
},
try_bool_from_abs_flag(row_0.0),
try_u32_from_rowname(row_0.1)?,
try_bool_from_abs_flag(col_0.0),
try_u32_from_colname(col_0.1)?,
match sheet_name_1 {
None => None,
Some((_, v)) => Some(unquote(v)),
},
try_bool_from_abs_flag(row_1.0),
try_u32_from_rowname(row_1.1)?,
try_bool_from_abs_flag(col_1.0),
try_u32_from_colname(col_1.1)?,
);
Ok((rest, (cell_range, tok)))
}
Err(e @ nom::Err::Error(_)) => Err(CellRefError::nom_error(i, e)),
Err(e @ nom::Err::Failure(_)) => Err(CellRefError::nom_failure(i, e)),
Err(nom::Err::Incomplete(_)) => unreachable!(),
}
}
pub fn lah_colrange(i: Span<'_>) -> bool {
tokens::lah_iri(i) || tokens::lah_sheet_name(i) || tokens::lah_dot(i)
}
#[allow(clippy::manual_map)]
pub fn parse_col_range(i: Span<'_>) -> ParseResult<'_, (ColRange, Span<'_>)> {
match consumed(tuple((
opt(tokens::iri),
opt(tokens::sheet_name),
tokens::dot,
tokens::col,
tokens::colon,
opt(tokens::sheet_name),
tokens::dot,
tokens::col,
)))(i)
{
Ok((
rest,
(tok, (iri, sheet_name_0, _dot_0, col_0, _colon, sheet_name_1, _dot_1, col_1)),
)) => {
let col_range = ColRange::new_all(
iri.map(|v| unquote(v)),
match sheet_name_0 {
None => None,
Some((_, v)) => Some(unquote(v)),
},
try_bool_from_abs_flag(col_0.0),
try_u32_from_colname(col_0.1)?,
match sheet_name_1 {
None => None,
Some((_, v)) => Some(unquote(v)),
},
try_bool_from_abs_flag(col_1.0),
try_u32_from_colname(col_1.1)?,
);
Ok((rest, (col_range, tok)))
}
Err(e @ nom::Err::Error(_)) => Err(CellRefError::nom_error(i, e)),
Err(e @ nom::Err::Failure(_)) => Err(CellRefError::nom_failure(i, e)),
Err(nom::Err::Incomplete(_)) => unreachable!(),
}
}
pub fn lah_row_range(i: Span<'_>) -> bool {
tokens::lah_iri(i) || tokens::lah_sheet_name(i) || tokens::lah_dot(i)
}
#[allow(clippy::manual_map)]
pub fn parse_row_range(i: Span<'_>) -> ParseResult<'_, (RowRange, Span<'_>)> {
match consumed(tuple((
opt(tokens::iri),
opt(tokens::sheet_name),
tokens::dot,
tokens::row,
tokens::colon,
opt(tokens::sheet_name),
tokens::dot,
tokens::row,
)))(i)
{
Ok((
rest,
(tok, (iri, sheet_name_0, _dot_0, row_0, _colon, sheet_name_1, _dot_1, row_1)),
)) => {
let row_range = RowRange::new_all(
iri.map(|v| unquote(v)),
match sheet_name_0 {
None => None,
Some((_, v)) => Some(unquote(v)),
},
try_bool_from_abs_flag(row_0.0),
try_u32_from_rowname(row_0.1)?,
match sheet_name_1 {
None => None,
Some((_, v)) => Some(unquote(v)),
},
try_bool_from_abs_flag(row_1.0),
try_u32_from_rowname(row_1.1)?,
);
Ok((rest, (row_range, tok)))
}
Err(e @ nom::Err::Error(_)) => Err(CellRefError::nom_error(i, e)),
Err(e @ nom::Err::Failure(_)) => Err(CellRefError::nom_failure(i, e)),
Err(nom::Err::Incomplete(_)) => unreachable!(),
}
}
pub fn try_bool_from_abs_flag(i: Option<Span<'_>>) -> bool {
if let Some(i) = i {
*i == "$"
} else {
false
}
}
#[allow(variant_size_differences)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseRownameError {
Empty,
InvalidDigit,
PosOverflow,
NegOverflow,
Zero,
Other,
}
impl Display for ParseRownameError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ParseRownameError::Empty => write!(f, "Input was empty")?,
ParseRownameError::InvalidDigit => write!(f, "Invalid digit")?,
ParseRownameError::PosOverflow => write!(f, "Positive overflow")?,
ParseRownameError::NegOverflow => write!(f, "Negative overflow")?,
ParseRownameError::Zero => write!(f, "Zero")?,
ParseRownameError::Other => write!(f, "Other")?,
}
Ok(())
}
}
impl Error for ParseRownameError {}
#[allow(clippy::explicit_auto_deref)]
pub fn try_u32_from_rowname(i: Span<'_>) -> Result<u32, CellRefError> {
match u32::from_str(*i) {
Ok(v) if v > 0 => Ok(v - 1),
Ok(_v) => Err(CellRefError::ErrRowname(i.into(), ParseRownameError::Zero)),
Err(e) => Err(CellRefError::ErrRowname(
i.into(),
match e.kind() {
IntErrorKind::Empty => ParseRownameError::Empty,
IntErrorKind::InvalidDigit => ParseRownameError::InvalidDigit,
IntErrorKind::PosOverflow => ParseRownameError::PosOverflow,
IntErrorKind::NegOverflow => ParseRownameError::NegOverflow,
IntErrorKind::Zero => ParseRownameError::Zero,
_ => ParseRownameError::Other,
},
)),
}
}
#[allow(variant_size_differences)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseColnameError {
InvalidChar(char),
InvalidColname(String),
}
impl Display for ParseColnameError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ParseColnameError::InvalidChar(e) => {
write!(f, "Invalid char '{}'", e)?;
}
ParseColnameError::InvalidColname(e) => {
write!(f, "Invalid colname {}", e)?;
}
}
Ok(())
}
}
impl Error for ParseColnameError {}
pub fn try_u32_from_colname(i: Span<'_>) -> Result<u32, CellRefError> {
let mut col = 0u32;
for c in (*i).chars() {
if !('A'..='Z').contains(&c) {
return Err(CellRefError::ErrColname(
i.into(),
ParseColnameError::InvalidChar(c),
));
}
let mut v = c as u32 - b'A' as u32;
if v == 25 {
v = 0;
col = (col + 1) * 26;
} else {
v += 1;
col *= 26;
}
col += v;
}
if col == 0 {
Err(CellRefError::ErrColname(
i.into(),
ParseColnameError::InvalidColname(format!("{:?}", i)),
))
} else {
Ok(col - 1)
}
}