use std::io::{self, IsTerminal};
use std::path::Path;
use crate::ast::Declaration;
use crate::lexer::{Lexer, LexerError};
use crate::parser::{ParseError, Parser};
use crate::type_checker::TypeChecker;
struct Colors {
green_bold: &'static str,
red_bold: &'static str,
yellow_bold: &'static str,
bold: &'static str,
dim: &'static str,
reset: &'static str,
}
impl Colors {
fn new(enabled: bool) -> Self {
if enabled {
Colors {
green_bold: "\x1b[1;32m",
red_bold: "\x1b[1;31m",
yellow_bold: "\x1b[1;33m",
bold: "\x1b[1m",
dim: "\x1b[2m",
reset: "\x1b[0m",
}
} else {
Colors {
green_bold: "",
red_bold: "",
yellow_bold: "",
bold: "",
dim: "",
reset: "",
}
}
}
}
fn count_declarations(decls: &[Declaration]) -> usize {
let mut count = 0;
for decl in decls {
count += 1;
if let Declaration::Epistemic(eb) = decl {
count += count_declarations(&eb.body);
}
}
count
}
pub fn run_check(file: &str, no_color: bool, strict: bool) -> i32 {
let use_color = !no_color && io::stdout().is_terminal();
let c = Colors::new(use_color);
let path = Path::new(file);
let filename = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| file.to_string());
let source = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(_) => {
eprintln!("{}X File not found: {}{}", c.red_bold, file, c.reset);
return 2;
}
};
let tokens = match Lexer::new(&source, file).tokenize() {
Ok(t) => t,
Err(LexerError {
message,
line,
column,
}) => {
let loc = if column > 0 {
format!(":{line}:{column}")
} else {
format!(":{line}")
};
eprintln!("{}X {filename}{loc}{} {message}", c.red_bold, c.reset);
return 1;
}
};
let token_count = tokens.len();
let mut parser = Parser::new(tokens);
let program = match parser.parse() {
Ok(p) => p,
Err(ParseError {
message,
line,
column,
}) => {
let loc = if column > 0 {
format!(":{line}:{column}")
} else {
format!(":{line}")
};
eprintln!(
"{}X {filename}{loc}{} Parse error: {message}",
c.red_bold, c.reset
);
return 1;
}
};
let declaration_count = count_declarations(&program.declarations);
let (type_errors, type_warnings) = TypeChecker::new(&program).check_with_warnings();
if !type_errors.is_empty() {
eprintln!(
"{}X {filename}{} {} error(s){}",
c.red_bold,
c.reset,
type_errors.len(),
if type_warnings.is_empty() {
String::new()
} else {
format!(", {} warning(s)", type_warnings.len())
}
);
for te in &type_errors {
eprintln!(" error [line {}]: {}", te.line, te.message);
}
for tw in &type_warnings {
eprintln!(" warning [line {}]: {}", tw.line, tw.message);
}
return 1;
}
if strict && !type_warnings.is_empty() {
eprintln!(
"{}X {filename}{} 0 errors, {} warning(s) {}(--strict){}",
c.red_bold,
c.reset,
type_warnings.len(),
c.red_bold,
c.reset,
);
for tw in &type_warnings {
eprintln!(" error [line {}]: {}", tw.line, tw.message);
}
return 1;
}
if !type_warnings.is_empty() {
println!(
"{}\u{26A0}{} {}{filename}{} {}{token_count} tokens \u{00B7} {declaration_count} declarations \u{00B7} 0 errors \u{00B7} {} warning(s){}",
c.yellow_bold, c.reset,
c.bold, c.reset,
c.dim, type_warnings.len(), c.reset,
);
for tw in &type_warnings {
println!(" warning [line {}]: {}", tw.line, tw.message);
}
return 0;
}
println!(
"{}\u{2713}{} {}{filename}{} {}{token_count} tokens \u{00B7} {declaration_count} declarations \u{00B7} 0 errors{}",
c.green_bold, c.reset,
c.bold, c.reset,
c.dim, c.reset,
);
0
}