use {
proc_macro2::TokenStream,
quote::{quote_spanned, ToTokens},
syn::{Error, Lifetime, Type},
thunderdome::Index,
};
use crate::{
cfg::{Cfg, Ir},
target::Target,
CompileError, Spanned,
};
#[derive(Debug, Clone)]
pub struct Invocation {
pub syntax: Spanned<Syntax>,
}
impl Invocation {
pub fn compile(&self) -> Result<Spanned<Target>, Error> {
compile(&self.syntax)
}
}
#[derive(Debug, Clone)]
pub enum Syntax {
Recv(Type),
Send(Type),
Call(Box<Spanned<Syntax>>),
Choose(Vec<Spanned<Syntax>>),
Offer(Vec<Spanned<Syntax>>),
Split {
tx_only: Box<Spanned<Syntax>>,
rx_only: Box<Spanned<Syntax>>,
},
Loop(Option<String>, Box<Spanned<Syntax>>),
Break(Option<String>),
Continue(Option<String>),
Block(Vec<Spanned<Syntax>>),
Type(Type),
}
impl Syntax {
pub fn recv(ty: &str) -> Self {
Syntax::Recv(syn::parse_str(ty).unwrap())
}
pub fn send(ty: &str) -> Self {
Syntax::Send(syn::parse_str(ty).unwrap())
}
pub fn call(callee: impl Into<Spanned<Syntax>>) -> Self {
Syntax::Call(Box::new(callee.into()))
}
pub fn loop_(label: Option<String>, body: impl Into<Spanned<Syntax>>) -> Self {
Syntax::Loop(label, Box::new(body.into()))
}
pub fn type_(ty: &str) -> Self {
Syntax::Type(syn::parse_str(ty).unwrap())
}
}
pub fn compile(syntax: &Spanned<Syntax>) -> Result<Spanned<Target>, Error> {
let mut cfg = Cfg::new();
let head = to_cfg(syntax, &mut cfg, &mut Vec::new()).0;
cfg.resolve_scopes(head);
cfg.report_dead_code(head);
cfg.generate_target(head)
}
fn to_cfg<'a>(
syntax: &'a Spanned<Syntax>,
cfg: &mut Cfg,
env: &mut Vec<(&'a Option<String>, Index)>,
) -> (Option<Index>, Option<Index>) {
let mut convert_jump_to_cfg = |label: &Option<String>,
outside_loop_error: CompileError,
constructor: fn(Index) -> Ir|
-> (Option<Index>, Option<Index>) {
let node = match env.last() {
None => cfg.create_error(outside_loop_error, syntax.span),
Some((_, index)) => match label {
None => cfg.spanned(constructor(*index), syntax.span),
Some(label) => {
let found_index = env.iter().rev().find(|&l| l.0.as_ref() == Some(label));
match found_index {
None => cfg.create_error(
CompileError::UndeclaredLabel(label.clone()),
syntax.span,
),
Some((_, index)) => cfg.spanned(constructor(*index), syntax.span),
}
}
},
};
(Some(node), Some(node))
};
use Syntax::*;
let ir = match &syntax.inner {
Recv(ty) => Ir::Recv(ty.clone()),
Send(ty) => Ir::Send(ty.clone()),
Type(ty) => Ir::Type(ty.clone()),
Call(callee) => {
let callee_node = to_cfg(callee, cfg, env).0;
Ir::Call(callee_node)
}
Split { tx_only, rx_only } => {
let tx_only = to_cfg(tx_only, cfg, env).0;
let rx_only = to_cfg(rx_only, cfg, env).0;
Ir::Split { tx_only, rx_only }
}
Choose(choices) => {
let choice_nodes = choices
.iter()
.map(|choice| to_cfg(choice, cfg, env).0)
.collect();
Ir::Choose(choice_nodes)
}
Offer(choices) => {
let choice_nodes = choices
.iter()
.map(|choice| to_cfg(choice, cfg, env).0)
.collect();
Ir::Offer(choice_nodes)
}
Continue(label) => {
return convert_jump_to_cfg(label, CompileError::ContinueOutsideLoop, Ir::Continue)
}
Break(label) => {
return convert_jump_to_cfg(label, CompileError::BreakOutsideLoop, Ir::Break)
}
Loop(maybe_label, body) => {
let ir_node = cfg.spanned(Ir::Loop(None), syntax.span);
env.push((maybe_label, ir_node));
let head = to_cfg(body, cfg, env).0;
let _ = env.pop();
cfg[ir_node].expr = Ir::Loop(head);
if maybe_label.is_some() && env.iter().any(|scope| scope.0 == maybe_label) {
cfg.insert_error_at(
ir_node,
CompileError::ShadowedLabel(maybe_label.clone().unwrap()),
);
}
return (Some(ir_node), Some(ir_node));
}
Block(statements) => {
let mut next_head = None;
let mut next_tail = None;
for stmt in statements.iter().rev() {
let (head, tail) = to_cfg(stmt, cfg, env);
next_tail = next_tail.or(tail);
if let Some(tail) = tail {
cfg[tail].next = next_head
}
next_head = head;
}
return (next_head, next_tail);
}
};
let node = cfg.spanned(ir, syntax.span);
(Some(node), Some(node))
}
impl Spanned<Syntax> {
pub fn to_token_stream_with<F: ?Sized>(&self, add_optional: &mut F) -> TokenStream
where
F: FnMut() -> bool,
{
let mut acc = TokenStream::new();
self.to_tokens_with(add_optional, &mut acc);
acc
}
pub fn to_tokens_with<F: ?Sized>(&self, mut add_optional: &mut F, tokens: &mut TokenStream)
where
F: FnMut() -> bool,
{
use Syntax::*;
let sp = self.span;
let choice_arms_to_tokens =
|add_optional: &mut dyn FnMut() -> bool, arms: &[Spanned<Syntax>]| -> TokenStream {
let mut acc = TokenStream::new();
for (i, choice) in arms.iter().enumerate() {
quote_spanned!(sp=> #i => ).to_tokens(&mut acc);
choice.to_tokens_with(add_optional, &mut acc);
if i < arms.len() - 1 || add_optional() {
quote_spanned!(sp=> ,).to_tokens(&mut acc);
}
}
acc
};
match &self.inner {
Recv(t) => quote_spanned! {sp=> recv #t },
Send(t) => quote_spanned! {sp=> send #t },
Call(callee) => {
let mut acc = TokenStream::new();
quote_spanned!(sp=> call ).to_tokens(&mut acc);
if matches!(callee.inner, Block(_) | Type(_)) {
callee.to_tokens_with(add_optional, &mut acc);
} else {
let callee_tokens = callee.to_token_stream_with(add_optional);
quote_spanned!(sp=> { #callee_tokens }).to_tokens(&mut acc);
}
acc
}
Split { tx_only, rx_only } => {
let tx = tx_only.to_token_stream_with(add_optional);
let rx = rx_only.to_token_stream_with(add_optional);
quote_spanned! {sp=> split { -> #tx, <- #rx, } }
}
Choose(choices) => {
let arms = choice_arms_to_tokens(&mut add_optional, choices);
quote_spanned! {sp=> choose { #arms } }
}
Offer(choices) => {
let arms = choice_arms_to_tokens(&mut add_optional, choices);
quote_spanned! {sp=> offer { #arms } }
}
Loop(None, body) => {
let body_tokens = body.to_token_stream_with(add_optional);
quote_spanned! {sp=> loop #body_tokens }
}
Loop(Some(label), body) => {
let lt = Lifetime::new(&format!("'{}", label), sp);
let body_tokens = body.to_token_stream_with(add_optional);
quote_spanned! {sp=> #lt: loop #body_tokens }
}
Break(None) => quote_spanned! {sp=> break },
Break(Some(label)) => {
let lt = Lifetime::new(&format!("'{}", label), sp);
quote_spanned! {sp=> break #lt }
}
Continue(None) => quote_spanned! {sp=> continue },
Continue(Some(label)) => {
let lt = Lifetime::new(&format!("'{}", label), sp);
quote_spanned! {sp=> continue #lt }
}
Block(stmts) => {
let mut acc = TokenStream::new();
if let Some((last, up_to_penultimate)) = stmts.split_last() {
for stmt in up_to_penultimate {
stmt.to_tokens_with(add_optional, &mut acc);
let is_call_of_block = matches!(
&stmt.inner,
Call(callee) if matches!(&callee.inner, Block(_)),
);
let ends_with_block = matches!(
&stmt.inner,
Block(_) | Split { .. } | Offer(_) | Choose(_) | Loop(_, _),
);
if !(is_call_of_block || ends_with_block) || add_optional() {
quote_spanned!(sp=> ;).to_tokens(&mut acc);
}
}
last.to_tokens_with(add_optional, &mut acc);
if add_optional() {
quote_spanned!(sp=> ;).to_tokens(&mut acc);
}
}
quote_spanned!(sp=> { #acc })
}
Type(ty) => quote_spanned! {sp=> #ty },
}
.to_tokens(tokens);
}
}
impl ToTokens for Spanned<Syntax> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.to_tokens_with(&mut || false, tokens);
}
}
#[cfg(feature = "quickcheck")]
mod tests {
use super::*;
use {
quickcheck::{Arbitrary, Gen},
syn::parse_quote,
};
#[derive(Debug, Clone)]
struct Label(String);
impl Arbitrary for Label {
fn arbitrary(g: &mut Gen) -> Self {
let s = *g
.choose(&[
"foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply", "waldo",
"fred", "plugh", "xyzzy", "thud", "wibble", "wobble", "wubble", "flob",
])
.unwrap();
Label(s.to_owned())
}
}
impl Arbitrary for Spanned<Syntax> {
fn arbitrary(g: &mut Gen) -> Self {
use Syntax::*;
let g = &mut Gen::new(g.size().max(2) - 1);
let syntax = match *g
.choose(&[
"recv", "send", "call", "choose", "offer", "split", "loop", "break",
"continue", "block", "type",
])
.unwrap()
{
"recv" => Recv(parse_quote!(())),
"send" => Send(parse_quote!(())),
"call" => match *g.choose(&["block", "type"]).unwrap() {
"block" => Call(Box::new(Spanned::from(Block(Arbitrary::arbitrary(g))))),
"type" => Type(parse_quote!(())),
other => unreachable!("{}", other),
},
"choose" => Choose(Arbitrary::arbitrary(g)),
"offer" => Offer(Arbitrary::arbitrary(g)),
"split" => Split {
tx_only: Arbitrary::arbitrary(g),
rx_only: Arbitrary::arbitrary(g),
},
"loop" => Loop(
<Option<Label>>::arbitrary(g).map(|l| l.0),
Box::new(Spanned::from(Block(Arbitrary::arbitrary(g)))),
),
"break" => Break(<Option<Label>>::arbitrary(g).map(|l| l.0)),
"continue" => Continue(<Option<Label>>::arbitrary(g).map(|l| l.0)),
"block" => Block(Arbitrary::arbitrary(g)),
"type" => Type(parse_quote!(())),
other => unreachable!("{}", other),
};
Spanned::from(syntax)
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
use Syntax::*;
let span = self.span;
let v = match &self.inner {
Recv(_) | Send(_) | Type(_) | Break(None) | Continue(None) => vec![],
Call(callee) => callee.shrink().map(Call).collect(),
Split { tx_only, rx_only } => tx_only
.shrink()
.flat_map(|tx_shrunk| {
rx_only.shrink().map(move |rx_shrunk| Split {
tx_only: tx_shrunk.clone(),
rx_only: rx_shrunk,
})
})
.collect(),
Choose(choices) => choices.shrink().map(Choose).collect(),
Offer(choices) => choices.shrink().map(Offer).collect(),
Loop(label, body) => body
.shrink()
.map(|body_shrunk| Loop(label.clone(), body_shrunk))
.chain(label.as_ref().map(|_| Loop(None, body.clone())))
.collect(),
Block(stmts) => stmts.shrink().map(Block).collect(),
Break(Some(_)) => vec![Break(None)],
Continue(Some(_)) => vec![Continue(None)],
};
Box::new(v.into_iter().map(move |inner| Spanned { inner, span }))
}
}
}