intent_codegen/
typescript.rs1use intent_parser::ast;
4
5use crate::types::map_type;
6use crate::{Language, doc_text, format_ensures_item, format_expr, to_camel_case};
7
8pub fn generate(file: &ast::File) -> String {
10 let lang = Language::TypeScript;
11 let mut out = String::new();
12
13 out.push_str(&format!("// Generated from {}.intent\n", file.module.name));
15 if let Some(doc) = &file.doc {
16 out.push_str("/**\n");
17 for line in &doc.lines {
18 out.push_str(&format!(" * {line}\n"));
19 }
20 out.push_str(" */\n");
21 }
22 out.push('\n');
23
24 for item in &file.items {
25 match item {
26 ast::TopLevelItem::Entity(e) => generate_entity(&mut out, e, &lang),
27 ast::TopLevelItem::Action(a) => generate_action(&mut out, a, &lang),
28 ast::TopLevelItem::Invariant(inv) => generate_invariant(&mut out, inv),
29 ast::TopLevelItem::EdgeCases(ec) => generate_edge_cases(&mut out, ec),
30 ast::TopLevelItem::Test(_) => {}
31 }
32 }
33
34 out
35}
36
37fn generate_entity(out: &mut String, entity: &ast::EntityDecl, lang: &Language) {
38 if let Some(doc) = &entity.doc {
40 out.push_str("/**\n");
41 for line in doc_text(doc).lines() {
42 out.push_str(&format!(" * {line}\n"));
43 }
44 out.push_str(" */\n");
45 }
46
47 out.push_str(&format!("export interface {} {{\n", entity.name));
48
49 for field in &entity.fields {
50 let ty = map_type(&field.ty, lang);
51 out.push_str(&format!(" {}: {};\n", to_camel_case(&field.name), ty));
52 }
53
54 out.push_str("}\n\n");
55}
56
57fn generate_action(out: &mut String, action: &ast::ActionDecl, lang: &Language) {
58 out.push_str("/**\n");
60 if let Some(doc) = &action.doc {
61 for line in doc_text(doc).lines() {
62 out.push_str(&format!(" * {line}\n"));
63 }
64 out.push_str(" *\n");
65 }
66
67 for p in &action.params {
69 let ty = map_type(&p.ty, lang);
70 out.push_str(&format!(" * @param {} - {ty}\n", to_camel_case(&p.name)));
71 }
72
73 if let Some(req) = &action.requires {
75 out.push_str(" *\n * @requires\n");
76 for cond in &req.conditions {
77 out.push_str(&format!(" * - {}\n", format_expr(cond)));
78 }
79 }
80
81 if let Some(ens) = &action.ensures {
83 out.push_str(" *\n * @ensures\n");
84 for item in &ens.items {
85 out.push_str(&format!(" * - {}\n", format_ensures_item(item)));
86 }
87 }
88
89 if let Some(props) = &action.properties {
91 out.push_str(" *\n * @properties\n");
92 for entry in &props.entries {
93 out.push_str(&format!(
94 " * - {}: {}\n",
95 entry.key,
96 crate::format_prop_value(&entry.value)
97 ));
98 }
99 }
100
101 out.push_str(" */\n");
102
103 let fn_name = to_camel_case(&action.name);
105 let params: Vec<String> = action
106 .params
107 .iter()
108 .map(|p| {
109 let ty = map_type(&p.ty, lang);
110 format!("{}: {ty}", to_camel_case(&p.name))
111 })
112 .collect();
113
114 out.push_str(&format!(
115 "export function {fn_name}({}): void {{\n",
116 params.join(", ")
117 ));
118 out.push_str(&format!(
119 " throw new Error(\"TODO: implement {fn_name}\");\n"
120 ));
121 out.push_str("}\n\n");
122}
123
124fn generate_invariant(out: &mut String, inv: &ast::InvariantDecl) {
125 out.push_str(&format!("// Invariant: {}\n", inv.name));
126 if let Some(doc) = &inv.doc {
127 for line in doc_text(doc).lines() {
128 out.push_str(&format!("// {line}\n"));
129 }
130 }
131 out.push_str(&format!("// {}\n\n", format_expr(&inv.body)));
132}
133
134fn generate_edge_cases(out: &mut String, ec: &ast::EdgeCasesDecl) {
135 out.push_str("// Edge cases:\n");
136 for rule in &ec.rules {
137 out.push_str(&format!(
138 "// when {} => {}()\n",
139 format_expr(&rule.condition),
140 rule.action.name,
141 ));
142 }
143 out.push('\n');
144}