use std::fs;
use std::path::Path;
use std::sync::Arc;
use std::time::Instant;
use harn_parser::DiagnosticSeverity;
use crate::commands::time::RunTiming;
use crate::package;
pub(crate) struct LoadedChunk {
pub(crate) source: String,
pub(crate) chunk: harn_vm::Chunk,
pub(crate) link_table: Option<Arc<harn_vm::context_manifest::GraphLinkTable>>,
}
pub(crate) fn compile_or_load_chunk_for_run(
path: &str,
stderr: &mut String,
) -> Option<LoadedChunk> {
compile_or_load_chunk_with_timing(path, stderr, None)
}
#[allow(clippy::needless_option_as_deref)]
pub(crate) fn compile_or_load_chunk_with_timing(
path: &str,
stderr: &mut String,
mut timing: Option<&mut RunTiming>,
) -> Option<LoadedChunk> {
let source = match fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
stderr.push_str(&format!("Error reading {path}: {e}\n"));
return None;
}
};
if let Some(t) = timing.as_deref_mut() {
t.input_bytes = source.len() as u64;
}
let compile_phase_start = Instant::now();
let lookup = harn_vm::bytecode_cache::load(Path::new(path), &source);
if let Some(chunk) = lookup.chunk {
if let Some(t) = timing.as_deref_mut() {
t.cache_hit = true;
t.bytecode_compile = compile_phase_start.elapsed();
}
return Some(LoadedChunk {
source,
chunk,
link_table: lookup.link_table,
});
}
if let Some(t) = timing.as_deref_mut() {
t.cache_hit = false;
}
let parse_start = Instant::now();
let program = parse_source_for_run(path, &source, stderr)?;
if let Some(t) = timing.as_deref_mut() {
t.parse = parse_start.elapsed();
}
let typecheck_start = Instant::now();
let mut had_type_error = false;
let type_diagnostics = match typecheck_with_imports(&program, Path::new(path), &source) {
Ok(diagnostics) => diagnostics,
Err(error) => {
stderr.push_str(&format!("error: {error}\n"));
return None;
}
};
for diag in &type_diagnostics {
let rendered = harn_parser::diagnostic::render_type_diagnostic(&source, path, diag);
if matches!(diag.severity, DiagnosticSeverity::Error) {
had_type_error = true;
}
stderr.push_str(&rendered);
}
if let Some(t) = timing.as_deref_mut() {
t.typecheck = typecheck_start.elapsed();
}
if had_type_error {
return None;
}
let compile_step_start = Instant::now();
let chunk = match crate::compiler_for_source(Path::new(path), &source).compile(&program) {
Ok(c) => c,
Err(e) => {
stderr.push_str(&format!("error: compile error: {e}\n"));
return None;
}
};
if let Err(err) = lookup.store(&chunk) {
if std::env::var_os(crate::dispatch::CACHE_DEBUG_ENV).is_some() {
eprintln!("[harn] bytecode cache write skipped: {err}");
}
}
if let Some(t) = timing.as_deref_mut() {
t.bytecode_compile = compile_step_start.elapsed();
}
Some(LoadedChunk {
source,
chunk,
link_table: lookup.link_table,
})
}
pub(super) fn parse_source_for_run(
path: &str,
source: &str,
stderr: &mut String,
) -> Option<Vec<harn_parser::SNode>> {
crate::ensure_builtin_signatures_installed();
let mut lexer = harn_lexer::Lexer::new(source);
let tokens = match lexer.tokenize() {
Ok(tokens) => tokens,
Err(error) => {
let diagnostic = harn_parser::diagnostic::render_diagnostic_with_code(
source,
path,
&error_span_from_lex(&error),
"error",
harn_parser::diagnostic::lexer_error_code(&error),
&error.to_string(),
Some("here"),
None,
);
stderr.push_str(&diagnostic);
return None;
}
};
let mut parser = harn_parser::Parser::new(tokens);
match parser.parse() {
Ok(program) => Some(program),
Err(error) => {
if parser.all_errors().is_empty() {
render_parse_error(path, source, &error, stderr);
} else {
for error in parser.all_errors() {
render_parse_error(path, source, error, stderr);
}
}
None
}
}
}
fn render_parse_error(
path: &str,
source: &str,
error: &harn_parser::ParserError,
stderr: &mut String,
) {
let span = error_span_from_parse(error);
let diagnostic = harn_parser::diagnostic::render_diagnostic_with_code(
source,
path,
&span,
"error",
harn_parser::diagnostic::parser_error_code(error),
&harn_parser::diagnostic::parser_error_message(error),
Some(harn_parser::diagnostic::parser_error_label(error)),
harn_parser::diagnostic::parser_error_help(error),
);
stderr.push_str(&diagnostic);
}
fn error_span_from_lex(error: &harn_lexer::LexerError) -> harn_lexer::Span {
match error {
harn_lexer::LexerError::UnexpectedCharacter(_, span)
| harn_lexer::LexerError::UnterminatedString(span)
| harn_lexer::LexerError::IntegerLiteralOutOfRange(_, span)
| harn_lexer::LexerError::UnterminatedBlockComment(span) => *span,
}
}
fn error_span_from_parse(error: &harn_parser::ParserError) -> harn_lexer::Span {
match error {
harn_parser::ParserError::Unexpected { span, .. } => *span,
harn_parser::ParserError::UnexpectedEof { span, .. } => *span,
}
}
pub(super) fn typecheck_with_imports(
program: &[harn_parser::SNode],
path: &Path,
source: &str,
) -> Result<Vec<harn_parser::TypeDiagnostic>, String> {
package::ensure_dependencies_materialized(path)?;
let checker = crate::typecheck_imports::checker_with_resolved_imports(
harn_parser::TypeChecker::new(),
path,
);
Ok(checker.check_with_source(program, source))
}