intent_implement/
context.rs1use intent_codegen::{Language, format_ensures_item, format_expr, format_prop_value};
7use intent_parser::ast;
8
9pub struct PromptContext {
11 pub spec_source: String,
13 pub skeleton: String,
15 pub contracts: String,
17 pub test_harness: String,
19}
20
21pub fn build_context(file: &ast::File, lang: Language) -> PromptContext {
23 let spec_source = intent_render::format::format(file);
24 let skeleton = intent_codegen::generate(file, lang);
25 let contracts = build_contracts_summary(file);
26 let test_harness = intent_codegen::test_harness::generate(file, lang);
27
28 PromptContext {
29 spec_source,
30 skeleton,
31 contracts,
32 test_harness,
33 }
34}
35
36fn build_contracts_summary(file: &ast::File) -> String {
38 let mut out = String::new();
39
40 for item in &file.items {
41 match item {
42 ast::TopLevelItem::Action(action) => {
43 out.push_str(&format!("### Action: {}\n", action.name));
44
45 if !action.params.is_empty() {
47 let params: Vec<String> = action
48 .params
49 .iter()
50 .map(|p| format!("{} ({})", p.name, format_type(&p.ty)))
51 .collect();
52 out.push_str(&format!("Parameters: {}\n", params.join(", ")));
53 }
54
55 if let Some(req) = &action.requires {
57 out.push_str("\nPreconditions:\n");
58 for cond in &req.conditions {
59 out.push_str(&format!(" - {}\n", format_expr(cond)));
60 }
61 }
62
63 if let Some(ens) = &action.ensures {
65 out.push_str("\nPostconditions:\n");
66 for item in &ens.items {
67 out.push_str(&format!(" - {}\n", format_ensures_item(item)));
68 }
69 }
70
71 if let Some(props) = &action.properties {
73 out.push_str("\nProperties:\n");
74 for entry in &props.entries {
75 out.push_str(&format!(
76 " - {}: {}\n",
77 entry.key,
78 format_prop_value(&entry.value)
79 ));
80 }
81 }
82
83 out.push('\n');
84 }
85 ast::TopLevelItem::Invariant(inv) => {
86 out.push_str(&format!("### Invariant: {}\n", inv.name));
87 out.push_str(&format!(" {}\n\n", format_expr(&inv.body)));
88 }
89 ast::TopLevelItem::EdgeCases(ec) => {
90 out.push_str("### Edge Cases\n");
91 for rule in &ec.rules {
92 let args: Vec<String> = rule
93 .action
94 .args
95 .iter()
96 .map(|a| match a {
97 ast::CallArg::Named { key, value, .. } => {
98 format!("{}: {}", key, format_expr(value))
99 }
100 ast::CallArg::Positional(e) => format_expr(e),
101 })
102 .collect();
103 out.push_str(&format!(
104 " - when {} => {}({})\n",
105 format_expr(&rule.condition),
106 rule.action.name,
107 args.join(", "),
108 ));
109 }
110 out.push('\n');
111 }
112 ast::TopLevelItem::Entity(_) | ast::TopLevelItem::Test(_) => {
113 }
115 }
116 }
117
118 out
119}
120
121fn format_type(ty: &ast::TypeExpr) -> String {
123 intent_render::format_type(ty)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 fn parse(src: &str) -> ast::File {
131 intent_parser::parse_file(src).expect("parse failed")
132 }
133
134 #[test]
135 fn test_build_context_has_all_parts() {
136 let src = "module Test\n\nentity Foo {\n id: UUID\n name: String\n}\n\naction CreateFoo {\n name: String\n\n requires {\n name != \"\"\n }\n\n ensures {\n exists f: Foo => f.name == name\n }\n}\n";
137 let ast = parse(src);
138 let ctx = build_context(&ast, Language::Rust);
139
140 assert!(ctx.spec_source.contains("module Test"));
141 assert!(ctx.skeleton.contains("struct Foo"));
142 assert!(ctx.contracts.contains("Action: CreateFoo"));
143 assert!(ctx.contracts.contains("name != \"\""));
144 assert!(ctx.test_harness.is_empty());
146 }
147
148 #[test]
149 fn test_build_context_includes_test_harness() {
150 let src = "module Test\n\nentity Foo {\n id: UUID\n}\n\naction CreateFoo {\n name: String\n}\n\ntest \"creates foo\" {\n given { n = \"hello\" }\n when CreateFoo { name: n }\n then fails\n}\n";
151 let ast = parse(src);
152 let ctx = build_context(&ast, Language::Rust);
153
154 assert!(ctx.test_harness.contains("mod contract_tests"));
155 assert!(ctx.test_harness.contains("test_creates_foo"));
156 }
157
158 #[test]
159 fn test_contracts_summary_includes_invariant() {
160 let src = "module Test\n\nentity Acc {\n balance: Int\n}\n\ninvariant NoNeg {\n forall a: Acc => a.balance >= 0\n}\n";
161 let ast = parse(src);
162 let summary = build_contracts_summary(&ast);
163
164 assert!(summary.contains("Invariant: NoNeg"));
165 assert!(summary.contains("forall a: Acc => a.balance >= 0"));
166 }
167
168 #[test]
169 fn test_contracts_summary_includes_edge_cases() {
170 let src = "module Test\n\nentity Acc {\n id: UUID\n}\n\nedge_cases {\n when amount > 10000 => require_approval(level: \"manager\")\n}\n";
171 let ast = parse(src);
172 let summary = build_contracts_summary(&ast);
173
174 assert!(summary.contains("Edge Cases"));
175 assert!(summary.contains("require_approval"));
176 }
177
178 #[test]
179 fn test_contracts_summary_includes_properties() {
180 let src = "module Test\n\nentity X {\n id: UUID\n}\n\naction DoThing {\n x: X\n\n properties {\n idempotent: true\n atomic: true\n }\n}\n";
181 let ast = parse(src);
182 let summary = build_contracts_summary(&ast);
183
184 assert!(summary.contains("idempotent: true"));
185 assert!(summary.contains("atomic: true"));
186 }
187}