use std::{
collections::{HashMap, HashSet},
fs, io,
path::{Path, PathBuf},
string::FromUtf8Error,
sync::Arc,
};
use clvmr::{Allocator, NodePtr};
use id_arena::Arena;
use include_dir::{Dir, DirEntry, include_dir};
use indexmap::{IndexMap, IndexSet, indexset};
use rowan::{TextRange, TextSize};
use rue_ast::{AstDocument, AstNode};
use rue_diagnostic::{Name, Source, SourceKind};
use rue_hir::{
Declaration, DependencyGraph, Environment, Lowerer, ModuleDeclarations, ModuleSymbol, Scope,
ScopeId, Symbol, SymbolId,
};
use rue_lexer::Lexer;
use rue_parser::Parser;
use thiserror::Error;
use crate::{
Compiler, ImportCache, SyntaxItemKind, check_unused, compile_symbol_items, compile_type_items,
declare_module_items, declare_symbol_items, declare_type_items, resolve_imports,
};
#[derive(Debug, Error)]
pub enum Error {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Codegen error: {0}")]
Lir(#[from] rue_lir::Error),
#[error("Source not found in compilation unit")]
SourceNotFound(SourceKind),
#[error("UTF-8 conversion error: {0}")]
Utf8(#[from] FromUtf8Error),
}
#[derive(Debug, Clone)]
pub struct CompiledTest {
pub name: Option<String>,
pub path: SourceKind,
pub symbol: SymbolId,
pub ptr: NodePtr,
}
#[derive(Debug, Clone)]
pub struct CompiledExport {
pub name: String,
pub symbol: SymbolId,
pub ptr: NodePtr,
}
#[derive(Debug, Clone)]
pub enum FileTree {
File(File),
Directory(Directory),
}
static STD_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src/std");
impl FileTree {
pub fn empty(ctx: &mut Compiler) -> Result<Self, Error> {
Self::load_std_dir(ctx, "empty".to_string(), &[])
}
pub(crate) fn load_std(ctx: &mut Compiler) -> Result<Self, Error> {
Self::load_std_dir(ctx, "std".to_string(), STD_DIR.entries())
}
fn load_std_dir<'a>(
ctx: &mut Compiler,
name: String,
entries: &'a [DirEntry<'a>],
) -> Result<Self, Error> {
let scope = ctx.alloc_child_scope();
let module = ctx.alloc_symbol(Symbol::Module(ModuleSymbol {
name: Some(Name::new(name.as_str(), None)),
scope,
declarations: ModuleDeclarations::default(),
}));
let mut children = Vec::new();
for entry in entries {
children.extend(Self::try_load_std_entry(ctx, entry)?);
}
Ok(Self::Directory(Directory::new(ctx, name, module, children)))
}
fn try_load_std_entry<'a>(
ctx: &mut Compiler,
entry: &'a DirEntry<'a>,
) -> Result<Option<Self>, Error> {
let file_name = entry
.path()
.file_name()
.ok_or(io::Error::new(
io::ErrorKind::InvalidFilename,
"Missing file name",
))?
.to_string_lossy()
.to_string();
if let Some(file) = entry.as_file() {
#[allow(clippy::case_sensitive_file_extension_comparisons)]
if !file_name.ends_with(".rue") {
return Ok(None);
}
let source_kind = SourceKind::Std(entry.path().to_string_lossy().to_string());
let text = String::from_utf8(file.contents().to_vec())?;
let source = Source::new(Arc::from(text), source_kind);
Ok(Some(Self::File(File::new(
ctx,
file_name.replace(".rue", ""),
source,
))))
} else {
Ok(Some(Self::load_std_dir(ctx, file_name, entry.children())?))
}
}
pub fn compile_file(ctx: &mut Compiler, path: &Path) -> Result<Self, Error> {
let tree = Self::File(File::new(
ctx,
"main".to_string(),
Source::new(Arc::from(fs::read_to_string(path)?), normalize_path(path)?),
));
tree.compile(ctx);
Ok(tree)
}
pub fn compile_path(
ctx: &mut Compiler,
path: &Path,
cache: &mut HashMap<SourceKind, String>,
) -> Result<Self, Error> {
let tree = Self::try_from_path(ctx, path, cache)?
.ok_or(io::Error::other("Invalid file extension"))?;
tree.compile(ctx);
Ok(tree)
}
fn try_from_path(
ctx: &mut Compiler,
path: &Path,
cache: &mut HashMap<SourceKind, String>,
) -> Result<Option<Self>, Error> {
let file_name = path
.file_name()
.ok_or(io::Error::new(
io::ErrorKind::InvalidFilename,
"Missing file name",
))?
.to_string_lossy()
.to_string();
if fs::metadata(path)?.is_file() {
if !file_name.ends_with(".rue") {
return Ok(None);
}
let source_kind = normalize_path(path)?;
let text = if let Some(text) = cache.get(&source_kind) {
text.clone()
} else {
fs::read_to_string(path)?
};
let source = Source::new(Arc::from(text), source_kind);
Ok(Some(Self::File(File::new(
ctx,
file_name.replace(".rue", ""),
source,
))))
} else {
let scope = ctx.alloc_child_scope();
let module = ctx.alloc_symbol(Symbol::Module(ModuleSymbol {
name: Some(Name::new(file_name.as_str(), None)),
scope,
declarations: ModuleDeclarations::default(),
}));
let mut children = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
let tree = Self::try_from_path(ctx, &path, cache)?;
children.extend(tree);
}
Ok(Some(Self::Directory(Directory::new(
ctx, file_name, module, children,
))))
}
}
pub fn name(&self) -> &str {
match self {
Self::File(file) => &file.name,
Self::Directory(directory) => &directory.name,
}
}
pub fn module(&self) -> SymbolId {
match self {
Self::File(file) => file.module,
Self::Directory(directory) => directory.module,
}
}
pub fn all_modules(&self) -> IndexSet<SymbolId> {
match self {
Self::File(file) => indexset![file.module],
Self::Directory(directory) => directory
.children
.iter()
.flat_map(Self::all_modules)
.collect(),
}
}
pub fn all_files(&self) -> Vec<&File> {
match self {
Self::File(file) => vec![file],
Self::Directory(directory) => directory
.children
.iter()
.flat_map(Self::all_files)
.collect(),
}
}
pub fn compile(&self, ctx: &mut Compiler) {
self.compile_impl(ctx, true);
}
pub(crate) fn compile_impl(&self, ctx: &mut Compiler, unused_check: bool) {
let mut cache = ImportCache::default();
let modules = self.all_modules().into_iter().collect::<Vec<_>>();
self.declare_modules(ctx);
self.declare_types(ctx);
resolve_imports(ctx, modules.clone(), &mut cache, false);
self.declare_symbols(ctx);
resolve_imports(ctx, modules.clone(), &mut cache, false);
resolve_imports(ctx, modules.clone(), &mut cache, true);
self.compile_types(ctx);
self.compile_symbols(ctx);
if unused_check {
let entrypoints = self.entrypoints(ctx);
check_unused(ctx, &entrypoints);
}
}
pub fn entrypoints(&self, ctx: &Compiler) -> HashSet<Declaration> {
let mut entrypoints = ctx
.tests()
.map(|test| Declaration::Symbol(test.symbol))
.collect::<HashSet<_>>();
let mut stack = vec![self];
while let Some(current) = stack.pop() {
match current {
Self::File(file) => {
let scope = file.module(ctx).scope;
if let Some(main) = ctx.scope(scope).symbol("main") {
entrypoints.insert(Declaration::Symbol(main));
}
ctx.scope(scope).exported_symbols().for_each(|(_, symbol)| {
entrypoints.insert(Declaration::Symbol(symbol));
});
ctx.scope(scope).exported_types().for_each(|(_, ty)| {
entrypoints.insert(Declaration::Type(ty));
});
}
Self::Directory(directory) => {
directory.children.iter().for_each(|child| {
stack.push(child);
});
}
}
}
entrypoints
}
pub fn main(
&self,
ctx: &mut Compiler,
allocator: &mut Allocator,
path: &SourceKind,
base_path: PathBuf,
) -> Result<Option<NodePtr>, Error> {
let tree = self
.find(path)
.ok_or_else(|| Error::SourceNotFound(path.clone()))?;
let scope = ctx.module(tree.module).scope;
let Some(main) = ctx.scope(scope).symbol("main") else {
return Ok(None);
};
Ok(Some(codegen(ctx, allocator, main, base_path)?))
}
pub fn exports(
&self,
ctx: &mut Compiler,
allocator: &mut Allocator,
path: Option<&SourceKind>,
export_name: Option<&str>,
base_path: &Path,
) -> Result<Vec<CompiledExport>, Error> {
let Some(path) = path else {
return Ok(self
.all_files()
.iter()
.map(|file| {
self.exports(
ctx,
allocator,
Some(&file.source.kind),
export_name,
base_path,
)
})
.collect::<Result<Vec<_>, Error>>()?
.into_iter()
.flatten()
.collect());
};
let tree = self
.find(path)
.ok_or_else(|| Error::SourceNotFound(path.clone()))?;
let scope = ctx.module(tree.module).scope;
let mut exports = Vec::new();
for (name, symbol) in ctx
.scope(scope)
.exported_symbols()
.map(|(name, symbol)| (name.to_string(), symbol))
.collect::<Vec<_>>()
{
if let Some(export_name) = export_name
&& name != export_name
{
continue;
}
let ptr = codegen(ctx, allocator, symbol, base_path.to_path_buf())?;
exports.push(CompiledExport { name, symbol, ptr });
}
Ok(exports)
}
pub fn tests(
&self,
ctx: &mut Compiler,
allocator: &mut Allocator,
path: Option<&SourceKind>,
search_term: Option<&str>,
base_path: &Path,
std: bool,
) -> Result<Vec<CompiledTest>, Error> {
let tests = ctx
.tests()
.filter(|test| {
if let Some(path) = path
&& &test.path != path
{
return false;
}
if matches!(test.path, SourceKind::Std(_)) && !std {
return false;
}
if let Some(search_term) = search_term {
let Some(name) = &test.name else {
return false;
};
return name.to_lowercase().contains(&search_term.to_lowercase());
}
true
})
.cloned()
.collect::<Vec<_>>();
let mut outputs = Vec::new();
for test in tests {
let ptr = codegen(ctx, allocator, test.symbol, base_path.to_path_buf())?;
outputs.push(CompiledTest {
name: test.name,
path: test.path,
symbol: test.symbol,
ptr,
});
}
Ok(outputs)
}
pub fn find(&self, path: &SourceKind) -> Option<&File> {
match self {
Self::File(file) => {
if file.source.kind == *path {
Some(file)
} else {
None
}
}
Self::Directory(directory) => {
directory.children.iter().find_map(|child| child.find(path))
}
}
}
fn declare_modules(&self, ctx: &mut Compiler) {
match self {
Self::File(file) => file.declare_modules(ctx),
Self::Directory(directory) => directory.declare_modules(ctx),
}
}
fn declare_types(&self, ctx: &mut Compiler) {
match self {
Self::File(file) => file.declare_types(ctx),
Self::Directory(directory) => directory.declare_types(ctx),
}
}
fn declare_symbols(&self, ctx: &mut Compiler) {
match self {
Self::File(file) => file.declare_symbols(ctx),
Self::Directory(directory) => directory.declare_symbols(ctx),
}
}
fn compile_types(&self, ctx: &mut Compiler) {
match self {
Self::File(file) => file.compile_types(ctx),
Self::Directory(directory) => directory.compile_types(ctx),
}
}
fn compile_symbols(&self, ctx: &mut Compiler) {
match self {
Self::File(file) => file.compile_symbols(ctx),
Self::Directory(directory) => directory.compile_symbols(ctx),
}
}
}
#[derive(Debug, Clone)]
pub struct File {
pub name: String,
pub source: Source,
pub document: AstDocument,
pub module: SymbolId,
pub sibling_scope: ScopeId,
}
impl File {
pub fn new(ctx: &mut Compiler, name: String, source: Source) -> Self {
let tokens = Lexer::new(&source.text).collect::<Vec<_>>();
let parser = Parser::new(source.clone(), tokens);
let parse_result = parser.parse();
ctx.extend_diagnostics(parse_result.diagnostics);
let document = AstDocument::cast(parse_result.node).unwrap();
let sibling_scope = ctx.alloc_child_scope();
let scope = ctx.alloc_scope(Scope::new(Some(sibling_scope)));
let module = ctx.alloc_symbol(Symbol::Module(ModuleSymbol {
name: Some(Name::new(name.as_str(), None)),
scope,
declarations: ModuleDeclarations::default(),
}));
ctx.add_syntax_for_source(
SyntaxItemKind::FileModule(module),
document.syntax().text_range(),
source.kind.clone(),
);
Self {
name,
source,
document,
module,
sibling_scope,
}
}
pub(crate) fn module<'a>(&'a self, ctx: &'a Compiler) -> &'a ModuleSymbol {
match ctx.symbol(self.module) {
Symbol::Module(module) => module,
_ => unreachable!(),
}
}
fn module_mut<'a>(&'a self, ctx: &'a mut Compiler) -> &'a mut ModuleSymbol {
match ctx.symbol_mut(self.module) {
Symbol::Module(module) => module,
_ => unreachable!(),
}
}
fn begin(&self, ctx: &mut Compiler) -> ModuleDeclarations {
let scope = self.module(ctx).scope;
ctx.set_source(self.source.clone());
ctx.push_scope(
self.sibling_scope,
self.document.syntax().text_range().start(),
);
ctx.push_scope(scope, self.document.syntax().text_range().start());
self.module(ctx).declarations.clone()
}
fn end(&self, ctx: &mut Compiler, declarations: ModuleDeclarations) {
ctx.pop_scope(self.document.syntax().text_range().end());
ctx.pop_scope(self.document.syntax().text_range().end());
self.module_mut(ctx).declarations = declarations;
}
fn declare_modules(&self, ctx: &mut Compiler) {
let mut declarations = self.begin(ctx);
declare_module_items(ctx, self.document.items(), &mut declarations);
self.end(ctx, declarations);
}
fn declare_types(&self, ctx: &mut Compiler) {
let mut declarations = self.begin(ctx);
declare_type_items(ctx, self.document.items(), &mut declarations);
self.end(ctx, declarations);
}
fn declare_symbols(&self, ctx: &mut Compiler) {
let mut declarations = self.begin(ctx);
declare_symbol_items(ctx, self.document.items(), &mut declarations);
self.end(ctx, declarations);
}
fn compile_types(&self, ctx: &mut Compiler) {
let declarations = self.begin(ctx);
compile_type_items(ctx, self.document.items(), &declarations);
self.end(ctx, declarations);
}
fn compile_symbols(&self, ctx: &mut Compiler) {
let declarations = self.begin(ctx);
compile_symbol_items(ctx, self.document.items(), &declarations);
self.end(ctx, declarations);
for scope in ctx.scope_stack().into_iter().rev() {
ctx.add_syntax(
SyntaxItemKind::Scope(scope),
TextRange::new(
TextSize::from(0),
TextSize::from(self.source.text.len() as u32),
),
);
}
}
}
#[derive(Debug, Clone)]
pub struct Directory {
pub name: String,
pub children: Vec<FileTree>,
pub module: SymbolId,
}
impl Directory {
pub fn new(
ctx: &mut Compiler,
name: String,
module: SymbolId,
children: Vec<FileTree>,
) -> Self {
let scope = ctx.module(module).scope;
let mut child_modules = IndexMap::new();
for child in &children {
let name = child.name().to_string();
let module = child.module();
child_modules.insert(name.clone(), module);
ctx.scope_mut(scope).insert_symbol(name, module, true);
}
for child in &children {
let mut child_modules = child_modules.clone();
child_modules.shift_remove(child.name());
for (name, module) in child_modules {
if let FileTree::File(file) = child {
ctx.scope_mut(file.sibling_scope)
.insert_symbol(name, module, false);
}
}
}
Self {
name,
children,
module,
}
}
fn begin(&self, ctx: &mut Compiler) {
ctx.push_module(self.module);
}
#[allow(clippy::unused_self)]
fn end(&self, ctx: &mut Compiler) {
ctx.pop_module();
}
fn declare_modules(&self, ctx: &mut Compiler) {
self.begin(ctx);
for child in &self.children {
child.declare_modules(ctx);
}
self.end(ctx);
}
fn declare_types(&self, ctx: &mut Compiler) {
self.begin(ctx);
for child in &self.children {
child.declare_types(ctx);
}
self.end(ctx);
}
fn declare_symbols(&self, ctx: &mut Compiler) {
self.begin(ctx);
for child in &self.children {
child.declare_symbols(ctx);
}
self.end(ctx);
}
fn compile_types(&self, ctx: &mut Compiler) {
self.begin(ctx);
for child in &self.children {
child.compile_types(ctx);
}
self.end(ctx);
}
fn compile_symbols(&self, ctx: &mut Compiler) {
self.begin(ctx);
for child in &self.children {
child.compile_symbols(ctx);
}
self.end(ctx);
}
}
pub fn normalize_path(path: &Path) -> Result<SourceKind, Error> {
Ok(SourceKind::File(
path.canonicalize()?.to_string_lossy().to_string(),
))
}
fn codegen(
ctx: &mut Compiler,
allocator: &mut Allocator,
symbol: SymbolId,
base_path: PathBuf,
) -> Result<NodePtr, Error> {
let options = *ctx.options();
let graph = DependencyGraph::build(ctx, symbol, options);
let mut arena = Arena::new();
let mut lowerer = Lowerer::new(ctx, &mut arena, &graph, options, symbol, base_path);
let mut lir = lowerer.lower_symbol_value(&Environment::default(), symbol);
if options.optimize_lir {
lir = rue_lir::optimize(&mut arena, lir);
}
Ok(rue_lir::codegen(&arena, allocator, lir)?)
}