qualia_core_db/modalities/logic/shacl/
shacl_compiler.rs1use super::shacl_extension_bridge::append_extension_opcodes;
7use super::shacl_types::{
8 CompiledShape, NodeKindType, ShaclConstraint, ShaclSeverity, ShaclTarget,
9};
10use crate::webizen::SlgOpcode;
11
12pub struct ShaclCompiler;
14
15impl ShaclCompiler {
16 pub fn new() -> Self {
17 ShaclCompiler
18 }
19
20 pub fn compile(
22 &self,
23 target: ShaclTarget,
24 property_path: &str,
25 constraint: ShaclConstraint,
26 severity: ShaclSeverity,
27 ) -> CompiledShape {
28 let mut opcodes = Vec::new();
29 Self::push_constraint(&constraint, &mut opcodes);
30 Self::push_terminal(severity, &mut opcodes);
31
32 let shape_class = match &target {
33 ShaclTarget::TargetClass(s) => s.clone(),
34 ShaclTarget::TargetObjectsOf(s) => s.clone(),
35 ShaclTarget::TargetSubjectsOf(s) => s.clone(),
36 ShaclTarget::TargetNode(s) => s.clone(),
37 };
38
39 let mut shape = CompiledShape::new(shape_class, vec![constraint], severity);
40 shape.property_path = property_path.to_string();
41 shape.opcodes = opcodes;
42 shape
43 }
44
45 pub fn compile_class(
46 &self,
47 target_class: &str,
48 property_path: &str,
49 constraint: ShaclConstraint,
50 severity: ShaclSeverity,
51 ) -> CompiledShape {
52 self.compile(
53 ShaclTarget::TargetClass(target_class.to_string()),
54 property_path,
55 constraint,
56 severity,
57 )
58 }
59
60 pub fn compile_shape(
62 &self,
63 target_class: &str,
64 property_path: &str,
65 constraint_type: &str,
66 value: f32,
67 ) -> Vec<SlgOpcode> {
68 let constraint = Self::parse_str(constraint_type, value);
69 let shape = self.compile_class(
70 target_class,
71 property_path,
72 constraint,
73 ShaclSeverity::Violation,
74 );
75 shape.opcodes
76 }
77
78 pub fn compile_constraints(
80 constraints: &[ShaclConstraint],
81 severity: ShaclSeverity,
82 ) -> Vec<SlgOpcode> {
83 let mut opcodes = Vec::new();
84 for c in constraints {
85 Self::push_constraint(c, &mut opcodes);
86 }
87 Self::push_terminal(severity, &mut opcodes);
88 opcodes
89 }
90
91 pub fn compile_extension_shape(extension_id: &str) -> Vec<SlgOpcode> {
93 let mut opcodes = Vec::new();
94 append_extension_opcodes(&mut opcodes, extension_id);
95 opcodes
96 }
97
98 fn push_constraint(constraint: &ShaclConstraint, opcodes: &mut Vec<SlgOpcode>) {
99 match constraint {
100 ShaclConstraint::MinInclusive(min) => {
101 opcodes.push(SlgOpcode::CheckMinInclusive(*min));
102 }
103 ShaclConstraint::MaxInclusive(max) => {
104 opcodes.push(SlgOpcode::CheckMaxInclusive(*max));
105 }
106 ShaclConstraint::MinExclusive(min) => {
107 opcodes.push(SlgOpcode::CheckMinExclusive(*min));
108 }
109 ShaclConstraint::MaxExclusive(max) => {
110 opcodes.push(SlgOpcode::CheckMaxExclusive(*max));
111 }
112 ShaclConstraint::MinCount(min) => {
113 opcodes.push(SlgOpcode::CheckMinCount(*min));
114 }
115 ShaclConstraint::MaxCount(max) => {
116 opcodes.push(SlgOpcode::CheckMaxCount(*max));
117 }
118 ShaclConstraint::MinLength(min) => {
119 opcodes.push(SlgOpcode::CheckMinLength(*min));
120 }
121 ShaclConstraint::MaxLength(max) => {
122 opcodes.push(SlgOpcode::CheckMaxLength(*max));
123 }
124 ShaclConstraint::Pattern(pattern) => {
125 opcodes.push(SlgOpcode::CheckPattern(crate::q_hash(pattern)));
126 }
127 ShaclConstraint::In(values) => {
128 for value in values {
129 opcodes.push(SlgOpcode::CheckHasValue(crate::q_hash(value)));
130 }
131 }
132 ShaclConstraint::HasValue(value) => {
133 opcodes.push(SlgOpcode::CheckHasValue(crate::q_hash(value)));
134 }
135 ShaclConstraint::Node(shape) | ShaclConstraint::Class(shape) => {
136 opcodes.push(SlgOpcode::CheckNodeShape(crate::q_hash(shape)));
137 }
138 ShaclConstraint::Not(shape) => {
139 opcodes.push(SlgOpcode::CheckNotShape(crate::q_hash(shape)));
140 }
141 ShaclConstraint::And(shapes) => {
142 for shape in shapes {
143 opcodes.push(SlgOpcode::CheckNodeShape(crate::q_hash(shape)));
144 }
145 }
146 ShaclConstraint::Or(shapes) => {
147 for shape in shapes {
148 opcodes.push(SlgOpcode::SoftCheckNodeShape(crate::q_hash(shape)));
149 }
150 opcodes.push(SlgOpcode::RequireAnyShape);
151 }
152 ShaclConstraint::Xone(shapes) => {
153 for shape in shapes {
155 opcodes.push(SlgOpcode::SoftCheckNodeShape(crate::q_hash(shape)));
156 }
157 opcodes.push(SlgOpcode::RequireAnyShape);
158 }
159 ShaclConstraint::DataType(dt) => {
160 if let Some(tag) = datatype_to_tag(dt) {
161 opcodes.push(SlgOpcode::CheckObjectDatatype(tag));
162 }
163 }
164 ShaclConstraint::NodeKind(_) => {}
165 ShaclConstraint::NodeKindStrict(kind) => {
166 let _tag = node_kind_to_tag(*kind);
167 }
168 ShaclConstraint::Equals(value) => {
169 opcodes.push(SlgOpcode::CheckHasValue(crate::q_hash(value)));
170 }
171 ShaclConstraint::LessThan(value) => {
172 opcodes.push(SlgOpcode::CheckMaxExclusive(crate::q_hash(value) as f64));
173 }
174 ShaclConstraint::LessThanOrEquals(value) => {
175 opcodes.push(SlgOpcode::CheckMaxInclusive(crate::q_hash(value) as f64));
176 }
177 ShaclConstraint::GreaterThan(value) => {
178 opcodes.push(SlgOpcode::CheckMinExclusive(crate::q_hash(value) as f64));
179 }
180 ShaclConstraint::GreaterThanOrEquals(value) => {
181 opcodes.push(SlgOpcode::CheckMinInclusive(crate::q_hash(value) as f64));
182 }
183 ShaclConstraint::DatatypeRange {
184 min_inclusive,
185 max_inclusive,
186 min_exclusive,
187 max_exclusive,
188 } => {
189 if let Some(v) = min_inclusive {
190 opcodes.push(SlgOpcode::CheckMinInclusive(*v));
191 }
192 if let Some(v) = max_inclusive {
193 opcodes.push(SlgOpcode::CheckMaxInclusive(*v));
194 }
195 if let Some(v) = min_exclusive {
196 opcodes.push(SlgOpcode::CheckMinExclusive(*v));
197 }
198 if let Some(v) = max_exclusive {
199 opcodes.push(SlgOpcode::CheckMaxExclusive(*v));
200 }
201 }
202 ShaclConstraint::PropertyPath { constraint, .. } => {
203 Self::push_constraint(constraint, opcodes);
204 }
205 ShaclConstraint::DeonticPolicy { .. } => {
206 opcodes.push(SlgOpcode::NativeDeonticEval);
207 }
208 ShaclConstraint::DeonticObligate => {
209 opcodes.push(SlgOpcode::NativeDeonticEval);
210 }
211 ShaclConstraint::DeonticPermit => {
212 opcodes.push(SlgOpcode::NativeDeonticEval);
213 }
214 ShaclConstraint::DeonticForbid => {
215 opcodes.push(SlgOpcode::NativeDeonticEval);
216 }
217 ShaclConstraint::DeonticNotExpired { .. } => {
218 opcodes.push(SlgOpcode::NativeDeonticEval);
219 }
220 ShaclConstraint::EpistemicConstraint {
221 certainty_threshold,
222 } => {
223 let min = (*certainty_threshold * 255.0).clamp(0.0, 255.0) as u8;
224 opcodes.push(SlgOpcode::NativeEpistemicEval(min));
225 }
226 ShaclConstraint::EpistemicKnowledge { min_certainty } => {
227 opcodes.push(SlgOpcode::NativeEpistemicEval(*min_certainty));
228 }
229 ShaclConstraint::EpistemicBelief { min_certainty } => {
230 opcodes.push(SlgOpcode::NativeEpistemicEval(*min_certainty));
231 }
232 ShaclConstraint::CommonKnowledge => {
233 opcodes.push(SlgOpcode::NativeEpistemicEval(255));
235 }
236 ShaclConstraint::LtlConstraint { .. } => {
237 opcodes.push(SlgOpcode::NativeLtlGlobally);
238 }
239 ShaclConstraint::ParaconsistentConstraint { .. } => {
240 opcodes.push(SlgOpcode::NativeParaconsistentIsolate);
241 }
242 ShaclConstraint::CalculusConstraint { .. } => {
243 opcodes.push(SlgOpcode::NativeCalcSimpsons(0, 0, 0, 0));
244 }
245 ShaclConstraint::GraphConstraint { .. } => {
246 opcodes.push(SlgOpcode::NativeAllenInterval(0));
247 }
248 ShaclConstraint::ArgumentationConstraint { .. } => {
249 opcodes.push(SlgOpcode::NativeUnless);
250 }
251 ShaclConstraint::DialecticalConstraint { .. } => {
252 opcodes.push(SlgOpcode::NativeDialecticalSynthesis);
253 }
254 ShaclConstraint::AspConstraint { .. } => {
255 opcodes.push(SlgOpcode::NativeAspStableModels);
256 }
257 ShaclConstraint::ProbabilisticConstraint { .. } => {
258 opcodes.push(SlgOpcode::NativeEconomics);
259 }
260 ShaclConstraint::DiffusionConstraint { .. } => {
261 opcodes.push(SlgOpcode::NativeThermodynamics);
262 }
263 ShaclConstraint::LinearLogicConstraint { .. } => {
264 opcodes.push(SlgOpcode::NativeLinearConsume);
265 }
266 ShaclConstraint::ControlFeedbackConstraint { .. } => {
267 opcodes.push(SlgOpcode::NativeOdeSolver);
268 }
269 ShaclConstraint::IntervalArithmeticConstraint { .. } => {
270 opcodes.push(SlgOpcode::NativeAllenInterval(1));
271 }
272 ShaclConstraint::LanguageIn(_)
273 | ShaclConstraint::UniqueLang
274 | ShaclConstraint::Closed { .. }
275 | ShaclConstraint::QualifierValue { .. }
276 | ShaclConstraint::EconVaRPositive
280 | ShaclConstraint::EconConvergedModel
281 | ShaclConstraint::EconPositivePrice
282 | ShaclConstraint::EconRiskBelowThreshold { .. }
283 | ShaclConstraint::EconWelfareAboveFloor { .. } => {}
284 }
285 }
286
287 fn push_terminal(severity: ShaclSeverity, opcodes: &mut Vec<SlgOpcode>) {
288 match severity {
289 ShaclSeverity::Violation => opcodes.push(SlgOpcode::Halt),
290 ShaclSeverity::Warning | ShaclSeverity::Info => opcodes.push(SlgOpcode::WarnOnly),
291 }
292 }
293
294 fn parse_str(constraint_type: &str, value: f32) -> ShaclConstraint {
295 match constraint_type {
296 "minInclusive" => ShaclConstraint::MinInclusive(value as f64),
297 "maxInclusive" => ShaclConstraint::MaxInclusive(value as f64),
298 "minExclusive" => ShaclConstraint::MinExclusive(value as f64),
299 "maxExclusive" => ShaclConstraint::MaxExclusive(value as f64),
300 "minCount" => ShaclConstraint::MinCount(value as u32),
301 "maxCount" => ShaclConstraint::MaxCount(value as u32),
302 "minLength" => ShaclConstraint::MinLength(value as u32),
303 "maxLength" => ShaclConstraint::MaxLength(value as u32),
304 _ => ShaclConstraint::MinInclusive(value as f64),
305 }
306 }
307}
308
309fn node_kind_to_tag(kind: NodeKindType) -> u8 {
310 match kind {
311 NodeKindType::BlankNode => 0,
312 NodeKindType::Iri => 1,
313 NodeKindType::Literal => 2,
314 NodeKindType::BlankNodeOrIri => 3,
315 NodeKindType::BlankNodeOrLiteral => 4,
316 NodeKindType::IriOrLiteral => 5,
317 }
318}
319
320fn datatype_to_tag(dt: &str) -> Option<u8> {
321 let h = crate::q_hash(dt);
322 if h == crate::q_hash("xsd:string") {
323 Some(0)
324 } else if h == crate::q_hash("xsd:integer") {
325 Some(1)
326 } else if h == crate::q_hash("xsd:decimal") || h == crate::q_hash("xsd:double") {
327 Some(2)
328 } else if h == crate::q_hash("xsd:boolean") {
329 Some(3)
330 } else if h == crate::q_hash("xsd:dateTime") {
331 Some(1)
332 } else {
333 None
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn compile_shape_returns_opcodes() {
343 let compiler = ShaclCompiler::new();
344 let ops = compiler.compile_shape("ex:Person", "ex:age", "minInclusive", 18.0);
345 assert!(!ops.is_empty());
346 assert!(ops
347 .iter()
348 .any(|o| matches!(o, SlgOpcode::CheckMinInclusive(v) if *v == 18.0)));
349 }
350
351 #[test]
352 fn or_constraint_emits_soft_checks() {
353 let mut opcodes = Vec::new();
354 ShaclCompiler::push_constraint(
355 &ShaclConstraint::Or(vec!["ex:A".into(), "ex:B".into()]),
356 &mut opcodes,
357 );
358 assert!(opcodes
359 .iter()
360 .any(|o| matches!(o, SlgOpcode::SoftCheckNodeShape(_))));
361 assert!(opcodes.contains(&SlgOpcode::RequireAnyShape));
362 }
363
364 #[test]
365 fn deontic_constraint_emits_native_eval() {
366 let mut opcodes = Vec::new();
367 ShaclCompiler::push_constraint(
368 &ShaclConstraint::DeonticPolicy {
369 policy_id: "p1".into(),
370 obligation: "permit".into(),
371 },
372 &mut opcodes,
373 );
374 assert!(opcodes.contains(&SlgOpcode::NativeDeonticEval));
375 }
376
377 #[test]
378 fn deontic_typed_constraints_emit_native_eval() {
379 for constraint in &[
380 ShaclConstraint::DeonticObligate,
381 ShaclConstraint::DeonticPermit,
382 ShaclConstraint::DeonticForbid,
383 ShaclConstraint::DeonticNotExpired { now_unix: 1000 },
384 ] {
385 let mut opcodes = Vec::new();
386 ShaclCompiler::push_constraint(constraint, &mut opcodes);
387 assert!(
388 opcodes.contains(&SlgOpcode::NativeDeonticEval),
389 "Deontic typed constraint must emit NativeDeonticEval"
390 );
391 }
392 }
393
394 #[test]
395 fn epistemic_knowledge_constraint_emits_native_eval_with_min_certainty() {
396 let mut opcodes = Vec::new();
397 ShaclCompiler::push_constraint(
398 &ShaclConstraint::EpistemicKnowledge { min_certainty: 200 },
399 &mut opcodes,
400 );
401 assert!(
402 opcodes
403 .iter()
404 .any(|o| matches!(o, SlgOpcode::NativeEpistemicEval(m) if *m == 200)),
405 "EpistemicKnowledge must emit NativeEpistemicEval with the specified min_certainty"
406 );
407 }
408
409 #[test]
410 fn epistemic_belief_constraint_emits_native_eval_with_min_certainty() {
411 let mut opcodes = Vec::new();
412 ShaclCompiler::push_constraint(
413 &ShaclConstraint::EpistemicBelief { min_certainty: 128 },
414 &mut opcodes,
415 );
416 assert!(
417 opcodes
418 .iter()
419 .any(|o| matches!(o, SlgOpcode::NativeEpistemicEval(m) if *m == 128)),
420 "EpistemicBelief must emit NativeEpistemicEval with the specified min_certainty"
421 );
422 }
423
424 #[test]
425 fn common_knowledge_constraint_emits_native_eval_with_max_certainty() {
426 let mut opcodes = Vec::new();
427 ShaclCompiler::push_constraint(&ShaclConstraint::CommonKnowledge, &mut opcodes);
428 assert!(
429 opcodes
430 .iter()
431 .any(|o| matches!(o, SlgOpcode::NativeEpistemicEval(m) if *m == 255)),
432 "CommonKnowledge must emit NativeEpistemicEval with certainty 255 (max)"
433 );
434 }
435}