Skip to content

Commit

Permalink
propagate non_exhaustive attribute to the generated Rule enums (#901)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomtau committed Jul 26, 2023
1 parent da0feb0 commit 5bfa99e
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Install toolchain
uses: dtolnay/rust-toolchain@1.62.0
uses: dtolnay/rust-toolchain@1.63.0 # needed for pest_debugger (linux-raw-sys v0.4.3 requires it)
- name: Bootstraping Grammars - Building
run: cargo build --package pest_bootstrap
- name: Bootstraping Grammars - Executing
Expand Down
8 changes: 4 additions & 4 deletions debugger/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pest_debugger"
description = "pest grammar debugger"
version = "2.7.1"
version = "2.7.2"
edition = "2021"
authors = ["Dragoș Tiselice <dragostiselice@gmail.com>", "Tomas Tauber <me@tomtau.be>"]
homepage = "https://pest.rs/"
Expand All @@ -14,9 +14,9 @@ readme = "_README.md"
rust-version = "1.60"

[dependencies]
pest = { path = "../pest", version = "2.7.1" }
pest_meta = { path = "../meta", version = "2.7.1" }
pest_vm = { path = "../vm", version = "2.7.1" }
pest = { path = "../pest", version = "2.7.2" }
pest_meta = { path = "../meta", version = "2.7.2" }
pest_vm = { path = "../vm", version = "2.7.2" }
reqwest = { version = "= 0.11.13", default-features = false, features = ["blocking", "json", "default-tls"] }
rustyline = "10"
serde_json = "1"
Expand Down
6 changes: 3 additions & 3 deletions derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pest_derive"
description = "pest's derive macro"
version = "2.7.1"
version = "2.7.2"
edition = "2021"
authors = ["Dragoș Tiselice <dragostiselice@gmail.com>"]
homepage = "https://pest.rs/"
Expand All @@ -25,5 +25,5 @@ grammar-extras = ["pest_generator/grammar-extras"]

[dependencies]
# for tests, included transitively anyway
pest = { path = "../pest", version = "2.7.1", default-features = false }
pest_generator = { path = "../generator", version = "2.7.1", default-features = false }
pest = { path = "../pest", version = "2.7.2", default-features = false }
pest_generator = { path = "../generator", version = "2.7.2", default-features = false }
6 changes: 3 additions & 3 deletions generator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pest_generator"
description = "pest code generator"
version = "2.7.1"
version = "2.7.2"
edition = "2021"
authors = ["Dragoș Tiselice <dragostiselice@gmail.com>"]
homepage = "https://pest.rs/"
Expand All @@ -20,8 +20,8 @@ not-bootstrap-in-src = ["pest_meta/not-bootstrap-in-src"]
grammar-extras = ["pest_meta/grammar-extras"]

[dependencies]
pest = { path = "../pest", version = "2.7.1", default-features = false }
pest_meta = { path = "../meta", version = "2.7.1" }
pest = { path = "../pest", version = "2.7.2", default-features = false }
pest_meta = { path = "../meta", version = "2.7.2" }
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"
64 changes: 41 additions & 23 deletions generator/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,32 @@ use std::path::PathBuf;

use proc_macro2::TokenStream;
use quote::{ToTokens, TokenStreamExt};
use syn::{self, Generics, Ident};
use syn::{self, Ident};

use pest::unicode::unicode_property_names;
use pest_meta::ast::*;
use pest_meta::optimizer::*;

use crate::docs::DocComment;
use crate::ParsedDerive;

pub(crate) fn generate(
name: Ident,
generics: &Generics,
parsed_derive: ParsedDerive,
paths: Vec<PathBuf>,
rules: Vec<OptimizedRule>,
defaults: Vec<&str>,
doc_comment: &DocComment,
include_grammar: bool,
) -> TokenStream {
let uses_eoi = defaults.iter().any(|name| *name == "EOI");

let name = parsed_derive.name;
let builtins = generate_builtin_rules();
let include_fix = if include_grammar {
generate_include(&name, paths)
} else {
quote!()
};
let rule_enum = generate_enum(&rules, doc_comment, uses_eoi);
let rule_enum = generate_enum(&rules, doc_comment, uses_eoi, parsed_derive.non_exhaustive);
let patterns = generate_patterns(&rules, uses_eoi);
let skip = generate_skip(&rules);

Expand All @@ -49,7 +49,7 @@ pub(crate) fn generate(
}
}));

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let (impl_generics, ty_generics, where_clause) = parsed_derive.generics.split_for_impl();

let result = result_type();

Expand Down Expand Up @@ -197,7 +197,12 @@ fn generate_include(name: &Ident, paths: Vec<PathBuf>) -> TokenStream {
}
}

fn generate_enum(rules: &[OptimizedRule], doc_comment: &DocComment, uses_eoi: bool) -> TokenStream {
fn generate_enum(
rules: &[OptimizedRule],
doc_comment: &DocComment,
uses_eoi: bool,
non_exhaustive: bool,
) -> TokenStream {
let rules = rules.iter().map(|rule| {
let rule_name = format_ident!("r#{}", rule.name);

Expand All @@ -213,26 +218,34 @@ fn generate_enum(rules: &[OptimizedRule], doc_comment: &DocComment, uses_eoi: bo
});

let grammar_doc = &doc_comment.grammar_doc;
let mut result = quote! {
#[doc = #grammar_doc]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
};
if non_exhaustive {
result.append_all(quote! {
#[non_exhaustive]
});
}
result.append_all(quote! {
pub enum Rule
});
if uses_eoi {
quote! {
#[doc = #grammar_doc]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
result.append_all(quote! {
{
EOI,
#( #rules ),*
}
}
});
} else {
quote! {
#[doc = #grammar_doc]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
result.append_all(quote! {
{
#( #rules ),*
}
}
}
})
};
result
}

fn generate_patterns(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
Expand Down Expand Up @@ -756,6 +769,7 @@ mod tests {

use proc_macro2::Span;
use std::collections::HashMap;
use syn::Generics;

#[test]
fn rule_enum_simple() {
Expand All @@ -774,7 +788,7 @@ mod tests {
};

assert_eq!(
generate_enum(&rules, doc_comment, false).to_string(),
generate_enum(&rules, doc_comment, false, false).to_string(),
quote! {
#[doc = "Rule doc\nhello"]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
Expand Down Expand Up @@ -1095,9 +1109,13 @@ mod tests {

let base_path = current_dir.join("base.pest").to_str().unwrap().to_string();
let test_path = current_dir.join("test.pest").to_str().unwrap().to_string();

let parsed_derive = ParsedDerive {
name,
generics,
non_exhaustive: false,
};
assert_eq!(
generate(name, &generics, vec![PathBuf::from("base.pest"), PathBuf::from("test.pest")], rules, defaults, doc_comment, true).to_string(),
generate(parsed_derive, vec![PathBuf::from("base.pest"), PathBuf::from("test.pest")], rules, defaults, doc_comment, true).to_string(),
quote! {
#[allow(non_upper_case_globals)]
const _PEST_GRAMMAR_MyParser: [&'static str; 2usize] = [include_str!(#base_path), include_str!(#test_path)];
Expand Down
50 changes: 41 additions & 9 deletions generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use pest_meta::{optimizer, unwrap_or_report, validator};
/// "include_str" statement (done in pest_derive, but turned off in the local bootstrap).
pub fn derive_parser(input: TokenStream, include_grammar: bool) -> TokenStream {
let ast: DeriveInput = syn::parse2(input).unwrap();
let (name, generics, contents) = parse_derive(ast);
let (parsed_derive, contents) = parse_derive(ast);

let mut data = String::new();
let mut paths = vec![];
Expand Down Expand Up @@ -97,8 +97,7 @@ pub fn derive_parser(input: TokenStream, include_grammar: bool) -> TokenStream {
let optimized = optimizer::optimize(ast);

generator::generate(
name,
&generics,
parsed_derive,
paths,
optimized,
defaults,
Expand All @@ -120,7 +119,13 @@ enum GrammarSource {
Inline(String),
}

fn parse_derive(ast: DeriveInput) -> (Ident, Generics, Vec<GrammarSource>) {
struct ParsedDerive {
pub(crate) name: Ident,
pub(crate) generics: Generics,
pub(crate) non_exhaustive: bool,
}

fn parse_derive(ast: DeriveInput) -> (ParsedDerive, Vec<GrammarSource>) {
let name = ast.ident;
let generics = ast.generics;

Expand All @@ -142,7 +147,19 @@ fn parse_derive(ast: DeriveInput) -> (Ident, Generics, Vec<GrammarSource>) {
grammar_sources.push(get_attribute(attr))
}

(name, generics, grammar_sources)
let non_exhaustive = ast
.attrs
.iter()
.any(|attr| attr.meta.path().is_ident("non_exhaustive"));

(
ParsedDerive {
name,
generics,
non_exhaustive,
},
grammar_sources,
)
}

fn get_attribute(attr: &Attribute) -> GrammarSource {
Expand Down Expand Up @@ -177,7 +194,7 @@ mod tests {
pub struct MyParser<'a, T>;
";
let ast = syn::parse_str(definition).unwrap();
let (_, _, filenames) = parse_derive(ast);
let (_, filenames) = parse_derive(ast);
assert_eq!(filenames, [GrammarSource::Inline("GRAMMAR".to_string())]);
}

Expand All @@ -189,8 +206,9 @@ mod tests {
pub struct MyParser<'a, T>;
";
let ast = syn::parse_str(definition).unwrap();
let (_, _, filenames) = parse_derive(ast);
let (parsed_derive, filenames) = parse_derive(ast);
assert_eq!(filenames, [GrammarSource::File("myfile.pest".to_string())]);
assert!(!parsed_derive.non_exhaustive);
}

#[test]
Expand All @@ -202,7 +220,7 @@ mod tests {
pub struct MyParser<'a, T>;
";
let ast = syn::parse_str(definition).unwrap();
let (_, _, filenames) = parse_derive(ast);
let (_, filenames) = parse_derive(ast);
assert_eq!(
filenames,
[
Expand All @@ -212,6 +230,19 @@ mod tests {
);
}

#[test]
fn derive_nonexhaustive() {
let definition = "
#[non_exhaustive]
#[grammar = \"myfile.pest\"]
pub struct MyParser<'a, T>;
";
let ast = syn::parse_str(definition).unwrap();
let (parsed_derive, filenames) = parse_derive(ast);
assert_eq!(filenames, [GrammarSource::File("myfile.pest".to_string())]);
assert!(parsed_derive.non_exhaustive);
}

#[test]
#[should_panic(expected = "grammar attribute must be a string")]
fn derive_wrong_arg() {
Expand Down Expand Up @@ -242,6 +273,7 @@ mod tests {
fn test_generate_doc() {
let input = quote! {
#[derive(Parser)]
#[non_exhaustive]
#[grammar = "../tests/test.pest"]
pub struct TestParser;
};
Expand All @@ -252,7 +284,7 @@ mod tests {
#[doc = "A parser for JSON file.\nAnd this is a example for JSON parser.\n\n indent-4-space\n"]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]

#[non_exhaustive]
pub enum Rule {
#[doc = "Matches foo str, e.g.: `foo`"]
r#foo,
Expand Down
6 changes: 3 additions & 3 deletions grammars/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pest_grammars"
description = "pest popular grammar implementations"
version = "2.7.1"
version = "2.7.2"
edition = "2021"
authors = ["Dragoș Tiselice <dragostiselice@gmail.com>"]
homepage = "https://pest.rs/"
Expand All @@ -14,8 +14,8 @@ readme = "_README.md"
rust-version = "1.60"

[dependencies]
pest = { path = "../pest", version = "2.7.1" }
pest_derive = { path = "../derive", version = "2.7.1" }
pest = { path = "../pest", version = "2.7.2" }
pest_derive = { path = "../derive", version = "2.7.2" }

[dev-dependencies]
criterion = "0.5"
Expand Down
4 changes: 2 additions & 2 deletions meta/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pest_meta"
description = "pest meta language parser and validator"
version = "2.7.1"
version = "2.7.2"
edition = "2021"
authors = ["Dragoș Tiselice <dragostiselice@gmail.com>"]
homepage = "https://pest.rs/"
Expand All @@ -16,7 +16,7 @@ include = ["Cargo.toml", "src/**/*", "src/grammar.rs", "_README.md", "LICENSE-*"
rust-version = "1.60"

[dependencies]
pest = { path = "../pest", version = "2.7.1" }
pest = { path = "../pest", version = "2.7.2" }
once_cell = "1.8.0"

[build-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion pest/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pest"
description = "The Elegant Parser"
version = "2.7.1"
version = "2.7.2"
edition = "2021"
authors = ["Dragoș Tiselice <dragostiselice@gmail.com>"]
homepage = "https://pest.rs/"
Expand Down
6 changes: 3 additions & 3 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pest_vm"
description = "pest grammar virtual machine"
version = "2.7.1"
version = "2.7.2"
edition = "2021"
authors = ["Dragoș Tiselice <dragostiselice@gmail.com>"]
homepage = "https://pest.rs/"
Expand All @@ -14,8 +14,8 @@ readme = "_README.md"
rust-version = "1.60"

[dependencies]
pest = { path = "../pest", version = "2.7.1" }
pest_meta = { path = "../meta", version = "2.7.1" }
pest = { path = "../pest", version = "2.7.2" }
pest_meta = { path = "../meta", version = "2.7.2" }

[features]
grammar-extras = ["pest_meta/grammar-extras"]

0 comments on commit 5bfa99e

Please sign in to comment.