Skip to main content

telltale_runtime/
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(
12    clippy::missing_errors_doc,
13    clippy::missing_panics_doc,
14    clippy::must_use_candidate
15)]
16
17pub mod ast;
18pub mod compiler;
19pub mod effects;
20pub mod extensions;
21pub mod heap;
22pub mod identifiers;
23pub mod runtime;
24pub mod testing;
25pub mod topology;
26pub mod tracing;
27
28// Re-export runtime support types
29pub use runtime::{SystemClock, SystemRng};
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::{TelltaleEndpoint, TelltaleHandler, TelltaleSession};
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, RecordingObserver, Rng, SeededRng,
73    SimulatedTransport, StepInput, StepOutput, WallClock,
74};
75
76// Re-export macros from telltale-macros
77pub use telltale_macros::tell;
78pub use telltale_types::{ChannelCapacity, MessageLenBytes, QueueCapacity};
79
80/// Unstable low-level extension integration surfaces.
81///
82/// These items are exposed for advanced integrations and may evolve faster than
83/// the stable root-level API.
84#[doc(hidden)]
85pub mod unstable {
86    pub use crate::extensions::{CodegenContext, ParseContext, ProjectionContext, StatementParser};
87}
88
89// High-level API functions for extension-aware compilation
90
91/// Parse and generate choreography code with extension support
92pub fn parse_and_generate_with_extensions(
93    input: &str,
94    extension_registry: &ExtensionRegistry,
95) -> std::result::Result<proc_macro2::TokenStream, CompilationError> {
96    use compiler::codegen::generate_choreography_code_with_extensions;
97    use compiler::parser::parse_choreography_str_with_extensions;
98    use compiler::projection::project;
99
100    let (choreography, extensions) =
101        parse_choreography_str_with_extensions(input, extension_registry)
102            .map_err(CompilationError::Parse)?;
103
104    // Validate the choreography
105    choreography
106        .validate()
107        .map_err(|e| CompilationError::Validation(e.to_string()))?;
108
109    // Project to local types
110    let mut local_types = Vec::new();
111    for role in &choreography.roles {
112        let local_type = project(&choreography, role)
113            .map_err(|e| CompilationError::Projection(e.to_string()))?;
114        local_types.push((role.clone(), local_type));
115    }
116
117    // Generate code with extensions
118    let generated_code =
119        generate_choreography_code_with_extensions(&choreography, &local_types, &extensions);
120
121    Ok(generated_code)
122}
123
124/// Convenience function for compiling choreography with built-in extensions
125pub fn compile_choreography_with_extensions(
126    input: &str,
127) -> std::result::Result<proc_macro2::TokenStream, CompilationError> {
128    let registry = ExtensionRegistry::with_builtin_extensions();
129    parse_and_generate_with_extensions(input, &registry)
130}
131
132/// Parse choreography with extension support
133pub fn parse_choreography_with_extensions(
134    input: &str,
135    extension_registry: &ExtensionRegistry,
136) -> std::result::Result<(Choreography, Vec<Box<dyn ProtocolExtension>>), CompilationError> {
137    use compiler::parser::parse_choreography_str_with_extensions;
138
139    parse_choreography_str_with_extensions(input, extension_registry)
140        .map_err(CompilationError::Parse)
141}
142
143/// Compilation errors that can occur during choreography processing
144#[derive(Debug, thiserror::Error)]
145pub enum CompilationError {
146    #[error("parse error: {0}")]
147    Parse(#[from] compiler::parser::ParseError),
148
149    #[error("validation error: {0}")]
150    Validation(String),
151
152    #[error("projection error: {0}")]
153    Projection(String),
154
155    #[error("code generation error: {0}")]
156    Codegen(String),
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use crate::effects::{LabelId, RoleId};
163    use crate::identifiers::RoleName;
164
165    // Simple test role type for unit tests
166    #[allow(dead_code)]
167    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
168    enum TestRole {
169        Alice,
170        Bob,
171    }
172
173    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
174    enum TestLabel {
175        Test,
176    }
177
178    impl LabelId for TestLabel {
179        fn as_str(&self) -> &'static str {
180            match self {
181                TestLabel::Test => "test",
182            }
183        }
184
185        fn from_str(label: &str) -> Option<Self> {
186            match label {
187                "test" => Some(TestLabel::Test),
188                _ => None,
189            }
190        }
191    }
192
193    impl RoleId for TestRole {
194        type Label = TestLabel;
195
196        fn role_name(&self) -> RoleName {
197            match self {
198                TestRole::Alice => RoleName::from_static("Alice"),
199                TestRole::Bob => RoleName::from_static("Bob"),
200            }
201        }
202    }
203
204    #[test]
205    fn test_module_structure() {
206        // Test that main re-exports are available
207        let _choreography: Option<Choreography> = None;
208        let _protocol: Option<Protocol> = None;
209        let _role: Option<Role> = None;
210        let _message_type: Option<MessageType> = None;
211
212        // Test effect system is available
213        let _program: Option<Program<TestRole, String>> = None;
214        let _result: Option<ChoreoResult<()>> = None;
215        let _label: Option<TestLabel> = None;
216    }
217
218    #[test]
219    fn test_free_algebra_integration() {
220        use std::time::Duration;
221
222        // Test that Program can be built using the free algebra API
223        let program = Program::<TestRole, String>::new()
224            .send(TestRole::Bob, "hello".to_string())
225            .recv::<String>(TestRole::Bob)
226            .choose(TestRole::Bob, TestLabel::Test)
227            .offer(TestRole::Bob)
228            .with_timeout(
229                TestRole::Bob,
230                Duration::from_millis(100),
231                Program::new().end(),
232            )
233            .parallel(vec![Program::new().end()])
234            .end();
235
236        // Basic analysis should work
237        assert_eq!(program.send_count(), 1);
238        assert_eq!(program.recv_count(), 1);
239        assert!(program.has_timeouts());
240        assert!(program.has_parallel());
241    }
242}