lnmp_codec/
config.rs

1//! Configuration types for LNMP parsing and encoding.
2
3use crate::equivalence::EquivalenceMapper;
4use crate::normalizer::NormalizationConfig;
5
6use lnmp_core::StructuralLimits;
7
8/// Parsing mode configuration
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum ParsingMode {
11    /// Strict mode: only accepts canonical LNMP format
12    Strict,
13    /// Loose mode: tolerates formatting variations (default)
14    #[default]
15    Loose,
16}
17
18// Default implementation derived via #[derive(Default)] on the enum
19
20/// Controls how text input is pre-processed before strict parsing.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TextInputMode {
23    /// Do not alter text, return errors on malformed inputs.
24    Strict,
25    /// Run text through the lenient sanitizer before strict parsing.
26    Lenient,
27}
28
29/// Parser configuration
30#[derive(Debug, Clone)]
31pub struct ParserConfig {
32    /// Parsing mode (strict or loose)
33    pub mode: ParsingMode,
34    /// Whether to validate checksums when present (v0.3 feature)
35    pub validate_checksums: bool,
36    /// Whether to normalize text values into numeric/boolean types when possible
37    pub normalize_values: bool,
38    /// Whether to require checksums on all fields (v0.3 feature)
39    pub require_checksums: bool,
40    /// Optional maximum nesting depth; if None, no limit is enforced
41    pub max_nesting_depth: Option<usize>,
42    /// How to handle incoming text before lexing/parsing
43    pub text_input_mode: TextInputMode,
44    /// Optional structural limits (depth/field counts/string lengths)
45    pub structural_limits: Option<StructuralLimits>,
46    /// Optional semantic dictionary for equivalence normalization
47    pub semantic_dictionary: Option<lnmp_sfe::SemanticDictionary>,
48}
49
50impl Default for ParserConfig {
51    fn default() -> Self {
52        Self {
53            mode: ParsingMode::Loose,
54            validate_checksums: false,
55            normalize_values: true,
56            require_checksums: false,
57            max_nesting_depth: None,
58            text_input_mode: TextInputMode::Strict,
59            structural_limits: None,
60            semantic_dictionary: None,
61        }
62    }
63}
64
65impl ParserConfig {
66    /// Applies structural limits to the parser.
67    pub fn with_structural_limits(mut self, limits: StructuralLimits) -> Self {
68        self.structural_limits = Some(limits);
69        self
70    }
71
72    /// Attaches a semantic dictionary for equivalence normalization.
73    pub fn with_semantic_dictionary(mut self, dict: lnmp_sfe::SemanticDictionary) -> Self {
74        self.semantic_dictionary = Some(dict);
75        self
76    }
77}
78
79/// Prompt optimization configuration for LLM-optimized encoding
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
81pub struct PromptOptimizationConfig {
82    /// Whether to minimize symbols for better tokenization
83    pub minimize_symbols: bool,
84    /// Whether to align with common tokenizer boundaries
85    pub align_token_boundaries: bool,
86    /// Whether to optimize array encoding
87    pub optimize_arrays: bool,
88}
89
90// Default implementation derived via #[derive(Default)] on the struct
91
92/// Encoder configuration
93#[derive(Debug, Clone)]
94pub struct EncoderConfig {
95    /// Whether to include type hints in output
96    pub include_type_hints: bool,
97    /// Whether to use canonical format (always true for v0.2)
98    pub canonical: bool,
99    /// Whether to append semantic checksums (v0.3 feature)
100    pub enable_checksums: bool,
101    /// Whether to enable explain mode with inline comments (v0.3 feature)
102    pub enable_explain_mode: bool,
103    /// Prompt optimization configuration (v0.3 feature)
104    pub prompt_optimization: PromptOptimizationConfig,
105    /// Value normalization configuration (v0.3 feature)
106    pub normalization_config: NormalizationConfig,
107    /// Semantic equivalence mapper (v0.3 feature)
108    pub equivalence_mapper: Option<EquivalenceMapper>,
109    /// Optional semantic dictionary for value normalization
110    pub semantic_dictionary: Option<lnmp_sfe::SemanticDictionary>,
111}
112
113impl Default for EncoderConfig {
114    fn default() -> Self {
115        Self {
116            include_type_hints: false,
117            canonical: true,
118            enable_checksums: false,
119            enable_explain_mode: false,
120            prompt_optimization: PromptOptimizationConfig::default(),
121            normalization_config: NormalizationConfig::default(),
122            equivalence_mapper: None,
123            semantic_dictionary: None,
124        }
125    }
126}
127
128impl EncoderConfig {
129    /// Creates a new encoder configuration with default settings
130    pub fn new() -> Self {
131        Self::default()
132    }
133
134    /// Enables semantic checksums
135    pub fn with_checksums(mut self, enable: bool) -> Self {
136        self.enable_checksums = enable;
137        self
138    }
139
140    /// Enables explain mode with inline comments
141    pub fn with_explain_mode(mut self, enable: bool) -> Self {
142        self.enable_explain_mode = enable;
143        self
144    }
145
146    /// Sets prompt optimization configuration
147    pub fn with_prompt_optimization(mut self, config: PromptOptimizationConfig) -> Self {
148        self.prompt_optimization = config;
149        self
150    }
151
152    /// Sets value normalization configuration
153    pub fn with_normalization(mut self, config: NormalizationConfig) -> Self {
154        self.normalization_config = config;
155        self
156    }
157
158    /// Sets semantic equivalence mapper
159    pub fn with_equivalence_mapper(mut self, mapper: EquivalenceMapper) -> Self {
160        self.equivalence_mapper = Some(mapper);
161        self
162    }
163
164    /// Attaches a semantic dictionary for normalization.
165    pub fn with_semantic_dictionary(mut self, dict: lnmp_sfe::SemanticDictionary) -> Self {
166        self.semantic_dictionary = Some(dict);
167        self
168    }
169
170    /// Enables type hints in output
171    pub fn with_type_hints(mut self, enable: bool) -> Self {
172        self.include_type_hints = enable;
173        self
174    }
175
176    /// Sets canonical format mode
177    pub fn with_canonical(mut self, enable: bool) -> Self {
178        self.canonical = enable;
179        self
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_parsing_mode_default() {
189        assert_eq!(ParsingMode::default(), ParsingMode::Loose);
190    }
191
192    #[test]
193    fn test_encoder_config_default() {
194        let config = EncoderConfig::default();
195        assert!(!config.include_type_hints);
196        assert!(config.canonical);
197        assert!(!config.enable_checksums);
198        assert!(!config.enable_explain_mode);
199        assert!(!config.prompt_optimization.minimize_symbols);
200        assert!(!config.prompt_optimization.align_token_boundaries);
201        assert!(!config.prompt_optimization.optimize_arrays);
202        assert!(config.equivalence_mapper.is_none());
203    }
204
205    #[test]
206    fn test_parsing_mode_equality() {
207        assert_eq!(ParsingMode::Strict, ParsingMode::Strict);
208        assert_eq!(ParsingMode::Loose, ParsingMode::Loose);
209        assert_ne!(ParsingMode::Strict, ParsingMode::Loose);
210    }
211
212    #[test]
213    fn test_encoder_config_with_checksums() {
214        let config = EncoderConfig::new().with_checksums(true);
215        assert!(config.enable_checksums);
216    }
217
218    #[test]
219    fn test_encoder_config_with_explain_mode() {
220        let config = EncoderConfig::new().with_explain_mode(true);
221        assert!(config.enable_explain_mode);
222    }
223
224    #[test]
225    fn test_encoder_config_with_prompt_optimization() {
226        let prompt_opt = PromptOptimizationConfig {
227            minimize_symbols: true,
228            align_token_boundaries: true,
229            optimize_arrays: true,
230        };
231        let config = EncoderConfig::new().with_prompt_optimization(prompt_opt);
232        assert!(config.prompt_optimization.minimize_symbols);
233        assert!(config.prompt_optimization.align_token_boundaries);
234        assert!(config.prompt_optimization.optimize_arrays);
235    }
236
237    #[test]
238    fn test_encoder_config_with_normalization() {
239        use crate::normalizer::StringCaseRule;
240
241        let norm_config = NormalizationConfig {
242            string_case: StringCaseRule::Lower,
243            float_precision: Some(2),
244            remove_trailing_zeros: true,
245            semantic_dictionary: None,
246        };
247        let config = EncoderConfig::new().with_normalization(norm_config.clone());
248        assert_eq!(
249            config.normalization_config.string_case,
250            StringCaseRule::Lower
251        );
252        assert_eq!(config.normalization_config.float_precision, Some(2));
253        assert!(config.normalization_config.remove_trailing_zeros);
254    }
255
256    #[test]
257    fn test_encoder_config_with_equivalence_mapper() {
258        let mut mapper = EquivalenceMapper::new();
259        mapper.add_mapping(7, "yes".to_string(), "1".to_string());
260
261        let config = EncoderConfig::new().with_equivalence_mapper(mapper);
262        assert!(config.equivalence_mapper.is_some());
263
264        let mapper_ref = config.equivalence_mapper.as_ref().unwrap();
265        assert_eq!(mapper_ref.map(7, "yes"), Some("1".to_string()));
266    }
267
268    #[test]
269    fn test_encoder_config_with_type_hints() {
270        let config = EncoderConfig::new().with_type_hints(true);
271        assert!(config.include_type_hints);
272    }
273
274    #[test]
275    fn test_encoder_config_with_canonical() {
276        let config = EncoderConfig::new().with_canonical(false);
277        assert!(!config.canonical);
278    }
279
280    #[test]
281    fn test_encoder_config_builder_chain() {
282        let mut mapper = EquivalenceMapper::new();
283        mapper.add_mapping(7, "yes".to_string(), "1".to_string());
284
285        let config = EncoderConfig::new()
286            .with_checksums(true)
287            .with_explain_mode(true)
288            .with_type_hints(true)
289            .with_equivalence_mapper(mapper);
290
291        assert!(config.enable_checksums);
292        assert!(config.enable_explain_mode);
293        assert!(config.include_type_hints);
294        assert!(config.equivalence_mapper.is_some());
295    }
296
297    #[test]
298    fn test_prompt_optimization_config_default() {
299        let config = PromptOptimizationConfig::default();
300        assert!(!config.minimize_symbols);
301        assert!(!config.align_token_boundaries);
302        assert!(!config.optimize_arrays);
303    }
304
305    #[test]
306    fn test_parser_config_default() {
307        let config = ParserConfig::default();
308        assert_eq!(config.mode, ParsingMode::Loose);
309        assert!(!config.validate_checksums);
310        assert!(!config.require_checksums);
311        assert!(config.max_nesting_depth.is_none());
312        assert_eq!(config.text_input_mode, TextInputMode::Strict);
313        assert!(config.structural_limits.is_none());
314    }
315
316    #[test]
317    fn test_parser_config_with_checksum_validation() {
318        let config = ParserConfig {
319            mode: ParsingMode::Strict,
320            validate_checksums: true,
321            normalize_values: false,
322            require_checksums: false,
323            max_nesting_depth: None,
324            text_input_mode: TextInputMode::Strict,
325            structural_limits: None,
326            semantic_dictionary: None,
327        };
328        assert_eq!(config.mode, ParsingMode::Strict);
329        assert!(config.validate_checksums);
330        assert!(!config.require_checksums);
331    }
332
333    #[test]
334    fn test_parser_config_with_required_checksums() {
335        let config = ParserConfig {
336            mode: ParsingMode::Strict,
337            validate_checksums: true,
338            normalize_values: false,
339            require_checksums: true,
340            max_nesting_depth: None,
341            text_input_mode: TextInputMode::Strict,
342            structural_limits: None,
343            semantic_dictionary: None,
344        };
345        assert!(config.validate_checksums);
346        assert!(config.require_checksums);
347    }
348
349    #[test]
350    fn test_parser_config_with_structural_limits() {
351        let limits = StructuralLimits {
352            max_fields: 2,
353            ..Default::default()
354        };
355        let config = ParserConfig::default().with_structural_limits(limits.clone());
356        assert_eq!(
357            config.structural_limits.unwrap().max_fields,
358            limits.max_fields
359        );
360    }
361}