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}