use clap::{Parser, Subcommand};
use std::{collections::HashMap, fs, path::PathBuf, process, sync::OnceLock};
use mutica_compiler::{
ariadne,
parser::{
BuildContext, MultiFileBuilder, MultiFileBuilderError, ParseContext, PatternCounter,
SyntaxError, ast::LinearizeContext, inject_std_library,
},
};
use mutica_core::{
arc_gc::{arc::GCArcWeak, gc::GC, traceable::GCTraceable},
scheduler::{self, ContinuationOrHandler, stack::Stack},
stacksafe::{set_minimum_stack_size, set_stack_allocation_size},
tokio,
types::{AsDispatcher, GcAllocObject, Representable, TaggedPtr, Type, TypeError, TypeRef},
util::{cycle_detector::FastCycleDetector, rootstack::RootStack},
};
pub struct TypeGcOnceLock {
inner: OnceLock<Type<TypeGcOnceLock>>,
}
impl GcAllocObject<TypeGcOnceLock> for TypeGcOnceLock {
type Inner = Type<TypeGcOnceLock>;
fn new_placeholder() -> Self {
TypeGcOnceLock {
inner: OnceLock::new(),
}
}
fn get_value(&self) -> Option<&Self::Inner> {
self.inner.get()
}
fn map_value<F, R>(&self, path: &mut FastCycleDetector<TaggedPtr<()>>, f: F) -> Option<R>
where
F: FnOnce(
&mut FastCycleDetector<TaggedPtr<()>>,
<Self::Inner as AsDispatcher<Self::Inner, TypeGcOnceLock>>::RefDispatcher<'_>,
) -> R,
{
self.get_value()
.map(|inner| f(path, inner.as_ref_dispatcher()))
}
fn set_value(&self, _value: Self::Inner) -> Result<(), TypeError<Self::Inner, TypeGcOnceLock>> {
self.inner
.set(_value)
.map_err(|_| TypeError::RedeclaredType)
}
fn take_value<F, R>(&self, path: &mut FastCycleDetector<TaggedPtr<()>>, f: F) -> Option<R>
where
F: FnOnce(&mut FastCycleDetector<TaggedPtr<()>>, Self::Inner) -> R,
TypeGcOnceLock: GcAllocObject<TypeGcOnceLock>,
{
self.get_value().map(|inner| f(path, inner.clone()))
}
}
impl GCTraceable<TypeGcOnceLock> for TypeGcOnceLock {
fn collect(&self, queue: &mut std::collections::VecDeque<GCArcWeak<TypeGcOnceLock>>) {
if let Some(t) = self.inner.get() {
t.collect(queue);
}
}
}
#[derive(Parser)]
#[command(name = "mutica")]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Run {
file: String,
},
Version,
}
#[tokio::main]
async fn main() {
set_stack_allocation_size(16 * 1024 * 1024); set_minimum_stack_size(512 * 1024); let cli = Cli::parse();
match cli.command {
Command::Run { file } => {
let code = match fs::read_to_string(&file) {
Ok(c) => c,
Err(e) => {
eprintln!("Failed to read file '{}': {}", file, e);
process::exit(1);
}
};
parse_and_reduce(&code, PathBuf::from(file)).await;
}
Command::Version => {
println!("Mutica version {}", env!("CARGO_PKG_VERSION"));
}
}
}
pub async fn parse_and_reduce(expr: &str, path: PathBuf) {
#[cfg(debug_assertions)]
println!("Parsing expression:\n{}\n", expr);
let mut imported_ast = HashMap::new();
let mut cycle_detector = FastCycleDetector::new();
let mut builder_errors = Vec::new();
let mut multifile_builder =
MultiFileBuilder::new(&mut imported_ast, &mut cycle_detector, &mut builder_errors);
let (mut ast, source) = multifile_builder.build(path.clone(), expr.to_string());
if let Some((ast, _)) = ast.as_mut() {
*ast = inject_std_library(ast.clone(), &mut builder_errors);
}
let basic = match ast {
Some(ast) if builder_errors.is_empty() => ast,
None | Some(_) => {
for error_with_loc in &builder_errors {
let (filepath, source_content) = if let Some(location) = error_with_loc.location() {
let source = location.source();
(source.filepath(), source.content().to_string())
} else {
(path.to_string_lossy().to_string(), expr.to_string())
};
match error_with_loc.value() {
MultiFileBuilderError::SyntaxError(e) => {
let syntax_error = SyntaxError::new(e.clone());
let report = syntax_error.report(filepath.clone(), &source_content, None);
report
.eprint((filepath, ariadne::Source::from(source_content)))
.ok();
}
MultiFileBuilderError::RecoveryError(e) => {
let report = mutica_compiler::parser::report_error_recovery(
e,
filepath.clone(),
&source_content,
);
report
.eprint((filepath, ariadne::Source::from(source_content)))
.ok();
}
MultiFileBuilderError::IOError(e) => {
let range = error_with_loc
.location()
.map(|r| r.span().clone())
.unwrap_or(0..0);
ariadne::Report::build(
ariadne::ReportKind::Error,
filepath.as_str(),
range.start,
)
.with_label(ariadne::Label::new((filepath.as_str(), range)).with_message(e))
.finish()
.eprint((filepath.as_str(), ariadne::Source::from(source_content)))
.ok();
}
}
}
return;
}
};
let linearized = basic
.0
.linearize(&mut LinearizeContext::new(), basic.0.location())
.finalize();
let mut flow_errors = Vec::new();
let flowed = linearized.flow(
&mut ParseContext::new(),
false,
linearized.location(),
&mut flow_errors,
);
if !flow_errors.is_empty() {
let filepath = source.filepath();
let source_content = source.content();
let mut has_error = false;
for e in &flow_errors {
let filepath = e
.location()
.map(|loc| loc.source().filepath())
.unwrap_or_else(|| filepath.clone());
let source_content = e
.location()
.map(|loc| loc.source().content().to_string())
.unwrap_or_else(|| source_content.to_string());
e.report()
.eprint((filepath, ariadne::Source::from(source_content)))
.ok();
if !e.is_warning() {
has_error = true;
}
}
if has_error {
return;
}
}
let flowed = flowed.ty().clone();
let mut gc = GC::new();
let mut roots = RootStack::new();
let built_type = match flowed.to_type(
&mut BuildContext::new(),
&mut PatternCounter::new(),
false,
&mut gc,
&mut roots,
flowed.location(),
) {
Ok(result) => result,
Err(Ok(type_error)) => {
println!("Type building error: {:?}", type_error);
return;
}
Err(Err(parse_error)) => {
let filepath = source.filepath();
let source_content = source.content().to_string();
parse_error
.report()
.eprint((filepath, ariadne::Source::from(source_content)))
.ok();
return;
}
};
#[cfg(debug_assertions)]
println!(
"Built type: {}\n",
built_type.ty().display(&mut FastCycleDetector::new(), 0, 2)
);
let mut linear_scheduler =
roots.context(|_| scheduler::LinearScheduler::new(built_type.ty().clone(), None));
let mut step_counter = 0;
const SWEEP_INTERVAL: usize = 8192;
let result = loop {
#[cfg(debug_assertions)]
gc.collect();
#[cfg(debug_assertions)]
linear_scheduler.sweep_roots();
match linear_scheduler.step(&mut gc).await {
Ok(true) => (),
Ok(false) => break Ok(linear_scheduler.current().clone()),
Err(e) => break Err(e),
}
step_counter += 1;
if step_counter >= SWEEP_INTERVAL {
linear_scheduler.sweep_roots();
step_counter = 0;
}
};
struct StackTraceReport {
pub report: mutica_compiler::ariadne::Report<'static, (String, std::ops::Range<usize>)>,
pub sources: Vec<(String, String)>, }
impl StackTraceReport {
pub fn new(
report: mutica_compiler::ariadne::Report<'static, (String, std::ops::Range<usize>)>,
sources: Vec<(String, String)>,
) -> Self {
Self { report, sources }
}
pub fn eprint(&self) -> std::io::Result<()> {
let cache = mutica_compiler::ariadne::sources(
self.sources
.iter()
.map(|(path, content)| (path.clone(), content.clone()))
.collect::<std::collections::HashMap<_, _>>(),
);
self.report.eprint(cache)
}
}
fn dump_stack_report(stack: &Stack<ContinuationOrHandler<TypeGcOnceLock>>) -> StackTraceReport {
use mutica_compiler::ariadne::{Color, Label, Report, ReportKind};
use mutica_core::types::CoinductiveType;
use mutica_core::util::source_info::byte_offset_to_char_offset;
let mut sources = Vec::new();
if stack.is_empty() {
let report = Report::build(ReportKind::Error, "<stack>".to_string(), 0)
.with_message("Stack Trace")
.with_note("Stack is empty")
.finish();
return StackTraceReport::new(report, sources);
}
let mut builder =
Report::build(ReportKind::Error, "<stack>".to_string(), 0).with_message("Stack Trace");
for (i, ty) in stack.iter().enumerate() {
let (kind, ty_ref) = match ty {
ContinuationOrHandler::Continuation(t) => ("Continuation", t),
ContinuationOrHandler::PerformHandler(v) => ("Perform Handler", v),
};
let display = ty_ref.display(&mut FastCycleDetector::new(), 0, 2);
if let Some(loc) = ty_ref.source_info() {
let filepath = loc.source().filepath().to_string();
let content = loc.source().content().to_string();
let span = loc.span().clone();
let char_start = byte_offset_to_char_offset(&content, span.start);
let char_end = byte_offset_to_char_offset(&content, span.end);
if !sources.iter().any(|(path, _)| path == &filepath) {
sources.push((filepath.clone(), content.clone()));
}
builder = builder.with_label(
Label::new((filepath, char_start..char_end))
.with_message(format!("[{}] {}: {}", i, kind, display))
.with_color(Color::Cyan),
);
} else {
builder = builder.with_note(format!("[{}] {}: {}", i, kind, display));
}
}
let report = builder.finish();
StackTraceReport::new(report, sources)
}
match result {
Ok(v) => v
.map(&mut FastCycleDetector::new(), |_, ty| match ty {
TypeRef::Tuple(tuple) if tuple.is_empty() => (),
_ => {
println!(
"{}",
v.display(&mut FastCycleDetector::new(), 0, usize::MAX)
);
}
})
.unwrap_or_else(|e| panic!("Error during type mapping: {:?}", e))
.unwrap_or(()),
Err(e) => {
dump_stack_report(linear_scheduler.stack()).eprint().ok();
e.to_report().eprint().ok();
}
}
}