query: unify PEG and engine parsers

PEG parser now handles both expression syntax (degree > 5 | sort degree)
and pipeline syntax (all | type:episodic | sort:timestamp). Deleted
Stage::parse() and helpers from engine.rs — it's now pure execution.

All callers use parse_stages() from parser.rs as the single entry point.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-11 20:42:58 -04:00
parent bc991c3521
commit aad227e487
8 changed files with 562 additions and 253 deletions

View file

@ -260,7 +260,7 @@ async fn query(args: &serde_json::Value) -> Result<String> {
let store = arc.lock().await;
let graph = store.build_graph();
let stages = crate::search::Stage::parse_pipeline(query_str)
let stages = crate::query_parser::parse_stages(query_str)
.map_err(|e| anyhow::anyhow!("{}", e))?;
let results = crate::search::run_query(&stages, vec![], &graph, &store, false, 100);
let keys: Vec<String> = results.into_iter().map(|(k, _)| k).collect();
@ -272,12 +272,61 @@ async fn query(args: &serde_json::Value) -> Result<String> {
Ok(crate::subconscious::prompts::format_nodes_section(&store, &items, &graph))
}
_ => {
crate::query_parser::query_to_string(&store, &graph, query_str)
.map_err(|e| anyhow::anyhow!("{}", e))
// Compact output: check for count/select stages, else just list keys
use crate::search::{Stage, Transform};
let has_count = stages.iter().any(|s| matches!(s, Stage::Transform(Transform::Count)));
if has_count {
return Ok(keys.len().to_string());
}
if keys.is_empty() {
return Ok("no results".to_string());
}
let select_fields: Option<&Vec<String>> = stages.iter().find_map(|s| match s {
Stage::Transform(Transform::Select(f)) => Some(f),
_ => None,
});
if let Some(fields) = select_fields {
let mut out = String::from("key\t");
out.push_str(&fields.join("\t"));
out.push('\n');
for key in &keys {
out.push_str(key);
for f in fields {
out.push('\t');
out.push_str(&resolve_field_str(&store, &graph, key, f));
}
out.push('\n');
}
Ok(out)
} else {
Ok(keys.join("\n"))
}
}
}
}
fn resolve_field_str(store: &crate::store::Store, graph: &crate::graph::Graph, key: &str, field: &str) -> String {
let node = match store.nodes.get(key) {
Some(n) => n,
None => return "-".to_string(),
};
match field {
"key" => key.to_string(),
"weight" => format!("{:.3}", node.weight),
"node_type" => format!("{:?}", node.node_type),
"provenance" => node.provenance.clone(),
"emotion" => format!("{}", node.emotion),
"retrievals" => format!("{}", node.retrievals),
"uses" => format!("{}", node.uses),
"wrongs" => format!("{}", node.wrongs),
"created" => format!("{}", node.created_at),
"timestamp" => format!("{}", node.timestamp),
"degree" => format!("{}", graph.degree(key)),
"content_len" => format!("{}", node.content.len()),
_ => "-".to_string(),
}
}
// ── Journal tools ──────────────────────────────────────────────
async fn journal_tail(args: &serde_json::Value) -> Result<String> {