Skip to main content

lemma/
lib.rs

1//! # Lemma Engine
2//!
3//! **Rules for man and machine**
4//!
5//! Consumer API: **`load`** → **`specs`** → **`show`** / **`source`** → **`evaluate`**.
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 spec_set_id;
25pub(crate) mod stdlib;
26pub(crate) mod string_distance;
27
28#[cfg(not(target_arch = "wasm32"))]
29pub mod deps;
30
31#[cfg(not(target_arch = "wasm32"))]
32pub mod mcp;
33
34#[cfg(target_arch = "wasm32")]
35pub mod wasm;
36
37pub use api::{
38    evaluate_data_value_from_json_value, parse_evaluate_data_object, resolve_evaluate_rules,
39    show_document,
40};
41pub use computation::arithmetic::{
42    arithmetic_operation, number_arithmetic, NumberArithmeticFailure, SignatureFactor,
43    SignatureIndex,
44};
45pub use computation::comparison::comparison_operation;
46pub use computation::rational::{
47    checked_div, checked_mul, decimal_to_rational, rational_new, NumericFailure, RationalInteger,
48};
49pub use computation::units::convert_unit_operand;
50pub use computation::{OperationResult, VetoType};
51pub use engine::{
52    format_repositories, resolve_effective, Engine, Errors, Repository, SpecDependency,
53    SpecVersion, EMBEDDED_STDLIB_REPOSITORY,
54};
55pub use error::{
56    EngineError, EngineErrorSource, Error, ErrorDetails, ErrorKind, RegistryErrorKind,
57    RequestErrorKind,
58};
59pub use evaluation::branch_semantics::{
60    condition_outcome, piecewise_decision, BranchOutcome, PiecewiseDecision,
61};
62pub use evaluation::evaluate_data::{
63    attribute_evaluate_data_error, evaluate_data_from_entries, evaluate_data_map_from_entries,
64    evaluate_data_null_list_item, parse_data_value, EvaluateData, EvaluateDataValue,
65};
66pub use evaluation::explanations::{format_explanation, Cause, Explanation, ExplanationNode};
67pub use evaluation::response::{Response, RuleResult};
68pub use planning::explanation::ExplainedListOp;
69
70#[deprecated(note = "use EvaluateDataValue")]
71pub type RunDataValue = EvaluateDataValue;
72#[deprecated(note = "use evaluate_data_value_from_json_value")]
73pub use api::evaluate_data_value_from_json_value as run_data_value_from_json_value;
74#[deprecated(note = "use parse_evaluate_data_object")]
75pub use api::parse_evaluate_data_object as parse_run_data_object;
76#[deprecated(note = "use resolve_evaluate_rules")]
77pub use api::resolve_evaluate_rules as resolve_run_rules;
78#[deprecated(note = "use attribute_evaluate_data_error")]
79pub use evaluation::evaluate_data::attribute_evaluate_data_error as attribute_run_data_error;
80#[deprecated(note = "use evaluate_data_from_entries")]
81pub use evaluation::evaluate_data::evaluate_data_from_entries as run_data_from_entries;
82#[deprecated(note = "use evaluate_data_map_from_entries")]
83pub use evaluation::evaluate_data::evaluate_data_map_from_entries as run_data_map_from_entries;
84#[deprecated(note = "use evaluate_data_null_list_item")]
85pub use evaluation::evaluate_data::evaluate_data_null_list_item as run_data_null_list_item;
86pub use formatting::{format_parse_result, format_source, format_specs};
87pub use indexmap::IndexMap;
88pub use limits::{
89    ResourceLimits, MAX_DATA_NAME_LENGTH, MAX_RULE_NAME_LENGTH, MAX_SPEC_NAME_LENGTH,
90};
91pub use literals::{DateGranularity, MeasureUnit, MeasureUnits, RatioUnit, RatioUnits, UnitScale};
92pub use parsing::ast::{
93    ArithmeticComputation, CalendarPeriodUnit, ComparisonComputation, ConversionTypeKind,
94    DateCalendarKind, DateRelativeKind, ListPredicateForm, LogicalConnective,
95    MathematicalComputation, RangeBound, RoundingMode, VetoExpression,
96};
97pub use parsing::ast::{DateTimeValue, EffectiveDate};
98pub use parsing::source::SourceType;
99pub use planning::execution_plan::type_detail_lines;
100pub use planning::execution_plan::{
101    ExecutableRule, ExecutionPlan, Show, ShowBranch, ShowConversionTarget, ShowData,
102    ShowExpression, ShowRule,
103};
104pub use planning::explanation::{ConversionTraceRole, SerializedConversionTraceStep};
105pub use planning::explanation::{ExplainedRule, ExplainedRuleId};
106pub use planning::graph::ResolvedSpecTypes;
107pub use planning::normalize::{
108    ArithmeticTerm, DispatchRegion, LeafKind, NormalForm, NormalFormId, NormalFormKind,
109    NormalFormListOp, RowRead,
110};
111pub use planning::ordered_dispatch::{region_count, region_of_scrutinee, DispatchKey};
112pub use planning::semantics::{
113    negated_comparison, numbered_row_origin, primitive_number_arc, row_field_input_key,
114    BoundValueKind, DataDefinition, DataOrigin, DataPath, KeyField, LemmaType, ListKey,
115    ListPredKind, LiteralValue, PathSegment, ReferenceTarget, RowBinding, RowEnd, RowInstance,
116    RowOrigin, RulePath, SpecIdentity, SpecInstance, TypeFamily, TypeSpecification, TypedLiteral,
117    ValueKind,
118};
119pub use planning::unit_index::UnitIndex;
120pub use result_value::{
121    CalendarResult, RangeResult, RowReference, RowResult, RuleResultValue, RuleResultValueFailure,
122    SpecResult, SpecRuleResult,
123};
124pub use spec_set_id::parse_spec_set_id;
125pub use stdlib::UNITS_LEMMA;
126
127/// Checked-in JSON Schema for Show, Response, and related wire types
128/// (`engine/schemas/api.v1.json`). Regenerated by `cargo run -p xtask -- schema`.
129pub const API_V1_SCHEMA_JSON: &str = include_str!("../schemas/api.v1.json");
130
131/// Exact rational helpers for in-tree integration tests. Not a supported consumer API.
132#[doc(hidden)]
133pub mod __test_support {
134    pub use crate::literals::TimeValue;
135    pub use crate::planning::semantics::{
136        SemanticDateTime, SemanticTime, SemanticTimezone, TypedLiteral,
137    };
138    pub use crate::{checked_div, checked_mul, decimal_to_rational, rational_new};
139
140    /// Serializes an [`Error`](crate::Error) as [`EngineError`](crate::EngineError).
141    pub fn current_binding_error_json(error: &crate::Error) -> serde_json::Value {
142        serde_json::to_value(crate::EngineError::from(error))
143            .expect("BUG: EngineError must serialize")
144    }
145
146    #[cfg(not(target_arch = "wasm32"))]
147    pub use fixture_transport::FixtureTransport;
148
149    #[cfg(not(target_arch = "wasm32"))]
150    mod fixture_transport {
151        use crate::registry::{Fetch, Header, HttpResponse, HttpTransport, TransportFailure};
152        use std::collections::HashMap;
153        use std::path::{Path, PathBuf};
154
155        /// Offline [`HttpTransport`] over bundled (or custom) LemmaBase fixture files.
156        ///
157        /// Panics if asked for a URL that is not a LemmaBase source URL.
158        pub struct FixtureTransport {
159            fixtures: HashMap<String, String>,
160        }
161
162        impl FixtureTransport {
163            pub fn new(dir: impl AsRef<Path>) -> Self {
164                let dir = dir.as_ref();
165                let mut fixtures = HashMap::new();
166                collect_fixture_files(dir, dir, &mut fixtures);
167                Self { fixtures }
168            }
169
170            pub fn bundled() -> Self {
171                Self::new(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/registry_fixtures"))
172            }
173        }
174
175        impl HttpTransport for FixtureTransport {
176            fn get(&self, fetch: &Fetch) -> Result<HttpResponse, TransportFailure> {
177                match self.fixtures.get(&fetch.repository) {
178                    Some(body) => Ok(HttpResponse {
179                        status: 200,
180                        headers: Vec::<Header>::new(),
181                        body: body.clone(),
182                    }),
183                    None => Ok(HttpResponse {
184                        status: 404,
185                        headers: Vec::new(),
186                        body: String::new(),
187                    }),
188                }
189            }
190        }
191
192        fn read_fixture_dir(dir: &Path) -> std::fs::ReadDir {
193            std::fs::read_dir(dir)
194                .unwrap_or_else(|e| panic!("BUG: read fixture dir {}: {e}", dir.display()))
195        }
196
197        fn collect_fixture_files(dir: &Path, base: &Path, fixtures: &mut HashMap<String, String>) {
198            let mut stack = vec![(dir.to_path_buf(), read_fixture_dir(dir))];
199            while let Some((current_dir, entries)) = stack.last_mut() {
200                let Some(entry) = entries.next() else {
201                    stack.pop();
202                    continue;
203                };
204                let entry = entry.unwrap_or_else(|e| {
205                    panic!("BUG: fixture dir entry in {}: {e}", current_dir.display())
206                });
207                let path = entry.path();
208                if path.is_dir() {
209                    let subdir = read_fixture_dir(&path);
210                    stack.push((path, subdir));
211                    continue;
212                }
213                if path.extension().is_none_or(|e| e != "lemma") {
214                    continue;
215                }
216                let relative = path.strip_prefix(base).unwrap_or_else(|_| {
217                    panic!("BUG: fixture path not under base: {}", path.display())
218                });
219                let identifier = relative
220                    .with_extension("")
221                    .to_string_lossy()
222                    .replace('\\', "/");
223                let content = std::fs::read_to_string(&path)
224                    .unwrap_or_else(|e| panic!("BUG: read fixture {}: {e}", path.display()));
225                fixtures.insert(identifier, content);
226            }
227        }
228    }
229}
230
231// Tier 1 — language surface (always)
232pub use parsing::ast::{
233    try_parse_type_constraint_command, CommandArg, DataValue, LemmaData, ParentType, Span, SpecRef,
234    TimezoneValue, TypeConstraintCommand,
235};
236pub use parsing::lexer::{can_be_label, Lexer, TokenKind, COMPLETION_KEYWORD_LABELS};
237pub use parsing::source::Source;
238pub use parsing::{parse, ParseResult};
239pub use quality::Recommendation;
240
241// Tier 2 — registry resolution (sans-IO; hosts supply HttpTransport)
242pub use engine::Context;
243pub use parsing::ast::{LemmaRepository, LemmaSpec, RepositoryQualifier};
244pub use planning::LemmaSpecSet;
245pub use registry::{
246    Fetch, Header, HttpResponse, HttpTransport, Install, InstallStep, LemmaBase, Registries,
247    Registry, RegistryBundle, RegistryError, RepositoryInstallResult, Resolve, ResolveStep,
248    TransportFailure,
249};