Skip to main content

telltale_choreography/
lib.rs

1//! Choreographic Programming for Telltale
2//!
3//! This crate provides a choreographic programming layer on top of Telltale's
4//! session types, enabling global protocol specification with automatic projection.
5//!
6//! The choreographic approach allows you to write distributed protocols from a
7//! global viewpoint, with automatic generation of local session types for each
8//! participant. This includes an effect handler system that decouples protocol
9//! logic from transport implementation.
10
11#![allow(clippy::unwrap_used)]
12#![allow(clippy::expect_used)]
13
14pub mod ast;
15pub mod compiler;
16pub mod effects;
17pub mod extensions;
18pub mod heap;
19pub mod identifiers;
20pub mod runtime;
21pub mod testing;
22pub mod topology;
23pub mod tracing;
24
25// Re-export runtime adapter types
26pub use runtime::{
27    ChoiceLabel, ChoreographicAdapter, ChoreographicAdapterExt, Message, ProtocolContext,
28    ProtocolOutput, SystemClock, SystemRng,
29};
30
31// Re-export typed identifiers
32pub use identifiers::{Datacenter, Endpoint as TopologyEndpoint, Namespace, Region, RoleName};
33
34// Re-export main APIs
35pub use ast::{Choreography, MessageType, Protocol, Role};
36pub use compiler::generate_effects_protocol;
37pub use compiler::{
38    create_standard_extension_parser, format_choreography, format_choreography_str,
39    format_choreography_with_config, ExtensionParseError, ExtensionParser, ExtensionParserBuilder,
40    GrammarComposer, GrammarComposerBuilder, GrammarCompositionError, PrettyConfig,
41};
42pub use effects::middleware::{Metrics, Retry, Trace};
43pub use effects::NoOpHandler;
44pub use effects::{
45    interpret, ChoreoHandler, ChoreoHandlerExt, ChoreoResult, ChoreographyError, Effect, Endpoint,
46    InterpretResult, InterpreterState, LabelId, MessageTag, Program, ProgramBuilder,
47    ProgramMessage, RoleId,
48};
49pub use effects::{InMemoryHandler, RecordedEvent, RecordingHandler};
50pub use effects::{SimpleChannel, TelltaleEndpoint, TelltaleHandler};
51pub use extensions::{
52    CodegenContext, ExtensionRegistry, ExtensionValidationError, GrammarExtension, ParseContext,
53    ParseError, ProjectionContext, ProtocolExtension, StatementParser,
54};
55pub use runtime::{spawn, spawn_local};
56pub use topology::{
57    parse_topology, ByteMessage, InMemoryChannelTransport, Location, ParsedTopology, Topology,
58    TopologyBuilder, TopologyConstraint, TopologyError, TopologyHandler, TopologyHandlerBuilder,
59    TopologyLoadError, TopologyMode, TopologyParseError, TopologyValidation, Transport,
60    TransportError, TransportFactory, TransportMessage, TransportResult, TransportType,
61};
62
63// Re-export heap types for resource management
64pub use heap::{
65    ChannelState, Direction, Heap, HeapCommitment, HeapError, MerkleProof, MerkleTree,
66    Message as HeapMessage, MessagePayload, ProofStep, Resource, ResourceId,
67};
68
69// Re-export testing types for protocol testing
70pub use testing::{
71    AsyncClock, BlockedOn, Checkpoint, Clock, InMemoryTransport, MockClock, NullObserver,
72    ProtocolEnvelope, ProtocolObserver, ProtocolStateMachine, ProtocolTest, ProtocolTestBuilder,
73    RecordingObserver, Rng, RoleBinding, SeededRng, SimulatedTransport, StepInput, StepOutput,
74    TestConfig, TestResult, WallClock,
75};
76
77// Re-export macros from telltale-macros
78pub use telltale_macros::choreography;
79pub use telltale_types::{ChannelCapacity, MessageLenBytes, QueueCapacity};
80
81// High-level API functions for extension-aware compilation
82
83/// Parse and generate choreography code with extension support
84pub fn parse_and_generate_with_extensions(
85    input: &str,
86    extension_registry: &ExtensionRegistry,
87) -> std::result::Result<proc_macro2::TokenStream, CompilationError> {
88    use compiler::codegen::generate_choreography_code_with_extensions;
89    use compiler::parser::parse_choreography_str_with_extensions;
90    use compiler::projection::project;
91
92    let (choreography, extensions) =
93        parse_choreography_str_with_extensions(input, extension_registry)
94            .map_err(CompilationError::Parse)?;
95
96    // Validate the choreography
97    choreography
98        .validate()
99        .map_err(|e| CompilationError::Validation(e.to_string()))?;
100
101    // Project to local types
102    let mut local_types = Vec::new();
103    for role in &choreography.roles {
104        let local_type = project(&choreography, role)
105            .map_err(|e| CompilationError::Projection(e.to_string()))?;
106        local_types.push((role.clone(), local_type));
107    }
108
109    // Generate code with extensions
110    let generated_code =
111        generate_choreography_code_with_extensions(&choreography, &local_types, &extensions);
112
113    Ok(generated_code)
114}
115
116/// Convenience function for compiling choreography with built-in extensions
117pub fn compile_choreography_with_extensions(
118    input: &str,
119) -> std::result::Result<proc_macro2::TokenStream, CompilationError> {
120    let registry = ExtensionRegistry::with_builtin_extensions();
121    parse_and_generate_with_extensions(input, &registry)
122}
123
124/// Parse choreography with extension support
125pub fn parse_choreography_with_extensions(
126    input: &str,
127    extension_registry: &ExtensionRegistry,
128) -> std::result::Result<(Choreography, Vec<Box<dyn ProtocolExtension>>), CompilationError> {
129    use compiler::parser::parse_choreography_str_with_extensions;
130
131    parse_choreography_str_with_extensions(input, extension_registry)
132        .map_err(CompilationError::Parse)
133}
134
135/// Compilation errors that can occur during choreography processing
136#[derive(Debug, thiserror::Error)]
137pub enum CompilationError {
138    #[error("parse error: {0}")]
139    Parse(#[from] compiler::parser::ParseError),
140
141    #[error("validation error: {0}")]
142    Validation(String),
143
144    #[error("projection error: {0}")]
145    Projection(String),
146
147    #[error("code generation error: {0}")]
148    Codegen(String),
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::effects::{LabelId, RoleId};
155    use crate::identifiers::RoleName;
156
157    // Simple test role type for unit tests
158    #[allow(dead_code)]
159    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
160    enum TestRole {
161        Alice,
162        Bob,
163    }
164
165    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
166    enum TestLabel {
167        Test,
168    }
169
170    impl LabelId for TestLabel {
171        fn as_str(&self) -> &'static str {
172            match self {
173                TestLabel::Test => "test",
174            }
175        }
176
177        fn from_str(label: &str) -> Option<Self> {
178            match label {
179                "test" => Some(TestLabel::Test),
180                _ => None,
181            }
182        }
183    }
184
185    impl RoleId for TestRole {
186        type Label = TestLabel;
187
188        fn role_name(&self) -> RoleName {
189            match self {
190                TestRole::Alice => RoleName::from_static("Alice"),
191                TestRole::Bob => RoleName::from_static("Bob"),
192            }
193        }
194    }
195
196    #[test]
197    fn test_module_structure() {
198        // Test that main re-exports are available
199        let _choreography: Option<Choreography> = None;
200        let _protocol: Option<Protocol> = None;
201        let _role: Option<Role> = None;
202        let _message_type: Option<MessageType> = None;
203
204        // Test effect system is available
205        let _program: Option<Program<TestRole, String>> = None;
206        let _result: Option<ChoreoResult<()>> = None;
207        let _label: Option<TestLabel> = None;
208    }
209
210    #[test]
211    fn test_free_algebra_integration() {
212        use std::time::Duration;
213
214        // Test that Program can be built using the free algebra API
215        let program = Program::<TestRole, String>::new()
216            .send(TestRole::Bob, "hello".to_string())
217            .recv::<String>(TestRole::Bob)
218            .choose(TestRole::Bob, TestLabel::Test)
219            .offer(TestRole::Bob)
220            .with_timeout(
221                TestRole::Bob,
222                Duration::from_millis(100),
223                Program::new().end(),
224            )
225            .parallel(vec![Program::new().end()])
226            .end();
227
228        // Basic analysis should work
229        assert_eq!(program.send_count(), 1);
230        assert_eq!(program.recv_count(), 1);
231        assert!(program.has_timeouts());
232        assert!(program.has_parallel());
233    }
234}