1use std::{
2 collections::{HashMap, HashSet},
3 fs, io,
4 path::{Path, PathBuf},
5 string::FromUtf8Error,
6 sync::Arc,
7};
8
9use clvmr::{Allocator, NodePtr};
10use id_arena::Arena;
11use include_dir::{Dir, DirEntry, include_dir};
12use indexmap::{IndexMap, IndexSet, indexset};
13use rowan::{TextRange, TextSize};
14use rue_ast::{AstDocument, AstNode};
15use rue_diagnostic::{Name, Source, SourceKind};
16use rue_hir::{
17 Declaration, DependencyGraph, Environment, Lowerer, ModuleDeclarations, ModuleSymbol, Scope,
18 ScopeId, Symbol, SymbolId,
19};
20use rue_lexer::Lexer;
21use rue_parser::Parser;
22use thiserror::Error;
23
24use crate::{
25 Compiler, ImportCache, SyntaxItemKind, check_unused, compile_symbol_items, compile_type_items,
26 declare_module_items, declare_symbol_items, declare_type_items, resolve_imports,
27};
28
29#[derive(Debug, Error)]
30pub enum Error {
31 #[error("IO error: {0}")]
32 Io(#[from] io::Error),
33
34 #[error("Codegen error: {0}")]
35 Lir(#[from] rue_lir::Error),
36
37 #[error("Source not found in compilation unit")]
38 SourceNotFound(SourceKind),
39
40 #[error("UTF-8 conversion error: {0}")]
41 Utf8(#[from] FromUtf8Error),
42}
43
44#[derive(Debug, Clone)]
45pub struct CompiledTest {
46 pub name: Option<String>,
47 pub path: SourceKind,
48 pub symbol: SymbolId,
49 pub ptr: NodePtr,
50}
51
52#[derive(Debug, Clone)]
53pub struct CompiledExport {
54 pub name: String,
55 pub symbol: SymbolId,
56 pub ptr: NodePtr,
57}
58
59#[derive(Debug, Clone)]
60pub enum FileTree {
61 File(File),
62 Directory(Directory),
63}
64
65static STD_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src/std");
66
67impl FileTree {
68 pub fn empty(ctx: &mut Compiler) -> Result<Self, Error> {
69 Self::load_std_dir(ctx, "empty".to_string(), &[])
70 }
71
72 pub(crate) fn load_std(ctx: &mut Compiler) -> Result<Self, Error> {
73 Self::load_std_dir(ctx, "std".to_string(), STD_DIR.entries())
74 }
75
76 fn load_std_dir<'a>(
77 ctx: &mut Compiler,
78 name: String,
79 entries: &'a [DirEntry<'a>],
80 ) -> Result<Self, Error> {
81 let scope = ctx.alloc_child_scope();
82
83 let module = ctx.alloc_symbol(Symbol::Module(ModuleSymbol {
84 name: Some(Name::new(name.as_str(), None)),
85 scope,
86 declarations: ModuleDeclarations::default(),
87 }));
88
89 let mut children = Vec::new();
90
91 for entry in entries {
92 children.extend(Self::try_load_std_entry(ctx, entry)?);
93 }
94
95 Ok(Self::Directory(Directory::new(ctx, name, module, children)))
96 }
97
98 fn try_load_std_entry<'a>(
99 ctx: &mut Compiler,
100 entry: &'a DirEntry<'a>,
101 ) -> Result<Option<Self>, Error> {
102 let file_name = entry
103 .path()
104 .file_name()
105 .ok_or(io::Error::new(
106 io::ErrorKind::InvalidFilename,
107 "Missing file name",
108 ))?
109 .to_string_lossy()
110 .to_string();
111
112 if let Some(file) = entry.as_file() {
113 #[allow(clippy::case_sensitive_file_extension_comparisons)]
114 if !file_name.ends_with(".rue") {
115 return Ok(None);
116 }
117
118 let source_kind = SourceKind::Std(entry.path().to_string_lossy().to_string());
119
120 let text = String::from_utf8(file.contents().to_vec())?;
121
122 let source = Source::new(Arc::from(text), source_kind);
123
124 Ok(Some(Self::File(File::new(
125 ctx,
126 file_name.replace(".rue", ""),
127 source,
128 ))))
129 } else {
130 Ok(Some(Self::load_std_dir(ctx, file_name, entry.children())?))
131 }
132 }
133
134 pub fn compile_file(ctx: &mut Compiler, path: &Path) -> Result<Self, Error> {
135 let tree = Self::File(File::new(
136 ctx,
137 "main".to_string(),
138 Source::new(Arc::from(fs::read_to_string(path)?), normalize_path(path)?),
139 ));
140
141 tree.compile(ctx);
142
143 Ok(tree)
144 }
145
146 pub fn compile_path(
147 ctx: &mut Compiler,
148 path: &Path,
149 cache: &mut HashMap<SourceKind, String>,
150 ) -> Result<Self, Error> {
151 let tree = Self::try_from_path(ctx, path, cache)?
152 .ok_or(io::Error::other("Invalid file extension"))?;
153
154 tree.compile(ctx);
155
156 Ok(tree)
157 }
158
159 fn try_from_path(
160 ctx: &mut Compiler,
161 path: &Path,
162 cache: &mut HashMap<SourceKind, String>,
163 ) -> Result<Option<Self>, Error> {
164 let file_name = path
165 .file_name()
166 .ok_or(io::Error::new(
167 io::ErrorKind::InvalidFilename,
168 "Missing file name",
169 ))?
170 .to_string_lossy()
171 .to_string();
172
173 if fs::metadata(path)?.is_file() {
174 if !file_name.ends_with(".rue") {
175 return Ok(None);
176 }
177
178 let source_kind = normalize_path(path)?;
179
180 let text = if let Some(text) = cache.get(&source_kind) {
181 text.clone()
182 } else {
183 fs::read_to_string(path)?
184 };
185
186 let source = Source::new(Arc::from(text), source_kind);
187
188 Ok(Some(Self::File(File::new(
189 ctx,
190 file_name.replace(".rue", ""),
191 source,
192 ))))
193 } else {
194 let scope = ctx.alloc_child_scope();
195
196 let module = ctx.alloc_symbol(Symbol::Module(ModuleSymbol {
197 name: Some(Name::new(file_name.as_str(), None)),
198 scope,
199 declarations: ModuleDeclarations::default(),
200 }));
201
202 let mut children = Vec::new();
203
204 for entry in fs::read_dir(path)? {
205 let entry = entry?;
206 let path = entry.path();
207 let tree = Self::try_from_path(ctx, &path, cache)?;
208 children.extend(tree);
209 }
210
211 Ok(Some(Self::Directory(Directory::new(
212 ctx, file_name, module, children,
213 ))))
214 }
215 }
216
217 pub fn name(&self) -> &str {
218 match self {
219 Self::File(file) => &file.name,
220 Self::Directory(directory) => &directory.name,
221 }
222 }
223
224 pub fn module(&self) -> SymbolId {
225 match self {
226 Self::File(file) => file.module,
227 Self::Directory(directory) => directory.module,
228 }
229 }
230
231 pub fn all_modules(&self) -> IndexSet<SymbolId> {
232 match self {
233 Self::File(file) => indexset![file.module],
234 Self::Directory(directory) => directory
235 .children
236 .iter()
237 .flat_map(Self::all_modules)
238 .collect(),
239 }
240 }
241
242 pub fn all_files(&self) -> Vec<&File> {
243 match self {
244 Self::File(file) => vec![file],
245 Self::Directory(directory) => directory
246 .children
247 .iter()
248 .flat_map(Self::all_files)
249 .collect(),
250 }
251 }
252
253 pub fn compile(&self, ctx: &mut Compiler) {
254 self.compile_impl(ctx, true);
255 }
256
257 pub(crate) fn compile_impl(&self, ctx: &mut Compiler, unused_check: bool) {
258 let mut cache = ImportCache::default();
259
260 let modules = self.all_modules().into_iter().collect::<Vec<_>>();
261
262 self.declare_modules(ctx);
263 self.declare_types(ctx);
264
265 resolve_imports(ctx, modules.clone(), &mut cache, false);
266
267 self.declare_symbols(ctx);
268
269 resolve_imports(ctx, modules.clone(), &mut cache, false);
270 resolve_imports(ctx, modules.clone(), &mut cache, true);
271
272 self.compile_types(ctx);
273 self.compile_symbols(ctx);
274
275 if unused_check {
276 let entrypoints = self.entrypoints(ctx);
277 check_unused(ctx, &entrypoints);
278 }
279 }
280
281 pub fn entrypoints(&self, ctx: &Compiler) -> HashSet<Declaration> {
282 let mut entrypoints = ctx
283 .tests()
284 .map(|test| Declaration::Symbol(test.symbol))
285 .collect::<HashSet<_>>();
286
287 let mut stack = vec![self];
288
289 while let Some(current) = stack.pop() {
290 match current {
291 Self::File(file) => {
292 let scope = file.module(ctx).scope;
293
294 if let Some(main) = ctx.scope(scope).symbol("main") {
295 entrypoints.insert(Declaration::Symbol(main));
296 }
297
298 ctx.scope(scope).exported_symbols().for_each(|(_, symbol)| {
299 entrypoints.insert(Declaration::Symbol(symbol));
300 });
301
302 ctx.scope(scope).exported_types().for_each(|(_, ty)| {
303 entrypoints.insert(Declaration::Type(ty));
304 });
305 }
306 Self::Directory(directory) => {
307 directory.children.iter().for_each(|child| {
308 stack.push(child);
309 });
310 }
311 }
312 }
313
314 entrypoints
315 }
316
317 pub fn main(
318 &self,
319 ctx: &mut Compiler,
320 allocator: &mut Allocator,
321 path: &SourceKind,
322 base_path: PathBuf,
323 ) -> Result<Option<NodePtr>, Error> {
324 let tree = self
325 .find(path)
326 .ok_or_else(|| Error::SourceNotFound(path.clone()))?;
327
328 let scope = ctx.module(tree.module).scope;
329 let Some(main) = ctx.scope(scope).symbol("main") else {
330 return Ok(None);
331 };
332
333 Ok(Some(codegen(ctx, allocator, main, base_path)?))
334 }
335
336 pub fn exports(
337 &self,
338 ctx: &mut Compiler,
339 allocator: &mut Allocator,
340 path: Option<&SourceKind>,
341 export_name: Option<&str>,
342 base_path: &Path,
343 ) -> Result<Vec<CompiledExport>, Error> {
344 let Some(path) = path else {
345 return Ok(self
346 .all_files()
347 .iter()
348 .map(|file| {
349 self.exports(
350 ctx,
351 allocator,
352 Some(&file.source.kind),
353 export_name,
354 base_path,
355 )
356 })
357 .collect::<Result<Vec<_>, Error>>()?
358 .into_iter()
359 .flatten()
360 .collect());
361 };
362
363 let tree = self
364 .find(path)
365 .ok_or_else(|| Error::SourceNotFound(path.clone()))?;
366
367 let scope = ctx.module(tree.module).scope;
368
369 let mut exports = Vec::new();
370
371 for (name, symbol) in ctx
372 .scope(scope)
373 .exported_symbols()
374 .map(|(name, symbol)| (name.to_string(), symbol))
375 .collect::<Vec<_>>()
376 {
377 if let Some(export_name) = export_name
378 && name != export_name
379 {
380 continue;
381 }
382
383 let ptr = codegen(ctx, allocator, symbol, base_path.to_path_buf())?;
384 exports.push(CompiledExport { name, symbol, ptr });
385 }
386
387 Ok(exports)
388 }
389
390 pub fn tests(
391 &self,
392 ctx: &mut Compiler,
393 allocator: &mut Allocator,
394 path: Option<&SourceKind>,
395 search_term: Option<&str>,
396 base_path: &Path,
397 std: bool,
398 ) -> Result<Vec<CompiledTest>, Error> {
399 let tests = ctx
400 .tests()
401 .filter(|test| {
402 if let Some(path) = path
403 && &test.path != path
404 {
405 return false;
406 }
407
408 if matches!(test.path, SourceKind::Std(_)) && !std {
409 return false;
410 }
411
412 if let Some(search_term) = search_term {
413 let Some(name) = &test.name else {
414 return false;
415 };
416
417 return name.to_lowercase().contains(&search_term.to_lowercase());
418 }
419
420 true
421 })
422 .cloned()
423 .collect::<Vec<_>>();
424
425 let mut outputs = Vec::new();
426
427 for test in tests {
428 let ptr = codegen(ctx, allocator, test.symbol, base_path.to_path_buf())?;
429 outputs.push(CompiledTest {
430 name: test.name,
431 path: test.path,
432 symbol: test.symbol,
433 ptr,
434 });
435 }
436
437 Ok(outputs)
438 }
439
440 pub fn find(&self, path: &SourceKind) -> Option<&File> {
441 match self {
442 Self::File(file) => {
443 if file.source.kind == *path {
444 Some(file)
445 } else {
446 None
447 }
448 }
449 Self::Directory(directory) => {
450 directory.children.iter().find_map(|child| child.find(path))
451 }
452 }
453 }
454
455 fn declare_modules(&self, ctx: &mut Compiler) {
456 match self {
457 Self::File(file) => file.declare_modules(ctx),
458 Self::Directory(directory) => directory.declare_modules(ctx),
459 }
460 }
461
462 fn declare_types(&self, ctx: &mut Compiler) {
463 match self {
464 Self::File(file) => file.declare_types(ctx),
465 Self::Directory(directory) => directory.declare_types(ctx),
466 }
467 }
468
469 fn declare_symbols(&self, ctx: &mut Compiler) {
470 match self {
471 Self::File(file) => file.declare_symbols(ctx),
472 Self::Directory(directory) => directory.declare_symbols(ctx),
473 }
474 }
475
476 fn compile_types(&self, ctx: &mut Compiler) {
477 match self {
478 Self::File(file) => file.compile_types(ctx),
479 Self::Directory(directory) => directory.compile_types(ctx),
480 }
481 }
482
483 fn compile_symbols(&self, ctx: &mut Compiler) {
484 match self {
485 Self::File(file) => file.compile_symbols(ctx),
486 Self::Directory(directory) => directory.compile_symbols(ctx),
487 }
488 }
489}
490
491#[derive(Debug, Clone)]
492pub struct File {
493 pub name: String,
494 pub source: Source,
495 pub document: AstDocument,
496 pub module: SymbolId,
497 pub sibling_scope: ScopeId,
498}
499
500impl File {
501 pub fn new(ctx: &mut Compiler, name: String, source: Source) -> Self {
502 let tokens = Lexer::new(&source.text).collect::<Vec<_>>();
503 let parser = Parser::new(source.clone(), tokens);
504 let parse_result = parser.parse();
505
506 ctx.extend_diagnostics(parse_result.diagnostics);
507
508 let document = AstDocument::cast(parse_result.node).unwrap();
509
510 let sibling_scope = ctx.alloc_child_scope();
511 let scope = ctx.alloc_scope(Scope::new(Some(sibling_scope)));
512
513 let module = ctx.alloc_symbol(Symbol::Module(ModuleSymbol {
514 name: Some(Name::new(name.as_str(), None)),
515 scope,
516 declarations: ModuleDeclarations::default(),
517 }));
518
519 ctx.add_syntax_for_source(
520 SyntaxItemKind::FileModule(module),
521 document.syntax().text_range(),
522 source.kind.clone(),
523 );
524
525 Self {
526 name,
527 source,
528 document,
529 module,
530 sibling_scope,
531 }
532 }
533
534 pub(crate) fn module<'a>(&'a self, ctx: &'a Compiler) -> &'a ModuleSymbol {
535 match ctx.symbol(self.module) {
536 Symbol::Module(module) => module,
537 _ => unreachable!(),
538 }
539 }
540
541 fn module_mut<'a>(&'a self, ctx: &'a mut Compiler) -> &'a mut ModuleSymbol {
542 match ctx.symbol_mut(self.module) {
543 Symbol::Module(module) => module,
544 _ => unreachable!(),
545 }
546 }
547
548 fn begin(&self, ctx: &mut Compiler) -> ModuleDeclarations {
549 let scope = self.module(ctx).scope;
550 ctx.set_source(self.source.clone());
551 ctx.push_scope(
552 self.sibling_scope,
553 self.document.syntax().text_range().start(),
554 );
555 ctx.push_scope(scope, self.document.syntax().text_range().start());
556 self.module(ctx).declarations.clone()
558 }
559
560 fn end(&self, ctx: &mut Compiler, declarations: ModuleDeclarations) {
561 ctx.pop_scope(self.document.syntax().text_range().end());
563 ctx.pop_scope(self.document.syntax().text_range().end());
564 self.module_mut(ctx).declarations = declarations;
565 }
566
567 fn declare_modules(&self, ctx: &mut Compiler) {
568 let mut declarations = self.begin(ctx);
569 declare_module_items(ctx, self.document.items(), &mut declarations);
570 self.end(ctx, declarations);
571 }
572
573 fn declare_types(&self, ctx: &mut Compiler) {
574 let mut declarations = self.begin(ctx);
575 declare_type_items(ctx, self.document.items(), &mut declarations);
576 self.end(ctx, declarations);
577 }
578
579 fn declare_symbols(&self, ctx: &mut Compiler) {
580 let mut declarations = self.begin(ctx);
581 declare_symbol_items(ctx, self.document.items(), &mut declarations);
582 self.end(ctx, declarations);
583 }
584
585 fn compile_types(&self, ctx: &mut Compiler) {
586 let declarations = self.begin(ctx);
587 compile_type_items(ctx, self.document.items(), &declarations);
588 self.end(ctx, declarations);
589 }
590
591 fn compile_symbols(&self, ctx: &mut Compiler) {
592 let declarations = self.begin(ctx);
593 compile_symbol_items(ctx, self.document.items(), &declarations);
594 self.end(ctx, declarations);
595
596 for scope in ctx.scope_stack().into_iter().rev() {
597 ctx.add_syntax(
598 SyntaxItemKind::Scope(scope),
599 TextRange::new(
600 TextSize::from(0),
601 TextSize::from(self.source.text.len() as u32),
602 ),
603 );
604 }
605 }
606}
607
608#[derive(Debug, Clone)]
609pub struct Directory {
610 pub name: String,
611 pub children: Vec<FileTree>,
612 pub module: SymbolId,
613}
614
615impl Directory {
616 pub fn new(
617 ctx: &mut Compiler,
618 name: String,
619 module: SymbolId,
620 children: Vec<FileTree>,
621 ) -> Self {
622 let scope = ctx.module(module).scope;
623
624 let mut child_modules = IndexMap::new();
625
626 for child in &children {
627 let name = child.name().to_string();
628 let module = child.module();
629 child_modules.insert(name.clone(), module);
630 ctx.scope_mut(scope).insert_symbol(name, module, true);
631 }
632
633 for child in &children {
634 let mut child_modules = child_modules.clone();
635
636 child_modules.shift_remove(child.name());
637
638 for (name, module) in child_modules {
639 if let FileTree::File(file) = child {
640 ctx.scope_mut(file.sibling_scope)
641 .insert_symbol(name, module, false);
642 }
643 }
644 }
645
646 Self {
647 name,
648 children,
649 module,
650 }
651 }
652
653 fn begin(&self, ctx: &mut Compiler) {
654 ctx.push_module(self.module);
655 }
656
657 #[allow(clippy::unused_self)]
658 fn end(&self, ctx: &mut Compiler) {
659 ctx.pop_module();
660 }
661
662 fn declare_modules(&self, ctx: &mut Compiler) {
663 self.begin(ctx);
664 for child in &self.children {
665 child.declare_modules(ctx);
666 }
667 self.end(ctx);
668 }
669
670 fn declare_types(&self, ctx: &mut Compiler) {
671 self.begin(ctx);
672 for child in &self.children {
673 child.declare_types(ctx);
674 }
675 self.end(ctx);
676 }
677
678 fn declare_symbols(&self, ctx: &mut Compiler) {
679 self.begin(ctx);
680 for child in &self.children {
681 child.declare_symbols(ctx);
682 }
683 self.end(ctx);
684 }
685
686 fn compile_types(&self, ctx: &mut Compiler) {
687 self.begin(ctx);
688 for child in &self.children {
689 child.compile_types(ctx);
690 }
691 self.end(ctx);
692 }
693
694 fn compile_symbols(&self, ctx: &mut Compiler) {
695 self.begin(ctx);
696 for child in &self.children {
697 child.compile_symbols(ctx);
698 }
699 self.end(ctx);
700 }
701}
702
703pub fn normalize_path(path: &Path) -> Result<SourceKind, Error> {
704 Ok(SourceKind::File(
705 path.canonicalize()?.to_string_lossy().to_string(),
706 ))
707}
708
709fn codegen(
710 ctx: &mut Compiler,
711 allocator: &mut Allocator,
712 symbol: SymbolId,
713 base_path: PathBuf,
714) -> Result<NodePtr, Error> {
715 let options = *ctx.options();
716 let graph = DependencyGraph::build(ctx, symbol, options);
717
718 let mut arena = Arena::new();
719 let mut lowerer = Lowerer::new(ctx, &mut arena, &graph, options, symbol, base_path);
720 let mut lir = lowerer.lower_symbol_value(&Environment::default(), symbol);
721
722 if options.optimize_lir {
723 lir = rue_lir::optimize(&mut arena, lir);
724 }
725
726 Ok(rue_lir::codegen(&arena, allocator, lir)?)
727}