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};
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
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    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}