telltale_runtime/compiler/codegen/
mod.rs1mod annotation;
11mod dynamic;
12mod topology;
13
14pub(crate) use annotation::{
16 generate_annotation_docs, generate_annotation_metadata, generate_runtime_annotation_access,
17};
18
19pub use dynamic::{generate_choreography_code_with_dynamic_roles, generate_dynamic_role_support};
20pub use topology::{
21 generate_choreography_code_with_topology, generate_topology_integration, InlineTopology,
22};
23
24use crate::ast::{Choreography, LocalType, MessageType, Role};
25use crate::extensions::ProtocolExtension;
26use proc_macro2::{Ident, TokenStream};
27use quote::{format_ident, quote};
28use std::collections::HashMap;
29
30#[must_use]
32pub fn generate_session_type(
33 role: &Role,
34 local_type: &LocalType,
35 protocol_name: &str,
36) -> TokenStream {
37 let type_name = format_ident!("{}_{}", role.name(), protocol_name);
38 let inner_type = generate_type_expr(local_type);
39
40 quote! {
41 #[session]
42 type #type_name = #inner_type;
43 }
44}
45
46fn generate_type_expr(local_type: &LocalType) -> TokenStream {
49 match local_type {
50 LocalType::Send {
51 to,
52 message,
53 continuation,
54 } => generate_send_type_expr(to, message, continuation),
55 LocalType::Receive {
56 from,
57 message,
58 continuation,
59 } => generate_receive_type_expr(from, message, continuation),
60 LocalType::Select { to, branches } => generate_select_type_expr(to, branches),
61 LocalType::Branch { from, branches } => generate_branch_type_expr(from, branches),
62 LocalType::LocalChoice { branches } => generate_local_choice_type_expr(branches),
63 LocalType::Loop { condition, body } => generate_loop_type_expr(condition, body),
64 LocalType::Rec {
65 label: _label,
66 body,
67 } => generate_rec_type_expr(body),
68
69 LocalType::Var(label) => {
70 quote! { #label }
74 }
75
76 LocalType::End => {
77 quote! { End }
78 }
79
80 LocalType::Timeout { duration: _, body } => {
81 generate_type_expr(body)
83 }
84 }
85}
86
87fn generate_send_type_expr(
88 to: &Role,
89 message: &MessageType,
90 continuation: &LocalType,
91) -> TokenStream {
92 let to_name = to.name();
93 let msg_name = &message.name;
94 let cont = generate_type_expr(continuation);
95 quote! { Send<#to_name, #msg_name, #cont> }
96}
97
98fn generate_receive_type_expr(
99 from: &Role,
100 message: &MessageType,
101 continuation: &LocalType,
102) -> TokenStream {
103 let from_name = from.name();
104 let msg_name = &message.name;
105 let cont = generate_type_expr(continuation);
106 quote! { Receive<#from_name, #msg_name, #cont> }
107}
108
109fn generate_select_type_expr(to: &Role, branches: &[(Ident, LocalType)]) -> TokenStream {
110 let to_name = to.name();
111 let choice_type = generate_choice_enum(branches, true);
112 quote! { Select<#to_name, #choice_type> }
113}
114
115fn generate_branch_type_expr(from: &Role, branches: &[(Ident, LocalType)]) -> TokenStream {
116 let from_name = from.name();
117 let choice_type = generate_choice_enum(branches, false);
118 quote! { Branch<#from_name, #choice_type> }
119}
120
121fn generate_local_choice_type_expr(branches: &[(Ident, LocalType)]) -> TokenStream {
122 let choice_type = generate_choice_enum(branches, true);
123 quote! { LocalChoice<#choice_type> }
124}
125
126fn generate_loop_type_expr(
127 condition: &Option<crate::ast::Condition>,
128 body: &LocalType,
129) -> TokenStream {
130 let body_expr = generate_type_expr(body);
131 match condition {
132 Some(crate::ast::Condition::Count(_)) => quote! { Loop<#body_expr> },
133 Some(crate::ast::Condition::RoleDecides(_)) => quote! { Loop<#body_expr> },
134 Some(crate::ast::Condition::Custom(_)) => quote! { Loop<#body_expr> },
135 Some(crate::ast::Condition::Fuel(_)) => quote! { Loop<#body_expr> },
136 Some(crate::ast::Condition::YieldAfter(_)) => quote! { Loop<#body_expr> },
137 Some(crate::ast::Condition::YieldWhen(_)) => quote! { Loop<#body_expr> },
138 None => quote! { Loop<#body_expr> },
139 }
140}
141
142fn generate_rec_type_expr(body: &LocalType) -> TokenStream {
143 let body_expr = generate_type_expr(body);
144 quote! { #body_expr }
145}
146
147fn generate_choice_enum(branches: &[(Ident, LocalType)], _is_select: bool) -> TokenStream {
149 let enum_name = format_ident!(
150 "Choice{}",
151 branches
152 .iter()
153 .map(|(l, _)| l.to_string())
154 .collect::<String>()
155 );
156
157 let variants: Vec<TokenStream> = branches
158 .iter()
159 .map(|(label, local_type)| {
160 let continuation = generate_type_expr(local_type);
161 quote! {
162 #label(#label, #continuation)
163 }
164 })
165 .collect();
166
167 quote! {
168 {
169 #[session]
170 enum #enum_name {
171 #(#variants),*
172 }
173 #enum_name
174 }
175 }
176}
177
178#[must_use]
180pub fn generate_choreography_code(
181 name: &str,
182 roles: &[Role],
183 local_types: &[(Role, LocalType)],
184) -> TokenStream {
185 let role_struct_defs = generate_role_structs(roles);
186 let session_type_defs = local_types
187 .iter()
188 .map(|(role, local_type)| generate_session_type(role, local_type, name));
189
190 quote! {
191 #role_struct_defs
192 #(#session_type_defs)*
193 }
194}
195
196pub fn generate_choreography_code_with_extensions(
198 choreography: &Choreography,
199 local_types: &[(Role, LocalType)],
200 extensions: &[Box<dyn ProtocolExtension>],
201) -> TokenStream {
202 let base_code = generate_choreography_code(
204 &choreography.name.to_string(),
205 &choreography.roles,
206 local_types,
207 );
208
209 let extension_code = generate_extension_code(extensions, choreography);
211
212 quote! {
214 #base_code
215 #extension_code
216 }
217}
218
219fn generate_extension_code(
221 extensions: &[Box<dyn ProtocolExtension>],
222 choreography: &Choreography,
223) -> TokenStream {
224 if extensions.is_empty() {
225 return quote! {};
226 }
227
228 let mut extension_impls = Vec::new();
229
230 for extension in extensions {
231 let context = crate::extensions::CodegenContext {
232 choreography_name: &choreography.name.to_string(),
233 roles: &choreography.roles,
234 namespace: choreography.namespace.as_deref(),
235 };
236 let ext_code = extension.generate_code(&context);
237 extension_impls.push(ext_code);
238 }
239
240 quote! {
241 #(#extension_impls)*
243
244 pub fn create_extension_registry() -> ::telltale_runtime::extensions::ExtensionRegistry {
246 let mut registry = ::telltale_runtime::extensions::ExtensionRegistry::new();
247
248 registry
250 }
251 }
252}
253
254fn generate_role_structs(roles: &[Role]) -> TokenStream {
256 let _n = roles.len();
257 let role_names: Vec<&Ident> = roles.iter().map(|r| r.name()).collect();
258
259 let roles_struct = quote! {
261 #[derive(Roles)]
262 struct Roles(#(#role_names),*);
263 };
264
265 let role_structs = roles.iter().enumerate().map(|(i, role)| {
267 let role_name = role.name();
268 let other_roles: Vec<_> = roles
269 .iter()
270 .enumerate()
271 .filter(|(j, _)| i != *j)
272 .map(|(_, r)| r.name())
273 .collect();
274
275 if other_roles.is_empty() {
276 quote! {
278 #[derive(Role)]
279 #[message(Label)]
280 struct #role_name;
281 }
282 } else {
283 let routes = other_roles.iter().map(|other| {
284 quote! {
285 #[route(#other)] Channel
286 }
287 });
288
289 quote! {
290 #[derive(Role)]
291 #[message(Label)]
292 struct #role_name(#(#routes),*);
293 }
294 }
295 });
296
297 quote! {
298 #roles_struct
299 #(#role_structs)*
300 }
301}
302
303#[must_use]
305pub fn generate_role_implementations(
306 role: &Role,
307 local_type: &LocalType,
308 protocol_name: &str,
309) -> TokenStream {
310 let role_name = role.name();
311 let fn_name = format_ident!("{}_protocol", role_name.to_string().to_lowercase());
312 let session_type = format_ident!("{}_{}", role_name, protocol_name);
313
314 let impl_body = generate_implementation_body(local_type);
315
316 quote! {
317 async fn #fn_name(role: &mut #role_name) -> Result<()> {
318 try_session(role, |s: #session_type<'_, _>| async move {
319 #impl_body
320 Ok(((), s))
321 }).await
322 }
323 }
324}
325
326fn generate_implementation_body(local_type: &LocalType) -> TokenStream {
329 match local_type {
330 LocalType::Send {
331 message,
332 continuation,
333 ..
334 } => generate_send_impl(&message.name, continuation),
335
336 LocalType::Receive {
337 message,
338 continuation,
339 ..
340 } => generate_recv_impl(&message.name, continuation),
341
342 LocalType::Select { branches, .. } => generate_select_impl(branches),
343
344 LocalType::Branch { branches, .. } => {
345 let match_arms = branches.iter().map(generate_branch_match_arm);
346
347 quote! {
348 let s = match s.branch().await? {
349 #(#match_arms)*
350 };
351 }
352 }
353
354 LocalType::End => quote! {},
355
356 _ => quote! { },
357 }
358}
359
360fn generate_send_impl(msg_name: &Ident, continuation: &LocalType) -> TokenStream {
361 let cont_impl = generate_implementation_body(continuation);
362 quote! {
363 let s = s.send(#msg_name()).await?;
364 #cont_impl
365 }
366}
367
368fn generate_recv_impl(msg_name: &Ident, continuation: &LocalType) -> TokenStream {
369 let cont_impl = generate_implementation_body(continuation);
370 quote! {
371 let (#msg_name(value), s) = s.receive().await?;
372 #cont_impl
373 }
374}
375
376fn generate_select_impl(branches: &[(Ident, LocalType)]) -> TokenStream {
377 let first_branch = &branches[0];
378 let label = &first_branch.0;
379 let cont_impl = generate_implementation_body(&first_branch.1);
380 quote! {
381 let s = s.select(#label()).await?;
382 #cont_impl
383 }
384}
385
386fn generate_branch_match_arm(branch: &(Ident, LocalType)) -> TokenStream {
387 let (label, local_type) = branch;
388 let impl_body = generate_implementation_body(local_type);
389 quote! {
390 Choice::#label(value, s) => {
391 #impl_body
392 }
393 }
394}
395
396#[must_use]
398pub fn generate_helpers(_name: &str, messages: &[MessageType]) -> TokenStream {
399 let message_enum = if messages.is_empty() {
400 quote! {}
401 } else {
402 let variants = messages.iter().map(|msg| {
403 let name = &msg.name;
404 quote! { #name(#name) }
405 });
406
407 quote! {
408 #[derive(Message)]
409 enum Label {
410 #(#variants),*
411 }
412 }
413 };
414
415 let message_structs = messages.iter().map(|msg| {
416 let name = &msg.name;
417 if let Some(payload) = &msg.payload {
418 quote! { struct #name #payload; }
419 } else {
420 quote! { struct #name; }
421 }
422 });
423
424 quote! {
425 #message_enum
426 #(#message_structs)*
427
428 type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
429 type Channel = Bidirectional<UnboundedSender<Label>, UnboundedReceiver<Label>>;
430 }
431}
432
433#[must_use]
435pub fn generate_choreography_code_with_namespacing(
436 choreo: &Choreography,
437 local_types: &[(Role, LocalType)],
438) -> TokenStream {
439 let inner_code = generate_choreography_code_with_annotations(
440 &choreo.name.to_string(),
441 &choreo.roles,
442 local_types,
443 choreo,
444 );
445
446 let choreo_docs = generate_annotation_docs(choreo.get_attributes());
448 let choreo_metadata =
449 generate_annotation_metadata(&choreo.name.to_string(), choreo.get_attributes());
450
451 match &choreo.namespace {
452 Some(ns) => {
453 let ns_ident = format_ident!("{}", ns);
454 quote! {
455 #choreo_docs
456 #[allow(dead_code, unused_imports, unused_variables)]
457 pub mod #ns_ident {
458 use super::*;
459
460 #choreo_metadata
461 #inner_code
462 }
463 }
464 }
465 None => {
466 quote! {
467 #choreo_docs
468 #[allow(dead_code, unused_imports, unused_variables)]
469 mod __generated_choreography {
470 use super::*;
471 #choreo_metadata
472 #inner_code
473 }
474 pub use __generated_choreography::*;
475 }
476 }
477 }
478}
479
480#[must_use]
482pub fn generate_choreography_code_with_annotations(
483 name: &str,
484 roles: &[Role],
485 local_types: &[(Role, LocalType)],
486 choreo: &Choreography,
487) -> TokenStream {
488 let role_struct_defs = generate_role_structs(roles);
489 let session_type_defs = local_types
490 .iter()
491 .map(|(role, local_type)| generate_session_type(role, local_type, name));
492
493 let protocol_annotation_access = generate_runtime_annotation_access(name, &choreo.protocol);
495
496 let role_metadata: Vec<TokenStream> = roles
498 .iter()
499 .filter(|role| role.index().is_some() || role.param().is_some())
500 .map(|role| {
501 let mut role_annotations = HashMap::new();
502 if role.index().is_some() {
503 role_annotations.insert("indexed".to_string(), "true".to_string());
504 }
505 if role.param().is_some() {
506 role_annotations.insert("parameterized".to_string(), "true".to_string());
507 }
508 generate_annotation_metadata(&role.name().to_string(), &role_annotations)
509 })
510 .collect();
511
512 quote! {
513 #role_struct_defs
514 #(#session_type_defs)*
515 #protocol_annotation_access
516 #(#role_metadata)*
517
518 pub mod annotations {
520 use super::*;
521 use std::collections::HashMap;
522
523 pub fn get_all_protocol_annotations() -> HashMap<String, HashMap<String, String>> {
525 let mut all_annotations = HashMap::new();
526 all_annotations
528 }
529 }
530 }
531}