Implement comprehensive diagnostic reporting system: - Add Diagnostic struct with severity levels and span-based error tracking - Add diagnostic rendering with source context and caret positioning - Replace ParseError with diagnostic collection in lexer and parser - Add LexResult and ParseResult types to carry diagnostics Enhance driver crate with frontend output: - Replace CompileResult with FrontendOutput containing diagnostics - Add has_errors() and render_diagnostics() methods - Add AstSummary for
181 lines
4.8 KiB
Rust
181 lines
4.8 KiB
Rust
use nxc_frontend::{
|
|
BinaryOp, ExprKind, Item, Lexer, Literal, Parser, StmtKind, UnaryOp,
|
|
};
|
|
|
|
fn parse(source: &str) -> nxc_frontend::ParseResult {
|
|
let lexed = Lexer::new(source).lex();
|
|
assert!(
|
|
lexed.diagnostics.is_empty(),
|
|
"unexpected lexer diagnostics: {:?}",
|
|
lexed.diagnostics
|
|
);
|
|
Parser::new(lexed.tokens).parse_module()
|
|
}
|
|
|
|
#[test]
|
|
fn parses_function_with_if_else_and_returns() {
|
|
let source = "\
|
|
fn classify(value: Int) -> Int:
|
|
if value > 10:
|
|
return 1
|
|
else:
|
|
return 0
|
|
";
|
|
|
|
let parsed = parse(source);
|
|
assert!(parsed.diagnostics.is_empty(), "{:?}", parsed.diagnostics);
|
|
assert_eq!(parsed.module.items.len(), 1);
|
|
|
|
let Item::Function(function) = &parsed.module.items[0] else {
|
|
panic!("expected function item");
|
|
};
|
|
|
|
assert_eq!(function.name, "classify");
|
|
assert_eq!(function.params.len(), 1);
|
|
assert_eq!(function.body.statements.len(), 1);
|
|
|
|
let StmtKind::If(if_stmt) = &function.body.statements[0].kind else {
|
|
panic!("expected if statement");
|
|
};
|
|
|
|
match &if_stmt.condition.kind {
|
|
ExprKind::Binary { op, .. } => assert_eq!(*op, BinaryOp::Greater),
|
|
other => panic!("unexpected condition: {other:?}"),
|
|
}
|
|
assert!(if_stmt.else_block.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn respects_binary_operator_precedence() {
|
|
let source = "\
|
|
fn main() -> Int:
|
|
return 1 + 2 * 3 == 7 || false
|
|
";
|
|
|
|
let parsed = parse(source);
|
|
assert!(parsed.diagnostics.is_empty(), "{:?}", parsed.diagnostics);
|
|
|
|
let Item::Function(function) = &parsed.module.items[0] else {
|
|
panic!("expected function item");
|
|
};
|
|
|
|
let StmtKind::Return(Some(expr)) = &function.body.statements[0].kind else {
|
|
panic!("expected return statement");
|
|
};
|
|
|
|
let ExprKind::Binary { left, op, right } = &expr.kind else {
|
|
panic!("expected binary expression");
|
|
};
|
|
assert_eq!(*op, BinaryOp::LogicalOr);
|
|
|
|
let ExprKind::Binary {
|
|
left: equality_left,
|
|
op: equality_op,
|
|
right: equality_right,
|
|
} = &left.kind
|
|
else {
|
|
panic!("expected equality expression");
|
|
};
|
|
assert_eq!(*equality_op, BinaryOp::Equal);
|
|
|
|
let ExprKind::Binary {
|
|
left: add_left,
|
|
op: add_op,
|
|
right: add_right,
|
|
} = &equality_left.kind
|
|
else {
|
|
panic!("expected additive expression");
|
|
};
|
|
assert_eq!(*add_op, BinaryOp::Add);
|
|
|
|
match &add_left.kind {
|
|
ExprKind::Literal(Literal::Integer(1)) => {}
|
|
other => panic!("unexpected left additive operand: {other:?}"),
|
|
}
|
|
|
|
let ExprKind::Binary {
|
|
left: mul_left,
|
|
op: mul_op,
|
|
right: mul_right,
|
|
} = &add_right.kind
|
|
else {
|
|
panic!("expected multiplicative expression");
|
|
};
|
|
assert_eq!(*mul_op, BinaryOp::Multiply);
|
|
|
|
match &mul_left.kind {
|
|
ExprKind::Literal(Literal::Integer(2)) => {}
|
|
other => panic!("unexpected left multiplicative operand: {other:?}"),
|
|
}
|
|
match &mul_right.kind {
|
|
ExprKind::Literal(Literal::Integer(3)) => {}
|
|
other => panic!("unexpected right multiplicative operand: {other:?}"),
|
|
}
|
|
match &equality_right.kind {
|
|
ExprKind::Literal(Literal::Integer(7)) => {}
|
|
other => panic!("unexpected equality right operand: {other:?}"),
|
|
}
|
|
match &right.kind {
|
|
ExprKind::Literal(Literal::Bool(false)) => {}
|
|
other => panic!("unexpected logical-or right operand: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn parses_grouping_unary_and_calls() {
|
|
let source = "\
|
|
fn main() -> Int:
|
|
return -(compute(1, 2) + 3)
|
|
";
|
|
|
|
let parsed = parse(source);
|
|
assert!(parsed.diagnostics.is_empty(), "{:?}", parsed.diagnostics);
|
|
|
|
let Item::Function(function) = &parsed.module.items[0] else {
|
|
panic!("expected function item");
|
|
};
|
|
let StmtKind::Return(Some(expr)) = &function.body.statements[0].kind else {
|
|
panic!("expected return statement");
|
|
};
|
|
|
|
let ExprKind::Unary { op, expr: inner } = &expr.kind else {
|
|
panic!("expected unary expression");
|
|
};
|
|
assert_eq!(*op, UnaryOp::Negate);
|
|
|
|
let ExprKind::Group(grouped) = &inner.kind else {
|
|
panic!("expected grouped expression");
|
|
};
|
|
|
|
let ExprKind::Binary { left, op, .. } = &grouped.kind else {
|
|
panic!("expected additive expression");
|
|
};
|
|
assert_eq!(*op, BinaryOp::Add);
|
|
|
|
let ExprKind::Call { args, .. } = &left.kind else {
|
|
panic!("expected call expression");
|
|
};
|
|
assert_eq!(args.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn recovers_and_reports_syntax_errors() {
|
|
let source = "\
|
|
fn broken(value: Int) -> Int
|
|
let x = 1
|
|
|
|
struct Config:
|
|
port: Int
|
|
";
|
|
|
|
let parsed = parse(source);
|
|
assert!(!parsed.diagnostics.is_empty());
|
|
assert_eq!(parsed.module.items.len(), 1);
|
|
|
|
let Item::Struct(struct_decl) = &parsed.module.items[0] else {
|
|
panic!("expected recovered struct declaration");
|
|
};
|
|
assert_eq!(struct_decl.name, "Config");
|
|
}
|
|
|