use std::cell::Cell;
use std::sync::Arc;
use query_lang::{Database, QueryError, System};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Query {
Source,
Tokens,
SymbolCount,
Report,
}
#[derive(Default)]
struct FrontEnd {
tokenize_runs: Cell<u64>,
symbol_runs: Cell<u64>,
report_runs: Cell<u64>,
}
type Val = Arc<String>;
impl System for FrontEnd {
type Key = Query;
type Value = Val;
fn compute(&self, db: &Database<Self>, key: &Query) -> Result<Val, QueryError> {
match key {
Query::Source => Ok(Arc::new(String::new())),
Query::Tokens => {
self.tokenize_runs.set(self.tokenize_runs.get() + 1);
let source = db.get(&Query::Source)?;
let tokens = tokenize(&source);
Ok(Arc::new(tokens.join("\n")))
}
Query::SymbolCount => {
self.symbol_runs.set(self.symbol_runs.get() + 1);
let tokens = db.get(&Query::Tokens)?;
let count = tokens
.lines()
.filter(|t| t.chars().next().is_some_and(char::is_alphabetic))
.collect::<std::collections::BTreeSet<_>>()
.len();
Ok(Arc::new(count.to_string()))
}
Query::Report => {
self.report_runs.set(self.report_runs.get() + 1);
let symbols = db.get(&Query::SymbolCount)?;
Ok(Arc::new(format!("{symbols} distinct identifier(s)")))
}
}
}
}
fn tokenize(source: &str) -> Vec<String> {
let mut tokens = Vec::new();
for line in source.lines() {
let code = line.split("//").next().unwrap_or("");
let mut current = String::new();
for ch in code.chars() {
if ch.is_alphanumeric() || ch == '_' {
current.push(ch);
} else if !current.is_empty() {
tokens.push(std::mem::take(&mut current));
}
}
if !current.is_empty() {
tokens.push(current);
}
}
tokens
}
fn main() {
let mut db = Database::new(FrontEnd::default());
db.set(Query::Source, Arc::new("let x = add(x, y)".to_string()));
println!("report: {}", get(&db, Query::Report));
print_runs(&db, "after first build");
println!("\nedit: reformat + add a comment (tokens unchanged)");
db.set(
Query::Source,
Arc::new("let x = add( x , y ) // a note".to_string()),
);
println!("report: {}", get(&db, Query::Report));
print_runs(&db, "after reformat");
println!("\nedit: introduce a new identifier `z`");
db.set(Query::Source, Arc::new("let x = add(x, y, z)".to_string()));
println!("report: {}", get(&db, Query::Report));
print_runs(&db, "after real change");
println!("\ncache metrics: {}", db.stats());
}
fn get(db: &Database<FrontEnd>, key: Query) -> String {
match db.get(&key) {
Ok(value) => value.as_str().to_string(),
Err(_) => "<cycle>".to_string(), }
}
fn print_runs(db: &Database<FrontEnd>, label: &str) {
let s = db.system();
println!(
" {label}: tokenize={}, symbol_count={}, report={}",
s.tokenize_runs.get(),
s.symbol_runs.get(),
s.report_runs.get()
);
}