#![warn(missing_docs)]
use nargo_ir::IRModule;
use nargo_optimizer::Optimizer;
use nargo_parser::{Parser, ParserRegistry};
use nargo_transformer::Transformer;
pub use nargo_types as types;
pub use nargo_types::{CompileMode, CompileOptions, Result};
use std::{
collections::HashMap,
hash::Hash,
sync::{Arc, Mutex, OnceLock},
thread,
time::Instant,
};
pub struct ThreadPoolConfig {
pub thread_count: usize,
}
impl Default for ThreadPoolConfig {
fn default() -> Self {
Self { thread_count: num_cpus::get() }
}
}
impl Clone for ThreadPoolConfig {
fn clone(&self) -> Self {
Self { thread_count: self.thread_count }
}
}
#[derive(Hash, PartialEq, Eq, Clone)]
pub struct CompileCacheKey {
pub name: String,
pub source_hash: u64,
pub options_hash: u64,
}
#[derive(Clone)]
pub struct CompileCacheValue {
pub result: CompileResult,
pub timestamp: Instant,
}
pub fn compile(name: &str, source: &str) -> Result<CompileResult> {
let compiler_cache = COMPILER_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut cache = compiler_cache.lock().unwrap();
let compiler = cache.entry(name.to_string()).or_insert_with(|| Compiler::new());
compiler.compile(name, source)
}
pub fn compile_vmz(name: &str, source: &str) -> Result<CompileResult> {
let compiler_cache = COMPILER_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut cache = compiler_cache.lock().unwrap();
let compiler = cache.entry(name.to_string()).or_insert_with(|| Compiler::new());
compiler.compile(name, source)
}
pub fn compile_with_options(name: &str, source: &str, options: CompileOptions) -> Result<CompileResult> {
let compiler_cache = COMPILER_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut cache = compiler_cache.lock().unwrap();
let compiler = cache.entry(name.to_string()).or_insert_with(|| Compiler::new());
compiler.compile_with_options(name, source, options)
}
pub mod prelude {
pub use crate::{compile, compile_vmz, compile_with_options, CompileMode, CompileOptions, CompileResult, Compiler};
pub use nargo_types::{Error, ErrorKind, Position, Result, Span};
}
use serde::Serialize;
pub mod adapter;
pub mod codegen;
#[derive(Serialize, Clone)]
pub struct CompileResult {
pub code: String,
pub css: String,
pub html: String,
pub wasm: String,
pub compile_time_ms: u64,
}
#[derive(Debug, Clone, Serialize, Eq, Hash, PartialEq)]
pub enum CompileStage {
Parse,
Transform,
Optimize,
CodeGen,
}
#[derive(Debug, Clone, Serialize)]
pub struct CompileStats {
pub stage_times: std::collections::HashMap<CompileStage, u64>,
pub total_time: u64,
pub source_size: usize,
pub output_size: usize,
}
static REGISTRY_CACHE: OnceLock<Arc<ParserRegistry>> = OnceLock::new();
type CompilerCache = Mutex<HashMap<String, Compiler>>;
static COMPILER_CACHE: OnceLock<CompilerCache> = OnceLock::new();
pub struct Compiler {
pub registry: Arc<ParserRegistry>,
pub last_css: String,
pub transformer: Transformer,
pub stats: Option<CompileStats>,
pub thread_pool_config: ThreadPoolConfig,
pub compile_cache: HashMap<CompileCacheKey, CompileCacheValue>,
pub cache_size_limit: usize,
pub hot_cache: Option<(CompileCacheKey, CompileResult)>,
}
impl Default for Compiler {
fn default() -> Self {
Self::new()
}
}
impl Clone for Compiler {
fn clone(&self) -> Self {
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() }
}
}
impl Compiler {
pub fn new() -> Self {
let registry = REGISTRY_CACHE.get_or_init(|| {
let registry = ParserRegistry::new();
Arc::new(registry)
});
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 }
}
pub fn with_cache_size_limit(mut self, limit: usize) -> Self {
self.cache_size_limit = limit;
self
}
pub fn with_thread_pool_config(mut self, config: ThreadPoolConfig) -> Self {
self.thread_pool_config = config;
self
}
pub fn compile(&mut self, name: &str, source: &str) -> Result<CompileResult> {
self.compile_with_options(name, source, CompileOptions::default())
}
pub fn compile_with_options(&mut self, name: &str, source: &str, mut options: CompileOptions) -> Result<CompileResult> {
let source_hash = fast_hash(source);
let options_hash = fast_hash_options(&options);
let cache_key = CompileCacheKey { name: name.to_string(), source_hash, options_hash };
if let Some((ref hot_key, ref hot_result)) = self.hot_cache {
if hot_key == &cache_key {
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() });
return Ok(hot_result.clone());
}
}
if let Some(cache_value) = self.compile_cache.get(&cache_key) {
self.hot_cache = Some((cache_key.clone(), cache_value.result.clone()));
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() });
return Ok(cache_value.result.clone());
}
let start_time = Instant::now();
let mut stage_times = std::collections::HashMap::with_capacity(4);
let parse_start = Instant::now();
let _ir = self.compile_to_ir(name, source, &mut options)?;
stage_times.insert(CompileStage::Parse, parse_start.elapsed().as_millis() as u64);
let codegen_start = Instant::now();
let code = String::new();
let html = String::new(); let wasm = String::new(); stage_times.insert(CompileStage::CodeGen, codegen_start.elapsed().as_millis() as u64);
let total_time = start_time.elapsed().as_millis() as u64;
self.stats = Some(CompileStats { stage_times, total_time, source_size: source.len(), output_size: code.len() + self.last_css.len() });
let result = CompileResult { code, css: std::mem::take(&mut self.last_css), html, wasm, compile_time_ms: total_time };
self.hot_cache = Some((cache_key.clone(), result.clone()));
self.compile_cache.insert(cache_key, CompileCacheValue { result: result.clone(), timestamp: Instant::now() });
if self.compile_cache.len() > self.cache_size_limit * 2 {
self.cleanup_cache();
}
Ok(result)
}
pub fn compile_to_ir(&mut self, name: &str, source: &str, options: &mut CompileOptions) -> Result<IRModule> {
let mut parser = Parser::new(name.to_string(), source, self.registry.clone());
let mut ir = parser.parse_all()?;
let ts_adapter = adapter::TsAdapter::new();
ts_adapter.transform(&mut ir)?;
let analyzer = nargo_script_analyzer::ScriptAnalyzer::new();
if let Some(script) = &ir.script {
if let Ok(meta) = analyzer.analyze(script) {
ir.script_meta = Some(meta.to_nargo_value());
}
}
let mut optimizer = Optimizer::new();
let has_scoped_style = ir.styles.iter().any(|s| s.scoped);
if has_scoped_style && options.scope_id.is_none() {
options.scope_id = Some(optimizer.generate_scope_id(name));
}
optimizer.optimize(&mut ir, options.i18n_locale.as_deref(), options.is_prod);
if let Some(scope_id) = &options.scope_id {
optimizer.apply_scope_id(&mut ir, scope_id);
}
optimizer.process_styles(&ir)?;
self.last_css = optimizer.get_css();
Ok(ir)
}
pub fn get_css(&self) -> String {
self.last_css.clone()
}
pub fn get_stats(&self) -> Option<&CompileStats> {
self.stats.as_ref()
}
pub fn reset(&mut self) {
self.last_css.clear();
self.transformer.clear_logs();
self.stats = None;
self.thread_pool_config = ThreadPoolConfig::default();
self.compile_cache.clear();
self.cache_size_limit = 1000;
self.hot_cache = None;
}
fn hash_string(&self, s: &str) -> u64 {
fast_hash(s)
}
fn hash_options(&self, options: &CompileOptions) -> u64 {
fast_hash_options(options)
}
fn cleanup_cache(&mut self) {
if self.compile_cache.len() > self.cache_size_limit {
let mut entries: Vec<(CompileCacheKey, CompileCacheValue)> = self.compile_cache.drain().collect();
entries.sort_by(|a, b| a.1.timestamp.cmp(&b.1.timestamp));
let keep_count = self.cache_size_limit;
let to_keep = entries.into_iter().take(keep_count).collect();
self.compile_cache = to_keep;
}
}
pub fn compile_parallel(&self, files: &HashMap<String, String>, options: CompileOptions) -> Result<HashMap<String, CompileResult>> {
let thread_count = self.thread_pool_config.thread_count;
let files: Vec<(String, String)> = files.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let chunk_size = (files.len() + thread_count - 1) / thread_count;
let mut handles = Vec::with_capacity(thread_count);
for chunk in files.chunks(chunk_size) {
let chunk_clone: Vec<(String, String)> = chunk.to_vec();
let options_clone = options.clone();
let compiler_clone = self.clone();
let handle = thread::spawn(move || {
let mut results = HashMap::with_capacity(chunk_clone.len());
for (name, source) in chunk_clone {
let mut compiler = compiler_clone.clone();
match compiler.compile_with_options(&name, &source, options_clone.clone()) {
Ok(result) => {
results.insert(name, result);
}
Err(e) => {
eprintln!("编译文件 {} 时出错: {:?}", name, e);
}
}
}
results
});
handles.push(handle);
}
let mut all_results = HashMap::with_capacity(files.len());
for handle in handles {
if let Ok(results) = handle.join() {
all_results.extend(results);
}
}
Ok(all_results)
}
}
#[inline(always)]
fn fast_hash(s: &str) -> u64 {
let mut hash = 0xcbf29ce484222325u64;
let prime = 0x100000001b3u64;
for byte in s.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(prime);
}
hash
}
#[inline(always)]
fn fast_hash_options(options: &CompileOptions) -> u64 {
let mut hash = 0xcbf29ce484222325u64;
let prime = 0x100000001b3u64;
hash ^= options.mode as u64;
hash = hash.wrapping_mul(prime);
hash ^= options.is_prod as u64;
hash = hash.wrapping_mul(prime);
if let Some(scope_id) = &options.scope_id {
for byte in scope_id.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(prime);
}
}
if let Some(locale) = &options.i18n_locale {
for byte in locale.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(prime);
}
}
hash
}