code-moniker-core 0.4.0

Core symbol-graph types and per-language extractors for code-moniker.
Documentation
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};

use code_moniker_core::core::moniker::MonikerBuilder;
use code_moniker_core::lang::rs;

struct SourceFile {
	rel: String,
	source: String,
}

fn main() {
	let iters: usize = env::args()
		.nth(1)
		.and_then(|value| value.parse().ok())
		.unwrap_or(200);
	let root = env::args().nth(2).unwrap_or_else(|| {
		"crates/workspace/tests/fixtures/projects/rust/multiproject".to_string()
	});
	let mode = env::args().nth(3).unwrap_or_else(|| "sdk".to_string());
	let root = PathBuf::from(root)
		.canonicalize()
		.expect("fixture root should exist");
	let files = read_sources(&root);
	assert!(!files.is_empty(), "fixture should contain Rust files");

	let anchor = MonikerBuilder::new().project(b".").build();
	let presets = rs::Presets::default();

	for file in &files {
		let _ = rs::extract(&file.rel, &file.source, &anchor, true, &presets);
	}

	println!("root:              {}", root.display());
	println!("files:             {}", files.len());
	println!("iterations:        {iters}");
	println!(
		"source:            {} KiB",
		files.iter().map(|file| file.source.len()).sum::<usize>() / 1024
	);

	match mode.as_str() {
		"sdk" => {
			let sdk = time_extract(iters, &files, |file| {
				rs::extract(&file.rel, &file.source, &anchor, true, &presets)
			});
			print_result("sdk", &sdk, iters, files.len());
		}
		other => panic!("unknown mode {other}; expected sdk"),
	}
}

fn read_sources(root: &Path) -> Vec<SourceFile> {
	let mut paths = Vec::new();
	collect_rust_files(root, &mut paths);
	paths.sort();
	paths
		.into_iter()
		.map(|path| {
			let rel = path
				.strip_prefix(root)
				.expect("fixture file under root")
				.to_string_lossy()
				.replace('\\', "/");
			let source = fs::read_to_string(&path).unwrap_or_else(|err| {
				panic!("read {}: {err}", path.display());
			});
			SourceFile { rel, source }
		})
		.collect()
}

fn collect_rust_files(dir: &Path, out: &mut Vec<PathBuf>) {
	for entry in fs::read_dir(dir).expect("fixture dir") {
		let path = entry.expect("fixture entry").path();
		if path.is_dir() {
			collect_rust_files(&path, out);
		} else if path.extension().and_then(|ext| ext.to_str()) == Some("rs") {
			out.push(path);
		}
	}
}

struct BenchResult {
	elapsed: Duration,
	defs: usize,
	refs: usize,
}

fn time_extract<F>(iters: usize, files: &[SourceFile], mut extract: F) -> BenchResult
where
	F: FnMut(&SourceFile) -> code_moniker_core::core::code_graph::CodeGraph,
{
	let started = Instant::now();
	let mut defs = 0usize;
	let mut refs = 0usize;
	for _ in 0..iters {
		for file in files {
			let graph = extract(file);
			defs += graph.def_count();
			refs += graph.ref_count();
		}
	}
	BenchResult {
		elapsed: started.elapsed(),
		defs,
		refs,
	}
}

fn print_result(label: &str, result: &BenchResult, iters: usize, file_count: usize) {
	let runs = iters * file_count;
	println!("{label} total:      {:?}", result.elapsed);
	println!(
		"{label} per file:   {:?}",
		result.elapsed / runs.try_into().expect("runs should fit in u32")
	);
	println!(
		"{label} files/sec:  {:.0}",
		runs as f64 / result.elapsed.as_secs_f64()
	);
	println!("{label} defs/run:   {}", result.defs / iters);
	println!("{label} refs/run:   {}", result.refs / iters);
}