qualia_cli/query/
compiler.rs1use spargebra::algebra::GraphPattern;
2use spargebra::term::{NamedNodePattern, TermPattern, TriplePattern};
3use std::collections::HashMap;
4
5const OP_MATCH_SUBJ: u8 = 0x01;
6const OP_MATCH_PRED: u8 = 0x02;
7const OP_MATCH_OBJ: u8 = 0x03;
8
9const OP_BIND_VAR: u8 = 0x10;
11const SLOT_SUBJ: u8 = 0x00;
12const SLOT_PRED: u8 = 0x01;
13const SLOT_OBJ: u8 = 0x02;
14
15const OP_HALT: u8 = 0xFF;
16
17struct VariableRegistry {
18 name_to_id: std::collections::HashMap<String, u8>,
19 next_id: u8,
20}
21
22impl VariableRegistry {
23 fn new() -> Self {
24 Self {
25 name_to_id: std::collections::HashMap::new(),
26 next_id: 0,
27 }
28 }
29 fn get_or_assign(&mut self, name: &str) -> u8 {
30 *self.name_to_id.entry(name.to_string()).or_insert_with(|| {
31 let id = self.next_id;
32 self.next_id += 1;
33 id
34 })
35 }
36}
37
38pub struct SparqlCompiler {
39 lexicon: HashMap<String, u64>,
41}
42
43impl SparqlCompiler {
44 pub fn new(lexicon: HashMap<String, u64>) -> Self {
45 Self { lexicon }
46 }
47
48 pub fn compile(&self, sparql_string: &str) -> Result<Vec<u8>, String> {
49 let query = spargebra::SparqlParser::new()
50 .parse_query(sparql_string)
51 .map_err(|e| e.to_string())?;
52 let mut bytecode = Vec::new();
53 let mut var_registry = VariableRegistry::new();
54
55 let spargebra::Query::Select { pattern, .. } = query else {
57 return Err("Only SELECT queries are supported in the MVP".to_string());
58 };
59
60 if let GraphPattern::Bgp { patterns } = pattern {
61 for triple in patterns {
62 self.compile_triple_pattern(&triple, &mut bytecode, &mut var_registry)?;
63 }
64 } else {
65 return Err("Only Basic Graph Patterns (BGPs) are currently supported".to_string());
66 }
67
68 bytecode.push(OP_HALT);
69 Ok(bytecode)
70 }
71
72 fn compile_triple_pattern(
73 &self,
74 triple: &TriplePattern,
75 bytecode: &mut Vec<u8>,
76 vars: &mut VariableRegistry,
77 ) -> Result<(), String> {
78 match &triple.subject {
80 TermPattern::NamedNode(node) => {
81 let hash = self.lexicon.get(node.as_str()).unwrap_or(&0); bytecode.push(OP_MATCH_SUBJ);
83 bytecode.extend_from_slice(&hash.to_le_bytes());
84 }
85 TermPattern::Variable(var) => {
86 let var_id = vars.get_or_assign(var.as_str());
87 bytecode.extend_from_slice(&[OP_BIND_VAR, SLOT_SUBJ, var_id]);
88 }
89 _ => return Err("Unsupported Subject Type".into()),
90 }
91
92 match &triple.predicate {
94 NamedNodePattern::NamedNode(node) => {
95 let hash = self.lexicon.get(node.as_str()).unwrap_or(&0);
96 bytecode.push(OP_MATCH_PRED);
97 bytecode.extend_from_slice(&hash.to_le_bytes());
98 }
99 NamedNodePattern::Variable(var) => {
100 let var_id = vars.get_or_assign(var.as_str());
101 bytecode.extend_from_slice(&[OP_BIND_VAR, SLOT_PRED, var_id]);
102 }
103 }
104
105 match &triple.object {
107 TermPattern::NamedNode(node) => {
108 let hash = self.lexicon.get(node.as_str()).unwrap_or(&0);
109 bytecode.push(OP_MATCH_OBJ);
110 bytecode.extend_from_slice(&hash.to_le_bytes());
111 }
112 TermPattern::Literal(literal) => {
113 let hash = self.lexicon.get(literal.value()).unwrap_or(&0);
116 bytecode.push(OP_MATCH_OBJ);
117 bytecode.extend_from_slice(&hash.to_le_bytes());
118 }
119 TermPattern::Variable(var) => {
120 let var_id = vars.get_or_assign(var.as_str());
121 bytecode.extend_from_slice(&[OP_BIND_VAR, SLOT_OBJ, var_id]);
122 }
123 _ => return Err("Unsupported Object Type".into()),
124 }
125
126 Ok(())
127 }
128}