1pub mod csharp;
8pub mod go;
9pub mod java;
10pub mod openapi;
11pub mod python;
12pub mod rust;
13pub mod rust_tests;
14pub mod swift;
15pub mod test_harness;
16mod types;
17pub mod typescript;
18
19#[cfg(test)]
20mod codegen_tests;
21#[cfg(test)]
22mod openapi_tests;
23
24use intent_parser::ast::{
25 self, ArithOp, CallArg, CmpOp, EnsuresItem, ExprKind, Literal, PropValue, QuantifierKind,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Language {
31 Rust,
32 TypeScript,
33 Python,
34 Go,
35 Java,
36 CSharp,
37 Swift,
38}
39
40pub fn generate(file: &ast::File, lang: Language) -> String {
42 match lang {
43 Language::Rust => rust::generate(file),
44 Language::TypeScript => typescript::generate(file),
45 Language::Python => python::generate(file),
46 Language::Go => go::generate(file),
47 Language::Java => java::generate(file),
48 Language::CSharp => csharp::generate(file),
49 Language::Swift => swift::generate(file),
50 }
51}
52
53pub fn to_snake_case(s: &str) -> String {
55 let mut result = String::new();
56 for (i, ch) in s.chars().enumerate() {
57 if ch.is_uppercase() && i > 0 {
58 result.push('_');
59 }
60 result.push(ch.to_ascii_lowercase());
61 }
62 result
63}
64
65pub fn to_camel_case(s: &str) -> String {
67 let snake = to_snake_case(s);
68 let mut result = String::new();
69 let mut capitalize_next = false;
70 for (i, ch) in snake.chars().enumerate() {
71 if ch == '_' {
72 capitalize_next = true;
73 } else if capitalize_next {
74 result.push(ch.to_ascii_uppercase());
75 capitalize_next = false;
76 } else if i == 0 {
77 result.push(ch.to_ascii_lowercase());
78 } else {
79 result.push(ch);
80 }
81 }
82 result
83}
84
85pub fn format_expr(expr: &ast::Expr) -> String {
87 match &expr.kind {
88 ExprKind::Implies(l, r) => format!("{} => {}", format_expr(l), format_expr(r)),
89 ExprKind::Or(l, r) => format!("{} || {}", format_expr(l), format_expr(r)),
90 ExprKind::And(l, r) => format!("{} && {}", format_expr(l), format_expr(r)),
91 ExprKind::Not(e) => format!("!{}", format_expr(e)),
92 ExprKind::Compare { left, op, right } => {
93 let op_str = match op {
94 CmpOp::Eq => "==",
95 CmpOp::Ne => "!=",
96 CmpOp::Lt => "<",
97 CmpOp::Gt => ">",
98 CmpOp::Le => "<=",
99 CmpOp::Ge => ">=",
100 };
101 format!("{} {} {}", format_expr(left), op_str, format_expr(right))
102 }
103 ExprKind::Arithmetic { left, op, right } => {
104 let op_str = match op {
105 ArithOp::Add => "+",
106 ArithOp::Sub => "-",
107 };
108 format!("{} {} {}", format_expr(left), op_str, format_expr(right))
109 }
110 ExprKind::Old(e) => format!("old({})", format_expr(e)),
111 ExprKind::Quantifier {
112 kind,
113 binding,
114 ty,
115 body,
116 } => {
117 let kw = match kind {
118 QuantifierKind::Forall => "forall",
119 QuantifierKind::Exists => "exists",
120 };
121 format!("{kw} {binding}: {ty} => {}", format_expr(body))
122 }
123 ExprKind::Call { name, args } => {
124 let args_str = format_call_args(args);
125 format!("{name}({args_str})")
126 }
127 ExprKind::FieldAccess { root, fields } => {
128 format!("{}.{}", format_expr(root), fields.join("."))
129 }
130 ExprKind::List(items) => {
131 let inner: Vec<_> = items.iter().map(format_expr).collect();
132 format!("[{}]", inner.join(", "))
133 }
134 ExprKind::Ident(name) => name.clone(),
135 ExprKind::Literal(lit) => format_literal(lit),
136 }
137}
138
139fn format_call_args(args: &[CallArg]) -> String {
140 args.iter()
141 .map(|a| match a {
142 CallArg::Named { key, value, .. } => format!("{key}: {}", format_expr(value)),
143 CallArg::Positional(e) => format_expr(e),
144 })
145 .collect::<Vec<_>>()
146 .join(", ")
147}
148
149fn format_literal(lit: &Literal) -> String {
150 match lit {
151 Literal::Null => "null".to_string(),
152 Literal::Bool(b) => b.to_string(),
153 Literal::Int(n) => n.to_string(),
154 Literal::Decimal(s) => s.clone(),
155 Literal::String(s) => format!("\"{s}\""),
156 }
157}
158
159pub fn format_ensures_item(item: &EnsuresItem) -> String {
161 match item {
162 EnsuresItem::Expr(e) => format_expr(e),
163 EnsuresItem::When(w) => {
164 format!(
165 "when {} => {}",
166 format_expr(&w.condition),
167 format_expr(&w.consequence)
168 )
169 }
170 }
171}
172
173pub fn format_prop_value(val: &PropValue) -> String {
175 match val {
176 PropValue::Literal(lit) => format_literal(lit),
177 PropValue::Ident(name) => name.clone(),
178 PropValue::List(items) => {
179 let inner: Vec<_> = items.iter().map(format_prop_value).collect();
180 format!("[{}]", inner.join(", "))
181 }
182 PropValue::Object(entries) => {
183 let inner: Vec<_> = entries
184 .iter()
185 .map(|(k, v)| format!("{k}: {}", format_prop_value(v)))
186 .collect();
187 format!("{{{}}}", inner.join(", "))
188 }
189 }
190}
191
192pub fn doc_text(doc: &ast::DocBlock) -> String {
194 doc.lines.join("\n")
195}
196
197pub fn file_extension(lang: Language) -> &'static str {
199 match lang {
200 Language::Rust => "rs",
201 Language::TypeScript => "ts",
202 Language::Python => "py",
203 Language::Go => "go",
204 Language::Java => "java",
205 Language::CSharp => "cs",
206 Language::Swift => "swift",
207 }
208}
209
210pub fn output_filename(module_name: &str, lang: Language) -> String {
212 match lang {
213 Language::Rust | Language::Python | Language::Go => {
214 format!("{}.{}", to_snake_case(module_name), file_extension(lang))
215 }
216 Language::TypeScript => {
217 format!("{}.{}", to_camel_case(module_name), file_extension(lang))
218 }
219 Language::Java | Language::CSharp | Language::Swift => {
220 format!("{}.{}", module_name, file_extension(lang))
221 }
222 }
223}