feat: add diagnostic system and improve CLI with check command

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
This commit is contained in:
2026-04-06 17:07:50 +02:00
parent 0da224325a
commit dfd2f10234
12 changed files with 1315 additions and 502 deletions

View File

@@ -1,41 +1,98 @@
use std::fmt::Write;
use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};
use nxc_frontend::{Lexer, Module, ParseError, Parser, Token};
use nxc_frontend::{
has_errors, Diagnostic, Item, LexResult, Lexer, Module, ParseResult, Parser, Token,
};
#[derive(Debug)]
pub struct CompileResult {
#[derive(Debug, Clone)]
pub struct FrontendOutput {
pub path: PathBuf,
pub source: String,
pub tokens: Vec<Token>,
pub module: Module,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug)]
pub enum CompileError {
pub enum DriverError {
Io(std::io::Error),
Parse(ParseError),
}
impl From<std::io::Error> for CompileError {
impl From<std::io::Error> for DriverError {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
impl From<ParseError> for CompileError {
fn from(value: ParseError) -> Self {
Self::Parse(value)
impl FrontendOutput {
pub fn has_errors(&self) -> bool {
has_errors(&self.diagnostics)
}
pub fn summary(&self) -> AstSummary {
let mut summary = AstSummary {
items: self.module.items.len(),
..AstSummary::default()
};
for item in &self.module.items {
match item {
Item::Function(_) => summary.functions += 1,
Item::Struct(_) => summary.structs += 1,
}
}
summary
}
pub fn render_diagnostics(&self) -> String {
let path = self.path.display().to_string();
let mut out = String::new();
for (index, diagnostic) in self.diagnostics.iter().enumerate() {
if index > 0 {
out.push('\n');
out.push('\n');
}
let _ = write!(out, "{}", diagnostic.render(&path, &self.source));
}
out
}
}
pub fn compile_file(path: impl AsRef<Path>) -> Result<CompileResult, CompileError> {
let source = fs::read_to_string(path)?;
compile_source(&source)
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct AstSummary {
pub items: usize,
pub functions: usize,
pub structs: usize,
}
pub fn compile_source(source: &str) -> Result<CompileResult, CompileError> {
let tokens = Lexer::new(source).tokenize();
let mut parser = Parser::new(tokens.clone());
let module = parser.parse_module()?;
Ok(CompileResult { tokens, module })
pub fn check_file(path: impl AsRef<Path>) -> Result<FrontendOutput, DriverError> {
let path = path.as_ref().to_path_buf();
let source = fs::read_to_string(&path)?;
Ok(check_source(path, source))
}
pub fn check_source(path: PathBuf, source: String) -> FrontendOutput {
let LexResult {
tokens,
diagnostics: mut lexer_diagnostics,
} = Lexer::new(&source).lex();
let ParseResult {
module,
diagnostics: parser_diagnostics,
} = Parser::new(tokens.clone()).parse_module();
lexer_diagnostics.extend(parser_diagnostics);
FrontendOutput {
path,
source,
tokens,
module,
diagnostics: lexer_diagnostics,
}
}