1use std::collections::BTreeMap;
9
10use sim_kernel::{Expr, Origin, Result, Symbol};
11
12use crate::error::{ensure, logic_eval_error};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct ClauseId(pub usize);
17
18#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct Clause {
23 pub id: ClauseId,
25 pub head: Expr,
27 pub body: Vec<Expr>,
29 pub source: Option<Origin>,
31}
32
33impl Clause {
34 pub fn predicate(&self) -> Result<Symbol> {
36 predicate_symbol(&self.head)
37 }
38
39 pub fn arity(&self) -> Result<usize> {
41 goal_arity(&self.head)
42 }
43
44 pub fn fact_expr(&self) -> Expr {
47 if self.body.is_empty() {
48 Expr::List(vec![
49 Expr::Symbol(Symbol::new("fact")),
50 normalize_goal_expr(&self.head),
51 ])
52 } else {
53 Expr::List(vec![
54 Expr::Symbol(Symbol::new("rule")),
55 normalize_goal_expr(&self.head),
56 Expr::List(self.body.iter().map(normalize_goal_expr).collect()),
57 ])
58 }
59 }
60}
61
62pub fn parse_clause_expr(id: ClauseId, expr: Expr) -> Result<Clause> {
87 let Expr::List(items) = expr else {
88 return Err(logic_eval_error("logic clause must be a list"));
89 };
90 let Some((head, tail)) = items.split_first() else {
91 return Err(logic_eval_error("logic clause cannot be empty"));
92 };
93 match head {
94 Expr::Symbol(symbol) if symbol.name.as_ref() == "fact" => parse_fact(id, tail),
95 Expr::Symbol(symbol) if symbol.name.as_ref() == "rule" => parse_rule(id, tail),
96 _ => Err(logic_eval_error(
97 "logic clause must start with fact or rule",
98 )),
99 }
100}
101
102pub fn parse_goal_expr(expr: &Expr) -> Result<Expr> {
103 ensure(is_goal_expr(expr), "logic goal must be a call-shaped list")?;
104 Ok(normalize_goal_expr(expr))
105}
106
107pub(crate) fn is_cut_expr(expr: &Expr) -> bool {
108 matches!(expr, Expr::Symbol(symbol) if symbol.namespace.is_none() && symbol.name.as_ref() == "!")
109}
110
111pub fn is_goal_expr(expr: &Expr) -> bool {
112 match expr {
113 Expr::List(items) => matches!(items.first(), Some(Expr::Symbol(_))),
114 Expr::Call { operator, .. } => matches!(operator.as_ref(), Expr::Symbol(_)),
115 _ => false,
116 }
117}
118
119pub fn predicate_symbol(expr: &Expr) -> Result<Symbol> {
120 match expr {
121 Expr::List(items) => match items.first() {
122 Some(Expr::Symbol(symbol)) => Ok(symbol.clone()),
123 _ => Err(logic_eval_error("goal head operator must be a symbol")),
124 },
125 Expr::Call { operator, .. } => match operator.as_ref() {
126 Expr::Symbol(symbol) => Ok(symbol.clone()),
127 _ => Err(logic_eval_error("goal head operator must be a symbol")),
128 },
129 _ => Err(logic_eval_error("goal must be call-shaped")),
130 }
131}
132
133pub fn goal_arity(expr: &Expr) -> Result<usize> {
134 match expr {
135 Expr::List(items) => Ok(items.len().saturating_sub(1)),
136 Expr::Call { args, .. } => Ok(args.len()),
137 _ => Err(logic_eval_error("goal must be call-shaped")),
138 }
139}
140
141pub fn goal_first_arg(expr: &Expr) -> Option<&Expr> {
142 match expr {
143 Expr::List(items) => items.get(1),
144 Expr::Call { args, .. } => args.first(),
145 _ => None,
146 }
147}
148
149pub fn normalize_goal_expr(expr: &Expr) -> Expr {
150 match expr {
151 Expr::Call { operator, args } => {
152 let mut items = Vec::with_capacity(args.len() + 1);
153 items.push((**operator).clone());
154 items.extend(args.iter().cloned());
155 Expr::List(items)
156 }
157 other => other.clone(),
158 }
159}
160
161pub fn rename_clause_apart(clause: &Clause, depth: usize) -> Clause {
162 let mut renamed = BTreeMap::new();
163 let mut anonymous_index = 0usize;
164 let suffix = format!("{}:{}", clause.id.0, depth);
165 Clause {
166 id: clause.id,
167 head: rename_expr(&clause.head, &suffix, &mut renamed, &mut anonymous_index),
168 body: clause
169 .body
170 .iter()
171 .map(|goal| rename_expr(goal, &suffix, &mut renamed, &mut anonymous_index))
172 .collect(),
173 source: clause.source.clone(),
174 }
175}
176
177fn parse_fact(id: ClauseId, tail: &[Expr]) -> Result<Clause> {
178 let [head] = tail else {
179 return Err(logic_eval_error("fact expects one call head"));
180 };
181 let head = parse_goal_expr(head)?;
182 Ok(Clause {
183 id,
184 head,
185 body: Vec::new(),
186 source: None,
187 })
188}
189
190fn parse_rule(id: ClauseId, tail: &[Expr]) -> Result<Clause> {
191 let [head, body] = tail else {
192 return Err(logic_eval_error("rule expects head plus body list"));
193 };
194 let head = parse_goal_expr(head)?;
195 let Expr::List(goals) = body else {
196 return Err(logic_eval_error("rule body must be a list of goals"));
197 };
198 let body = goals
199 .iter()
200 .map(parse_rule_body_goal)
201 .collect::<Result<Vec<_>>>()?;
202 Ok(Clause {
203 id,
204 head,
205 body,
206 source: None,
207 })
208}
209
210fn parse_rule_body_goal(expr: &Expr) -> Result<Expr> {
211 if is_cut_expr(expr) {
212 return Ok(expr.clone());
213 }
214 parse_goal_expr(expr)
215}
216
217fn rename_expr(
218 expr: &Expr,
219 suffix: &str,
220 renamed: &mut BTreeMap<Symbol, Symbol>,
221 anonymous_index: &mut usize,
222) -> Expr {
223 match expr {
224 Expr::Local(var) if var.name.as_ref() == "_" => {
225 *anonymous_index += 1;
226 Expr::Local(Symbol::new(format!("__anon_{suffix}_{anonymous_index}")))
227 }
228 Expr::Local(var) => {
229 let name = renamed
230 .entry(var.clone())
231 .or_insert_with(|| Symbol::new(format!("{}@{suffix}", var.name)))
232 .clone();
233 Expr::Local(name)
234 }
235 Expr::List(items) => Expr::List(
236 items
237 .iter()
238 .map(|item| rename_expr(item, suffix, renamed, anonymous_index))
239 .collect(),
240 ),
241 Expr::Vector(items) => Expr::Vector(
242 items
243 .iter()
244 .map(|item| rename_expr(item, suffix, renamed, anonymous_index))
245 .collect(),
246 ),
247 Expr::Map(entries) => Expr::Map(
248 entries
249 .iter()
250 .map(|(key, value)| {
251 (
252 rename_expr(key, suffix, renamed, anonymous_index),
253 rename_expr(value, suffix, renamed, anonymous_index),
254 )
255 })
256 .collect(),
257 ),
258 Expr::Set(items) => Expr::Set(
259 items
260 .iter()
261 .map(|item| rename_expr(item, suffix, renamed, anonymous_index))
262 .collect(),
263 ),
264 Expr::Call { operator, args } => Expr::Call {
265 operator: Box::new(rename_expr(operator, suffix, renamed, anonymous_index)),
266 args: args
267 .iter()
268 .map(|arg| rename_expr(arg, suffix, renamed, anonymous_index))
269 .collect(),
270 },
271 Expr::Infix {
272 operator,
273 left,
274 right,
275 } => Expr::Infix {
276 operator: operator.clone(),
277 left: Box::new(rename_expr(left, suffix, renamed, anonymous_index)),
278 right: Box::new(rename_expr(right, suffix, renamed, anonymous_index)),
279 },
280 Expr::Prefix { operator, arg } => Expr::Prefix {
281 operator: operator.clone(),
282 arg: Box::new(rename_expr(arg, suffix, renamed, anonymous_index)),
283 },
284 Expr::Postfix { operator, arg } => Expr::Postfix {
285 operator: operator.clone(),
286 arg: Box::new(rename_expr(arg, suffix, renamed, anonymous_index)),
287 },
288 Expr::Block(items) => Expr::Block(
289 items
290 .iter()
291 .map(|item| rename_expr(item, suffix, renamed, anonymous_index))
292 .collect(),
293 ),
294 Expr::Quote { mode, expr } => Expr::Quote {
295 mode: *mode,
296 expr: Box::new(rename_expr(expr, suffix, renamed, anonymous_index)),
297 },
298 Expr::Annotated { expr, annotations } => Expr::Annotated {
299 expr: Box::new(rename_expr(expr, suffix, renamed, anonymous_index)),
300 annotations: annotations
301 .iter()
302 .map(|(name, value)| {
303 (
304 name.clone(),
305 rename_expr(value, suffix, renamed, anonymous_index),
306 )
307 })
308 .collect(),
309 },
310 Expr::Extension { tag, payload } => Expr::Extension {
311 tag: tag.clone(),
312 payload: Box::new(rename_expr(payload, suffix, renamed, anonymous_index)),
313 },
314 other => other.clone(),
315 }
316}