Skip to main content

sim_lib_logic/
codec.rs

1use sim_kernel::{Cx, Error, Expr, Result, Symbol, Value};
2
3use crate::db::LogicDb;
4
5/// Consults clauses from a table/dir-backed relative path.
6///
7/// Higher-level language surfaces use this helper so host reads stay on the
8/// shared Table/Dir authority path.
9pub fn consult_table_path(
10    cx: &mut Cx,
11    db: &mut LogicDb,
12    source: &Value,
13    path: &str,
14) -> Result<usize> {
15    let expr = read_table_path_expr(cx, source, path)?;
16    consult_expr(db, expr)
17}
18
19pub(crate) fn consult_expr(db: &mut LogicDb, expr: Expr) -> Result<usize> {
20    match expr {
21        Expr::List(items) => {
22            let mut count = 0usize;
23            for item in items {
24                db.assert_clause_expr(item)?;
25                count += 1;
26            }
27            Ok(count)
28        }
29        other => {
30            db.assert_clause_expr(other)?;
31            Ok(1)
32        }
33    }
34}
35
36fn read_table_path_expr(cx: &mut Cx, source: &Value, path: &str) -> Result<Expr> {
37    let segments = relative_table_path(path)?;
38    let (leaf, parents) = segments
39        .split_last()
40        .ok_or_else(|| Error::Eval("consult path must not be empty".to_owned()))?;
41    let mut current = source.clone();
42    for segment in parents {
43        let dir = current.object().as_dir().ok_or_else(|| {
44            Error::Eval(format!(
45                "consult source does not expose directory segment {segment}"
46            ))
47        })?;
48        current = dir
49            .opendir(cx, Symbol::new(*segment))?
50            .ok_or_else(|| Error::Eval(format!("consult source does not contain {path}")))?;
51    }
52    let table = current
53        .object()
54        .as_table_impl()
55        .ok_or_else(|| Error::Eval("consult source does not implement Table".to_owned()))?;
56    let key = Symbol::new(*leaf);
57    if let Some(dir) = current.object().as_dir()
58        && dir.is_dir(cx, key.clone())?
59    {
60        return Err(Error::Eval(format!(
61            "consult source path {path} is a directory"
62        )));
63    }
64    if !table.has(cx, key.clone())? {
65        return Err(Error::Eval(format!(
66            "consult source does not contain {path}"
67        )));
68    }
69    table.get(cx, key)?.object().as_expr(cx)
70}
71
72fn relative_table_path(path: &str) -> Result<Vec<&str>> {
73    if path.is_empty() || path.starts_with('/') || path.ends_with('/') || path.contains('\\') {
74        return Err(Error::Eval(format!(
75            "consult path must be a non-empty relative table path: {path}"
76        )));
77    }
78    let segments: Vec<_> = path.split('/').collect();
79    if segments
80        .iter()
81        .any(|segment| segment.is_empty() || *segment == "." || *segment == "..")
82    {
83        return Err(Error::Eval(format!(
84            "consult path must be a non-empty relative table path: {path}"
85        )));
86    }
87    Ok(segments)
88}