use std::path::Path;
use praxis_ast::AstNode;
use praxis_codegen_cranelift::Jit;
use praxis_hir::{TypedItem, analyze_root, lower, mono::monomorphize};
use praxis_mir::{annotate, lower_module, verify};
use praxis_runtime::{Runtime, RuntimeContext};
use praxis_source::diagnostic::sort_by_position;
use crate::breakpoint_host;
use crate::debug_mode::DebugMode;
use crate::{diagnostic_render, exit_code, source_file};
pub fn run(
file: &str,
input_file: Option<&str>,
debug: DebugMode,
color: crate::color_mode::ColorMode,
) -> anyhow::Result<i32> {
let path = Path::new(file);
let text = match source_file::read(file) {
Ok(t) => t,
Err(code) => return Ok(code),
};
let source = praxis_source::SourceMap::new();
let id = source.intern(path, text.clone());
let parsed = praxis_parser::parse(id, &text);
let mut diagnostics = parsed.diagnostics;
let mut analysis = analyze_root(id, &parsed.tree);
diagnostics.extend(analysis.diagnostics.clone());
sort_by_position(&mut diagnostics);
let rendered = diagnostic_render::render_all(&source, &diagnostics, color.palette());
if rendered.has_errors() {
diagnostic_render::write_to(&mut std::io::stderr(), &rendered)?;
return Ok(exit_code::FAILED);
}
let root = match praxis_ast::SourceFile::cast(parsed.tree.clone()) {
Some(r) => r,
None => {
eprintln!("error: internal — parse tree root is not a SOURCE_FILE");
return Ok(exit_code::FAILED);
}
};
let module = lower(id, &root, &mut analysis);
if !module.diagnostics.is_empty() {
let mut all = module.diagnostics.clone();
sort_by_position(&mut all);
let rendered = diagnostic_render::render_all(&source, &all, color.palette());
diagnostic_render::write_to(&mut std::io::stderr(), &rendered)?;
return Ok(exit_code::FAILED);
}
let Some(entry_name) = praxis_hir::entry_point(|name| {
module
.items
.iter()
.any(|item| matches!(item, TypedItem::Fn(f) if f.name == name))
}) else {
eprintln!("error: no statements to run");
if module
.items
.iter()
.any(|item| matches!(item, TypedItem::Fn(f) if f.name == "main"))
{
eprintln!(
"note: this file declares `fn main`, but a Praxis program is its \
top-level statements — call it with `main()`, or move its body \
to the top level"
);
}
return Ok(exit_code::FAILED);
};
let module = monomorphize(module, &analysis.names, &mut analysis.db);
let mut funcs = lower_module(&module, &mut analysis.db);
for f in &mut funcs {
annotate(f);
if let Err(errs) = verify(f) {
eprintln!("internal error: {}", praxis_mir::verify::report(&errs));
return Ok(exit_code::FAILED);
}
}
let mut jit = match Jit::new() {
Ok(j) => j,
Err(e) => {
eprintln!("error: could not initialize the JIT: {e}");
return Ok(exit_code::FAILED);
}
};
let ids = match jit.compile(&funcs, &mut analysis.db) {
Ok(ids) => ids,
Err(e) => {
eprintln!("error: JIT compilation failed: {e}");
return Ok(exit_code::FAILED);
}
};
let Some(entry_id) = ids.get(entry_name).copied() else {
eprintln!("internal error: the entry point `{entry_name}` was not compiled");
return Ok(exit_code::FAILED);
};
let mut runtime = Runtime::new();
let mut ctx = runtime.context();
match input_file {
Some(path) => match std::fs::read_to_string(path) {
Ok(t) => {
let input_ref = runtime.alloc_text(&t);
ctx.input_source = input_ref;
lazy_stdin::record(t);
}
Err(err) => {
eprintln!("error: failed to read input file `{path}`: {err}");
return Ok(exit_code::USAGE);
}
},
None => praxis_runtime::install_input_reader(lazy_stdin::read),
}
let source_name = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
breakpoint_host::install(&analysis.db, &text, &source_name, debug, color);
let entry: praxis_debugger::session::EntryPoint =
unsafe { std::mem::transmute(jit.entry(entry_id)) };
let _ = unsafe { entry(&mut ctx as *mut RuntimeContext) };
breakpoint_host::disarm();
if runtime.has_pending_fault() {
let kind = runtime.fault();
let message = runtime.fault_message().map(str::to_string);
if debug.wants_repl() {
if let Some(snapshot) = runtime.take_crash_snapshot() {
let ctx = praxis_debugger::render::RenderCtx::new(&analysis.db, &text);
praxis_debugger::render::render_noninteractive(
&mut std::io::stderr(),
kind,
message.as_deref(),
Some(&snapshot),
Some(runtime.parse_detail()),
color.palette(),
&ctx,
)?;
let session = praxis_debugger::session::DebugSession {
jit,
entry_fn: entry,
runtime,
analysis,
source_text: text.clone(),
source_path: path.to_path_buf(),
input_text: lazy_stdin::text(),
eval_generation: std::rc::Rc::new(praxis_codegen_cranelift::Generation::new()),
};
praxis_runtime::clear_input_reader();
let mut repl = praxis_debugger::repl::Repl::new_session(snapshot, session);
if praxis_debugger::tui::should_use_tui() {
let stop = praxis_debugger::tui::Stop::Fault(kind, message.clone());
let tui = praxis_debugger::tui::Tui::new(repl, stop);
(repl, _) = praxis_debugger::tui::run(tui)?;
} else {
let stdin = std::io::stdin();
let mut stdin = stdin.lock();
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
let _ = repl.run(&mut stdin, &mut stderr);
}
if let Some(session) = repl.into_session() {
session.teardown();
}
} else {
let ctx = praxis_debugger::render::RenderCtx::new(&analysis.db, &text);
praxis_debugger::render::render_noninteractive(
&mut std::io::stderr(),
kind,
message.as_deref(),
None,
Some(runtime.parse_detail()),
color.palette(),
&ctx,
)?;
jit.retire(runtime.teardown());
}
} else {
let ctx = praxis_debugger::render::RenderCtx::new(&analysis.db, &text);
praxis_debugger::render::render_noninteractive(
&mut std::io::stderr(),
kind,
message.as_deref(),
runtime.crash_snapshot(),
Some(runtime.parse_detail()),
color.palette(),
&ctx,
)?;
jit.retire(runtime.teardown());
}
return Ok(exit_code::FAILED);
}
let proof = runtime.teardown();
praxis_runtime::retire_parser_plans(&proof);
jit.retire(proof);
Ok(exit_code::OK)
}
mod lazy_stdin {
use std::cell::RefCell;
thread_local! {
static TEXT: RefCell<String> = const { RefCell::new(String::new()) };
}
pub(super) fn text() -> String {
TEXT.with(|slot| slot.borrow().clone())
}
pub(super) fn record(input: String) {
TEXT.with(|slot| *slot.borrow_mut() = input);
}
pub(super) fn read() -> Vec<u8> {
use std::io::IsTerminal;
if std::io::stdin().is_terminal() {
return Vec::new();
}
match std::io::read_to_string(std::io::stdin()) {
Ok(t) => {
let bytes = t.as_bytes().to_vec();
record(t);
bytes
}
Err(err) => {
eprintln!("error: failed to read input from stdin: {err}");
std::process::exit(crate::exit_code::USAGE);
}
}
}
}