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