Skip to main content

lemma/
lib.rs

1//! # Lemma Engine
2//!
3//! **Rules for man and machine**
4//!
5//! Consumer API: **`load`** → **`list`** → **`show`** / **`source`** → **`run`**.
6
7#[cfg(test)]
8mod tests;
9
10pub mod api;
11pub(crate) mod computation;
12pub mod documentation;
13pub(crate) mod engine;
14pub(crate) mod error;
15pub(crate) mod evaluation;
16pub(crate) mod formatting;
17pub(crate) mod limits;
18pub(crate) mod literals;
19pub(crate) mod parsing;
20pub(crate) mod planning;
21pub mod quality;
22pub(crate) mod registry;
23pub mod result_value;
24pub(crate) mod snapshot;
25pub(crate) mod spec_set_id;
26pub(crate) mod stdlib;
27pub(crate) mod string_distance;
28
29#[cfg(not(target_arch = "wasm32"))]
30pub mod deps;
31
32#[cfg(not(target_arch = "wasm32"))]
33pub mod mcp;
34
35#[cfg(target_arch = "wasm32")]
36pub mod wasm;
37
38pub use computation::{OperationResult, VetoType};
39pub use engine::{
40    resolve_effective, Engine, Errors, ListedSpec, ResolvedRepository, EMBEDDED_STDLIB_REPOSITORY,
41};
42pub use error::{
43    EngineError, EngineErrorSource, Error, ErrorDetails, ErrorKind, RegistryErrorKind,
44    RequestErrorKind,
45};
46pub use evaluation::explanations::{format_explanation, Cause, Explanation, ExplanationNode};
47pub use evaluation::response::{Response, RuleResult};
48pub use evaluation::run_data::{
49    parse_run_data_object, resolve_run_rules, run_data_value_from_json_value, RunDataValue,
50};
51pub use formatting::{format_parse_result, format_source, format_specs};
52pub use limits::{
53    ResourceLimits, MAX_DATA_NAME_LENGTH, MAX_RULE_NAME_LENGTH, MAX_SPEC_NAME_LENGTH,
54};
55pub use literals::{DateGranularity, MeasureUnit, MeasureUnits, RatioUnit, RatioUnits};
56pub use parsing::ast::DateTimeValue;
57pub use parsing::source::SourceType;
58pub use planning::execution_plan::type_detail_lines;
59pub use planning::execution_plan::{
60    Show, ShowBranch, ShowConversionTarget, ShowData, ShowExpression, ShowRule, ShowVersion,
61};
62pub use planning::explanation::{ConversionTraceRole, SerializedConversionTraceStep};
63pub use planning::semantics::{
64    DataPath, LemmaType, LiteralValue, PathSegment, TypeSpecification, ValueKind,
65};
66pub use result_value::{CalendarResult, RangeResult, RuleResultValue, RuleResultValueFailure};
67pub use spec_set_id::parse_spec_set_id;
68pub use stdlib::UNITS_LEMMA;
69
70/// Exact rational helpers for in-tree integration tests. Not a supported consumer API.
71#[doc(hidden)]
72pub mod __test_support {
73    pub use crate::computation::rational::{
74        checked_div, checked_mul, decimal_to_rational, rational_new,
75    };
76    pub use crate::literals::TimeValue;
77    pub use crate::planning::semantics::{
78        SemanticDateTime, SemanticTime, SemanticTimezone, TypedLiteral,
79    };
80
81    /// Serializes an [`Error`](crate::Error) as [`EngineError`](crate::EngineError).
82    pub fn current_binding_error_json(error: &crate::Error) -> serde_json::Value {
83        serde_json::to_value(crate::EngineError::from(error))
84            .expect("BUG: EngineError must serialize")
85    }
86
87    #[cfg(not(target_arch = "wasm32"))]
88    pub use fixture_transport::FixtureTransport;
89
90    #[cfg(not(target_arch = "wasm32"))]
91    mod fixture_transport {
92        use crate::registry::{Fetch, Header, HttpResponse, HttpTransport, TransportFailure};
93        use std::collections::HashMap;
94        use std::path::{Path, PathBuf};
95
96        /// Offline [`HttpTransport`] over bundled (or custom) LemmaBase fixture files.
97        ///
98        /// Panics if asked for a URL that is not a LemmaBase source URL.
99        pub struct FixtureTransport {
100            fixtures: HashMap<String, String>,
101        }
102
103        impl FixtureTransport {
104            pub fn new(dir: impl AsRef<Path>) -> Self {
105                let dir = dir.as_ref();
106                let mut fixtures = HashMap::new();
107                collect_fixture_files(dir, dir, &mut fixtures);
108                Self { fixtures }
109            }
110
111            pub fn bundled() -> Self {
112                Self::new(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/registry_fixtures"))
113            }
114        }
115
116        impl HttpTransport for FixtureTransport {
117            fn get(&self, fetch: &Fetch) -> Result<HttpResponse, TransportFailure> {
118                match self.fixtures.get(&fetch.repository) {
119                    Some(body) => Ok(HttpResponse {
120                        status: 200,
121                        headers: Vec::<Header>::new(),
122                        body: body.clone(),
123                    }),
124                    None => Ok(HttpResponse {
125                        status: 404,
126                        headers: Vec::new(),
127                        body: String::new(),
128                    }),
129                }
130            }
131        }
132
133        fn collect_fixture_files(dir: &Path, base: &Path, fixtures: &mut HashMap<String, String>) {
134            let entries = std::fs::read_dir(dir)
135                .unwrap_or_else(|e| panic!("BUG: read fixture dir {}: {e}", dir.display()));
136            for entry in entries {
137                let entry = entry
138                    .unwrap_or_else(|e| panic!("BUG: fixture dir entry in {}: {e}", dir.display()));
139                let path = entry.path();
140                if path.is_dir() {
141                    collect_fixture_files(&path, base, fixtures);
142                    continue;
143                }
144                if path.extension().is_none_or(|e| e != "lemma") {
145                    continue;
146                }
147                let relative = path.strip_prefix(base).unwrap_or_else(|_| {
148                    panic!("BUG: fixture path not under base: {}", path.display())
149                });
150                let identifier = relative
151                    .with_extension("")
152                    .to_string_lossy()
153                    .replace('\\', "/");
154                let content = std::fs::read_to_string(&path)
155                    .unwrap_or_else(|e| panic!("BUG: read fixture {}: {e}", path.display()));
156                fixtures.insert(identifier, content);
157            }
158        }
159    }
160}
161
162// Tier 1 — language surface (always)
163pub use parsing::ast::{
164    try_parse_type_constraint_command, DataValue, Span, SpecRef, TimezoneValue,
165};
166pub use parsing::lexer::{Lexer, TokenKind};
167pub use parsing::source::Source;
168pub use parsing::{parse, ParseResult};
169pub use quality::Recommendation;
170
171// Tier 2 — registry resolution (sans-IO; hosts supply HttpTransport)
172pub use engine::Context;
173pub use parsing::ast::{LemmaRepository, LemmaSpec, RepositoryQualifier};
174pub use planning::LemmaSpecSet;
175pub use registry::{
176    Fetch, Header, HttpResponse, HttpTransport, Install, InstallStep, LemmaBase, Registries,
177    Registry, RegistryBundle, RegistryError, RepositoryInstallResult, Resolve, ResolveStep,
178    TransportFailure,
179};