Skip to content

Commit

Permalink
Bundle version-specific codegen logic for parser generator
Browse files Browse the repository at this point in the history
  • Loading branch information
Xanewok committed May 23, 2024
1 parent c6c69e2 commit b408825
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 99 deletions.
1 change: 1 addition & 0 deletions crates/codegen/runtime/generator/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod parser_definition;
mod precedence_parser_definition;
mod scanner_definition;
mod trie;
mod versioned;

use grammar::{
Grammar, GrammarVisitor, KeywordScannerAtomic, KeywordScannerDefinitionRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::parser::grammar::{KeywordScannerDefinitionRef, ScannerDefinitionNode};
use crate::parser::parser_definition::VersionQualityRangeVecExtensions;
use crate::parser::scanner_definition::ScannerDefinitionNodeExtensions;
use crate::parser::versioned::VersionedQuote;

pub trait KeywordScannerDefinitionExtensions {
fn to_scanner_code(&self) -> TokenStream;
Expand Down
78 changes: 8 additions & 70 deletions crates/codegen/runtime/generator/src/parser/parser_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ use codegen_language_definition::model::{Identifier, VersionSpecifier};
use inflector::Inflector;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use semver::Version;

use crate::parser::grammar::{
Labeled, ParserDefinitionNode, ParserDefinitionRef, TriviaParserDefinitionRef,
};
use crate::parser::versioned::{Versioned as _, VersionedQuote as _};

pub trait ParserDefinitionExtensions {
fn to_parser_code(&self) -> TokenStream;
}

impl ParserDefinitionExtensions for ParserDefinitionRef {
fn to_parser_code(&self) -> TokenStream {
self.node().applicable_version_quality_ranges().wrap_code(
self.node().version_specifier().to_conditional_code(
self.node().to_parser_code(self.context(), false),
Some(quote! { ParserResult::disabled() }),
)
Expand All @@ -29,7 +29,6 @@ impl ParserDefinitionExtensions for TriviaParserDefinitionRef {

pub trait ParserDefinitionNodeExtensions {
fn to_parser_code(&self, context_name: &Identifier, is_trivia: bool) -> TokenStream;
fn applicable_version_quality_ranges(&self) -> Option<&VersionSpecifier>;
}

impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
Expand Down Expand Up @@ -102,7 +101,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
(
value.to_parser_code(context_name, is_trivia),
label.clone(),
value.applicable_version_quality_ranges(),
value.version_specifier(),
)
})),
},
Expand All @@ -111,7 +110,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
let parser = make_choice_versioned(value.iter().map(|node| {
(
node.to_parser_code(context_name, is_trivia),
node.applicable_version_quality_ranges(),
node.version_specifier(),
)
}));

Expand Down Expand Up @@ -205,7 +204,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
let threshold = threshold.0;

let parser = body.to_parser_code(context_name, is_trivia);
let body_parser = body.applicable_version_quality_ranges().wrap_code(
let body_parser = body.version_specifier().to_conditional_code(
quote! {
seq.elem(#parser
.recover_until_with_nested_delims::<_, #lex_ctx>(input,
Expand Down Expand Up @@ -270,7 +269,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
};

let parser = body.to_parser_code(context_name, is_trivia);
let body_parser = body.applicable_version_quality_ranges().wrap_code(
let body_parser = body.version_specifier().to_conditional_code(
quote! {
seq.elem(
#parser
Expand Down Expand Up @@ -298,67 +297,6 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
}
}
}

fn applicable_version_quality_ranges(&self) -> Option<&VersionSpecifier> {
match self {
ParserDefinitionNode::Versioned(_, version_specifier) => Some(version_specifier),

ParserDefinitionNode::Optional(value)
| ParserDefinitionNode::ZeroOrMore(Labeled { value, .. })
| ParserDefinitionNode::OneOrMore(Labeled { value, .. }) => {
value.applicable_version_quality_ranges()
}

_ => None,
}
}
}

pub trait VersionQualityRangeVecExtensions {
fn wrap_code(&self, if_true: TokenStream, if_false: Option<TokenStream>) -> TokenStream;
// Quotes a boolean expression that is satisfied for the given version quality ranges
fn as_bool_expr(&self) -> TokenStream;
}

impl VersionQualityRangeVecExtensions for Option<&VersionSpecifier> {
fn wrap_code(&self, if_true: TokenStream, if_false: Option<TokenStream>) -> TokenStream {
if self.is_none() {
if_true
} else {
let condition = self.as_bool_expr();

let else_part = if_false.map(|if_false| quote! { else { #if_false } });
quote! { if #condition { #if_true } #else_part }
}
}

fn as_bool_expr(&self) -> TokenStream {
let to_version_flag_name = |v: &Version| {
format_ident!(
"version_is_at_least_{v}",
v = &v.to_string().replace('.', "_")
)
};

match self {
// No constraints imposed, so always enabled
None => quote!(true),
Some(VersionSpecifier::Never) => quote!(false),
Some(VersionSpecifier::From { from }) => {
let flag = to_version_flag_name(from);
quote! { self.#flag }
}
Some(VersionSpecifier::Till { till }) => {
let flag = to_version_flag_name(till);
quote! { ! self.#flag }
}
Some(VersionSpecifier::Range { from, till }) => {
let from_flag = to_version_flag_name(from);
let till_flag = to_version_flag_name(till);
quote! { self.#from_flag && ! self.#till_flag }
}
}
}
}

pub fn make_sequence(parsers: impl IntoIterator<Item = TokenStream>) -> TokenStream {
Expand All @@ -383,7 +321,7 @@ pub fn make_sequence_versioned<'a>(
quote! { seq.elem_labeled(EdgeLabel::#label, #parser)?; }
};

versions.wrap_code(code, None)
versions.to_conditional_code(code, None)
})
.collect::<Vec<_>>();
quote! {
Expand All @@ -404,7 +342,7 @@ fn make_choice_versioned<'a>(
let parsers = parsers
.into_iter()
.map(|(parser, versions)| {
versions.wrap_code(
versions.to_conditional_code(
quote! {
let result = #parser;
choice.consider(input, result)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::parser::grammar::{ScannerDefinitionNode, ScannerDefinitionRef};
use crate::parser::parser_definition::VersionQualityRangeVecExtensions;
use crate::parser::versioned::VersionedQuote;

pub trait ScannerDefinitionExtensions {
fn to_scanner_code(&self) -> TokenStream;
Expand Down Expand Up @@ -51,7 +51,7 @@ impl ScannerDefinitionNodeExtensions for ScannerDefinitionNode {
match self {
ScannerDefinitionNode::Versioned(body, version_quality_ranges) => {
let body = body.to_scanner_code();
Some(version_quality_ranges).wrap_code(body, Some(quote! { false }))
Some(version_quality_ranges).to_conditional_code(body, Some(quote! { false }))
}

ScannerDefinitionNode::Optional(node) => {
Expand Down
31 changes: 5 additions & 26 deletions crates/codegen/runtime/generator/src/parser/trie.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::collections::BTreeMap;
use std::fmt::Debug;

// use crate::grammar::model::KeywordDefinition;
use codegen_language_definition::model::{self, VersionSpecifier};
use codegen_language_definition::model::KeywordDefinition;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::parser::grammar::{KeywordScannerAtomic, ScannerDefinitionNode, ScannerDefinitionRef};
use crate::parser::parser_definition::VersionQualityRangeVecExtensions;
use crate::parser::grammar::{KeywordScannerAtomic, ScannerDefinitionRef};
use crate::parser::versioned::{Versioned as _, VersionedQuote as _};

#[derive(Clone, Debug, Default)]
pub struct Trie<T: Payload> {
Expand Down Expand Up @@ -91,26 +90,6 @@ impl<T: Payload> Trie<T> {
}
}

trait VersionWrapped {
fn applicable_version_quality_ranges(&self) -> Option<&VersionSpecifier>;
}

impl VersionWrapped for ScannerDefinitionNode {
fn applicable_version_quality_ranges(&self) -> Option<&VersionSpecifier> {
match self {
ScannerDefinitionNode::Versioned(_, version_quality_ranges) => {
Some(version_quality_ranges)
}

ScannerDefinitionNode::Optional(node)
| ScannerDefinitionNode::ZeroOrMore(node)
| ScannerDefinitionNode::OneOrMore(node) => node.applicable_version_quality_ranges(),

_ => None,
}
}
}

/// Used together with [`Trie`]. Represents the payload of a trie node and can be used to customize
/// the emitted code.
///
Expand All @@ -125,7 +104,7 @@ impl Payload for ScannerDefinitionRef {
fn to_leaf_code(&self) -> TokenStream {
let kind = format_ident!("{}", self.name());

self.node().applicable_version_quality_ranges().wrap_code(
self.node().version_specifier().to_conditional_code(
quote! { Some(TerminalKind::#kind) },
Some(Self::default_case()),
)
Expand All @@ -140,7 +119,7 @@ impl Payload for KeywordScannerAtomic {
fn to_leaf_code(&self) -> TokenStream {
let kind = format_ident!("{}", self.name());

let model::KeywordDefinition {
let KeywordDefinition {
enabled, reserved, ..
} = self.definition();

Expand Down
96 changes: 96 additions & 0 deletions crates/codegen/runtime/generator/src/parser/versioned.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use codegen_language_definition::model::VersionSpecifier;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use semver::Version;

use crate::parser::grammar::{Labeled, ParserDefinitionNode, ScannerDefinitionNode};

pub trait Versioned {
fn version_specifier(&self) -> Option<&VersionSpecifier>;
}

impl Versioned for ParserDefinitionNode {
fn version_specifier(&self) -> Option<&VersionSpecifier> {
match self {
ParserDefinitionNode::Versioned(_, version_specifier) => Some(version_specifier),

ParserDefinitionNode::Optional(value)
| ParserDefinitionNode::ZeroOrMore(Labeled { value, .. })
| ParserDefinitionNode::OneOrMore(Labeled { value, .. }) => value.version_specifier(),

_ => None,
}
}
}

impl Versioned for ScannerDefinitionNode {
fn version_specifier(&self) -> Option<&VersionSpecifier> {
match self {
ScannerDefinitionNode::Versioned(_, version_quality_ranges) => {
Some(version_quality_ranges)
}

ScannerDefinitionNode::Optional(node)
| ScannerDefinitionNode::ZeroOrMore(node)
| ScannerDefinitionNode::OneOrMore(node) => node.version_specifier(),

_ => None,
}
}
}

pub trait VersionedQuote {
/// Depending on the `as_bool_expr` result, wraps the given code in an `if` block and optionally includes an `else` block
fn to_conditional_code(
&self,
if_true: TokenStream,
if_false: Option<TokenStream>,
) -> TokenStream;
/// Quotes a boolean expression that is satisfied for the given version quality ranges
fn as_bool_expr(&self) -> TokenStream;
}

impl VersionedQuote for Option<&VersionSpecifier> {
fn to_conditional_code(
&self,
if_true: TokenStream,
if_false: Option<TokenStream>,
) -> TokenStream {
if self.is_none() {
if_true
} else {
let condition = self.as_bool_expr();

let else_part = if_false.map(|if_false| quote! { else { #if_false } });
quote! { if #condition { #if_true } #else_part }
}
}

fn as_bool_expr(&self) -> TokenStream {
let to_version_flag_name = |v: &Version| {
format_ident!(
"version_is_at_least_{v}",
v = &v.to_string().replace('.', "_")
)
};

match self {
// No constraints imposed, so always enabled
None => quote!(true),
Some(VersionSpecifier::Never) => quote!(false),
Some(VersionSpecifier::From { from }) => {
let flag = to_version_flag_name(from);
quote! { self.#flag }
}
Some(VersionSpecifier::Till { till }) => {
let flag = to_version_flag_name(till);
quote! { ! self.#flag }
}
Some(VersionSpecifier::Range { from, till }) => {
let from_flag = to_version_flag_name(from);
let till_flag = to_version_flag_name(till);
quote! { self.#from_flag && ! self.#till_flag }
}
}
}
}

0 comments on commit b408825

Please sign in to comment.