Repository: pest-parser/pest (language: rust)

Issue:
Request: Lift restriction against Rust keyword (by applying `r#` prefix)
Rust syntax allow keywords to be used as an identifier as long as it is prefixed with `r#`, for example: `r#if`.

Even better, non-keywords can also be prefixed with `r#`, so `r#std` is the same as `std`.



Discussion / hints:

Fix patch:
diff --git a/generator/src/generator.rs b/generator/src/generator.rs
index f3da1bac..9fd7a0e0 100644
--- a/generator/src/generator.rs
+++ b/generator/src/generator.rs
@@ -9,7 +9,7 @@
 
 use std::path::PathBuf;
 
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::TokenStream;
 use quote::{ToTokens, TokenStreamExt};
 use syn::{self, Generics, Ident};
 
@@ -169,7 +169,7 @@ fn generate_builtin_rules() -> Vec<(&'static str, TokenStream)> {
 
 // Needed because Cargo doesn't watch for changes in grammars.
 fn generate_include(name: &Ident, path: &str) -> TokenStream {
-    let const_name = Ident::new(&format!("_PEST_GRAMMAR_{}", name), Span::call_site());
+    let const_name = format_ident!("_PEST_GRAMMAR_{}", name);
     // Need to make this relative to the current directory since the path to the file
     // is derived from the CARGO_MANIFEST_DIR environment variable
     let mut current_dir = std::env::current_dir().expect("Unable to get current directory");
@@ -182,9 +182,7 @@ fn generate_include(name: &Ident, path: &str) -> TokenStream {
 }
 
 fn generate_enum(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
-    let rules = rules
-        .iter()
-        .map(|rule| Ident::new(rule.name.as_str(), Span::call_site()));
+    let rules = rules.iter().map(|rule| format_ident!("r#{}", rule.name));
     if uses_eoi {
         quote! {
             #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
@@ -209,7 +207,7 @@ fn generate_patterns(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
     let mut rules: Vec<TokenStream> = rules
         .iter()
         .map(|rule| {
-            let rule = Ident::new(rule.name.as_str(), Span::call_site());
+            let rule = format_ident!("r#{}", rule.name);
             quote! {
                 Rule::#rule => rules::#rule(state)
             }
@@ -228,10 +226,10 @@ fn generate_patterns(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
 }
 
 fn generate_rule(rule: OptimizedRule) -> TokenStream {
-    let name = Ident::new(&rule.name, Span::call_site());
+    let name = format_ident!("r#{}", rule.name);
     let expr = if rule.ty == RuleType::Atomic || rule.ty == RuleType::CompoundAtomic {
         generate_expr_atomic(rule.expr)
-    } else if name == "WHITESPACE" || name == "COMMENT" {
+    } else if rule.name == "WHITESPACE" || rule.name == "COMMENT" {
         let atomic = generate_expr_atomic(rule.expr);
 
         quote! {
@@ -364,7 +362,7 @@ fn generate_expr(expr: OptimizedExpr) -> TokenStream {
             }
         }
         OptimizedExpr::Ident(ident) => {
-            let ident = Ident::new(&ident, Span::call_site());
+            let ident = format_ident!("r#{}", ident);
             quote! { self::#ident(state) }
         }
         OptimizedExpr::PeekSlice(start, end_) => {
@@ -510,7 +508,7 @@ fn generate_expr_atomic(expr: OptimizedExpr) -> TokenStream {
             }
         }
         OptimizedExpr::Ident(ident) => {
-            let ident = Ident::new(&ident, Span::call_site());
+            let ident = format_ident!("r#{}", ident);
             quote! { self::#ident(state) }
         }
         OptimizedExpr::PeekSlice(start, end_) => {
@@ -659,6 +657,8 @@ fn option_type() -> TokenStream {
 
 #[cfg(test)]
 mod tests {
+    use proc_macro2::Span;
+
     use super::*;
 
     #[test]
@@ -675,7 +675,7 @@ mod tests {
                 #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
                 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
                 pub enum Rule {
-                    f
+                    r#f
                 }
             }
             .to_string()
@@ -863,7 +863,7 @@ mod tests {
         assert_eq!(
             generate_expr(expr).to_string(),
             quote! {
-                self::a(state).or_else(|state| {
+                self::r#a(state).or_else(|state| {
                     state.sequence(|state| {
                         state.match_range('a'..'b').and_then(|state| {
                             super::hidden::skip(state)
@@ -929,7 +929,7 @@ mod tests {
         assert_eq!(
             generate_expr_atomic(expr).to_string(),
             quote! {
-                self::a(state).or_else(|state| {
+                self::r#a(state).or_else(|state| {
                     state.sequence(|state| {
                         state.match_range('a'..'b').and_then(|state| {
                             state.lookahead(false, |state| {
@@ -960,11 +960,20 @@ mod tests {
     fn generate_complete() {
         let name = Ident::new("MyParser", Span::call_site());
         let generics = Generics::default();
-        let rules = vec![OptimizedRule {
-            name: "a".to_owned(),
-            ty: RuleType::Silent,
-            expr: OptimizedExpr::Str("b".to_owned()),
-        }];
+
+        let rules = vec![
+            OptimizedRule {
+                name: "a".to_owned(),
+                ty: RuleType::Silent,
+                expr: OptimizedExpr::Str("b".to_owned()),
+            },
+            OptimizedRule {
+                name: "if".to_owned(),
+                ty: RuleType::Silent,
+                expr: OptimizedExpr::Ident("a".to_owned()),
+            },
+        ];
+
         let defaults = vec!["ANY"];
         let result = result_type();
         let box_ty = box_type();
@@ -980,7 +989,8 @@ mod tests {
                 #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
                 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
                 pub enum Rule {
-                    a
+                    r#a,
+                    r#if
                 }
 
                 #[allow(clippy::all)]
@@ -1009,10 +1019,16 @@ mod tests {
 
                                 #[inline]
                                 #[allow(non_snake_case, unused_variables)]
-                                pub fn a(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
+                                pub fn r#a(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
                                     state.match_string("b")
                                 }
 
+                                #[inline]
+                                #[allow(non_snake_case, unused_variables)]
+                                pub fn r#if(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
+                                    self::r#a(state)
+                                }
+
                                 #[inline]
                                 #[allow(dead_code, non_snake_case, unused_variables)]
                                 pub fn ANY(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
@@ -1025,7 +1041,8 @@ mod tests {
 
                         ::pest::state(input, |state| {
                             match rule {
-                                Rule::a => rules::a(state)
+                                Rule::r#a => rules::r#a(state),
+                                Rule::r#if => rules::r#if(state)
                             }
                         })
                     }
diff --git a/meta/src/validator.rs b/meta/src/validator.rs
index 04eeeb2f..dc0555a2 100644
--- a/meta/src/validator.rs
+++ b/meta/src/validator.rs
@@ -71,7 +71,6 @@ static BUILTINS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
 });
 
 /// It checks the parsed grammar for common mistakes:
-/// - using Rust keywords
 /// - using Pest keywords
 /// - duplicate rules
 /// - undefined rules
@@ -84,6 +83,7 @@ pub fn validate_pairs(pairs: Pairs<'_, Rule>) -> Result<Vec<&str>, Vec<Error<Rul
         .filter(|pair| pair.as_rule() == Rule::grammar_rule)
         .map(|pair| pair.into_inner().next().unwrap().as_span())
         .collect();
+
     let called_rules: Vec<_> = pairs
         .clone()
         .filter(|pair| pair.as_rule() == Rule::grammar_rule)
@@ -98,7 +98,6 @@ pub fn validate_pairs(pairs: Pairs<'_, Rule>) -> Result<Vec<&str>, Vec<Error<Rul
 
     let mut errors = vec![];
 
-    errors.extend(validate_rust_keywords(&definitions));
     errors.extend(validate_pest_keywords(&definitions));
     errors.extend(validate_already_defined(&definitions));
     errors.extend(validate_undefined(&definitions, &called_rules));
@@ -117,6 +116,7 @@ pub fn validate_pairs(pairs: Pairs<'_, Rule>) -> Result<Vec<&str>, Vec<Error<Rul
 
 /// Validates that the given `definitions` do not contain any Rust keywords.
 #[allow(clippy::ptr_arg)]
+#[deprecated = "Rust keywords are no longer restricted from the pest grammar"]
 pub fn validate_rust_keywords(definitions: &Vec<Span<'_>>) -> Vec<Error<Rule>> {
     let mut errors = vec![];
 
@@ -507,22 +507,6 @@ mod tests {
     #[test]
     #[should_panic(expected = "grammar error
 
- --> 1:1
-  |
-1 | let = { \"a\" }
-  | ^-^
-  |
-  = let is a rust keyword")]
-    fn rust_keyword() {
-        let input = "let = { \"a\" }";
-        unwrap_or_report(validate_pairs(
-            PestParser::parse(Rule::grammar_rules, input).unwrap(),
-        ));
-    }
-
-    #[test]
-    #[should_panic(expected = "grammar error
-
  --> 1:1
   |
 1 | ANY = { \"a\" }
diff --git a/vm/src/lib.rs b/vm/src/lib.rs
index 7c9018a5..48391870 100644
--- a/vm/src/lib.rs
+++ b/vm/src/lib.rs
@@ -126,7 +126,7 @@ impl Vm {
         };
 
         if let Some(rule) = self.rules.get(rule) {
-            if &rule.name == "WHITESPACE" || &rule.name == "COMMENT" {
+            if rule.name == "WHITESPACE" || rule.name == "COMMENT" {
                 match rule.ty {
                     RuleType::Normal => state.rule(&rule.name, |state| {
                         state.atomic(Atomicity::Atomic, |state| {


Test patch: