rue_compiler/
file.rs

1use std::{collections::HashSet, sync::Arc};
2
3use clvmr::{Allocator, NodePtr};
4use id_arena::Arena;
5use indexmap::IndexMap;
6use rowan::TextSize;
7use rue_ast::{AstDocument, AstNode};
8use rue_diagnostic::{Diagnostic, DiagnosticSeverity, Source, SourceKind};
9use rue_hir::{
10    Declaration, DependencyGraph, Environment, Lowerer, ModuleDeclarations, ScopeId, SymbolId,
11};
12use rue_lexer::Lexer;
13use rue_lir::{Error, codegen, optimize};
14use rue_options::CompilerOptions;
15use rue_parser::Parser;
16
17use crate::{
18    Compiler, SyntaxMap, check_unused, compile_symbol_items, compile_type_items,
19    declare_module_items, declare_symbol_items, declare_type_items,
20};
21
22#[derive(Debug, Clone)]
23pub struct Compilation {
24    pub compiler: Compiler,
25    pub diagnostics: Vec<Diagnostic>,
26    pub main: Option<NodePtr>,
27    pub exports: IndexMap<String, NodePtr>,
28    pub tests: Vec<Test>,
29    pub syntax_map: SyntaxMap,
30    pub source: Source,
31}
32
33#[derive(Debug, Clone)]
34pub struct Test {
35    pub name: String,
36    pub program: NodePtr,
37}
38
39#[derive(Debug, Clone)]
40struct PartialCompilation {
41    diagnostics: Vec<Diagnostic>,
42    scope: ScopeId,
43}
44
45pub fn compile_file(
46    allocator: &mut Allocator,
47    file: Source,
48    options: CompilerOptions,
49) -> Result<Compilation, Error> {
50    compile_file_impl(allocator, file, options, true)
51}
52
53pub fn analyze_file(file: Source, options: CompilerOptions) -> Result<Compilation, Error> {
54    compile_file_impl(&mut Allocator::new(), file, options, false)
55}
56
57#[allow(clippy::needless_pass_by_value)]
58fn compile_file_impl(
59    allocator: &mut Allocator,
60    file: Source,
61    options: CompilerOptions,
62    do_codegen: bool,
63) -> Result<Compilation, Error> {
64    let mut ctx = Compiler::new(options);
65    let std_source = include_str!("./std.rue");
66    let std = compile_file_partial(
67        &mut ctx,
68        Source::new(Arc::from(std_source), SourceKind::Std),
69    );
70
71    let scope = ctx.alloc_child_scope();
72
73    for (name, symbol) in ctx
74        .scope(std.scope)
75        .exported_symbols()
76        .map(|(name, symbol)| (name.to_string(), symbol))
77        .collect::<Vec<_>>()
78    {
79        ctx.scope_mut(scope)
80            .insert_symbol(name.to_string(), symbol, false);
81    }
82
83    for (name, ty) in ctx
84        .scope(std.scope)
85        .exported_types()
86        .map(|(name, ty)| (name.to_string(), ty))
87        .collect::<Vec<_>>()
88    {
89        ctx.scope_mut(scope)
90            .insert_type(name.to_string(), ty, false);
91    }
92
93    ctx.push_scope(scope, TextSize::from(0));
94    let compiled_file = compile_file_partial(&mut ctx, file.clone());
95    #[allow(clippy::cast_possible_truncation)]
96    ctx.pop_scope(TextSize::from(std_source.len() as u32));
97
98    let mut diagnostics = [std.diagnostics, compiled_file.diagnostics].concat();
99    let mut exports = IndexMap::new();
100    let mut tests = Vec::new();
101
102    let syntax_map = ctx.syntax_map(&file.kind).unwrap().clone();
103    let main_symbol = ctx.scope(compiled_file.scope).symbol("main");
104
105    let mut entrypoints = HashSet::new();
106
107    entrypoints.extend(main_symbol.map(Declaration::Symbol));
108
109    for test in ctx.tests() {
110        entrypoints.insert(Declaration::Symbol(test));
111    }
112
113    for (_, symbol) in ctx.scope(compiled_file.scope).exported_symbols() {
114        entrypoints.insert(Declaration::Symbol(symbol));
115    }
116
117    for (_, ty) in ctx.scope(compiled_file.scope).exported_types() {
118        entrypoints.insert(Declaration::Type(ty));
119    }
120
121    check_unused(&mut ctx, &entrypoints);
122
123    diagnostics.extend(ctx.take_diagnostics());
124
125    let mut main = None;
126
127    if !do_codegen
128        || diagnostics
129            .iter()
130            .any(|d| d.kind.severity() == DiagnosticSeverity::Error)
131    {
132        return Ok(Compilation {
133            compiler: ctx,
134            diagnostics,
135            main,
136            exports,
137            tests,
138            syntax_map,
139            source: file,
140        });
141    }
142
143    if let Some(symbol) = main_symbol {
144        main = Some(generate(&mut ctx, allocator, symbol, options)?);
145    }
146
147    for (name, symbol) in ctx
148        .scope(compiled_file.scope)
149        .exported_symbols()
150        .map(|(name, symbol)| (name.to_string(), symbol))
151        .collect::<Vec<_>>()
152    {
153        exports.insert(
154            name.to_string(),
155            generate(&mut ctx, allocator, symbol, options)?,
156        );
157    }
158
159    for test in ctx.tests().collect::<Vec<_>>() {
160        let name = ctx.symbol(test).name().unwrap().text().to_string();
161
162        tests.push(Test {
163            name,
164            program: generate(&mut ctx, allocator, test, options)?,
165        });
166    }
167
168    Ok(Compilation {
169        compiler: ctx,
170        diagnostics,
171        main,
172        exports,
173        tests,
174        syntax_map,
175        source: file,
176    })
177}
178
179fn generate(
180    ctx: &mut Compiler,
181    allocator: &mut Allocator,
182    symbol: SymbolId,
183    options: CompilerOptions,
184) -> Result<NodePtr, Error> {
185    let graph = DependencyGraph::build(ctx, symbol, options);
186
187    let mut arena = Arena::new();
188    let mut lowerer = Lowerer::new(ctx, &mut arena, &graph, options, symbol);
189    let mut lir = lowerer.lower_symbol_value(&Environment::default(), symbol);
190
191    if options.optimize_lir {
192        lir = optimize(&mut arena, lir);
193    }
194
195    codegen(&arena, allocator, lir)
196}
197
198fn compile_file_partial(ctx: &mut Compiler, source: Source) -> PartialCompilation {
199    let tokens = Lexer::new(&source.text).collect::<Vec<_>>();
200    let parser = Parser::new(source.clone(), tokens);
201    let parse_result = parser.parse();
202
203    ctx.set_source(source);
204
205    let scope = ctx.alloc_child_scope();
206
207    let mut compilation = PartialCompilation {
208        diagnostics: parse_result.diagnostics,
209        scope,
210    };
211
212    let Some(ast) = AstDocument::cast(parse_result.node) else {
213        return compilation;
214    };
215
216    let mut declarations = ModuleDeclarations::default();
217
218    let range = ast.syntax().text_range();
219    ctx.push_scope(scope, range.start());
220    declare_module_items(ctx, ast.items(), &mut declarations);
221    declare_type_items(ctx, ast.items(), &mut declarations);
222    declare_symbol_items(ctx, ast.items(), &mut declarations);
223    compile_type_items(ctx, ast.items(), &declarations);
224    compile_symbol_items(ctx, ast.items(), &declarations);
225    ctx.pop_scope(range.end());
226
227    compilation.diagnostics.extend(ctx.take_diagnostics());
228
229    compilation
230}