Skip to main content

lace_compiler/
lib.rs

1//! Compile self-contained Interlang into ordinary canonical exchange policies.
2
3use std::fmt;
4
5mod interlang;
6
7pub use lace_sans_io::logic::InterlaceModule;
8use lace_sans_io::logic::prepare_facet_constraints;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CompilationErrorKind {
12    Policy,
13    LimitExceeded,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct CompilationError {
18    pub kind: CompilationErrorKind,
19    pub message: String,
20}
21impl CompilationError {
22    pub fn is_limit_exceeded(&self) -> bool {
23        self.kind == CompilationErrorKind::LimitExceeded
24    }
25    pub fn limit_exceeded(message: impl Into<String>) -> Self {
26        Self {
27            kind: CompilationErrorKind::LimitExceeded,
28            message: message.into(),
29        }
30    }
31}
32impl From<lace_sans_io::logic::PreparationError> for CompilationError {
33    fn from(error: lace_sans_io::logic::PreparationError) -> Self {
34        Self {
35            kind: if error.is_limit_exceeded() {
36                CompilationErrorKind::LimitExceeded
37            } else {
38                CompilationErrorKind::Policy
39            },
40            message: error.to_string(),
41        }
42    }
43}
44impl From<String> for CompilationError {
45    fn from(value: String) -> Self {
46        Self {
47            kind: CompilationErrorKind::Policy,
48            message: value,
49        }
50    }
51}
52impl From<&str> for CompilationError {
53    fn from(value: &str) -> Self {
54        Self::from(value.to_owned())
55    }
56}
57impl fmt::Display for CompilationError {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        self.message.fmt(f)
60    }
61}
62impl std::error::Error for CompilationError {}
63impl From<CompilationError> for lace_sans_io::runtime_values::Error {
64    fn from(error: CompilationError) -> Self {
65        let category = if error.is_limit_exceeded() {
66            lace_sans_io::core::ErrorCategory::LimitExceeded
67        } else {
68            lace_sans_io::core::ErrorCategory::Policy
69        };
70        Self::detailed(category, None, None, error.message)
71    }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct CompilationLimits {
76    /// Complete UTF-8 source bytes, before removing comments or blank lines.
77    pub max_source_bytes: usize,
78}
79impl Default for CompilationLimits {
80    fn default() -> Self {
81        Self {
82            max_source_bytes: 1 << 20,
83        }
84    }
85}
86
87/// Compile one self-contained Interlang contribution with the default source bound.
88/// Empty and helper-only contributions have no exchange facet and are rejected.
89pub fn compile(source: &str) -> Result<InterlaceModule, CompilationError> {
90    compile_with_limits(source, CompilationLimits::default())
91}
92
93/// Compile one complete source with an explicit source-byte bound. The fixed
94/// exchange rule, arity, and static TopK bounds apply to every emitted rule.
95/// Source loading and configuration remain application work, outside compilation.
96pub fn compile_with_limits(
97    source: &str,
98    limits: CompilationLimits,
99) -> Result<InterlaceModule, CompilationError> {
100    if source.len() > limits.max_source_bytes {
101        return Err(CompilationError::limit_exceeded(
102            "source byte limit exceeded",
103        ));
104    }
105    let canonical = interlang::compile_source(source).map_err(|error| match error {
106        interlang::InterlangError::Parse(error) => {
107            let location = error.line_col(source);
108            CompilationError::from(format!(
109                "{}:{}: {}",
110                location.line, location.column, error.message
111            ))
112        }
113        interlang::InterlangError::Compile(diagnostics) => {
114            let kind = if diagnostics
115                .iter()
116                .any(|d| d.kind == interlang::CompileDiagnosticKind::LimitExceeded)
117            {
118                CompilationErrorKind::LimitExceeded
119            } else {
120                CompilationErrorKind::Policy
121            };
122            let message = diagnostics
123                .iter()
124                .map(|diagnostic| {
125                    let location = diagnostic.line_col(source);
126                    format!(
127                        "{}:{}: {}",
128                        location.line, location.column, diagnostic.message
129                    )
130                })
131                .collect::<Vec<_>>()
132                .join("\n");
133            CompilationError { kind, message }
134        }
135    })?;
136    let module =
137        InterlaceModule::from_rules(canonical.program.strata.into_iter().flatten().collect())?;
138    Ok(prepare_facet_constraints(&[module], &[], &[])?)
139}