1use crate::rosetta::RosettaStone;
9use chrono::Utc;
10use regex::Regex;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum ConversionTier {
17 Minimal,
18 Standard,
19 Full,
20}
21
22impl std::fmt::Display for ConversionTier {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 ConversionTier::Minimal => write!(f, "minimal"),
26 ConversionTier::Standard => write!(f, "standard"),
27 ConversionTier::Full => write!(f, "full"),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Default)]
34pub struct ConversionOptions {
35 pub tier: Option<ConversionTier>,
37 pub confidence_threshold: Option<f64>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct TokenStats {
44 pub input: usize,
45 pub output: usize,
46 pub ratio: f64,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ConversionResult {
52 pub output: String,
54 pub confidence: f64,
56 pub unmapped: Vec<String>,
58 pub tier: ConversionTier,
60 pub tokens: TokenStats,
62 #[serde(default)]
64 pub used_fallback: bool,
65}
66
67pub struct AispConverter;
71
72impl AispConverter {
73 pub fn convert(prose: &str, options: Option<ConversionOptions>) -> ConversionResult {
90 let opts = options.unwrap_or_default();
91 let tier = opts.tier.unwrap_or_else(|| Self::detect_tier(prose));
92
93 let result = match tier {
94 ConversionTier::Minimal => Self::convert_minimal(prose),
95 ConversionTier::Standard => Self::convert_standard(prose),
96 ConversionTier::Full => Self::convert_full(prose),
97 };
98
99 ConversionResult {
100 tokens: TokenStats {
101 input: prose.len(),
102 output: result.output.len(),
103 ratio: if prose.is_empty() {
104 0.0
105 } else {
106 (result.output.len() as f64 / prose.len() as f64 * 100.0).round() / 100.0
107 },
108 },
109 ..result
110 }
111 }
112
113 pub fn detect_tier(prose: &str) -> ConversionTier {
126 let word_count = prose.split_whitespace().count();
127
128 let types_regex =
129 Regex::new(r"(?i)\b(type|class|struct|interface|schema|model|entity)\b").unwrap();
130 let rules_regex = Regex::new(
131 r"(?i)\b(must|should|always|never|require|ensure|guarantee|constraint|rule)\b",
132 )
133 .unwrap();
134 let proof_regex =
135 Regex::new(r"(?i)\b(prove|verify|validate|certify|demonstrate|qed|proven)\b").unwrap();
136 let complex_regex =
137 Regex::new(r"(?i)\b(for all|there exists|if and only if|implies|therefore)\b").unwrap();
138 let api_regex =
139 Regex::new(r"(?i)\b(api|endpoint|route|controller|handler|service)\b").unwrap();
140 let contractor_regex =
141 Regex::new(r"(?i)\b(delta|invariant|precondition|postcondition|requires|ensures)\b")
142 .unwrap();
143 let intent_regex =
144 Regex::new(r"(?i)\b(intent|goal|purpose|objective|fitness|risk|utility)\b").unwrap();
145
146 let has_types = types_regex.is_match(prose);
147 let has_rules = rules_regex.is_match(prose);
148 let has_proof = proof_regex.is_match(prose);
149 let has_complex = complex_regex.is_match(prose);
150 let has_api = api_regex.is_match(prose);
151 let has_contractor = contractor_regex.is_match(prose);
152 let has_intent = intent_regex.is_match(prose);
153
154 if has_proof || has_contractor || has_intent || (has_types && has_rules) {
156 return ConversionTier::Full;
157 }
158
159 if has_types || has_rules || has_complex || has_api || word_count > 20 {
161 return ConversionTier::Standard;
162 }
163
164 ConversionTier::Minimal
166 }
167
168 fn convert_minimal(prose: &str) -> ConversionResult {
170 let (output, mapped_chars, unmapped) = RosettaStone::convert(prose);
171 let confidence = RosettaStone::confidence(prose.len(), mapped_chars);
172
173 ConversionResult {
174 output,
175 confidence,
176 unmapped,
177 tier: ConversionTier::Minimal,
178 tokens: TokenStats {
179 input: 0,
180 output: 0,
181 ratio: 0.0,
182 },
183 used_fallback: false,
184 }
185 }
186
187 fn convert_standard(prose: &str) -> ConversionResult {
189 let minimal = Self::convert_minimal(prose);
190 let domain = Self::extract_domain(prose);
191 let date = Utc::now().format("%Y-%m-%d").to_string();
192
193 let output = format!(
194 r#"𝔸5.1.{domain}@{date}
195γ≔{domain}
196
197⟦Ω:Meta⟧{{
198 domain≜{domain}
199 version≜1.0.0
200}}
201
202⟦Σ:Types⟧{{
203 ∅
204}}
205
206⟦Γ:Rules⟧{{
207 ∅
208}}
209
210⟦Λ:Funcs⟧{{
211 {body}
212}}
213
214⟦Ε⟧⟨δ≜0.70;τ≜◊⁺⟩"#,
215 domain = domain,
216 date = date,
217 body = minimal.output
218 );
219
220 ConversionResult {
221 output,
222 confidence: minimal.confidence,
223 unmapped: minimal.unmapped,
224 tier: ConversionTier::Standard,
225 tokens: TokenStats {
226 input: 0,
227 output: 0,
228 ratio: 0.0,
229 },
230 used_fallback: false,
231 }
232 }
233
234 fn convert_full(prose: &str) -> ConversionResult {
236 let minimal = Self::convert_minimal(prose);
237 let domain = Self::extract_domain(prose);
238 let date = Utc::now().format("%Y-%m-%d").to_string();
239 let types = Self::infer_types(prose);
240 let rules = Self::infer_rules(prose);
241 let errors = Self::infer_errors(prose);
242
243 let output = format!(
244 r#"𝔸5.1.{domain}@{date}
245γ≔{domain}.definitions
246ρ≔⟨{domain},types,rules⟩
247
248⟦Ω:Meta⟧{{
249 domain≜{domain}
250 version≜1.0.0
251 ∀D∈AISP:Ambig(D)<0.02
252}}
253
254⟦Σ:Types⟧{{
255{types}
256}}
257
258⟦Γ:Rules⟧{{
259{rules}
260}}
261
262⟦Λ:Funcs⟧{{
263 {body}
264}}
265
266⟦Χ:Errors⟧{{
267{errors}
268}}
269
270⟦Ε⟧⟨δ≜0.82;φ≜100;τ≜◊⁺⁺;⊢valid;∎⟩"#,
271 domain = domain,
272 date = date,
273 types = types,
274 rules = rules,
275 body = minimal.output,
276 errors = errors
277 );
278
279 ConversionResult {
280 output,
281 confidence: minimal.confidence,
282 unmapped: minimal.unmapped,
283 tier: ConversionTier::Full,
284 tokens: TokenStats {
285 input: 0,
286 output: 0,
287 ratio: 0.0,
288 },
289 used_fallback: false,
290 }
291 }
292
293 fn extract_domain(prose: &str) -> &'static str {
295 let lower = prose.to_lowercase();
296
297 if lower.contains("api") || lower.contains("endpoint") {
298 return "api";
299 }
300 if lower.contains("auth") || lower.contains("login") || lower.contains("password") {
301 return "auth";
302 }
303 if lower.contains("math") || lower.contains("sum") || lower.contains("calculate") {
304 return "math";
305 }
306 if lower.contains("database") || lower.contains("store") || lower.contains("persist") {
307 return "data";
308 }
309 if lower.contains("file") || lower.contains("read") || lower.contains("write") {
310 return "io";
311 }
312 if lower.contains("test") || lower.contains("assert") || lower.contains("expect") {
313 return "test";
314 }
315 if lower.contains("user") {
316 return "user";
317 }
318
319 "domain"
320 }
321
322 fn infer_types(prose: &str) -> String {
324 let lower = prose.to_lowercase();
325 let mut types = Vec::new();
326
327 if lower.contains("number") || lower.contains("integer") || lower.contains("count") {
328 types.push(" ℕ≜natural_numbers");
329 }
330 if lower.contains("string") || lower.contains("text") || lower.contains("name") {
331 types.push(" 𝕊≜strings");
332 }
333 if lower.contains("bool")
334 || lower.contains("flag")
335 || lower.contains("true")
336 || lower.contains("false")
337 {
338 types.push(" 𝔹≜booleans");
339 }
340 if lower.contains("function") || lower.contains("lambda") {
341 types.push(" Fn⟨A,B⟩≜A→B");
342 }
343 if lower.contains("user") {
344 types.push(" User≜⟨id:ℕ,name:𝕊⟩");
345 }
346 if lower.contains("list") || lower.contains("array") {
347 types.push(" List⟨T⟩≜⟨items:T*⟩");
348 }
349
350 if types.is_empty() {
351 types.push(" T≜⟨value:Any⟩");
352 }
353
354 types.join("\n")
355 }
356
357 fn infer_rules(prose: &str) -> String {
359 let lower = prose.to_lowercase();
360 let mut rules = Vec::new();
361
362 if lower.contains("constant") || lower.contains("immutable") {
363 rules.push(" ∀c∈Const:c.immutable≡⊤");
364 }
365 if lower.contains("valid") || lower.contains("check") {
366 rules.push(" ∀x:T:valid(x)⇒accept(x)");
367 }
368 if lower.contains("all") || lower.contains("every") {
369 rules.push(" ∀x∈S:P(x)");
370 }
371 if lower.contains("must") || lower.contains("require") {
372 rules.push(" ∀x:T:require(x)⇒proceed(x)");
373 }
374 if lower.contains("unique") {
375 rules.push(" ∃!x:T:unique(x)");
376 }
377 if lower.contains("admin") {
378 rules.push(" ∀u∈User:u.admin≡⊤⇒allow(u)");
379 }
380
381 if lower.contains("invariant") || lower.contains("always true") {
383 rules.push(" Inv(s)≜always(s)");
384 }
385 if lower.contains("precondition") || lower.contains("before") {
386 rules.push(" Pre(f)≜req(args)");
387 }
388 if lower.contains("postcondition") || lower.contains("after") || lower.contains("ensures") {
389 rules.push(" Post(f)≜guarantee(result)");
390 }
391 if lower.contains("delta") || lower.contains("change") {
392 rules.push(" Δ(s)≜s'−s");
393 }
394
395 if rules.is_empty() {
396 rules.push(" ∀x:T:⊤");
397 }
398
399 rules.join("\n")
400 }
401
402 fn infer_errors(prose: &str) -> String {
404 let lower = prose.to_lowercase();
405 let mut errors = Vec::new();
406
407 if lower.contains("error") || lower.contains("exception") {
408 errors.push(" E≜GenericError");
409 }
410 if lower.contains("fail") || lower.contains("failure") {
411 errors.push(" fail(x)⇒⊥");
412 }
413 if lower.contains("crash") || lower.contains("panic") {
414 errors.push(" crash⇒⊥⊥");
415 }
416 if lower.contains("not found") || lower.contains("missing") {
417 errors.push(" NotFound⇒∅");
418 }
419 if lower.contains("unauthorized") || lower.contains("forbidden") || lower.contains("denied")
420 {
421 errors.push(" AuthError⇒⊘");
422 }
423
424 if errors.is_empty() {
425 errors.push(" ∅");
426 }
427
428 errors.join("\n")
429 }
430
431 pub fn to_prose(aisp: &str) -> String {
442 RosettaStone::to_prose(aisp)
443 }
444
445 pub fn validate(aisp: &str) -> aisp::ValidationResult {
447 aisp::validate(aisp)
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn test_detect_tier_minimal() {
457 assert_eq!(
458 AispConverter::detect_tier("Define x as 5"),
459 ConversionTier::Minimal
460 );
461 }
462
463 #[test]
464 fn test_detect_tier_standard() {
465 assert_eq!(
466 AispConverter::detect_tier("The user must provide valid authentication"),
467 ConversionTier::Standard
468 );
469 }
470
471 #[test]
472 fn test_detect_tier_full() {
473 assert_eq!(
474 AispConverter::detect_tier("Define a type User and verify all users are valid"),
475 ConversionTier::Full
476 );
477 }
478
479 #[test]
480 fn test_convert_minimal() {
481 let result = AispConverter::convert("Define x as 5", None);
482 assert!(result.output.contains("≜"));
483 assert_eq!(result.tier, ConversionTier::Minimal);
484 }
485
486 #[test]
487 fn test_convert_standard() {
488 let result = AispConverter::convert(
489 "Define x as 5",
490 Some(ConversionOptions {
491 tier: Some(ConversionTier::Standard),
492 ..Default::default()
493 }),
494 );
495 assert!(result.output.contains("𝔸5.1"));
496 assert!(result.output.contains("⟦Ω:Meta⟧"));
497 assert!(result.output.contains("⟦Σ:Types⟧"));
498 assert!(result.output.contains("⟦Γ:Rules⟧"));
499 assert!(result.output.contains("⟦Λ:Funcs⟧"));
500 }
501
502 #[test]
503 fn test_convert_full() {
504 let result = AispConverter::convert(
505 "Define x as 5",
506 Some(ConversionOptions {
507 tier: Some(ConversionTier::Full),
508 ..Default::default()
509 }),
510 );
511 assert!(result.output.contains("⟦Ω:Meta⟧"));
512 assert!(result.output.contains("⟦Σ:Types⟧"));
513 assert!(result.output.contains("⟦Γ:Rules⟧"));
514 assert!(result.output.contains("⟦Λ:Funcs⟧"));
515 assert!(result.output.contains("⟦Χ:Errors⟧"));
516 }
517
518 #[test]
519 fn test_to_prose() {
520 let prose = AispConverter::to_prose("∀x∈S");
521 assert!(prose.contains("for all"));
522 assert!(prose.contains("in"));
523 }
524}