1use crate::equivalence::EquivalenceMapper;
4use crate::normalizer::NormalizationConfig;
5
6use lnmp_core::profile::{LnmpProfile, StrictDeterministicConfig};
7use lnmp_core::StructuralLimits;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum ParsingMode {
12 Strict,
14 #[default]
16 Loose,
17}
18
19impl From<LnmpProfile> for ParsingMode {
20 fn from(profile: LnmpProfile) -> Self {
21 match profile {
22 LnmpProfile::Loose => ParsingMode::Loose,
23 LnmpProfile::Standard => ParsingMode::Loose, LnmpProfile::Strict => ParsingMode::Strict,
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum TextInputMode {
34 Strict,
36 Lenient,
38}
39
40#[derive(Debug, Clone)]
42pub struct ParserConfig {
43 pub mode: ParsingMode,
45 pub validate_checksums: bool,
47 pub normalize_values: bool,
49 pub require_checksums: bool,
51 pub max_nesting_depth: Option<usize>,
53 pub text_input_mode: TextInputMode,
55 pub structural_limits: Option<StructuralLimits>,
57 pub semantic_dictionary: Option<lnmp_sfe::SemanticDictionary>,
59 pub profile_config: Option<StrictDeterministicConfig>,
61}
62
63impl Default for ParserConfig {
64 fn default() -> Self {
65 Self {
66 mode: ParsingMode::Loose,
67 validate_checksums: false,
68 normalize_values: true,
69 require_checksums: false,
70 max_nesting_depth: None,
71 text_input_mode: TextInputMode::Strict,
72 structural_limits: None,
73 semantic_dictionary: None,
74 profile_config: None, }
76 }
77}
78
79impl ParserConfig {
80 pub fn from_profile(profile: LnmpProfile) -> Self {
82 let config = profile.config();
83 Self {
84 mode: profile.into(),
85 validate_checksums: config.require_type_hints, normalize_values: !config.canonical_boolean, require_checksums: false, max_nesting_depth: None,
89 text_input_mode: TextInputMode::Strict,
90 structural_limits: None,
91 semantic_dictionary: None,
92 profile_config: Some(config),
93 }
94 }
95
96 pub fn with_profile_config(mut self, config: StrictDeterministicConfig) -> Self {
98 self.profile_config = Some(config.clone());
99 if config.reject_unsorted_fields {
101 self.mode = ParsingMode::Strict;
102 }
103 self
104 }
105
106 pub fn with_structural_limits(mut self, limits: StructuralLimits) -> Self {
108 self.structural_limits = Some(limits);
109 self
110 }
111
112 pub fn with_semantic_dictionary(mut self, dict: lnmp_sfe::SemanticDictionary) -> Self {
114 self.semantic_dictionary = Some(dict);
115 self
116 }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
121pub struct PromptOptimizationConfig {
122 pub minimize_symbols: bool,
124 pub align_token_boundaries: bool,
126 pub optimize_arrays: bool,
128}
129
130#[derive(Debug, Clone)]
134pub struct EncoderConfig {
135 pub include_type_hints: bool,
137 pub canonical: bool,
139 pub enable_checksums: bool,
141 pub enable_explain_mode: bool,
143 pub prompt_optimization: PromptOptimizationConfig,
145 pub normalization_config: NormalizationConfig,
147 pub equivalence_mapper: Option<EquivalenceMapper>,
149 pub semantic_dictionary: Option<lnmp_sfe::SemanticDictionary>,
151}
152
153impl Default for EncoderConfig {
154 fn default() -> Self {
155 Self {
156 include_type_hints: false,
157 canonical: true,
158 enable_checksums: false,
159 enable_explain_mode: false,
160 prompt_optimization: PromptOptimizationConfig::default(),
161 normalization_config: NormalizationConfig::default(),
162 equivalence_mapper: None,
163 semantic_dictionary: None,
164 }
165 }
166}
167
168impl EncoderConfig {
169 pub fn new() -> Self {
171 Self::default()
172 }
173
174 pub fn with_checksums(mut self, enable: bool) -> Self {
176 self.enable_checksums = enable;
177 self
178 }
179
180 pub fn with_explain_mode(mut self, enable: bool) -> Self {
182 self.enable_explain_mode = enable;
183 self
184 }
185
186 pub fn with_prompt_optimization(mut self, config: PromptOptimizationConfig) -> Self {
188 self.prompt_optimization = config;
189 self
190 }
191
192 pub fn with_normalization(mut self, config: NormalizationConfig) -> Self {
194 self.normalization_config = config;
195 self
196 }
197
198 pub fn with_equivalence_mapper(mut self, mapper: EquivalenceMapper) -> Self {
200 self.equivalence_mapper = Some(mapper);
201 self
202 }
203
204 pub fn with_semantic_dictionary(mut self, dict: lnmp_sfe::SemanticDictionary) -> Self {
206 self.semantic_dictionary = Some(dict);
207 self
208 }
209
210 pub fn with_type_hints(mut self, enable: bool) -> Self {
212 self.include_type_hints = enable;
213 self
214 }
215
216 pub fn with_canonical(mut self, enable: bool) -> Self {
218 self.canonical = enable;
219 self
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_parsing_mode_default() {
229 assert_eq!(ParsingMode::default(), ParsingMode::Loose);
230 }
231
232 #[test]
233 fn test_encoder_config_default() {
234 let config = EncoderConfig::default();
235 assert!(!config.include_type_hints);
236 assert!(config.canonical);
237 assert!(!config.enable_checksums);
238 assert!(!config.enable_explain_mode);
239 assert!(!config.prompt_optimization.minimize_symbols);
240 assert!(!config.prompt_optimization.align_token_boundaries);
241 assert!(!config.prompt_optimization.optimize_arrays);
242 assert!(config.equivalence_mapper.is_none());
243 }
244
245 #[test]
246 fn test_parsing_mode_equality() {
247 assert_eq!(ParsingMode::Strict, ParsingMode::Strict);
248 assert_eq!(ParsingMode::Loose, ParsingMode::Loose);
249 assert_ne!(ParsingMode::Strict, ParsingMode::Loose);
250 }
251
252 #[test]
253 fn test_encoder_config_with_checksums() {
254 let config = EncoderConfig::new().with_checksums(true);
255 assert!(config.enable_checksums);
256 }
257
258 #[test]
259 fn test_encoder_config_with_explain_mode() {
260 let config = EncoderConfig::new().with_explain_mode(true);
261 assert!(config.enable_explain_mode);
262 }
263
264 #[test]
265 fn test_encoder_config_with_prompt_optimization() {
266 let prompt_opt = PromptOptimizationConfig {
267 minimize_symbols: true,
268 align_token_boundaries: true,
269 optimize_arrays: true,
270 };
271 let config = EncoderConfig::new().with_prompt_optimization(prompt_opt);
272 assert!(config.prompt_optimization.minimize_symbols);
273 assert!(config.prompt_optimization.align_token_boundaries);
274 assert!(config.prompt_optimization.optimize_arrays);
275 }
276
277 #[test]
278 fn test_encoder_config_with_normalization() {
279 use crate::normalizer::StringCaseRule;
280
281 let norm_config = NormalizationConfig {
282 string_case: StringCaseRule::Lower,
283 float_precision: Some(2),
284 remove_trailing_zeros: true,
285 semantic_dictionary: None,
286 };
287 let config = EncoderConfig::new().with_normalization(norm_config.clone());
288 assert_eq!(
289 config.normalization_config.string_case,
290 StringCaseRule::Lower
291 );
292 assert_eq!(config.normalization_config.float_precision, Some(2));
293 assert!(config.normalization_config.remove_trailing_zeros);
294 }
295
296 #[test]
297 fn test_encoder_config_with_equivalence_mapper() {
298 let mut mapper = EquivalenceMapper::new();
299 mapper.add_mapping(7, "yes".to_string(), "1".to_string());
300
301 let config = EncoderConfig::new().with_equivalence_mapper(mapper);
302 assert!(config.equivalence_mapper.is_some());
303
304 let mapper_ref = config.equivalence_mapper.as_ref().unwrap();
305 assert_eq!(mapper_ref.map(7, "yes"), Some("1".to_string()));
306 }
307
308 #[test]
309 fn test_encoder_config_with_type_hints() {
310 let config = EncoderConfig::new().with_type_hints(true);
311 assert!(config.include_type_hints);
312 }
313
314 #[test]
315 fn test_encoder_config_with_canonical() {
316 let config = EncoderConfig::new().with_canonical(false);
317 assert!(!config.canonical);
318 }
319
320 #[test]
321 fn test_encoder_config_builder_chain() {
322 let mut mapper = EquivalenceMapper::new();
323 mapper.add_mapping(7, "yes".to_string(), "1".to_string());
324
325 let config = EncoderConfig::new()
326 .with_checksums(true)
327 .with_explain_mode(true)
328 .with_type_hints(true)
329 .with_equivalence_mapper(mapper);
330
331 assert!(config.enable_checksums);
332 assert!(config.enable_explain_mode);
333 assert!(config.include_type_hints);
334 assert!(config.equivalence_mapper.is_some());
335 }
336
337 #[test]
338 fn test_prompt_optimization_config_default() {
339 let config = PromptOptimizationConfig::default();
340 assert!(!config.minimize_symbols);
341 assert!(!config.align_token_boundaries);
342 assert!(!config.optimize_arrays);
343 }
344
345 #[test]
346 fn test_parser_config_default() {
347 let config = ParserConfig::default();
348 assert_eq!(config.mode, ParsingMode::Loose);
349 assert!(!config.validate_checksums);
350 assert!(!config.require_checksums);
351 assert!(config.max_nesting_depth.is_none());
352 assert_eq!(config.text_input_mode, TextInputMode::Strict);
353 assert!(config.structural_limits.is_none());
354 }
355
356 #[test]
357 fn test_parser_config_with_checksum_validation() {
358 let config = ParserConfig {
359 mode: ParsingMode::Strict,
360 validate_checksums: true,
361 normalize_values: false,
362 require_checksums: false,
363 max_nesting_depth: None,
364 text_input_mode: TextInputMode::Strict,
365 structural_limits: None,
366 semantic_dictionary: None,
367 profile_config: None,
368 };
369 assert_eq!(config.mode, ParsingMode::Strict);
370 assert!(config.validate_checksums);
371 assert!(!config.require_checksums);
372 }
373
374 #[test]
375 fn test_parser_config_with_required_checksums() {
376 let config = ParserConfig {
377 mode: ParsingMode::Strict,
378 validate_checksums: true,
379 normalize_values: false,
380 require_checksums: true,
381 max_nesting_depth: None,
382 text_input_mode: TextInputMode::Strict,
383 structural_limits: None,
384 semantic_dictionary: None,
385 profile_config: None,
386 };
387 assert!(config.validate_checksums);
388 assert!(config.require_checksums);
389 }
390
391 #[test]
392 fn test_parser_config_with_structural_limits() {
393 let limits = StructuralLimits {
394 max_fields: 2,
395 ..Default::default()
396 };
397 let config = ParserConfig::default().with_structural_limits(limits.clone());
398 assert_eq!(
399 config.structural_limits.unwrap().max_fields,
400 limits.max_fields
401 );
402 }
403}