Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions shared/yeast-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,34 +104,44 @@ pub fn query(input: TokenStream) -> TokenStream {
/// top-level choice passed to `tree!` returns `Option<OutputId>`; ordinary
/// templates continue to return `OutputId`.
///
/// Uses the `BuildCtx` binding named `ctx` from the surrounding scope:
///
/// ```text
/// tree!((kind ...))
/// ```
#[doc(hidden)]
#[proc_macro]
pub fn tree(input: TokenStream) -> TokenStream {
pub fn tree_with_schema(input: TokenStream) -> TokenStream {
let input2: TokenStream2 = input.into();
match parse::parse_tree_top(input2) {
match parse::parse_tree_with_schema_top(input2) {
Ok(output) => output.into(),
Err(err) => err.to_compile_error().into(),
}
}

/// Build a list of AST nodes from a template, returning `Vec<OutputId>`.
///
/// Like `tree!` but returns `Vec<OutputId>` and supports multiple top-level
/// elements. All syntax from `tree!` is available.
#[doc(hidden)]
#[proc_macro]
pub fn trees_with_schema(input: TokenStream) -> TokenStream {
let input2: TokenStream2 = input.into();
match parse::parse_trees_with_schema_top(input2) {
Ok(output) => output.into(),
Err(err) => err.to_compile_error().into(),
}
}

/// Declare an output-schema namespace with importable `tree!`, `trees!`,
/// `tree_with_context!`, and `trees_with_context!` wrappers and the schema
/// contents.
///
/// Uses the `BuildCtx` binding named `ctx` from the surrounding scope:
/// ```ignore
/// yeast::tree_schema! {
/// swift_tree,
/// "ast_types.yml",
/// }
///
/// ```text
/// trees!((node1 ...) (node2 ...))
/// use swift_tree::{
/// tree, tree_with_context, trees, trees_with_context, SCHEMA,
/// };
/// ```
#[proc_macro]
pub fn trees(input: TokenStream) -> TokenStream {
pub fn tree_schema(input: TokenStream) -> TokenStream {
let input2: TokenStream2 = input.into();
match parse::parse_trees_top(input2) {
match parse::parse_tree_schema_top(input2) {
Ok(output) => output.into(),
Err(err) => err.to_compile_error().into(),
}
Expand Down
63 changes: 42 additions & 21 deletions shared/yeast-macros/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ use crate::ast::{
Alternative, Cardinality, Content, Field, Node, Pattern, Replacement, Rule, Template,
};

#[derive(Clone, Copy, Default)]
#[derive(Clone, Copy)]
struct LoweringContext<'a> {
choice_alternative_scope: Option<ChoiceAlternativeScope<'a>>,
ctx: &'a Ident,
}

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -69,12 +70,12 @@ impl Rule {
quote! { yeast::OutputId },
&translated_captures_ident,
);
let replacement = self.replacement.lower();
let ctx = Ident::new("ctx", Span::call_site());
let replacement = self.replacement.lower(&ctx);
let guard = self
.guard
.clone()
.unwrap_or_else(|| syn::parse_quote!(true));
let ctx = Ident::new("ctx", Span::call_site());

quote! {
{
Expand Down Expand Up @@ -105,16 +106,16 @@ impl Rule {
}

impl Replacement {
fn lower(&self) -> TokenStream {
fn lower(&self, ctx: &Ident) -> TokenStream {
match self {
Replacement::Templates(templates) => Template::lower_list(templates),
Replacement::Templates(templates) => Template::lower_list(templates, ctx),
Replacement::Rust { body, .. } => {
let body = rust_block(body);
quote! {
let __value = ::std::iter::IntoIterator::into_iter(#body)
.collect::<Vec<_>>();
let mut __ids: Vec<yeast::OutputId> = Vec::new();
ctx.extend_field_ids(__value, &mut __ids);
#ctx.extend_field_ids(__value, &mut __ids);
__ids
}
}
Expand Down Expand Up @@ -307,15 +308,23 @@ fn capture_bindings<'a>(
}

impl Template {
pub(crate) fn lower_root(&self) -> TokenStream {
pub(crate) fn lower_root(&self, ctx: &Ident) -> TokenStream {
let context = LoweringContext {
choice_alternative_scope: None,
ctx,
};
match self {
Template::Choice(alternatives) => Self::lower_choice(alternatives),
_ => self.lower_single(LoweringContext::default()),
Template::Choice(alternatives) => Self::lower_choice(alternatives, context),
_ => self.lower_single(context),
}
}

pub(crate) fn lower_list(templates: &[Self]) -> TokenStream {
let context = LoweringContext::default();
pub(crate) fn lower_list(templates: &[Self], ctx: &Ident) -> TokenStream {
let context = LoweringContext {
choice_alternative_scope: None,
ctx,
};
let ctx = context.ctx;
let items = templates
.iter()
.map(|template| match template {
Expand All @@ -328,15 +337,16 @@ impl Template {
let block = rust_block(block);
isolated_interpolation(
uses_ctx,
ctx,
quote! {
let __value = ::std::iter::IntoIterator::into_iter(#block)
.collect::<Vec<_>>();
ctx.extend_field_ids(__value, &mut __nodes);
#ctx.extend_field_ids(__value, &mut __nodes);
},
)
}
Template::Choice(alternatives) => {
let pick = Self::lower_choice(alternatives);
let pick = Self::lower_choice(alternatives, context);
quote! {
if let ::std::option::Option::Some(__id) = #pick {
__nodes.push(__id);
Expand All @@ -356,13 +366,15 @@ impl Template {
}

fn lower_single(&self, context: LoweringContext<'_>) -> TokenStream {
let ctx = context.ctx;
match self {
Template::Node(node) => node.lower(context),
Template::Splice(block) => {
let uses_ctx = block_uses_ctx(block);
let block = rust_block(block);
isolated_interpolation(
uses_ctx,
ctx,
quote! {
::std::convert::Into::<yeast::OutputId>::into(#block)
},
Expand All @@ -374,22 +386,27 @@ impl Template {
}
}

fn lower_choice(alternatives: &[Alternative]) -> TokenStream {
fn lower_choice(alternatives: &[Alternative], context: LoweringContext<'_>) -> TokenStream {
let pick = alternatives.iter().rev().fold(
quote! { ::std::option::Option::None },
|fallback, alternative| alternative.lower_candidate(fallback),
|fallback, alternative| alternative.lower_candidate(fallback, context),
);
quote! {{ #pick }}
}
}

impl Alternative {
fn lower_candidate(&self, fallback: TokenStream) -> TokenStream {
fn lower_candidate(
&self,
fallback: TokenStream,
parent_context: LoweringContext<'_>,
) -> TokenStream {
let interpolations = RefCell::new(Vec::new());
let context = LoweringContext {
choice_alternative_scope: Some(ChoiceAlternativeScope {
interpolations: &interpolations,
}),
ctx: parent_context.ctx,
};
let value = self.template.lower_single(context);
let interpolations = interpolations.into_inner();
Expand Down Expand Up @@ -456,7 +473,7 @@ impl Alternative {

impl Node {
fn lower(&self, context: LoweringContext<'_>) -> TokenStream {
let ctx = Ident::new("ctx", Span::call_site());
let ctx = context.ctx;
let kind_str = self.kind.to_string();

let node_init = if let Some(content) = &self.content {
Expand All @@ -470,6 +487,7 @@ impl Node {
let source_range = fresh_prepared_ident("source_range");
let expression = isolated_interpolation(
uses_ctx,
ctx,
quote! {
let __expr = #block;
yeast::MaybeYeastValue::maybe_yeast_value(&__expr).map(
Expand Down Expand Up @@ -506,6 +524,7 @@ impl Node {
} else {
isolated_interpolation(
uses_ctx,
ctx,
quote! {
let __expr = #block;
let __value =
Expand Down Expand Up @@ -542,6 +561,7 @@ impl Node {
let block = rust_block(block);
stmts.push(isolated_interpolation(
uses_ctx,
ctx,
quote! {
let __value = ::std::iter::IntoIterator::into_iter(#block)
.collect::<Vec<_>>();
Expand Down Expand Up @@ -570,6 +590,7 @@ impl Node {
let block = rust_block(block);
let interpolation = isolated_interpolation(
uses_ctx,
ctx,
quote! {
let __value = ::std::iter::IntoIterator::into_iter(#block)
.collect::<Vec<_>>();
Expand All @@ -587,7 +608,7 @@ impl Node {
});
}
Template::Choice(alternatives) => {
let pick = Template::lower_choice(alternatives);
let pick = Template::lower_choice(alternatives, context);
stmts.push(quote! {
let #temp: ::std::option::Option<yeast::OutputId> = #pick;
});
Expand Down Expand Up @@ -642,12 +663,12 @@ fn rust_block(block: &syn::Block) -> TokenStream {
quote!({ #(#statements)* })
}

fn isolated_interpolation(uses_ctx: bool, body: TokenStream) -> TokenStream {
fn isolated_interpolation(uses_ctx: bool, ctx: &Ident, body: TokenStream) -> TokenStream {
Comment thread
tausbn marked this conversation as resolved.
Resolved
if uses_ctx {
quote! {{
let __saved_user_ctx = (*ctx.user_ctx).clone();
let __saved_user_ctx = (*#ctx.user_ctx).clone();
let __result = { #body };
*ctx.user_ctx = __saved_user_ctx;
*#ctx.user_ctx = __saved_user_ctx;
__result
}}
} else {
Expand Down
Loading
Loading