1use std::sync::Arc;
2
3use sim_kernel::{
4 AbiVersion, Cx, Expr, Lib, LibManifest, LibTarget, Linker, Result, Symbol, Value, Version,
5};
6
7use crate::{
8 capabilities::{logic_config_write_capability, logic_db_write_capability},
9 codec::{consult_expr, consult_table_path},
10 error::logic_eval_error,
11 lisp_runtime::{
12 CONFIG_SYMBOL, DB_SYMBOL, LogicConfigState, LogicDbState, LogicFunction, config_value,
13 keyword, logic_config_state, logic_db_state, query_config, string_expr, symbol_expr,
14 unquote, usize_from_expr, value_expr,
15 },
16 model::SearchStrategy,
17 query::{query, query_all, query_bool, query_one},
18 shapes::{register_logic_shapes, require_logic_stream},
19};
20
21const LOGIC_LIB_ID: &str = "logic";
22
23pub struct LogicLib;
29
30impl Lib for LogicLib {
31 fn manifest(&self) -> LibManifest {
32 LibManifest {
33 id: Symbol::new(LOGIC_LIB_ID),
34 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
35 abi: AbiVersion { major: 0, minor: 1 },
36 target: LibTarget::HostRegistered,
37 requires: Vec::new(),
38 capabilities: Vec::new(),
39 exports: logic_exports(),
40 }
41 }
42
43 fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
44 register_logic_shapes(linker, cx)?;
45 register_logic_functions(cx, linker)?;
46 linker.value(
47 Symbol::qualified("logic", DB_SYMBOL),
48 cx.factory().opaque(Arc::new(LogicDbState::default()))?,
49 )?;
50 linker.value(
51 Symbol::qualified("logic", CONFIG_SYMBOL),
52 cx.factory().opaque(Arc::new(LogicConfigState::default()))?,
53 )?;
54 Ok(())
55 }
56}
57
58pub fn install_logic_lib(cx: &mut Cx) -> Result<()> {
62 sim_lib_core::install_once(cx, &LogicLib).map(|_| ())
63}
64
65pub fn realize_logic(
73 cx: &mut Cx,
74 goal: Expr,
75 answer_limit: Option<usize>,
76 stream_buffer: Option<usize>,
77 stream: bool,
78) -> Result<Value> {
79 install_logic_lib(cx)?;
80 let state = logic_config_state(cx)?;
81 let mut config = state.lock()?.clone();
82 if let Some(limit) = answer_limit {
83 config.limits.max_answers = Some(limit);
84 }
85 if let Some(buffer) = stream_buffer {
86 config.stream_buffer = buffer;
87 }
88 let db = logic_db_state(cx)?.lock()?.clone();
89 if stream {
90 let stream = query(cx, &db, &config, goal)?;
91 return cx.factory().opaque(Arc::new(stream));
92 }
93 match query_one(cx, &db, &config, goal)? {
94 Some(matched) => sim_kernel::shape_match_value(cx, matched),
95 None => cx.factory().nil(),
96 }
97}
98
99fn logic_exports() -> Vec<sim_kernel::Export> {
100 let mut exports = vec![
101 sim_kernel::Export::Value {
102 symbol: Symbol::qualified("logic", DB_SYMBOL),
103 },
104 sim_kernel::Export::Value {
105 symbol: Symbol::qualified("logic", CONFIG_SYMBOL),
106 },
107 ];
108 for symbol in [
109 Symbol::qualified("logic", "Var"),
110 Symbol::qualified("logic", "Goal"),
111 Symbol::qualified("logic", "Clause"),
112 Symbol::qualified("logic", "Fact"),
113 Symbol::qualified("logic", "Rule"),
114 Symbol::qualified("logic", "Answer"),
115 Symbol::qualified("logic", "Config"),
116 ] {
117 exports.push(sim_kernel::Export::Shape {
118 symbol,
119 shape_id: None,
120 });
121 }
122 for symbol in [
123 Symbol::qualified("logic", "config"),
124 Symbol::qualified("logic", "assert!"),
125 Symbol::qualified("logic", "retract!"),
126 Symbol::qualified("logic", "facts"),
127 Symbol::qualified("logic", "consult"),
128 Symbol::qualified("logic", "consult!"),
129 Symbol::qualified("logic", "stream-next"),
130 Symbol::qualified("logic", "stream-close"),
131 Symbol::qualified("logic", "query"),
132 Symbol::qualified("logic", "query/one"),
133 Symbol::qualified("logic", "query/all"),
134 Symbol::qualified("logic", "query?"),
135 Symbol::qualified("logic", "predicate?"),
136 ] {
137 exports.push(sim_kernel::Export::Function {
138 symbol,
139 function_id: None,
140 });
141 }
142 exports
143}
144
145fn register_logic_functions(cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
146 for (symbol, implementation) in [
147 (
148 Symbol::qualified("logic", "config"),
149 logic_config_fn as fn(&mut Cx, &[Expr]) -> Result<Value>,
150 ),
151 (Symbol::qualified("logic", "assert!"), logic_assert_fn),
152 (Symbol::qualified("logic", "retract!"), logic_retract_fn),
153 (Symbol::qualified("logic", "facts"), logic_facts_fn),
154 (Symbol::qualified("logic", "consult"), logic_consult_fn),
155 (
156 Symbol::qualified("logic", "consult!"),
157 logic_consult_bang_fn,
158 ),
159 (
160 Symbol::qualified("logic", "stream-next"),
161 logic_stream_next_fn,
162 ),
163 (
164 Symbol::qualified("logic", "stream-close"),
165 logic_stream_close_fn,
166 ),
167 (Symbol::qualified("logic", "query"), logic_query_fn),
168 (Symbol::qualified("logic", "query/one"), logic_query_one_fn),
169 (Symbol::qualified("logic", "query/all"), logic_query_all_fn),
170 (Symbol::qualified("logic", "query?"), logic_query_bool_fn),
171 (Symbol::qualified("logic", "predicate?"), logic_predicate_fn),
172 ] {
173 linker.function_value(
174 symbol.clone(),
175 cx.factory().opaque(Arc::new(LogicFunction {
176 symbol,
177 implementation,
178 }))?,
179 )?;
180 }
181 Ok(())
182}
183
184fn logic_config_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
185 cx.require(&logic_config_write_capability())?;
186 let state = logic_config_state(cx)?;
187 let mut config = state.lock()?.clone();
188 if !args.len().is_multiple_of(2) {
189 return Err(logic_eval_error(
190 "logic/config options must be key/value pairs",
191 ));
192 }
193 for pair in args.chunks(2) {
194 let key = keyword(&pair[0])?;
195 match key.as_str() {
196 "max-depth" => config.limits.max_depth = usize_from_expr(cx, &pair[1])?,
197 "stream-buffer" => config.stream_buffer = usize_from_expr(cx, &pair[1])?,
198 "answer-limit" => config.limits.max_answers = Some(usize_from_expr(cx, &pair[1])?),
199 "strategy" => {
200 let symbol = symbol_expr(cx, &pair[1])?;
201 config.strategy = SearchStrategy::from_symbol(&symbol)
202 .ok_or_else(|| logic_eval_error(format!("unsupported strategy {symbol}")))?;
203 }
204 other => {
205 return Err(logic_eval_error(format!(
206 "logic/config does not support :{other}"
207 )));
208 }
209 }
210 }
211 *state.lock()? = config.clone();
212 config_value(cx, &config)
213}
214
215fn logic_assert_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
216 cx.require(&logic_db_write_capability())?;
217 let [expr] = args else {
218 return Err(logic_eval_error("logic/assert! expects one quoted clause"));
219 };
220 let clause_expr = unquote(expr);
221 logic_db_state(cx)?
222 .lock()?
223 .assert_clause_expr(clause_expr)?;
224 cx.factory().bool(true)
225}
226
227fn logic_retract_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
228 cx.require(&logic_db_write_capability())?;
229 let [expr] = args else {
230 return Err(logic_eval_error("logic/retract! expects one quoted clause"));
231 };
232 let removed = logic_db_state(cx)?
233 .lock()?
234 .retract_clause_expr(&unquote(expr))?;
235 cx.factory().bool(removed)
236}
237
238fn logic_facts_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
239 let [expr] = args else {
240 return Err(logic_eval_error("logic/facts expects one predicate symbol"));
241 };
242 let predicate = symbol_expr(cx, expr)?;
243 let facts = logic_db_state(cx)?.lock()?.facts(&predicate);
244 cx.factory().list(
245 facts
246 .into_iter()
247 .map(|expr| cx.factory().expr(expr))
248 .collect::<Result<Vec<_>>>()?,
249 )
250}
251
252fn logic_consult_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
253 cx.require(&logic_db_write_capability())?;
254 let [source_expr, path_expr] = args else {
255 return Err(logic_eval_error(
256 "logic/consult expects a directory value and relative table path",
257 ));
258 };
259 let source = value_expr(cx, source_expr)?;
260 let path = string_expr(cx, path_expr)?;
261 let state = logic_db_state(cx)?;
262 let mut db = state.lock()?;
263 let count = consult_table_path(cx, &mut db, &source, &path)?;
264 drop(db);
265 cx.factory().string(count.to_string())
266}
267
268fn logic_consult_bang_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
269 cx.require(&logic_db_write_capability())?;
270 let [expr] = args else {
271 return Err(logic_eval_error(
272 "logic/consult! expects quoted clause data",
273 ));
274 };
275 let state = logic_db_state(cx)?;
276 let mut db = state.lock()?;
277 let count = consult_expr(&mut db, unquote(expr))?;
278 drop(db);
279 cx.factory().string(count.to_string())
280}
281
282fn logic_query_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
283 let [goal, rest @ ..] = args else {
284 return Err(logic_eval_error("query expects a goal"));
285 };
286 let config = query_config(cx, rest)?;
287 let goal = unquote(goal);
288 let db = logic_db_state(cx)?.lock()?.clone();
289 let stream = query(cx, &db, &config, goal)?;
290 cx.factory().opaque(Arc::new(stream))
291}
292
293fn logic_query_one_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
294 let [goal, rest @ ..] = args else {
295 return Err(logic_eval_error("query/one expects a goal"));
296 };
297 let config = query_config(cx, rest)?;
298 let db = logic_db_state(cx)?.lock()?.clone();
299 match query_one(cx, &db, &config, unquote(goal))? {
300 Some(matched) => sim_kernel::shape_match_value(cx, matched),
301 None => cx.factory().nil(),
302 }
303}
304
305fn logic_query_all_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
306 let [goal, rest @ ..] = args else {
307 return Err(logic_eval_error("query/all expects a goal"));
308 };
309 let config = query_config(cx, rest)?;
310 let db = logic_db_state(cx)?.lock()?.clone();
311 let answers = query_all(cx, &db, &config, unquote(goal), config.limits.max_answers)?;
312 let mut values = Vec::with_capacity(answers.len());
313 for matched in answers {
314 values.push(sim_kernel::shape_match_value(cx, matched)?);
315 }
316 cx.factory().list(values)
317}
318
319fn logic_query_bool_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
320 let [goal, rest @ ..] = args else {
321 return Err(logic_eval_error("query? expects a goal"));
322 };
323 let config = query_config(cx, rest)?;
324 let db = logic_db_state(cx)?.lock()?.clone();
325 let accepted = query_bool(cx, &db, &config, unquote(goal))?;
326 cx.factory().bool(accepted)
327}
328
329fn logic_predicate_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
330 let [expr] = args else {
331 return Err(logic_eval_error("predicate? expects a predicate symbol"));
332 };
333 let predicate = symbol_expr(cx, expr)?;
334 let exists = logic_db_state(cx)?.lock()?.predicate_exists(&predicate);
335 cx.factory().bool(exists)
336}
337
338fn logic_stream_next_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
339 let [stream_expr] = args else {
340 return Err(logic_eval_error("logic/stream-next expects a stream"));
341 };
342 let stream = cx.eval_expr(stream_expr.clone())?;
343 match sim_kernel::Stream::next(require_logic_stream(&stream)?, cx)? {
344 Some(value) => Ok(value),
345 None => cx.factory().nil(),
346 }
347}
348
349fn logic_stream_close_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
350 let [stream_expr] = args else {
351 return Err(logic_eval_error("logic/stream-close expects a stream"));
352 };
353 let stream = cx.eval_expr(stream_expr.clone())?;
354 sim_kernel::Stream::close(require_logic_stream(&stream)?, cx)?;
355 cx.factory().nil()
356}