dialectic_compiler/
syntax.rs

1//! The abstract surface syntax for the `Session!` macro, produced by the parser.
2
3use {
4    proc_macro2::TokenStream,
5    quote::{quote_spanned, ToTokens},
6    syn::{Error, Lifetime, Type},
7    thunderdome::Index,
8};
9
10use crate::{
11    cfg::{Cfg, Ir},
12    target::Target,
13    CompileError, Spanned,
14};
15
16/// A shim for parsing the root level of a macro invocation, so that a session type may be written
17/// as a block w/o an extra layer of braces.
18#[derive(Debug, Clone)]
19pub struct Invocation {
20    /// The spanned syntax representing a full invocation of the macro.
21    pub syntax: Spanned<Syntax>,
22}
23
24impl Invocation {
25    /// Compile this invocation into the corresponding target syntax.
26    pub fn compile(&self) -> Result<Spanned<Target>, Error> {
27        compile(&self.syntax)
28    }
29}
30
31/// The surface syntax for a macro invocation: a single statement-like item, or a block of them.
32///
33/// While the [`Target`] of the compiler (and its corresponding types in the Dialectic library) have
34/// continuations for almost every expression, the surface syntax is not in continuation passing
35/// style, instead encoding sequences of operations using the `;` operator within blocks.
36#[derive(Debug, Clone)]
37pub enum Syntax {
38    /// Syntax: `recv T`.
39    Recv(Type),
40    /// Syntax: `send T`.
41    Send(Type),
42    /// Syntax: `call T` or `call { ... }`.
43    Call(Box<Spanned<Syntax>>),
44    /// Syntax: `choose { 0 => ..., ... }`.
45    Choose(Vec<Spanned<Syntax>>),
46    /// Syntax: `offer { 0 => ..., ... }`.
47    Offer(Vec<Spanned<Syntax>>),
48    /// Syntax: `split { -> ..., <- ... }`.
49    Split {
50        /// The transmit-only half.
51        tx_only: Box<Spanned<Syntax>>,
52        /// The receive-only half.
53        rx_only: Box<Spanned<Syntax>>,
54    },
55    /// Syntax: `loop { ... }` or `'label loop { ... }`.
56    Loop(Option<String>, Box<Spanned<Syntax>>),
57    /// Syntax: `break` or `break 'label`.
58    Break(Option<String>),
59    /// Syntax: `continue` or `continue 'label`.
60    Continue(Option<String>),
61    /// Syntax: `{ ... }`
62    Block(Vec<Spanned<Syntax>>),
63    /// Syntax: `T`.
64    Type(Type),
65}
66
67impl Syntax {
68    /// Construct a [`Syntax::Recv`] from a string representing a type.
69    ///
70    /// # Panics
71    ///
72    /// If the type does not parse correctly, this panics.
73    pub fn recv(ty: &str) -> Self {
74        Syntax::Recv(syn::parse_str(ty).unwrap())
75    }
76
77    /// Construct a [`Syntax::Send`] from a string representing a type.
78    ///
79    /// # Panics
80    ///
81    /// If the type does not parse correctly, this panics.
82    pub fn send(ty: &str) -> Self {
83        Syntax::Send(syn::parse_str(ty).unwrap())
84    }
85
86    /// Construct a [`Syntax::Call`] from its inner callee.
87    pub fn call(callee: impl Into<Spanned<Syntax>>) -> Self {
88        Syntax::Call(Box::new(callee.into()))
89    }
90
91    /// Construct a [`Syntax::Loop`] from its (optional) label and its body.
92    pub fn loop_(label: Option<String>, body: impl Into<Spanned<Syntax>>) -> Self {
93        Syntax::Loop(label, Box::new(body.into()))
94    }
95
96    /// Construct a [`Syntax::Type`] from a string representing a type.
97    ///
98    /// # Panics
99    ///
100    /// If the type does not parse correctly, this panics.
101    pub fn type_(ty: &str) -> Self {
102        Syntax::Type(syn::parse_str(ty).unwrap())
103    }
104}
105
106/// Compile a spanned syntax tree into either a representation of a valid session type
107/// [`Target`], or an [`Error`].
108pub fn compile(syntax: &Spanned<Syntax>) -> Result<Spanned<Target>, Error> {
109    let mut cfg = Cfg::new();
110    let head = to_cfg(syntax, &mut cfg, &mut Vec::new()).0;
111    cfg.resolve_scopes(head);
112    cfg.report_dead_code(head);
113    cfg.generate_target(head)
114}
115
116/// Converts surface [`Syntax`] to a control-flow graph intermediate representation, suitable
117/// for further analysis and error reporting.
118///
119/// It should be passed the empty `Cfg` and empty environment to start, as it adds to these during
120/// recursive calls.
121fn to_cfg<'a>(
122    syntax: &'a Spanned<Syntax>,
123    cfg: &mut Cfg,
124    env: &mut Vec<(&'a Option<String>, Index)>,
125) -> (Option<Index>, Option<Index>) {
126    // Helper function for converting break and continue nodes to CFG nodes. This is here
127    // because the cases for `Break` and `Continue` are 100% identical minus the emitted errors
128    // and constructed IR node variants; so this function extracts that logic and parameterizes
129    // it over the error emitted in the case that the jump node exists outside of a loop as well
130    // as the constructor (which is always of the type `fn(Index) -> Ir`.)
131    let mut convert_jump_to_cfg = |label: &Option<String>,
132                                   outside_loop_error: CompileError,
133                                   constructor: fn(Index) -> Ir|
134     -> (Option<Index>, Option<Index>) {
135        let node = match env.last() {
136            // When not inside a loop at all:
137            None => cfg.create_error(outside_loop_error, syntax.span),
138            // When inside a loop:
139            Some((_, index)) => match label {
140                // With an unlabeled break/continue:
141                None => cfg.spanned(constructor(*index), syntax.span),
142                // With a labeled break/continue:
143                Some(label) => {
144                    let found_index = env.iter().rev().find(|&l| l.0.as_ref() == Some(label));
145                    match found_index {
146                        // If label cannot be found:
147                        None => cfg.create_error(
148                            CompileError::UndeclaredLabel(label.clone()),
149                            syntax.span,
150                        ),
151                        // If label is found:
152                        Some((_, index)) => cfg.spanned(constructor(*index), syntax.span),
153                    }
154                }
155            },
156        };
157
158        (Some(node), Some(node))
159    };
160
161    use Syntax::*;
162    let ir = match &syntax.inner {
163        Recv(ty) => Ir::Recv(ty.clone()),
164        Send(ty) => Ir::Send(ty.clone()),
165        Type(ty) => Ir::Type(ty.clone()),
166        Call(callee) => {
167            let callee_node = to_cfg(callee, cfg, env).0;
168            Ir::Call(callee_node)
169        }
170        Split { tx_only, rx_only } => {
171            let tx_only = to_cfg(tx_only, cfg, env).0;
172            let rx_only = to_cfg(rx_only, cfg, env).0;
173            Ir::Split { tx_only, rx_only }
174        }
175        Choose(choices) => {
176            let choice_nodes = choices
177                .iter()
178                .map(|choice| to_cfg(choice, cfg, env).0)
179                .collect();
180            Ir::Choose(choice_nodes)
181        }
182        Offer(choices) => {
183            let choice_nodes = choices
184                .iter()
185                .map(|choice| to_cfg(choice, cfg, env).0)
186                .collect();
187            Ir::Offer(choice_nodes)
188        }
189        Continue(label) => {
190            return convert_jump_to_cfg(label, CompileError::ContinueOutsideLoop, Ir::Continue)
191        }
192        Break(label) => {
193            return convert_jump_to_cfg(label, CompileError::BreakOutsideLoop, Ir::Break)
194        }
195        Loop(maybe_label, body) => {
196            // Constructing a loop node is a kind of fixed-point operation, where any break and
197            // continue nodes within need to know the index of their respective loop node. To
198            // solve this, we create an empty loop and use its index to hold the places of the
199            // data any break or continue needs, and then assign the correct `Ir::Loop(head)`
200            // value later.
201            let ir_node = cfg.spanned(Ir::Loop(None), syntax.span);
202
203            // Convert the body in the environment with this loop label
204            env.push((maybe_label, ir_node)); // NOTE: this is the only `push` in this function!
205            let head = to_cfg(body, cfg, env).0;
206            let _ = env.pop(); // NOTE: this is the only `pop` in this function!
207
208            // Close out that fixed point; the loop block is now correctly built.
209            cfg[ir_node].expr = Ir::Loop(head);
210
211            // Check to ensure the environment does not already contain this label. If it does,
212            // keep going, but insert an error on the relevant loop node.
213            if maybe_label.is_some() && env.iter().any(|scope| scope.0 == maybe_label) {
214                cfg.insert_error_at(
215                    ir_node,
216                    CompileError::ShadowedLabel(maybe_label.clone().unwrap()),
217                );
218            }
219
220            // Because we already know the index we must return and cannot allow another index
221            // to be created for this node, we have to early-return here.
222            return (Some(ir_node), Some(ir_node));
223        }
224        Block(statements) => {
225            // Connect the continuations of each statement in the block to the subsequent
226            // statement in the block, by inserting them in reverse order into the graph
227            let mut next_head = None;
228            let mut next_tail = None;
229            for stmt in statements.iter().rev() {
230                let (head, tail) = to_cfg(stmt, cfg, env);
231                next_tail = next_tail.or(tail);
232
233                if let Some(tail) = tail {
234                    cfg[tail].next = next_head
235                }
236
237                next_head = head;
238            }
239
240            // Only case where we return a differing head and tail, since a `Block` is the only
241            // expression to be compiled into multiple nodes
242            return (next_head, next_tail);
243        }
244    };
245
246    let node = cfg.spanned(ir, syntax.span);
247    (Some(node), Some(node))
248}
249
250impl Spanned<Syntax> {
251    /// Convenience function for converting a `Spanned<Syntax>` into a `TokenStream` via
252    /// [`Spanned::to_tokens_with`].
253    pub fn to_token_stream_with<F: ?Sized>(&self, add_optional: &mut F) -> TokenStream
254    where
255        F: FnMut() -> bool,
256    {
257        let mut acc = TokenStream::new();
258        self.to_tokens_with(add_optional, &mut acc);
259        acc
260    }
261
262    /// Convert a `Spanned<Syntax>` into tokens and append them to the provided token stream, with a
263    /// predicate to determine whether or not optional syntax should be added or not.
264    ///
265    /// The added optional syntax predicate is an `FnMut` closure so that we can randomly choose to
266    /// add or not add optional trailing commas and similar during testing.
267    pub fn to_tokens_with<F: ?Sized>(&self, mut add_optional: &mut F, tokens: &mut TokenStream)
268    where
269        F: FnMut() -> bool,
270    {
271        use Syntax::*;
272
273        let sp = self.span;
274
275        let choice_arms_to_tokens =
276            |add_optional: &mut dyn FnMut() -> bool, arms: &[Spanned<Syntax>]| -> TokenStream {
277                let mut acc = TokenStream::new();
278                for (i, choice) in arms.iter().enumerate() {
279                    quote_spanned!(sp=> #i => ).to_tokens(&mut acc);
280                    choice.to_tokens_with(add_optional, &mut acc);
281                    if i < arms.len() - 1 || add_optional() {
282                        quote_spanned!(sp=> ,).to_tokens(&mut acc);
283                    }
284                }
285                acc
286            };
287
288        match &self.inner {
289            Recv(t) => quote_spanned! {sp=> recv #t },
290            Send(t) => quote_spanned! {sp=> send #t },
291            Call(callee) => {
292                let mut acc = TokenStream::new();
293                quote_spanned!(sp=> call ).to_tokens(&mut acc);
294
295                if matches!(callee.inner, Block(_) | Type(_)) {
296                    callee.to_tokens_with(add_optional, &mut acc);
297                } else {
298                    let callee_tokens = callee.to_token_stream_with(add_optional);
299                    quote_spanned!(sp=> { #callee_tokens }).to_tokens(&mut acc);
300                }
301
302                acc
303            }
304            Split { tx_only, rx_only } => {
305                let tx = tx_only.to_token_stream_with(add_optional);
306                let rx = rx_only.to_token_stream_with(add_optional);
307                quote_spanned! {sp=> split { -> #tx, <- #rx, } }
308            }
309            Choose(choices) => {
310                let arms = choice_arms_to_tokens(&mut add_optional, choices);
311                quote_spanned! {sp=> choose { #arms } }
312            }
313            Offer(choices) => {
314                let arms = choice_arms_to_tokens(&mut add_optional, choices);
315                quote_spanned! {sp=> offer { #arms } }
316            }
317            Loop(None, body) => {
318                let body_tokens = body.to_token_stream_with(add_optional);
319                quote_spanned! {sp=> loop #body_tokens }
320            }
321            Loop(Some(label), body) => {
322                let lt = Lifetime::new(&format!("'{}", label), sp);
323                let body_tokens = body.to_token_stream_with(add_optional);
324                quote_spanned! {sp=> #lt: loop #body_tokens }
325            }
326            Break(None) => quote_spanned! {sp=> break },
327            Break(Some(label)) => {
328                let lt = Lifetime::new(&format!("'{}", label), sp);
329                quote_spanned! {sp=> break #lt }
330            }
331            Continue(None) => quote_spanned! {sp=> continue },
332            Continue(Some(label)) => {
333                let lt = Lifetime::new(&format!("'{}", label), sp);
334                quote_spanned! {sp=> continue #lt }
335            }
336            Block(stmts) => {
337                let mut acc = TokenStream::new();
338
339                // We want to separate the last statement from the rest so that we can decide
340                // whether or not to put an (optional) terminator on it.
341                if let Some((last, up_to_penultimate)) = stmts.split_last() {
342                    for stmt in up_to_penultimate {
343                        stmt.to_tokens_with(add_optional, &mut acc);
344
345                        // If the statement is a call w/ a block argument then a semicolon is
346                        // optional, rather than required.
347                        let is_call_of_block = matches!(
348                            &stmt.inner,
349                            Call(callee) if matches!(&callee.inner, Block(_)),
350                        );
351
352                        // If the statement is a block, split, offer, choose, or loop construct,
353                        // then it ends with a block/brace, and therefore a semicolon is optional,
354                        // rather than required.
355                        let ends_with_block = matches!(
356                            &stmt.inner,
357                            Block(_) | Split { .. } | Offer(_) | Choose(_) | Loop(_, _),
358                        );
359
360                        if !(is_call_of_block || ends_with_block) || add_optional() {
361                            quote_spanned!(sp=> ;).to_tokens(&mut acc);
362                        }
363                    }
364
365                    last.to_tokens_with(add_optional, &mut acc);
366
367                    if add_optional() {
368                        quote_spanned!(sp=> ;).to_tokens(&mut acc);
369                    }
370                }
371
372                quote_spanned!(sp=> { #acc })
373            }
374            Type(ty) => quote_spanned! {sp=> #ty },
375        }
376        .to_tokens(tokens);
377    }
378}
379
380impl ToTokens for Spanned<Syntax> {
381    fn to_tokens(&self, tokens: &mut TokenStream) {
382        self.to_tokens_with(&mut || false, tokens);
383    }
384}
385
386#[cfg(feature = "quickcheck")]
387mod tests {
388    use super::*;
389
390    use {
391        quickcheck::{Arbitrary, Gen},
392        syn::parse_quote,
393    };
394
395    #[derive(Debug, Clone)]
396    struct Label(String);
397
398    impl Arbitrary for Label {
399        fn arbitrary(g: &mut Gen) -> Self {
400            let s = *g
401                .choose(&[
402                    "foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply", "waldo",
403                    "fred", "plugh", "xyzzy", "thud", "wibble", "wobble", "wubble", "flob",
404                ])
405                .unwrap();
406            Label(s.to_owned())
407        }
408    }
409
410    /// Currently, we have a bit of a limitation on the arbitrary impl for syntax. The first part of
411    /// this is that it's annoying to generate valid labels for lifetimes, so for now we're using a
412    /// set of known valid lifetime names. The second is that generating valid Rust types is
413    /// somewhat nontrivial and the syn crate does not have a way to ask it to implement `Arbitrary`
414    /// for its `Type` AST node. So currently we just use the unit type `()` wherever we may need to
415    /// place a type.
416    impl Arbitrary for Spanned<Syntax> {
417        fn arbitrary(g: &mut Gen) -> Self {
418            use Syntax::*;
419            // Ensure that the size of the syntax tree strictly decreases as we progress. This makes
420            // sure that the generator size parameter decreases by one, down to a minimum of 1 (if
421            // the generator size becomes zero, quickcheck will panic.)
422            let g = &mut Gen::new(g.size().max(2) - 1);
423            let syntax = match *g
424                .choose(&[
425                    "recv", "send", "call", "choose", "offer", "split", "loop", "break",
426                    "continue", "block", "type",
427                ])
428                .unwrap()
429            {
430                "recv" => Recv(parse_quote!(())),
431                "send" => Send(parse_quote!(())),
432                // During parsing, call nodes are only allowed to contain blocks or types, and
433                // anything else triggers a parse-time error. We want to generate something which
434                // could conceivably come from a valid parse operation, so we must limit our call
435                // nodes to be either blocks or types.
436                "call" => match *g.choose(&["block", "type"]).unwrap() {
437                    "block" => Call(Box::new(Spanned::from(Block(Arbitrary::arbitrary(g))))),
438                    "type" => Type(parse_quote!(())),
439                    other => unreachable!("{}", other),
440                },
441                "choose" => Choose(Arbitrary::arbitrary(g)),
442                "offer" => Offer(Arbitrary::arbitrary(g)),
443                "split" => Split {
444                    tx_only: Arbitrary::arbitrary(g),
445                    rx_only: Arbitrary::arbitrary(g),
446                },
447                "loop" => Loop(
448                    <Option<Label>>::arbitrary(g).map(|l| l.0),
449                    Box::new(Spanned::from(Block(Arbitrary::arbitrary(g)))),
450                ),
451                "break" => Break(<Option<Label>>::arbitrary(g).map(|l| l.0)),
452                "continue" => Continue(<Option<Label>>::arbitrary(g).map(|l| l.0)),
453                "block" => Block(Arbitrary::arbitrary(g)),
454                "type" => Type(parse_quote!(())),
455                other => unreachable!("{}", other),
456            };
457
458            Spanned::from(syntax)
459        }
460
461        // We need to be careful here not to shrink the labels because shrinking a string can result
462        // in the empty string, which will panic when we try to construct a syn `Lifetime` from it.
463        fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
464            use Syntax::*;
465            let span = self.span;
466            let v = match &self.inner {
467                Recv(_) | Send(_) | Type(_) | Break(None) | Continue(None) => vec![],
468                Call(callee) => callee.shrink().map(Call).collect(),
469                Split { tx_only, rx_only } => tx_only
470                    .shrink()
471                    .flat_map(|tx_shrunk| {
472                        rx_only.shrink().map(move |rx_shrunk| Split {
473                            tx_only: tx_shrunk.clone(),
474                            rx_only: rx_shrunk,
475                        })
476                    })
477                    .collect(),
478                Choose(choices) => choices.shrink().map(Choose).collect(),
479                Offer(choices) => choices.shrink().map(Offer).collect(),
480                Loop(label, body) => body
481                    .shrink()
482                    .map(|body_shrunk| Loop(label.clone(), body_shrunk))
483                    .chain(label.as_ref().map(|_| Loop(None, body.clone())))
484                    .collect(),
485                Block(stmts) => stmts.shrink().map(Block).collect(),
486                Break(Some(_)) => vec![Break(None)],
487                Continue(Some(_)) => vec![Continue(None)],
488            };
489
490            Box::new(v.into_iter().map(move |inner| Spanned { inner, span }))
491        }
492    }
493}