185 lines
6.8 KiB
Rust
185 lines
6.8 KiB
Rust
use full_moon::ast::{Ast, Stmt, LastStmt, Expression};
|
|
use full_moon::node::Node;
|
|
use crate::common::diagnostics::{DiagnosticBundle, Diagnostic, DiagnosticLevel};
|
|
use crate::common::spans::Span;
|
|
|
|
pub fn validate_ast(ast: &Ast, file_id: usize) -> Result<(), DiagnosticBundle> {
|
|
let mut bundle = DiagnosticBundle::new();
|
|
|
|
let block = ast.nodes();
|
|
for stmt in block.stmts() {
|
|
validate_stmt(stmt, &mut bundle, file_id);
|
|
}
|
|
|
|
if let Some(last_stmt) = block.last_stmt() {
|
|
validate_last_stmt(last_stmt, &mut bundle, file_id);
|
|
}
|
|
|
|
if bundle.diagnostics.is_empty() {
|
|
Ok(())
|
|
} else {
|
|
Err(bundle)
|
|
}
|
|
}
|
|
|
|
fn get_span(node: impl Node, file_id: usize) -> Option<Span> {
|
|
node.tokens().next().and_then(|t| {
|
|
let start = t.token().start_position();
|
|
node.tokens().last().map(|last_t| {
|
|
let end = last_t.token().end_position();
|
|
Span::new(file_id, start.bytes() as u32, end.bytes() as u32)
|
|
})
|
|
})
|
|
}
|
|
|
|
fn validate_stmt(stmt: &Stmt, bundle: &mut DiagnosticBundle, file_id: usize) {
|
|
match stmt {
|
|
Stmt::LocalAssignment(assignment) => {
|
|
if assignment.expressions().len() == 0 {
|
|
bundle.push(Diagnostic {
|
|
message: "Local declaration must have an initializer in this subset.".into(),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(assignment, file_id),
|
|
});
|
|
}
|
|
// Check for multiple assignments (not supported in subset)
|
|
if assignment.names().len() > 1 || assignment.expressions().len() > 1 {
|
|
bundle.push(Diagnostic {
|
|
message: "Multiple assignments are not supported in this subset.".into(),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(assignment, file_id),
|
|
});
|
|
}
|
|
for expr in assignment.expressions() {
|
|
validate_expr(expr, bundle, file_id);
|
|
}
|
|
}
|
|
Stmt::Assignment(assignment) => {
|
|
if assignment.variables().len() > 1 || assignment.expressions().len() > 1 {
|
|
bundle.push(Diagnostic {
|
|
message: "Multiple assignments are not supported in this subset.".into(),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(assignment, file_id),
|
|
});
|
|
}
|
|
for expr in assignment.expressions() {
|
|
validate_expr(expr, bundle, file_id);
|
|
}
|
|
}
|
|
Stmt::FunctionCall(call) => {
|
|
validate_expr(&Expression::FunctionCall(call.clone()), bundle, file_id);
|
|
}
|
|
Stmt::If(if_stmt) => {
|
|
validate_expr(if_stmt.condition(), bundle, file_id);
|
|
for s in if_stmt.block().stmts() {
|
|
validate_stmt(s, bundle, file_id);
|
|
}
|
|
if let Some(else_ifs) = if_stmt.else_if() {
|
|
for else_if in else_ifs {
|
|
validate_expr(else_if.condition(), bundle, file_id);
|
|
for s in else_if.block().stmts() {
|
|
validate_stmt(s, bundle, file_id);
|
|
}
|
|
}
|
|
}
|
|
if let Some(else_block) = if_stmt.else_block() {
|
|
for s in else_block.stmts() {
|
|
validate_stmt(s, bundle, file_id);
|
|
}
|
|
}
|
|
}
|
|
Stmt::While(while_stmt) => {
|
|
validate_expr(while_stmt.condition(), bundle, file_id);
|
|
for s in while_stmt.block().stmts() {
|
|
validate_stmt(s, bundle, file_id);
|
|
}
|
|
}
|
|
Stmt::Do(do_stmt) => {
|
|
for s in do_stmt.block().stmts() {
|
|
validate_stmt(s, bundle, file_id);
|
|
}
|
|
if let Some(last) = do_stmt.block().last_stmt() {
|
|
validate_last_stmt(last, bundle, file_id);
|
|
}
|
|
}
|
|
Stmt::FunctionDeclaration(f) => {
|
|
// Check for varargs
|
|
if f.body().parameters().iter().any(|p| matches!(p, full_moon::ast::Parameter::Ellipsis(_))) {
|
|
bundle.push(Diagnostic {
|
|
message: "Varargs (...) are not supported in this subset.".into(),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(f, file_id),
|
|
});
|
|
}
|
|
for s in f.body().block().stmts() {
|
|
validate_stmt(s, bundle, file_id);
|
|
}
|
|
if let Some(last) = f.body().block().last_stmt() {
|
|
validate_last_stmt(last, bundle, file_id);
|
|
}
|
|
}
|
|
_ => {
|
|
// For color-square-lua, we might need to support more statement types if used.
|
|
// Let's see what's failing.
|
|
bundle.push(Diagnostic {
|
|
message: format!("Statement type not supported in this subset: {:?}", stmt),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(stmt, file_id),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn validate_expr(expr: &Expression, bundle: &mut DiagnosticBundle, file_id: usize) {
|
|
match expr {
|
|
Expression::BinaryOperator { lhs, rhs, .. } => {
|
|
validate_expr(lhs, bundle, file_id);
|
|
validate_expr(rhs, bundle, file_id);
|
|
}
|
|
Expression::UnaryOperator { expression, .. } => {
|
|
validate_expr(expression, bundle, file_id);
|
|
}
|
|
Expression::Number(_) | Expression::String(_) | Expression::Symbol(_) => {}
|
|
Expression::Var(_) => {
|
|
}
|
|
Expression::FunctionCall(_) => {
|
|
}
|
|
Expression::TableConstructor(_) => {
|
|
}
|
|
Expression::Function(_) => {
|
|
bundle.push(Diagnostic {
|
|
message: "Anonymous functions/closures are not supported in this subset.".into(),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(expr, file_id),
|
|
});
|
|
}
|
|
_ => {
|
|
bundle.push(Diagnostic {
|
|
message: "Expression type not supported in this subset.".into(),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(expr, file_id),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn validate_last_stmt(last_stmt: &LastStmt, bundle: &mut DiagnosticBundle, file_id: usize) {
|
|
match last_stmt {
|
|
LastStmt::Return(ret) => {
|
|
if ret.returns().len() > 1 {
|
|
bundle.push(Diagnostic {
|
|
message: "Multiple return values are not supported in this subset.".into(),
|
|
level: DiagnosticLevel::Error,
|
|
span: get_span(ret, file_id),
|
|
});
|
|
}
|
|
for expr in ret.returns() {
|
|
validate_expr(expr, bundle, file_id);
|
|
}
|
|
}
|
|
LastStmt::Break(_) => {
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|