Skip to main content

sim_lib_logic/
lisp.rs

1use std::sync::Arc;
2
3use sim_kernel::{
4    AbiVersion, Cx, Expr, Lib, LibManifest, LibTarget, Linker, Result, Symbol, Value, Version,
5    logic_db_write_capability,
6};
7
8use crate::{
9    codec::{consult_expr, consult_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,
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
23/// The loadable logic organ: shapes, functions, and database/config state.
24///
25/// Implements the kernel `Lib` contract and installs the `logic/*` surface
26/// (assert, retract, query, unify, and the logic shapes). Load it with
27/// [`install_logic_lib`]; see the [`README`](https://docs.rs/sim-runtime).
28pub 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
58/// Installs the [`LogicLib`] into `cx`, idempotently.
59///
60/// Repeated calls are no-ops once the organ is loaded.
61pub fn install_logic_lib(cx: &mut Cx) -> Result<()> {
62    sim_lib_core::install_once(cx, &LogicLib).map(|_| ())
63}
64
65/// Resolves `goal` against the installed logic database and returns the result.
66///
67/// Installs the logic organ if needed, then resolves under the stored
68/// [`LogicConfig`](crate::LogicConfig) (optionally overriding the answer limit
69/// and stream buffer). When `stream` is true the result is a logic answer
70/// stream object; otherwise it is the first answer as a value, or nil when the
71/// goal fails.
72pub 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    let state = logic_config_state(cx)?;
186    let mut config = state.lock()?.clone();
187    if !args.len().is_multiple_of(2) {
188        return Err(logic_eval_error(
189            "logic/config options must be key/value pairs",
190        ));
191    }
192    for pair in args.chunks(2) {
193        let key = keyword(&pair[0])?;
194        match key.as_str() {
195            "max-depth" => config.limits.max_depth = usize_from_expr(cx, &pair[1])?,
196            "stream-buffer" => config.stream_buffer = usize_from_expr(cx, &pair[1])?,
197            "answer-limit" => config.limits.max_answers = Some(usize_from_expr(cx, &pair[1])?),
198            "strategy" => {
199                let symbol = symbol_expr(cx, &pair[1])?;
200                config.strategy = SearchStrategy::from_symbol(&symbol)
201                    .ok_or_else(|| logic_eval_error(format!("unsupported strategy {symbol}")))?;
202            }
203            other => {
204                return Err(logic_eval_error(format!(
205                    "logic/config does not support :{other}"
206                )));
207            }
208        }
209    }
210    *state.lock()? = config.clone();
211    config_value(cx, &config)
212}
213
214fn logic_assert_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
215    cx.require(&logic_db_write_capability())?;
216    let [expr] = args else {
217        return Err(logic_eval_error("logic/assert! expects one quoted clause"));
218    };
219    let clause_expr = unquote(expr);
220    logic_db_state(cx)?
221        .lock()?
222        .assert_clause_expr(clause_expr)?;
223    cx.factory().bool(true)
224}
225
226fn logic_retract_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
227    cx.require(&logic_db_write_capability())?;
228    let [expr] = args else {
229        return Err(logic_eval_error("logic/retract! expects one quoted clause"));
230    };
231    let removed = logic_db_state(cx)?
232        .lock()?
233        .retract_clause_expr(&unquote(expr))?;
234    cx.factory().bool(removed)
235}
236
237fn logic_facts_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
238    let [expr] = args else {
239        return Err(logic_eval_error("logic/facts expects one predicate symbol"));
240    };
241    let predicate = symbol_expr(cx, expr)?;
242    let facts = logic_db_state(cx)?.lock()?.facts(&predicate);
243    cx.factory().list(
244        facts
245            .into_iter()
246            .map(|expr| cx.factory().expr(expr))
247            .collect::<Result<Vec<_>>>()?,
248    )
249}
250
251fn logic_consult_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
252    cx.require(&logic_db_write_capability())?;
253    let [path_expr] = args else {
254        return Err(logic_eval_error("logic/consult expects one path"));
255    };
256    let path = string_expr(cx, path_expr)?;
257    let state = logic_db_state(cx)?;
258    let mut db = state.lock()?;
259    let count = consult_path(cx, &mut db, &path)?;
260    drop(db);
261    cx.factory().string(count.to_string())
262}
263
264fn logic_consult_bang_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
265    cx.require(&logic_db_write_capability())?;
266    let [expr] = args else {
267        return Err(logic_eval_error(
268            "logic/consult! expects quoted clause data",
269        ));
270    };
271    let state = logic_db_state(cx)?;
272    let mut db = state.lock()?;
273    let count = consult_expr(&mut db, unquote(expr))?;
274    drop(db);
275    cx.factory().string(count.to_string())
276}
277
278fn logic_query_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
279    let [goal, rest @ ..] = args else {
280        return Err(logic_eval_error("query expects a goal"));
281    };
282    let config = query_config(cx, rest)?;
283    let goal = unquote(goal);
284    let db = logic_db_state(cx)?.lock()?.clone();
285    let stream = query(cx, &db, &config, goal)?;
286    cx.factory().opaque(Arc::new(stream))
287}
288
289fn logic_query_one_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
290    let [goal, rest @ ..] = args else {
291        return Err(logic_eval_error("query/one expects a goal"));
292    };
293    let config = query_config(cx, rest)?;
294    let db = logic_db_state(cx)?.lock()?.clone();
295    match query_one(cx, &db, &config, unquote(goal))? {
296        Some(matched) => sim_kernel::shape_match_value(cx, matched),
297        None => cx.factory().nil(),
298    }
299}
300
301fn logic_query_all_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
302    let [goal, rest @ ..] = args else {
303        return Err(logic_eval_error("query/all expects a goal"));
304    };
305    let config = query_config(cx, rest)?;
306    let db = logic_db_state(cx)?.lock()?.clone();
307    let answers = query_all(cx, &db, &config, unquote(goal), config.limits.max_answers)?;
308    let mut values = Vec::with_capacity(answers.len());
309    for matched in answers {
310        values.push(sim_kernel::shape_match_value(cx, matched)?);
311    }
312    cx.factory().list(values)
313}
314
315fn logic_query_bool_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
316    let [goal, rest @ ..] = args else {
317        return Err(logic_eval_error("query? expects a goal"));
318    };
319    let config = query_config(cx, rest)?;
320    let db = logic_db_state(cx)?.lock()?.clone();
321    let accepted = query_bool(cx, &db, &config, unquote(goal))?;
322    cx.factory().bool(accepted)
323}
324
325fn logic_predicate_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
326    let [expr] = args else {
327        return Err(logic_eval_error("predicate? expects a predicate symbol"));
328    };
329    let predicate = symbol_expr(cx, expr)?;
330    let exists = logic_db_state(cx)?.lock()?.predicate_exists(&predicate);
331    cx.factory().bool(exists)
332}
333
334fn logic_stream_next_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
335    let [stream_expr] = args else {
336        return Err(logic_eval_error("logic/stream-next expects a stream"));
337    };
338    let stream = cx.eval_expr(stream_expr.clone())?;
339    match sim_kernel::Stream::next(require_logic_stream(&stream)?, cx)? {
340        Some(value) => Ok(value),
341        None => cx.factory().nil(),
342    }
343}
344
345fn logic_stream_close_fn(cx: &mut Cx, args: &[Expr]) -> Result<Value> {
346    let [stream_expr] = args else {
347        return Err(logic_eval_error("logic/stream-close expects a stream"));
348    };
349    let stream = cx.eval_expr(stream_expr.clone())?;
350    sim_kernel::Stream::close(require_logic_stream(&stream)?, cx)?;
351    cx.factory().nil()
352}