Choreographic DSL
Overview
The parser translates the layout-sensitive Telltale DSL into the internal AST
(Choreography and Protocol). The DSL is direct style. Statements are
newline separated. Indentation defines blocks. Records keep braces, but
structural blocks do not.
The parser lives in rust/language/src/compiler/parser/. It uses Pest plus a
layout preprocessor. The canonical source-file extension for Telltale source
files is .tell.
The preferred surface style mixes MPST operators with a small functional-language layout:
- keep
->,->*,|,rec, andcontinuefor protocol structure - reserve
=>for branch/result bodies - use indentation to make control flow visually obvious
- keep braces for record/data layout instead of general structural blocks
- format standalone records and effect-semantics records as multiline brace blocks with
key : valuefields - use sender records like
Role { priority : high } - use
Message of module.Typewith dotted paths instead of Rust-style::
Scope
The DSL describes protocol structure, role-local communication obligations, and protocol-critical authority checks. It does not model arbitrary host state or general application ownership. Session and fragment ownership boundaries are still derived at runtime from composition and link metadata when available. Protocol-visible authority queries and evidence flow are part of the language surface.
The current DSL includes an authority-oriented surface for protocol-critical host queries and typed local branching:
- top-level
typeandtype aliasdeclarations - nominal
effectinterface declarations - protocol-level
usesdeclarations authoritative let/observe letbindings for effect queriesletbindings for linear transfer receiptscase ... ofoverResult/Maybe-style constructorstimeout ... on timeout ... on cancel ...- evidence guards of the form
when check Effect.op(...) yields witness
These forms participate in explicit language tiers:
- full spec: the DSL can express the construct and the semantic object model justifies it
- session projectable: the construct admits MPST/session projection
- protocol-machine executable: the construct is executable through the protocol-machine path even when it is not session projectable
- proof only: the construct parses, but lacks the metadata required for executable lowering
The generated Rust surface exposes this status through
Protocol::proof_status.
DSL Syntax
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
protocol PingPong =
roles Alice, Bob
Alice -> Bob : Ping
Bob -> Alice : Pong
}
}
tell! is the canonical public DSL entrypoint. String-literal macro input and
legacy brace-based structural syntax are rejected. The canonical surface is
token-form, indentation-based, and proof-directed.
tell! is outside the current formal-verification claim. The formal claim
currently covers the Lean models/theorems plus strict correspondence and
operational compiler gates, not Rust macro expansion itself.
When a protocol projects cleanly to sessions, the generated module exposes
Protocol::sessions. Every generated protocol also exposes
Protocol::proof_status, which reports the strongest supported tier and any
projection blocker.
Namespaces
Namespaces are expressed via a module declaration (rather than attributes) in source files:
module threshold_ceremony exposing (ThresholdProtocol)
protocol ThresholdProtocol =
roles Coordinator, Signers[*]
Coordinator -> Signers[*] : Request
Multiple modules can coexist in separate files. Inside tell! you typically
omit the module header and define one protocol-oriented spec directly in Rust.
The parser recognizes import declarations for completeness. Import resolution is not currently applied during compilation.
Supported Constructs
1) Send Statement
Buyer { priority : high }
-> Seller : Request of shop.Order
Seller
-> Buyer : Quote of shop.Price
The send statement transfers a message from one role to another. Sender metadata is written as a record attached to the sender term. Typed message attachment is written as Message of module.Type.
2) Broadcast Statement
Leader ->* : Announcement
Broadcast sends a message to all other roles.
3) Choice Statement (explicit decider)
choice Client at
| Buy when (balance > price) =>
Client
-> Server : Purchase of shop.Order
| Cancel =>
Client
-> Server : Cancel
The deciding role (Client) selects a branch. Guards are optional. Guard expressions are parsed through a typed predicate IR and must be boolean-like. Valid examples include ready, balance > price, and is_open().
4) Loop Statement
loop decide by Client
Client -> Server : Request
Server -> Client : Response
This loop continues while the deciding role chooses to continue. The decide by form is
desugared to a choice+recursion pattern at parse time. The first message in the loop body
serves as the “continue” signal - when the deciding role sends it, the loop continues.
A synthetic Done message is added as the termination signal.
The example above desugars to:
rec RoleDecidesLoop
choice Client at
| Request =>
Client -> Server : Request
Server -> Client : Response
continue RoleDecidesLoop
| Done =>
Client -> Server : Done
Requirement: The first statement in a loop decide by Role body must be a send from
the deciding role. This ensures the decision to continue is fused with the first message,
saving a round trip compared to sending a separate “continue” signal.
This desugaring converts the RoleDecides loop into standard multiparty session type
constructs (choice + recursion), keeping the surface inside the Lean-modeled theory
fragment and compatible with standard MPST projection algorithms.
loop repeat 5
A -> B : Ping
B -> A : Pong
This loop repeats a fixed number of times. The compiler records the iteration count in the AST.
loop while "has_more_data"
A -> B : Data
This loop parses the string content through the same typed predicate IR used by guards. The parser rejects non-boolean predicates such as "count + 1" before building the AST.
loop forever
A -> B : Tick
This loop has no exit condition. Use it for persistent background protocols.
5) Parallel Statement
par
| A -> B : Msg1
B
-> A : Ack
| C -> D : Msg2
Parallel composition is expressed by par with leading | branches. A solitary branch is a parse error.
6) Recursion and Calls
rec Loop
A -> B : Tick
continue Loop
This defines a recursive label Loop and uses continue Loop to jump back, modeling unbounded recursion.
call Handshake
This calls another protocol that is in scope. The call target must be defined in the same file or where block.
The continue keyword is for recursive back-references within a rec block. The call keyword is for invoking sub-protocols defined in where blocks.
7) Protocol Composition (where block)
Define and reuse local protocol fragments inside a where block.
protocol Main =
roles A, B, C
call Handshake
call DataTransfer
A -> C : Done
where
protocol Handshake =
A -> B : Hello
B -> A : Hi
protocol DataTransfer =
A -> B : Data
B -> A : Ack
Local protocols can call each other and can be used within choices, loops, and branches.
8) Message Types and Payloads
The preferred typed message form is Message of module.Type. Dotted paths are preferred in DSL surface syntax.
A
-> B : Request of api.Request
B
-> A : Response of api.Response
A
-> B : Msg { data : String, count : Int }
B
-> A : Result(i : Int, ok : Bool)
This sequence demonstrates payload and result handling patterns with mixed message shapes.
9) Dynamic Role Count Support
Dynamic role counts are supported via wildcard and symbolic parameters.
protocol ThresholdProtocol =
roles Coordinator, Signers[*]
Coordinator -> Signers[*] : Request
Signers[0..threshold] -> Coordinator : Response
This example uses a wildcard role family. It also uses a symbolic bound in the role index.
protocol ConsensusProtocol =
roles Leader, Followers[N]
Leader -> Followers[*] : Proposal
Followers[i] -> Leader : Vote
The consensus protocol above demonstrates a symbolic role count (N) with an index variable (i) iterating over the follower set.
10) Profiles, Progress, And Proof Status
Profiles and explicit progress attachment are part of the proof-backed surface.
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
profile Replay fairness strong
agreement_profile MembershipSoftSafe
visibility pending
rule threshold_participants
usable_at provisional
finalized_at finalized
evidence commit_fact
operation MembershipCheck at Coordinator progress MembershipProgress requires Replay within bounded agreement MembershipSoftSafe compose first_success =
publish MembershipQueued
protocol Membership under Replay =
roles Coordinator, Member
Coordinator -> Member : Invite
}
assert_eq!(Membership::proof_status::STRONGEST_TIER, "session_projectable");
assert!(Membership::proof_status::PROTOCOL_MACHINE_EXECUTABLE);
}
Profiles, theorem-pack requirements, and language-tier diagnostics are surfaced as generated metadata because they are part of the verified model, not an afterthought layered on top.
For effect interfaces, the canonical Rust import is use Protocol::effects.
Traits, request/outcome enums, effect-domain data, and per-operation semantic
metadata all live under that one boundary. Interface metadata is exposed under
snake-case effect modules such as Protocol::effects::runtime.
This example mixes a named count with index variables. It enables parameterized protocols.
protocol PartialBroadcast =
roles Broadcaster, Receivers[*]
Broadcaster -> Receivers[0..count] : Message
Receivers[0..threshold] -> Broadcaster : Ack
This example shows bounded ranges for role indices. It models partial broadcasts and threshold acknowledgments.
10) Timing Patterns
Timing patterns provide constructs for building time-aware protocols. They are part of the protocol-machine surface today, but they are not all session-projectable yet.
Timed Choice
A protocol-visible timeout branches the protocol on deadline expiry:
protocol TimedRequest =
roles Client, Server
timeout 5s Client at
Server -> Client : Response
on timeout =>
Client -> Server : Cancel
Use timeout duration Role at when deadline expiry is a protocol-visible
outcome. The runtime enforces the deadline. Projection and theory conversion
currently reject this form until the authority-sensitive lowering rules are
complete, so it is protocol-machine executable but not part of the MPST/common
theory subset yet.
Durations support: ms (milliseconds), s (seconds), m (minutes), h (hours).
Liveness Pattern
The old heartbeat surface is removed from the proof-backed DSL. Model
connection liveness with explicit protocol-visible timeouts plus ordinary
messages:
protocol Liveness =
roles Alice, Bob
Alice -> Bob : Ping
timeout 1s Bob at
Bob -> Alice : Pong
on timeout =>
Bob -> Alice : Disconnect
This keeps liveness handling inside the current verified surface. If you only
need an operational transport hint rather than a protocol branch, use
runtime_timeout sender metadata instead.
Runtime Timeout Metadata
Record metadata can carry transport-level timeout hints:
protocol TimedOps =
roles Client, Server
Client { runtime_timeout : 5s }
-> Server : Request
Server -> Client : Response
Unlike timeout ... on timeout ..., this metadata does not change the session
type. It is a hint to the transport layer and is not verified in Lean. Use it
for operational timeouts when you do not want the protocol to branch on
timeout.
11) Proof Bundles and Semantic Statements
proof_bundle declarations define capability sets. protocol ... requires ...
selects the bundles required by a protocol.
proof_bundle DelegationBase version "1.0.0" issuer "did:example:issuer" constraint "fresh_nonce" requires [delegation, guard_tokens]
proof_bundle KnowledgeBase requires [knowledge_flow]
protocol TransferFlow requires DelegationBase, KnowledgeBase =
roles Coordinator, Worker
let receipt = transfer Session from Coordinator to Worker
publish receipt as DelegationRecorded
materialize delegationProof from DelegationRecorded
handoff acceptInvite to Worker with receipt
Coordinator -> Worker : Commit
The parser stores bundle declarations and required bundle names in typed choreography metadata. Bundle records include optional version, issuer, and repeated constraint fields.
Validation fails on duplicate bundles and missing required bundles. If
requires is omitted and bundle coverage is unambiguous, the parser can infer
required bundles from the semantic statements that remain in the DSL surface.
Legacy protocol-machine-core statements such as acquire, fork, join,
delegate, and tag are removed from the proof-backed DSL and are rejected
fail closed. Keep those instructions in the runtime model rather than in
choreography source.
The accepted DSL still includes protocol-visible semantic statements such as
publish, materialize, handoff, dependent work, begin, await, and
resolve.
Runtime upgrade or reconfiguration statements are not part of the choreography DSL today. They remain explicit runtime/model surfaces until they have a real shared Rust and Lean semantics.
12) Authority and Failure Surface
The authority/failure additions keep the language expression-oriented and Elm/PureScript-like while making protocol-critical host decisions explicit.
Nominal Effect Interfaces and uses
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
observe watchPresence : Session -> PresenceView
{
class : observational
progress : immediate
region : session
agreement_use : forbidden
reentrancy : allow
}
command cancel : Session -> Result CancelError CancelReceipt
{
class : best_effort
progress : immediate
region : session
agreement_use : required
reentrancy : allow
}
effect Audit
command record : AuditEvent -> Unit
{
class : best_effort
progress : immediate
region : session
agreement_use : forbidden
reentrancy : allow
}
protocol CommitFlow uses Runtime, Audit =
roles Coordinator, Worker
authoritative let readiness = check Runtime.ready(session)
Coordinator -> Worker : Commit
Effects are nominal declarations. uses makes protocol dependencies explicit.
Only declared effects named in uses may be referenced by check.
Result and Maybe with case/of
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(TimedOut) =>
Coordinator -> Worker : Cancel
Result and Maybe are first-class sum-style forms for protocol-visible
authority checks. Exhaustiveness is required for Ok/Err and
Just/Nothing.
Custom Unions and Type Aliases
type CommitError =
| TimedOut
| Cancelled
| InvalidWitness
type alias ReadyWitness =
{ epoch : Int, issuedBy : Role }
Use custom unions for typed failure surfaces and aliases for structured evidence payloads.
Evidence Binding with let
observe let presence = observe Runtime.watchPresence(session)
authoritative let readiness = check Runtime.ready(session)
let receipt = transfer Session from Coordinator to Worker
handoff acceptInvite to Worker with receipt
Authority queries use explicit observe let or authoritative let bindings.
Linear transfer receipts still use ordinary let, but the resulting receipt is
a first-class transition artifact rather than an untyped helper value.
Typed Timeout and Cancellation Blocks
timeout 5s Coordinator at
Worker -> Coordinator : Ready(readyWitness)
on timeout =>
Coordinator -> Worker : Cancel
on cancel =>
Coordinator -> Worker : Cancelled
Use timeout ... on timeout ... on cancel ... when timeout and cancellation are
protocol-visible outcomes rather than ambient runtime metadata.
Evidence Guards
choice Coordinator at
| Commit when check Runtime.ready(session) yields witness =>
Coordinator -> Worker : Commit(witness)
| Abort =>
Coordinator -> Worker : Abort
Evidence guards are the concise path for “take this branch only if a typed authoritative query succeeds and binds evidence”.
Linear Single-Use Bindings
let receipt = transfer Session from Coordinator to Worker
handoff acceptInvite to Worker with receipt
Bindings produced by transfer-like authority operations are linear. The compiler rejects duplicate use and implicit discard.
The first-class capability/finalization mapping for this DSL surface is:
- authoritative and observational reads: evidence/finalization inputs
- transfer receipts and handoffs: transition artifacts
- publications, materialization proofs, and canonical handles: evidence-side finalization objects
Scoped Bindings with let ... in
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(reason) =>
Coordinator -> Worker : Abort(reason)
let ... in introduces an indented statement block. Use it to keep authority
logic readable without pushing protocol-critical branching back into untyped
host code.
No Implicit Default Branches
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(TimedOut) =>
Coordinator -> Worker : Cancel
Protocol-critical authority flows do not allow implicit wildcard/default
masking for Result and Maybe. Missing cases are validation errors.
Typed External Query Surface
authoritative let readiness = check Runtime.ready(session)
check Effect.op(...) is the canonical language-level form for typed host
queries, and it must be bound with authoritative let. Observation-only
queries use observe let value = observe Effect.op(...). These user-facing
forms lower to the same typed runtime/effect boundary used by the host bridge.
Choosing Between Timeout Metadata and Timeout Branching
Use runtime_timeout : 5s sender metadata for operational transport hints that
do not change the protocol. Use timeout ... on timeout ... on cancel ... when
the timeout result must be explicit in the protocol and replay/audit surface.
This lowering preserves statement order and continuation structure. Projection skips extension-local behavior for now and continues projecting the remaining protocol.
13) Child-Effect Aggregation
compose ... is a secondary child-effect aggregation clause on an operation.
It does not define distributed agreement by itself. Agreement lives in named
agreement profiles. Child-effect aggregation only says how sibling child
effects roll up underneath that parent agreement.
agreement_profile PendingPublication
visibility pending
rule threshold_participants
usable_at provisional
finalized_at finalized
evidence publication
operation syncMembership(channel : ChannelId) at Worker progress MembershipProgress requires Replay within bounded agreement PendingPublication prestate ChannelMembership compose threshold_success(2) =
publish SyncQueued(channel)
The compose threshold_success(2) clause attaches a child-effect rollup policy to the operation without overriding the parent agreement profile.
Reusable Domain Agreement Profiles
Domain systems should define their own agreement vocabulary as named profiles
over Telltale’s generic core instead of expecting core built-ins such as
quorum or fallback to carry all the meaning.
agreement_profile ContactCeremonySoftSafe
visibility pending
rule aura_soft_safe
usable_at soft_safe
finalized_at finalized
evidence commit_fact
operation addContact(contactId : String) at Coordinator progress ContactProgress requires Replay within bounded agreement ContactCeremonySoftSafe prestate ContactContext compose threshold_success(3) =
publish ContactQueued(contactId)
This keeps Telltale generic:
visibility,usable_at,finalized_at, andevidenceare the core semantic vocabularyContactCeremonySoftSafeandaura_soft_safeare domain-defined namescompose ...only describes child-effect rollup below the agreement contract, not the agreement contract itself
The generated Rust surface preserves those domain names through
Protocol::agreements and Protocol::proof_status, so downstream host code
can keep a natural domain vocabulary without forking the core language model.
14) Role Sets and Topologies
Role sets and topologies are declared at the top level and stored as typed metadata.
role_set Signers = Alice, Bob, Carol
role_set Quorum = subset(Signers, 0..2)
cluster LocalCluster = Signers, Quorum
ring RingNet = Alice, Bob, Carol
mesh FullMesh = Alice, Bob, Carol
These declarations do not change core protocol semantics. They provide structured topology context for tooling and simulation setup.
15) Lowering Diagnostics
Use choreo-fmt --explain-lowering to inspect canonical lowering output.
choreo-fmt --explain-lowering protocol.tell
The report includes proof-bundle metadata, inferred requirements, lowered protocol shape, and lint suggestions.
16) String-based Protocol Definition
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::parser::parse_choreography_str;
let protocol = r#"
protocol PingPong =
roles Alice, Bob
Alice -> Bob : Ping
Bob -> Alice : Pong
"#;
let choreography = parse_choreography_str(protocol)?;
}
This parses a string into a Choreography AST. It is the entry point for runtime parsing.
Namespaced protocols are expressed with a module header.
#![allow(unused)]
fn main() {
let protocol = r#"
module secure_messaging exposing (EncryptedProtocol)
protocol EncryptedProtocol =
roles Sender, Receiver
Sender -> Receiver : SecureMessage
"#;
let choreography = parse_choreography_str(protocol)?;
}
The parser builds the AST for projection, validation, and code generation.
Implementation Details
Parser Stack
- Layout preprocessor converts indentation into explicit block delimiters.
- Pest grammar parses the canonical indentation-based syntax after layout normalization.
- Parser module constructs the AST and runs validation.
Parse Pipeline
- Preprocess layout (indentation ->
{}/()). - Parse with Pest grammar.
- Build statements and normalize
parbranches intoParallel. - Resolve
callreferences and lower protocol-machine-core statements toProtocol::Extension. - Build
Choreographyand attach typed proof-bundle metadata. - Run semantic checks with
choreography.validate()when required by your integration.
API
Primary Functions
parse_choreography_str parses a DSL string into a Choreography AST.
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::parser::parse_choreography_str;
let choreo = parse_choreography_str(r#"
protocol Example =
roles A, B
A -> B : Hello
"#)?;
}
This example parses a DSL string into a Choreography. It uses parse_choreography_str directly.
parse_choreography_file parses a DSL file from disk.
It expects the canonical .tell source extension.
#![allow(unused)]
fn main() {
use std::path::Path;
use telltale_runtime::compiler::parser::parse_choreography_file;
let choreo = parse_choreography_file(Path::new("protocol.tell"))?;
}
This example parses a file path into a Choreography. The file must contain a valid DSL definition.
parse_dsl is an alias for parse_choreography_str.
Error Handling
#![allow(unused)]
fn main() {
match parse_choreography_str(input) {
Ok(choreo) => { /* use choreography */ }
Err(ParseError::UndefinedRole { role, .. }) => {
eprintln!("Role '{}' used but not declared", role);
}
Err(ParseError::DuplicateRole { role, .. }) => {
eprintln!("Role '{}' declared multiple times", role);
}
Err(ParseError::Syntax { span, message }) => {
eprintln!("Syntax error at {}:{}: {}", span.line, span.column, message);
}
Err(e) => {
eprintln!("Parse error: {}", e);
}
}
}
This pattern matches common parse errors. It formats diagnostics with the reported context.
Grammar Details
Tokens and Keywords
- Identifiers:
[a-zA-Z][a-zA-Z0-9_]* - Integers:
[0-9]+ - Strings:
"..."(used inloop while) - Keywords:
protocol,roles,case,of,choice,at,loop,decide,by,repeat,while,forever,par,rec,continue,call,where,module,import,exposing,proof_bundle,requires,acquire,release,fork,join,abort,transfer,delegate,tag,check,using,into,as,to,from,with,bundle,version,issuer,constraint,role_set,subset,cluster,ring,mesh,let,in,type,alias,effect,uses,timeout,on,cancel,when,yields,heartbeat,every,on_missing,body,observe,authoritative,command,Ok,Err,Just,Nothing,Result,Maybe,Unit,publish,handoff,dependent,work,required,for,fragment,operation,within,guest,runtime,entry - Operators:
->,->*,<->,:,=>,=,..,{},(),[],,,|
Comments
Single-line comments use --. Multi-line comments use {- ... -}.
-- This is a single-line comment
{- This is a
multi-line comment -}
These comments are ignored by the parser. They use the same syntax as Haskell and PureScript.
Whitespace and Layout
Indentation defines blocks. Use {} to force an empty block or to opt out of layout. Parenthesized blocks must be non-empty.
Validation
The parser validates role declarations and parallel branch structure. choreography.validate() also validates proof-bundle declarations, required bundle references, and protocol-machine-core capability coverage.
Error Messages
Example undefined role error:
Undefined role 'Charlie'
--> input:5:14
|
5 | Alice -> Charlie : Hello
^^^^^^^
This error indicates a role that was not declared in roles. The location points to the undefined identifier.
Example duplicate role error:
Duplicate role declaration 'Alice'
--> input:3:33
|
3 | roles Alice, Bob, Charlie, Alice
^^^^^
This error indicates that a role name appears more than once. The location points to the duplicate entry.
Examples
Simple Two-Party Protocol
protocol PingPong =
roles Alice, Bob
Alice
-> Bob : Ping
Bob
-> Alice : Pong
This example shows a simple two role protocol. It uses a single send and reply.
Protocol with Choice
protocol Negotiation =
roles Buyer, Seller
Buyer
-> Seller : Offer
choice Seller at
| Accept =>
Seller
-> Buyer : Accept
| Reject =>
Seller
-> Buyer : Reject
This example shows an explicit choice decided by Seller. Each branch starts with a send from the deciding role.
Complex E-Commerce Protocol
protocol ECommerce =
roles Buyer, Seller, Shipper
Buyer
-> Seller : Inquiry of commerce.Inquiry
Seller
-> Buyer : Quote of commerce.Quote
choice Buyer at
| Order =>
Buyer
-> Seller : Order of commerce.Order
Seller
-> Shipper : ShipRequest of logistics.ShipRequest
Shipper
-> Buyer : Tracking of logistics.Tracking
loop decide by Buyer
Buyer
-> Shipper : StatusCheck
Shipper
-> Buyer : StatusUpdate
Shipper
-> Buyer : Delivered
Buyer
-> Seller : Confirmation
| Cancel =>
Buyer
-> Seller : Cancel
This example combines choice and looping. It models a longer interaction with a buyer controlled loop.
Parallel Example
protocol ParallelDemo =
roles A, B, C, D
par
| A -> B : Msg1
| C -> D : Msg2
This example uses par with leading | branches. Each branch defines a parallel sub protocol.
par remains protocol-machine executable and session-projectable, but it is not
part of the theory-convertible subset tracked by language_tier_status().
Integration
With Projection
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::{parser, projection};
let choreo = parser::parse_choreography_str(input)?;
for role in &choreo.roles {
let local_type = projection::project(&choreo, role)?;
println!("Local type for {}: {:?}", role.name(), local_type);
}
}
This projects the global protocol to a local type for each role. The result can be used for type driven code generation.
With Code Generation
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::{parser, projection, codegen};
let choreo = parser::parse_choreography_str(input)?;
let mut local_types = Vec::new();
for role in &choreo.roles {
let local_type = projection::project(&choreo, role)?;
local_types.push((role.clone(), local_type));
}
let code = codegen::generate_choreography_code(
&choreo.name.to_string(),
&choreo.roles,
&local_types,
);
}
This generates Rust code for the choreography. The generated code includes session types and role APIs.
Testing
Run parser tests with:
cargo test --package telltale-runtime parser
This runs the parser test suite for the choreography crate. It exercises grammar and layout handling.