Generate actual parsing code in grammar! macro

This commit is contained in:
2025-05-25 21:33:30 -07:00
parent 63dc9790ed
commit 1e90a68ee8
4 changed files with 85 additions and 31 deletions
Generated
+16
View File
@@ -62,6 +62,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "1.0.0" version = "1.0.0"
@@ -81,6 +87,15 @@ dependencies = [
"similar", "similar",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
@@ -131,6 +146,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
name = "peg_macro" name = "peg_macro"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"itertools",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
+1
View File
@@ -7,6 +7,7 @@ edition = "2024"
proc-macro = true proc-macro = true
[dependencies] [dependencies]
itertools = "0.14.0"
proc-macro2 = "1.0.95" proc-macro2 = "1.0.95"
quote = "1.0.40" quote = "1.0.40"
syn = "2.0.101" syn = "2.0.101"
+57 -20
View File
@@ -1,3 +1,4 @@
use itertools::{Itertools, Position};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{
@@ -68,16 +69,52 @@ impl Term {
fn generate_code(&self) -> proc_macro2::TokenStream { fn generate_code(&self) -> proc_macro2::TokenStream {
match self { match self {
Term::Rule(ident) => quote! { Term::Rule(ident) => quote! {
println!("call subrule"); #ident(p)
// call subrule
}, },
Term::Literal(lit_str) => quote! { Term::Literal(lit_str) => quote! {
println!("parse literal"); p.literal(#lit_str)
// call p.literal()
}, },
Term::Sequence(terms) => quote! { Term::Sequence(terms) => {
println!("sequence"); let parts: Vec<_> = terms
.iter()
.with_position()
.map(|(pos, term)| {
let code = term.generate_wrapped_code();
match pos {
Position::First => quote! {
let result = #code;
if !result {
return false;
}
},
Position::Middle => quote! {
let result = #code;
if !result {
p.restore(save);
return false;
}
},
_ => code,
}
})
.collect();
quote! {
let save = p.save();
#(#parts)*
}
}
}
}
fn generate_wrapped_code(&self) -> proc_macro2::TokenStream {
let code = self.generate_code();
match self {
Term::Sequence(_) => quote! {
(|| {
#code
})()
}, },
_ => code,
} }
} }
} }
@@ -116,24 +153,24 @@ pub fn grammar(ts: TokenStream) -> TokenStream {
.map(|r| { .map(|r| {
let fn_name = &r.name; let fn_name = &r.name;
// Generate a string for debugging // // Generate a string for debugging
fn term2str(t: &Term) -> String { // fn term2str(t: &Term) -> String {
match t { // match t {
Term::Rule(ident) => format!("rule {}", ident.to_string()), // Term::Rule(ident) => format!("rule {}", ident.to_string()),
Term::Literal(lit_str) => format!("literal \"{}\"", lit_str.value()), // Term::Literal(lit_str) => format!("literal \"{}\"", lit_str.value()),
Term::Sequence(terms) => { // Term::Sequence(terms) => {
let parts: Vec<_> = terms.iter().map(term2str).collect(); // let parts: Vec<_> = terms.iter().map(term2str).collect();
format!("sequence [{}]", parts.join(", ")) // format!("sequence [{}]", parts.join(", "))
} // }
} // }
} // }
let text = format!("I'm generated! ({})", term2str(&r.definition)); // let text = format!("I'm generated! ({})", term2str(&r.definition));
let generated = r.definition.generate_code(); let generated = r.definition.generate_code();
quote! { quote! {
fn #fn_name(p: &mut crate::peg::ParseState) -> String { fn #fn_name(p: &mut crate::peg::ParseState) -> bool {
// println!(#text.into());
#generated #generated
#text.into()
} }
} }
}) })
+11 -11
View File
@@ -1,5 +1,3 @@
use peg_macro::grammar;
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum Foo { pub enum Foo {
@@ -64,17 +62,18 @@ fn parse_oo(p: &mut ParseState) -> bool {
p.literal("oo") p.literal("oo")
} }
grammar! {
generated_this = foo;
generated_that = "bar" baz;
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use insta::assert_snapshot; use peg_macro::grammar;
use super::*; use super::*;
grammar! {
fake_csv = line "\n" line;
line = foo "," foo;
foo = "foo";
}
#[test] #[test]
fn test_parser() { fn test_parser() {
let mut p = ParseState::new("foo"); let mut p = ParseState::new("foo");
@@ -85,8 +84,9 @@ mod tests {
#[test] #[test]
fn test_generated() { fn test_generated() {
let mut p = ParseState::new(""); let mut p = ParseState::new("foo,foo\nfoo,foo");
assert_snapshot!(generated_this(&mut p), @"I'm generated! (rule foo)"); assert!(fake_csv(&mut p));
assert_snapshot!(generated_that(&mut p), @r#"I'm generated! (sequence [literal "bar", rule baz])"#); let mut p = ParseState::new("foo,foo\nfoo;foo");
assert!(!fake_csv(&mut p));
} }
} }