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