1#![warn(missing_docs)]
2
3use nargo_ir::IRModule;
4use nargo_optimizer::Optimizer;
5use nargo_parser::{Parser, ParserRegistry};
6use nargo_transformer::Transformer;
7pub use nargo_types as types;
8pub use nargo_types::{CompileMode, CompileOptions, Result};
9use std::{
10 collections::HashMap,
11 hash::Hash,
12 sync::{Arc, Mutex, OnceLock},
13 thread,
14 time::Instant,
15};
16
17pub struct ThreadPoolConfig {
19 pub thread_count: usize,
21}
22
23impl Default for ThreadPoolConfig {
24 fn default() -> Self {
25 Self { thread_count: num_cpus::get() }
26 }
27}
28
29impl Clone for ThreadPoolConfig {
30 fn clone(&self) -> Self {
31 Self { thread_count: self.thread_count }
32 }
33}
34
35#[derive(Hash, PartialEq, Eq, Clone)]
37pub struct CompileCacheKey {
38 pub name: String,
40 pub source_hash: u64,
42 pub options_hash: u64,
44}
45
46#[derive(Clone)]
48pub struct CompileCacheValue {
49 pub result: CompileResult,
51 pub timestamp: Instant,
53}
54
55pub fn compile(name: &str, source: &str) -> Result<CompileResult> {
59 let compiler_cache = COMPILER_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
61
62 let mut cache = compiler_cache.lock().unwrap();
64 let compiler = cache.entry(name.to_string()).or_insert_with(|| Compiler::new());
65
66 compiler.compile(name, source)
68}
69
70pub fn compile_vmz(name: &str, source: &str) -> Result<CompileResult> {
81 let compiler_cache = COMPILER_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
83
84 let mut cache = compiler_cache.lock().unwrap();
86 let compiler = cache.entry(name.to_string()).or_insert_with(|| Compiler::new());
87
88 compiler.compile(name, source)
90}
91
92pub fn compile_with_options(name: &str, source: &str, options: CompileOptions) -> Result<CompileResult> {
94 let compiler_cache = COMPILER_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
96
97 let mut cache = compiler_cache.lock().unwrap();
99 let compiler = cache.entry(name.to_string()).or_insert_with(|| Compiler::new());
100
101 compiler.compile_with_options(name, source, options)
103}
104
105pub mod prelude {
107 pub use crate::{compile, compile_vmz, compile_with_options, CompileMode, CompileOptions, CompileResult, Compiler};
108 pub use nargo_types::{Error, ErrorKind, Position, Result, Span};
109}
110
111use serde::Serialize;
112
113pub mod adapter;
114pub mod codegen;
115
116#[derive(Serialize, Clone)]
118pub struct CompileResult {
119 pub code: String,
121 pub css: String,
123 pub html: String,
125 pub wasm: String,
127 pub compile_time_ms: u64,
129}
130
131#[derive(Debug, Clone, Serialize, Eq, Hash, PartialEq)]
133pub enum CompileStage {
134 Parse,
136 Transform,
138 Optimize,
140 CodeGen,
142}
143
144#[derive(Debug, Clone, Serialize)]
146pub struct CompileStats {
147 pub stage_times: std::collections::HashMap<CompileStage, u64>,
149 pub total_time: u64,
151 pub source_size: usize,
153 pub output_size: usize,
155}
156
157static REGISTRY_CACHE: OnceLock<Arc<ParserRegistry>> = OnceLock::new();
159
160type CompilerCache = Mutex<HashMap<String, Compiler>>;
162static COMPILER_CACHE: OnceLock<CompilerCache> = OnceLock::new();
163
164pub struct Compiler {
168 pub registry: Arc<ParserRegistry>,
170 pub last_css: String,
172 pub transformer: Transformer,
174 pub stats: Option<CompileStats>,
176 pub thread_pool_config: ThreadPoolConfig,
178 pub compile_cache: HashMap<CompileCacheKey, CompileCacheValue>,
180 pub cache_size_limit: usize,
182 pub hot_cache: Option<(CompileCacheKey, CompileResult)>,
184}
185
186impl Default for Compiler {
187 fn default() -> Self {
188 Self::new()
189 }
190}
191
192impl Clone for Compiler {
193 fn clone(&self) -> Self {
194 Self { registry: self.registry.clone(), last_css: self.last_css.clone(), transformer: self.transformer.clone(), stats: self.stats.clone(), thread_pool_config: self.thread_pool_config.clone(), compile_cache: self.compile_cache.clone(), cache_size_limit: self.cache_size_limit, hot_cache: self.hot_cache.clone() }
195 }
196}
197
198impl Compiler {
199 pub fn new() -> Self {
201 let registry = REGISTRY_CACHE.get_or_init(|| {
203 let registry = ParserRegistry::new();
204
205 Arc::new(registry)
211 });
212
213 Self { registry: registry.clone(), last_css: String::new(), transformer: Transformer::new(), stats: None, thread_pool_config: ThreadPoolConfig::default(), compile_cache: HashMap::with_capacity(100), cache_size_limit: 1000, hot_cache: None }
214 }
215
216 pub fn with_cache_size_limit(mut self, limit: usize) -> Self {
226 self.cache_size_limit = limit;
227 self
228 }
229
230 pub fn with_thread_pool_config(mut self, config: ThreadPoolConfig) -> Self {
240 self.thread_pool_config = config;
241 self
242 }
243
244 pub fn compile(&mut self, name: &str, source: &str) -> Result<CompileResult> {
255 self.compile_with_options(name, source, CompileOptions::default())
256 }
257
258 pub fn compile_with_options(&mut self, name: &str, source: &str, mut options: CompileOptions) -> Result<CompileResult> {
270 let source_hash = fast_hash(source);
272 let options_hash = fast_hash_options(&options);
273 let cache_key = CompileCacheKey { name: name.to_string(), source_hash, options_hash };
274
275 if let Some((ref hot_key, ref hot_result)) = self.hot_cache {
277 if hot_key == &cache_key {
278 self.stats = Some(CompileStats { stage_times: HashMap::new(), total_time: 0, source_size: source.len(), output_size: hot_result.code.len() + hot_result.css.len() });
280 return Ok(hot_result.clone());
281 }
282 }
283
284 if let Some(cache_value) = self.compile_cache.get(&cache_key) {
286 self.hot_cache = Some((cache_key.clone(), cache_value.result.clone()));
288 self.stats = Some(CompileStats { stage_times: HashMap::new(), total_time: 0, source_size: source.len(), output_size: cache_value.result.code.len() + cache_value.result.css.len() });
289 return Ok(cache_value.result.clone());
290 }
291
292 let start_time = Instant::now();
294
295 let mut stage_times = std::collections::HashMap::with_capacity(4);
297
298 let parse_start = Instant::now();
300 let _ir = self.compile_to_ir(name, source, &mut options)?;
301 stage_times.insert(CompileStage::Parse, parse_start.elapsed().as_millis() as u64);
302
303 let codegen_start = Instant::now();
305 let code = String::new(); let html = String::new(); let wasm = String::new(); stage_times.insert(CompileStage::CodeGen, codegen_start.elapsed().as_millis() as u64);
311
312 let total_time = start_time.elapsed().as_millis() as u64;
314
315 self.stats = Some(CompileStats { stage_times, total_time, source_size: source.len(), output_size: code.len() + self.last_css.len() });
317
318 let result = CompileResult { code, css: std::mem::take(&mut self.last_css), html, wasm, compile_time_ms: total_time };
320
321 self.hot_cache = Some((cache_key.clone(), result.clone()));
323 self.compile_cache.insert(cache_key, CompileCacheValue { result: result.clone(), timestamp: Instant::now() });
324
325 if self.compile_cache.len() > self.cache_size_limit * 2 {
327 self.cleanup_cache();
328 }
329
330 Ok(result)
331 }
332
333 pub fn compile_to_ir(&mut self, name: &str, source: &str, options: &mut CompileOptions) -> Result<IRModule> {
345 let mut parser = Parser::new(name.to_string(), source, self.registry.clone());
347 let mut ir = parser.parse_all()?;
348
349 let ts_adapter = adapter::TsAdapter::new();
351 ts_adapter.transform(&mut ir)?;
352
353 let analyzer = nargo_script_analyzer::ScriptAnalyzer::new();
355 if let Some(script) = &ir.script {
356 if let Ok(meta) = analyzer.analyze(script) {
357 ir.script_meta = Some(meta.to_nargo_value());
358 }
359 }
360
361 let mut optimizer = Optimizer::new();
363
364 let has_scoped_style = ir.styles.iter().any(|s| s.scoped);
366 if has_scoped_style && options.scope_id.is_none() {
367 options.scope_id = Some(optimizer.generate_scope_id(name));
368 }
369
370 optimizer.optimize(&mut ir, options.i18n_locale.as_deref(), options.is_prod);
372
373 if let Some(scope_id) = &options.scope_id {
375 optimizer.apply_scope_id(&mut ir, scope_id);
376 }
377
378 optimizer.process_styles(&ir)?;
380
381 self.last_css = optimizer.get_css();
383
384 Ok(ir)
385 }
386
387 pub fn get_css(&self) -> String {
393 self.last_css.clone()
394 }
395
396 pub fn get_stats(&self) -> Option<&CompileStats> {
402 self.stats.as_ref()
403 }
404
405 pub fn reset(&mut self) {
407 self.last_css.clear();
408 self.transformer.clear_logs();
409 self.stats = None;
410 self.thread_pool_config = ThreadPoolConfig::default();
411 self.compile_cache.clear();
412 self.cache_size_limit = 1000;
413 self.hot_cache = None;
414 }
415
416 fn hash_string(&self, s: &str) -> u64 {
418 fast_hash(s)
419 }
420
421 fn hash_options(&self, options: &CompileOptions) -> u64 {
423 fast_hash_options(options)
424 }
425
426 fn cleanup_cache(&mut self) {
428 if self.compile_cache.len() > self.cache_size_limit {
429 let mut entries: Vec<(CompileCacheKey, CompileCacheValue)> = self.compile_cache.drain().collect();
431 entries.sort_by(|a, b| a.1.timestamp.cmp(&b.1.timestamp));
432 let keep_count = self.cache_size_limit;
433 let to_keep = entries.into_iter().take(keep_count).collect();
434 self.compile_cache = to_keep;
435 }
436 }
437
438 pub fn compile_parallel(&self, files: &HashMap<String, String>, options: CompileOptions) -> Result<HashMap<String, CompileResult>> {
449 let thread_count = self.thread_pool_config.thread_count;
450 let files: Vec<(String, String)> = files.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
451 let chunk_size = (files.len() + thread_count - 1) / thread_count;
452
453 let mut handles = Vec::with_capacity(thread_count);
455
456 for chunk in files.chunks(chunk_size) {
457 let chunk_clone: Vec<(String, String)> = chunk.to_vec();
459 let options_clone = options.clone();
460 let compiler_clone = self.clone();
461
462 let handle = thread::spawn(move || {
463 let mut results = HashMap::with_capacity(chunk_clone.len());
465 for (name, source) in chunk_clone {
466 let mut compiler = compiler_clone.clone();
467 match compiler.compile_with_options(&name, &source, options_clone.clone()) {
468 Ok(result) => {
469 results.insert(name, result);
470 }
471 Err(e) => {
472 eprintln!("编译文件 {} 时出错: {:?}", name, e);
474 }
475 }
476 }
477 results
478 });
479
480 handles.push(handle);
481 }
482
483 let mut all_results = HashMap::with_capacity(files.len());
485 for handle in handles {
486 if let Ok(results) = handle.join() {
487 all_results.extend(results);
488 }
489 }
490
491 Ok(all_results)
492 }
493}
494
495#[inline(always)]
497fn fast_hash(s: &str) -> u64 {
498 let mut hash = 0xcbf29ce484222325u64;
500 let prime = 0x100000001b3u64;
501
502 for byte in s.bytes() {
503 hash ^= byte as u64;
504 hash = hash.wrapping_mul(prime);
505 }
506
507 hash
508}
509
510#[inline(always)]
512fn fast_hash_options(options: &CompileOptions) -> u64 {
513 let mut hash = 0xcbf29ce484222325u64;
514 let prime = 0x100000001b3u64;
515
516 hash ^= options.mode as u64;
518 hash = hash.wrapping_mul(prime);
519
520 hash ^= options.is_prod as u64;
522 hash = hash.wrapping_mul(prime);
523
524 if let Some(scope_id) = &options.scope_id {
526 for byte in scope_id.bytes() {
527 hash ^= byte as u64;
528 hash = hash.wrapping_mul(prime);
529 }
530 }
531
532 if let Some(locale) = &options.i18n_locale {
534 for byte in locale.bytes() {
535 hash ^= byte as u64;
536 hash = hash.wrapping_mul(prime);
537 }
538 }
539
540 hash
541}