use cairo_lang_filesystem::span::TextWidth;
use num_bigint::BigInt;
use num_traits::Num;
use unescaper::unescape;
use crate::diagnostic::ParserDiagnosticKind;
pub struct ValidationError {
pub kind: ParserDiagnosticKind,
pub location: ValidationLocation,
}
impl ValidationError {
fn full(kind: ParserDiagnosticKind) -> Self {
ValidationError { kind, location: ValidationLocation::Full }
}
}
pub enum ValidationLocation {
Full,
After,
Cursor(TextWidth),
}
pub fn validate_literal_number(text: &str) -> Option<ValidationError> {
let (text, ty) = match text.split_once('_') {
Some((text, ty)) => (text, Some(ty)),
None => (text, None),
};
{
let (text, radix) = if let Some(num_no_prefix) = text.strip_prefix("0x") {
(num_no_prefix, 16)
} else if let Some(num_no_prefix) = text.strip_prefix("0o") {
(num_no_prefix, 8)
} else if let Some(num_no_prefix) = text.strip_prefix("0b") {
(num_no_prefix, 2)
} else {
(text, 10)
};
if BigInt::from_str_radix(text, radix).is_err() {
return Some(ValidationError::full(ParserDiagnosticKind::InvalidNumericLiteralValue));
}
}
if let Some(ty) = ty
&& ty.is_empty()
{
Some(ValidationError {
kind: ParserDiagnosticKind::MissingLiteralSuffix,
location: ValidationLocation::After,
})
} else {
None
}
}
pub fn validate_short_string(text: &str) -> Option<ValidationError> {
validate_any_string(
text,
'\'',
ParserDiagnosticKind::UnterminatedShortString,
ParserDiagnosticKind::ShortStringMustBeAscii,
)
}
pub fn validate_string(text: &str) -> Option<ValidationError> {
validate_any_string(
text,
'"',
ParserDiagnosticKind::UnterminatedString,
ParserDiagnosticKind::StringMustBeAscii,
)
}
fn validate_any_string(
text: &str,
delimiter: char,
unterminated_string_diagnostic_kind: ParserDiagnosticKind,
ascii_only_diagnostic_kind: ParserDiagnosticKind,
) -> Option<ValidationError> {
let (_, text) = text.split_once(delimiter).unwrap();
let Some((body, _suffix)) = text.rsplit_once(delimiter) else {
return Some(ValidationError::full(unterminated_string_diagnostic_kind));
};
validate_string_body(body, unterminated_string_diagnostic_kind, ascii_only_diagnostic_kind)
}
fn validate_string_body(
body: &str,
unterminated_string_diagnostic_kind: ParserDiagnosticKind,
ascii_only_diagnostic_kind: ParserDiagnosticKind,
) -> Option<ValidationError> {
let body = match unescape(body) {
Ok(body) => body,
Err(unescaper::Error::IncompleteStr(_)) => {
return Some(ValidationError::full(unterminated_string_diagnostic_kind));
}
Err(
unescaper::Error::InvalidChar { pos, .. } | unescaper::Error::ParseIntError { pos, .. },
) => {
let start = body.chars().take(pos).map(TextWidth::from_char).sum();
return Some(ValidationError {
kind: ParserDiagnosticKind::IllegalStringEscaping,
location: ValidationLocation::Cursor(start),
});
}
};
if !body.is_ascii() {
return Some(ValidationError::full(ascii_only_diagnostic_kind));
}
None
}