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