1use sqlitegraph::{
2 backend::BackendDirection,
3 pattern::{NodeConstraint, PatternLeg, PatternQuery},
4 pipeline::{ReasoningPipeline, ReasoningStep},
5 subgraph::SubgraphRequest,
6};
7
8#[derive(Debug, Clone)]
9pub enum DslResult {
10 Pattern(PatternQuery),
11 Pipeline(ReasoningPipeline),
12 Subgraph(SubgraphRequest),
13 Error(String),
14}
15
16pub fn parse_dsl(input: &str) -> DslResult {
17 let trimmed = input.trim();
18 if trimmed.is_empty() {
19 return DslResult::Error("empty DSL string".into());
20 }
21 if trimmed.starts_with("pattern ") {
22 return parse_pattern_pipeline(trimmed);
23 }
24 if let Some(result) = parse_hop_command(trimmed) {
25 return result;
26 }
27 if trimmed.contains("->") || trimmed.contains('*') {
28 return DslResult::Pattern(parse_repetitive_pattern(trimmed));
29 }
30 DslResult::Error(format!("unsupported DSL form: {trimmed}"))
31}
32
33fn parse_pattern_pipeline(input: &str) -> DslResult {
34 let rest = input.trim_start_matches("pattern").trim();
35 let mut segments = rest.splitn(2, "filter");
36 let pattern_part = segments.next().unwrap_or("").trim();
37 if pattern_part.is_empty() {
38 return DslResult::Error("missing pattern segment".into());
39 }
40 let query = parse_repetitive_pattern(pattern_part);
41 let mut steps = vec![ReasoningStep::Pattern(query)];
42 if let Some(filter_part) = segments.next() {
43 let text = filter_part.trim();
44 let mut tokens = text.split_whitespace();
45 let clause = tokens.next().unwrap_or("");
46 if tokens.next().is_some() {
47 return DslResult::Error("multiple filter clauses are not supported".into());
48 }
49 if let Some(kind) = clause.strip_prefix("type=") {
50 let trimmed = kind.trim();
51 if trimmed.is_empty() {
52 return DslResult::Error("unsupported filter clause".into());
53 }
54 steps.push(ReasoningStep::Filter(NodeConstraint::kind(trimmed)));
55 } else {
56 return DslResult::Error("unsupported filter clause".into());
57 }
58 }
59 DslResult::Pipeline(ReasoningPipeline { steps })
60}
61
62fn parse_repetitive_pattern(segment: &str) -> PatternQuery {
63 let mut legs = Vec::new();
64 for token in segment.split("->") {
65 let trimmed = token.trim();
66 if trimmed.is_empty() {
67 continue;
68 }
69 let (ty, count) = if let Some((name, times)) = trimmed.split_once('*') {
70 let repeats = times.trim().parse::<usize>().unwrap_or(1);
71 (name.trim(), repeats)
72 } else {
73 (trimmed, 1)
74 };
75 for _ in 0..count {
76 legs.push(PatternLeg {
77 direction: BackendDirection::Outgoing,
78 edge_type: Some(ty.to_string()),
79 constraint: None,
80 });
81 }
82 }
83 PatternQuery { root: None, legs }
84}
85
86fn parse_hop_command(input: &str) -> Option<DslResult> {
87 if let Some((prefix, suffix)) = input.split_once("-hop") {
88 let depth = prefix.trim().parse::<u32>().ok()?;
89 let mut allowed = Vec::new();
90 if let Some(filter) = suffix.trim().strip_prefix("type=") {
91 allowed.push(filter.trim().to_string());
92 }
93 return Some(DslResult::Subgraph(SubgraphRequest {
94 root: 0,
95 depth,
96 allowed_edge_types: Vec::new(),
97 allowed_node_types: allowed,
98 }));
99 }
100 None
101}