Skip to main content

qualia_cli/query/
compiler.rs

1use 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
9// OP_BIND_VAR is followed by 2 bytes: [Target Slot (Subj=0, Pred=1, Obj=2), Var Register ID]
10const 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    // In a real run, this would be loaded from the .lex file
40    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        // Extract the base GraphPattern (ignoring SELECT projections for the MVP)
56        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        // 1. Compile Subject
79        match &triple.subject {
80            TermPattern::NamedNode(node) => {
81                let hash = self.lexicon.get(node.as_str()).unwrap_or(&0); // Mock fallback
82                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        // 2. Compile Predicate
93        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        // 3. Compile Object (Includes Inline Datatype packing)
106        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                // For MVP, parse as string hash.
114                // TODO: If literal.datatype() is xsd:integer, pack into Inline u64 here.
115                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}