Skip to main content

rue_compiler/
file.rs

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        // ctx.push_module(self.module);
557        self.module(ctx).declarations.clone()
558    }
559
560    fn end(&self, ctx: &mut Compiler, declarations: ModuleDeclarations) {
561        // ctx.pop_module();
562        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}