use cairo_lang_diagnostics::{DiagnosticsBuilder, Maybe};
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::kind::SyntaxKind;
use cairo_lang_syntax::node::{ast, SyntaxNode, Terminal, TypedSyntaxNode};
use num_bigint::BigInt;
use num_traits::Num;
use unescaper::unescape;
use crate::diagnostic::ParserDiagnosticKind;
use crate::ParserDiagnostic;
pub fn validate(
root: SyntaxNode,
db: &dyn SyntaxGroup,
diagnostics: &mut DiagnosticsBuilder<ParserDiagnostic>,
file_id: FileId,
) -> Maybe<()> {
root.descendants(db).fold(Ok(()), |result, node| {
result.and(match node.kind(db) {
SyntaxKind::TerminalLiteralNumber => {
let node = ast::TerminalLiteralNumber::from_syntax_node(db, node);
validate_literal_number(node, db, diagnostics, file_id)
}
SyntaxKind::TerminalShortString => {
let node = ast::TerminalShortString::from_syntax_node(db, node);
validate_short_string(node, db, diagnostics, file_id)
}
_ => Ok(()),
})
})
}
fn validate_literal_number(
node: ast::TerminalLiteralNumber,
db: &dyn SyntaxGroup,
diagnostics: &mut DiagnosticsBuilder<ParserDiagnostic>,
file_id: FileId,
) -> Maybe<()> {
let mut result = Ok(());
let text = node.text(db);
let (text, ty) = match text.split_once('_') {
Some((text, ty)) => (text, Some(ty)),
None => (text.as_str(), 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() {
result = Err(diagnostics.add(ParserDiagnostic {
file_id,
span: node.as_syntax_node().span(db),
kind: ParserDiagnosticKind::InvalidNumericLiteralValue,
}));
}
}
if let Some(ty) = ty {
if ty.is_empty() {
result = Err(diagnostics.add(ParserDiagnostic {
file_id,
span: node.as_syntax_node().span(db).after(),
kind: ParserDiagnosticKind::MissingLiteralSuffix,
}));
}
}
result
}
fn validate_short_string(
node: ast::TerminalShortString,
db: &dyn SyntaxGroup,
diagnostics: &mut DiagnosticsBuilder<ParserDiagnostic>,
file_id: FileId,
) -> Maybe<()> {
let mut result = Ok(());
let text = node.text(db);
let mut text = text.as_str();
if text.starts_with('\'') {
(_, text) = text.split_once('\'').unwrap();
} else {
result = Err(diagnostics.add(ParserDiagnostic {
file_id,
span: node.as_syntax_node().span(db),
kind: ParserDiagnosticKind::UnterminatedString,
}));
}
let (body, _suffix) = match text.rsplit_once('\'') {
Some((body, suffix)) => (body, (!suffix.is_empty()).then_some(suffix)),
None => {
result = Err(diagnostics.add(ParserDiagnostic {
file_id,
span: node.as_syntax_node().span(db),
kind: ParserDiagnosticKind::UnterminatedString,
}));
(text, None)
}
};
let body = match unescape(body) {
Ok(body) => body,
Err(_) => {
result = Err(diagnostics.add(ParserDiagnostic {
file_id,
span: node.as_syntax_node().span(db),
kind: ParserDiagnosticKind::IllegalStringEscaping,
}));
String::new()
}
};
if !body.is_ascii() {
result = Err(diagnostics.add(ParserDiagnostic {
file_id,
span: node.as_syntax_node().span(db),
kind: ParserDiagnosticKind::ShortStringMustBeAscii,
}));
}
result
}