From 979eeccfe8ce52a2ce720caebfa1fe1e7f20618c Mon Sep 17 00:00:00 2001 From: Chris Mounce Date: Sun, 8 Jun 2025 18:54:14 -0700 Subject: [PATCH] Completely remove legacy combinator-based parser --- peg_macro/src/lib.rs | 2 +- src/labels/labels.rs | 232 +----------------- src/preprocess/mod.rs | 1 - src/preprocess/peg.rs | 549 ------------------------------------------ 4 files changed, 8 insertions(+), 776 deletions(-) delete mode 100644 src/preprocess/peg.rs diff --git a/peg_macro/src/lib.rs b/peg_macro/src/lib.rs index 3bddc2f..c461536 100644 --- a/peg_macro/src/lib.rs +++ b/peg_macro/src/lib.rs @@ -366,7 +366,7 @@ pub fn grammar(ts: TokenStream) -> TokenStream { .map(|x| Ident::new(x, proc_macro2::Span::call_site())) .collect(); let enum_tag = quote! { - #[derive(Copy, Clone, Debug)] + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Tag { #(#tag_idents),* } diff --git a/src/labels/labels.rs b/src/labels/labels.rs index 57b379a..286d910 100644 --- a/src/labels/labels.rs +++ b/src/labels/labels.rs @@ -1,28 +1,16 @@ -use crate::{ - peg::ParseState, - plus, - preprocess::peg::{Alt, And, Dot, EOF, NoCase, Not, Opt, Parser, Rule, Tag}, - star, - world::Stat, -}; +use crate::{peg::ParseState, world::Stat}; pub fn print_labels(b: &Stat) { let code = &b.code; - let mut ps = ParseState::new(code); + let mut parser = ParseState::new(code); assert!( - grammar::program(&mut ps), - "New parser couldn't parse: {:?}", + grammar::program(&mut parser), + "Couldn't parse code: {:?}", code ); - let mut parser = Parser::new(&b.code); - if !program.parse(&mut parser) { - eprintln!("Couldn't parse stat's code: {:?}", b.code); - return; - } - - for capture in parser.iter() { - if capture.kind() == "label" { + for capture in parser.captures() { + if capture.kind() == grammar::Tag::Label { println!("- {}", capture.text()); } } @@ -47,7 +35,7 @@ mod grammar { ("give" / "take") sp counter sp value / "if" sp condition / "try" sp direction - ) s (statement / bare_command); + ) s (statement / bare_command / eol); @icase bare_simple_command = ( &'b'..'c' ( @@ -161,212 +149,6 @@ mod grammar { } } -fn program() -> impl Rule { - (Opt((line, star!(("\n", line)))), EOF) -} - -fn line() -> impl Rule { - (motion_prefix, Alt((command_line, label_line, any_line))) -} - -fn motion_prefix() -> impl Rule { - star!(Alt(("/", "?")), w, direction) -} - -fn command_line() -> impl Rule { - ("#", w, bare_command) -} - -fn bare_command() -> impl Rule { - Alt((bare_if, bare_send)) -} - -fn bare_if() -> impl Rule { - ( - "if", - w, - condition, - w, - Opt(("then", w)), - Alt((shorthand_send, Box::new(line) as Box)), - ) -} - -/// `send` without a preceding `#` -fn bare_send() -> impl Rule { - (NoCase("send"), ww, label_reference, w, eol) -} - -/// `send` without a send keyword -fn shorthand_send() -> impl Rule { - let af = ( - And('a'..='f'), - Alt(( - "become", "bind", "change", "char", "clear", "cycle", "die", "end", "endgame", - )), - ); - let gr = ( - And('g'..='r'), - Alt(( - "go", "idle", "if", "lock", "play", "put", "restart", "restore", - )), - ); - let sz = ( - And('s'..='z'), - Alt(( - "send", - "set", - "shoot", - "take", - "throwstar", - "try", - "unlock", - "walk", - "zap", - )), - ); - let command = Alt((af, gr, sz)); - (Not(command), label_reference, w, eol) -} - -fn any_line() -> impl Rule { - (star!(Not("\n"), Dot), eol) -} - -fn label_line() -> impl Rule { - (":", Tag("label", label_name), eol) -} - -fn tile_kind() -> impl Rule { - (Opt((tile_color, ww)), tile_base_kind) -} - -fn tile_color() -> impl Rule { - ( - Alt(("blue", "green", "cyan", "red", "purple", "yellow", "white")), - Not('a'..='z'), - ) -} - -fn tile_base_kind() -> impl Rule { - let ac = ( - And('a'..='c'), - Alt(( - "ammo", - "bear", - "blinkwall", - "bomb", - "boulder", - "breakable", - "bullet", - "clockwise", - "counter", - )), - ); - let dk = ( - And('d'..='k'), - Alt(( - "door", - "duplicator", - "empty", - "energizer", - "fake", - "forest", - "gem", - "head", - "invisible", - "key", - )), - ); - let lr = ( - And('l'..='r'), - Alt(( - "line", "lion", "monitor", "normal", "object", "passage", "player", "pusher", - "ricochet", "ruffian", - )), - ); - let s = ( - And("s"), - Alt(( - "scroll", - "segment", - "shark", - "sliderew", - "sliderns", - "slime", - "solid", - "spinninggun", - "star", - )), - ); - let tz = ( - And('t'..='z'), - Alt(("tiger", "torch", "transporter", "water")), - ); - Alt((ac, dk, lr, s, tz)) -} - -fn condition() -> impl Rule { - let base = Alt(( - "alligned", - ("any", ww, tile_kind), - ("blocked", ww, direction), - "contact", - "energized", - ('a'..='z', star!(word_char)), - )); - (star!("not", ww), base) -} - -fn direction() -> impl Rule { - let modifier = Alt(("cw", "ccw", "rndp", "opp")); - (star!(modifier, ww), base_direction, Not('a'..='z')) -} - -fn base_direction() -> impl Rule { - // Some directions are prefixes of others, which can break parsing. - // To prevent this, we roughly order the strings from longest to shortest. - let dynamic = Alt(("flow", "rndne", "rndns", "rnd", "seek")); - let long = Alt(("north", "south", "east", "west", "idle")); - let short = Alt(("n", "s", "e", "w", "i")); - (Alt((dynamic, long, short)), Not('a'..='z')) -} - -fn label_name() -> impl Rule { - let namespace = (plus!(word_char), "~"); - ( - Opt(namespace), - star!(word_char), - Opt((".", plus!(word_char))), - ) -} - -fn label_reference() -> impl Rule { - Tag( - "ref", - ( - Opt((Tag("dest", plus!(word_char)), ":")), - Tag("name", label_name), - ), - ) -} - -fn word_char() -> impl Rule { - Alt(('A'..='Z', 'a'..='z', '0'..='9', "_")) -} - -fn eol() -> impl Rule { - Alt((And("\n"), EOF)) -} - -fn w() -> impl Rule { - star!(" ") -} - -fn ww() -> impl Rule { - plus!(" ") -} - #[cfg(test)] mod test { use std::fs; diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index 78eb8f4..d45ed7b 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -1,4 +1,3 @@ pub mod eval; pub mod parse; -pub mod peg; pub mod scan; diff --git a/src/preprocess/peg.rs b/src/preprocess/peg.rs deleted file mode 100644 index 9224885..0000000 --- a/src/preprocess/peg.rs +++ /dev/null @@ -1,549 +0,0 @@ -use std::{ - num::NonZero, - ops::{Range, RangeInclusive}, -}; - -pub struct Parser { - input: String, - offset: usize, - captures: Vec, - case_sensitive: bool, -} - -impl Parser { - pub fn new(input: &str) -> Self { - Self { - input: input.into(), - offset: 0, - captures: Vec::new(), - case_sensitive: true, - } - } - - pub fn save(&self) -> Savepoint { - Savepoint { - offset: self.offset, - num_captures: self.captures.len(), - } - } - - pub fn restore(&mut self, sp: Savepoint) { - self.offset = sp.offset; - self.captures.truncate(sp.num_captures); - } - - pub fn iter<'a>(&'a self) -> Captures<'a> { - Captures { - input: &self.input, - raw: &self.captures, - index: 0, - } - } -} - -#[derive(Clone, Copy)] -pub struct Savepoint { - offset: usize, - num_captures: usize, -} - -pub struct Captures<'a> { - input: &'a str, - raw: &'a [RawCapture], - index: usize, -} - -impl<'a> Iterator for Captures<'a> { - type Item = Capture<'a>; - - fn next(&mut self) -> Option { - let head = self.raw.get(self.index)?; - let subtree_len = head.subtree_len.unwrap().get(); - let subtree_slice = &self.raw[self.index..self.index + subtree_len]; - self.index += subtree_len; - Some(Capture { - input: &self.input, - raw: subtree_slice, - }) - } -} - -pub struct Capture<'a> { - input: &'a str, - raw: &'a [RawCapture], -} - -impl<'a> Capture<'a> { - pub fn children(&self) -> Captures<'a> { - Captures { - input: &self.input, - raw: &self.raw[1..], - index: 0, - } - } - - pub fn kind(&self) -> &'static str { - &self.raw[0].kind - } - - pub fn span(&self) -> Range { - self.raw[0].span.clone() - } - - pub fn text(&self) -> &'a str { - &self.input[self.span()] - } -} - -#[derive(Debug)] -struct RawCapture { - kind: &'static str, - span: Range, - subtree_len: Option>, -} - -pub trait Rule { - fn parse(&self, p: &mut Parser) -> bool; -} - -impl Rule for Box { - fn parse(&self, p: &mut Parser) -> bool { - self.as_ref().parse(p) - } -} - -pub struct Ref(pub T); - -impl Rule for Ref<&T> -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - self.0.parse(p) - } -} - -impl Rule for F -where - R: Rule, - F: Fn() -> R, -{ - fn parse(&self, p: &mut Parser) -> bool { - self().parse(p) - } -} - -impl Rule for &str { - fn parse(&self, p: &mut Parser) -> bool { - let matches = if p.case_sensitive { - p.input[p.offset..].starts_with(self) - } else { - p.input[p.offset..(p.offset + self.len())].eq_ignore_ascii_case(self) - }; - if matches { - p.offset += self.len(); - true - } else { - false - } - } -} - -impl Rule for RangeInclusive { - fn parse(&self, p: &mut Parser) -> bool { - if let Some(c) = p.input[p.offset..].chars().next() { - let matches = if p.case_sensitive { - self.contains(&c) - } else { - self.contains(&c.to_ascii_uppercase()) || self.contains(&c.to_ascii_lowercase()) - }; - if matches { - p.offset += c.len_utf8(); - return true; - } - } - false - } -} - -macro_rules! impl_rule_for_tuple { - ($($x:ident)+) => { - impl<$($x),+> Rule for ($($x),+,) where $($x: Rule),+, { - fn parse(&self, p: &mut Parser) -> bool { - let save = p.save(); - let mut lambda = || { - #[allow(non_snake_case)] - let ($($x),+,) = self; - $(if !$x.parse(p) {return false; })+ - true - }; - if lambda() { - true - } else { - p.restore(save); - false - } - } - } - }; -} - -#[derive(Clone)] -pub struct Alt(pub T); - -macro_rules! impl_rule_for_alt { - ($($x:ident)+) => { - impl<$($x),+> Rule for Alt<($($x),+,)> where $($x: Rule),+ { - fn parse(&self, p: &mut Parser) -> bool { - let save = p.save(); - #[allow(non_snake_case)] - let ($($x),+,) = &self.0; - $( - if $x.parse(p) { - return true - } - p.restore(save); - )+ - false - } - } - }; -} - -pub struct And(pub T); - -impl Rule for And -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - let save = p.save(); - let result = self.0.parse(p); - p.restore(save); - result - } -} - -pub struct Dot; - -impl Rule for Dot { - fn parse(&self, p: &mut Parser) -> bool { - if let Some(c) = p.input[p.offset..].chars().next() { - p.offset += c.len_utf8(); - true - } else { - false - } - } -} - -pub struct NoCase(pub T); - -impl Rule for NoCase -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - let old_value = p.case_sensitive; - p.case_sensitive = false; - let result = self.0.parse(p); - p.case_sensitive = old_value; - result - } -} - -pub struct Not(pub T); - -impl Rule for Not -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - let save = p.save(); - if self.0.parse(p) { - p.restore(save); - false - } else { - true - } - } -} - -pub struct Opt(pub T); - -impl Rule for Opt -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - let _ = self.0.parse(p); - true - } -} - -pub struct Plus(pub T); - -impl Rule for Plus -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - if !self.0.parse(p) { - return false; - } - while self.0.parse(p) {} - true - } -} - -#[macro_export] -macro_rules! plus { - ($($item:expr),+ $(,)?) => { - crate::preprocess::peg::Plus(( - $($item),+, - )) - }; -} - -pub struct Star(pub T); - -impl Rule for Star -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - while self.0.parse(p) {} - true - } -} - -#[macro_export] -macro_rules! star { - ($($item:expr),+ $(,)?) => { - crate::preprocess::peg::Star(( - $($item),+, - )) - }; -} - -pub struct Tag(pub &'static str, pub T); - -impl Rule for Tag -where - T: Rule, -{ - fn parse(&self, p: &mut Parser) -> bool { - let save = p.save(); - let index = p.captures.len(); - let start_offset = p.offset; - p.captures.push(RawCapture { - kind: self.0, - span: 0..0, - subtree_len: None, - }); - if self.1.parse(p) { - let subtree_len = NonZero::new(p.captures.len() - index).unwrap(); - p.captures[index].span = start_offset..p.offset; - p.captures[index].subtree_len = Some(subtree_len); - true - } else { - p.restore(save); - false - } - } -} - -pub struct EOF; - -impl Rule for EOF { - fn parse(&self, p: &mut Parser) -> bool { - p.offset >= p.input.len() - } -} - -macro_rules! impl_rule_for_many { - () => {}; - ($head:ident $($tail:ident)*) => { - impl_rule_for_alt!($head $($tail)*); - impl_rule_for_tuple!($head $($tail)*); - impl_rule_for_many!($($tail)*); - } -} - -impl_rule_for_many!(A B C D E F G H I J); - -#[cfg(test)] -mod test { - use insta::assert_debug_snapshot; - - use super::*; - - fn parse(rule: &T, input: &str) { - let mut p = Parser::new(input); - let rule = (Ref(rule), EOF); - assert!(rule.parse(&mut p)); - } - - fn parse_err(rule: &T, input: &str) { - let mut p = Parser::new(input); - let rule = (Ref(rule), EOF); - assert!(!rule.parse(&mut p)); - } - - #[test] - fn test_char_range() { - let rule = plus!('0'..='9'); - parse(&rule, "0"); - parse(&rule, "123"); - parse(&rule, "9"); - } - - #[test] - fn test_combinator_tuples() { - let foo = ("f", "o", "o"); - let bar = "bar"; - let rule = (foo, " ", bar); - parse(&rule, "foo bar"); - } - - #[test] - fn test_combinator_fn_wrapper() { - let foo = || "foo"; - let bar = (foo, " ", foo); - parse(&bar, "foo foo"); - } - - #[test] - fn test_combinator_alt() { - let item = Alt(("foo", "bar", "baz")); - let rule = (Ref(&item), ", ", Ref(&item)); - parse(&rule, "foo, bar"); - parse(&rule, "bar, baz"); - parse(&rule, "baz, foo"); - } - - #[test] - fn test_combinator_and() { - let has = |x| (star!(Not(x), Dot), x); - let foo_bar = (And(has("foo")), And(has("bar")), star!(Dot)); - parse(&foo_bar, "foo bar"); - parse(&foo_bar, "bar foo"); - parse_err(&foo_bar, "foo foo"); - parse_err(&foo_bar, "bar bar"); - } - - #[test] - fn test_combinator_dot() { - let rule = ("foo", Dot, "bar"); - parse(&rule, "foo bar"); - parse(&rule, "foodbar"); - parse(&rule, "foo\nbar"); - } - - #[test] - fn test_combinator_nocase() { - let rule = NoCase(("foo-", 'a'..='z', 'A'..='Z')); - parse(&rule, "foo-ab"); - parse(&rule, "fOO-cD"); - parse(&rule, "Foo-Ef"); - parse(&rule, "FOO-GH"); - } - - #[test] - fn test_mixed_case() { - let rule = ("foo ", NoCase("bar"), " baz"); - parse(&rule, "foo bar baz"); - parse(&rule, "foo BAR baz"); - parse_err(&rule, "FOO bar baz"); - parse_err(&rule, "foo bar BAZ"); - } - - #[test] - fn test_combinator_not() { - let rule = (Not("foo"), star!(Dot)); - parse(&rule, "bar"); - parse(&rule, "barfoo"); - parse_err(&rule, "foobar"); - } - - #[test] - fn test_combinator_opt() { - let rule = ("foo", Opt(" "), "bar"); - parse(&rule, "foobar"); - parse(&rule, "foo bar"); - parse_err(&rule, "foo bar"); - } - - #[test] - fn test_combinator_star() { - let item = "foo"; - let csv = (item, star!(", ", item)); - parse(&csv, "foo"); - parse(&csv, "foo, foo"); - parse(&csv, "foo, foo, foo"); - } - - #[test] - fn test_captures() { - let num = Tag("num", plus!('0'..='9')); - let rule = star!(Alt((num, Dot))); - let mut p = Parser::new("I have 123 gems and 45 torches."); - assert!(rule.parse(&mut p)); - let results: Vec<_> = p.iter().map(|x| (x.span(), x.text())).collect(); - assert_debug_snapshot!(results, @r#" - [ - ( - 7..10, - "123", - ), - ( - 20..22, - "45", - ), - ] - "#); - } - - #[test] - fn test_nested_captures() { - let letter = || 'a'..='z'; - let user = Tag("user", plus!(letter)); - let domain = Tag("domain", (letter, star!(Opt("."), letter))); - let email = Tag("email", (user, "@", domain)); - let rule = star!(Alt((email, Dot))); - let mut p = Parser::new("Send to alice@foo.net or bob@bar.com."); - assert!(rule.parse(&mut p)); - - // Make sure the capture groups were correct - let mut results = Vec::new(); - for email in p.iter() { - assert_eq!(email.kind(), "email"); - for group in email.children() { - results.push(format!("{}: {}", group.kind(), group.text())); - } - } - assert_debug_snapshot!(results, @r#" - [ - "user: alice", - "domain: foo.net", - "user: bob", - "domain: bar.com", - ] - "#); - - // Make sure &str lifetimes outlive the Capture structs - let slices: Vec<&str> = p - .iter() - .flat_map(|email| email.children()) - .map(|group| group.text()) - .collect(); - assert_debug_snapshot!(slices, @r#" - [ - "alice", - "foo.net", - "bob", - "bar.com", - ] - "#); - } -}