From c15507ad6a61c95d456c8dc66acafe7410cc05d9 Mon Sep 17 00:00:00 2001 From: matt rice Date: Mon, 30 Sep 2024 06:13:52 -0700 Subject: [PATCH] Relax `%parse-param` bounds from `Copy` to `Clone` --- Cargo.toml | 1 + doc/src/actioncode.md | 6 ++-- lrpar/cttests/src/lib.rs | 13 +++++++++ lrpar/cttests/src/parseparam_copy.test | 18 ++++++++++++ lrpar/examples/clone_param/Cargo.toml | 20 +++++++++++++ lrpar/examples/clone_param/README.md | 20 +++++++++++++ lrpar/examples/clone_param/build.rs | 20 +++++++++++++ lrpar/examples/clone_param/src/main.rs | 40 ++++++++++++++++++++++++++ lrpar/examples/clone_param/src/param.l | 5 ++++ lrpar/examples/clone_param/src/param.y | 21 ++++++++++++++ lrpar/src/lib/cpctplus.rs | 14 ++++----- lrpar/src/lib/parser.rs | 12 ++++---- 12 files changed, 174 insertions(+), 16 deletions(-) create mode 100644 lrpar/cttests/src/parseparam_copy.test create mode 100644 lrpar/examples/clone_param/Cargo.toml create mode 100644 lrpar/examples/clone_param/README.md create mode 100644 lrpar/examples/clone_param/build.rs create mode 100644 lrpar/examples/clone_param/src/main.rs create mode 100644 lrpar/examples/clone_param/src/param.l create mode 100644 lrpar/examples/clone_param/src/param.y diff --git a/Cargo.toml b/Cargo.toml index 24286930e..4fb67f257 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members=[ "lrpar/examples/calc_ast", "lrpar/examples/calc_parsetree", "lrpar/examples/start_states", + "lrpar/examples/mut_param", "lrtable", "nimbleparse", ] diff --git a/doc/src/actioncode.md b/doc/src/actioncode.md index 136472f10..61dce026f 100644 --- a/doc/src/actioncode.md +++ b/doc/src/actioncode.md @@ -51,9 +51,9 @@ make use of the following: A single extra parameter can be passed to action functions if the `%parse-param : ` declaration is used. The variable `` is then visible in all -action code. `` must implement the [`Copy` -trait](https://doc.rust-lang.org/std/marker/trait.Copy.html) (note that `&` -references implement `Copy`). +action code. `` must implement the [`Clone` +trait](https://doc.rust-lang.org/stable/std/clone/trait.Clone.html) (note that `Copy` +bounds imply `Clone`, and `&` references implement `Copy`). For example if a grammar has a declaration: diff --git a/lrpar/cttests/src/lib.rs b/lrpar/cttests/src/lib.rs index aabd61d3d..34f9c8be9 100644 --- a/lrpar/cttests/src/lib.rs +++ b/lrpar/cttests/src/lib.rs @@ -34,6 +34,9 @@ lrpar_mod!("multitypes.y"); lrlex_mod!("parseparam.l"); lrpar_mod!("parseparam.y"); +lrlex_mod!("parseparam_copy.l"); +lrpar_mod!("parseparam_copy.y"); + lrlex_mod!("passthrough.l"); lrpar_mod!("passthrough.y"); @@ -247,6 +250,16 @@ fn test_parseparam() { } } +#[test] +fn test_parseparam_copy() { + let lexerdef = parseparam_copy_l::lexerdef(); + let lexer = lexerdef.lexer("101"); + match parseparam_copy_y::parse(&lexer, 3) { + (Some(104), _) => (), + _ => unreachable!(), + } +} + #[test] fn test_passthrough() { let lexerdef = passthrough_l::lexerdef(); diff --git a/lrpar/cttests/src/parseparam_copy.test b/lrpar/cttests/src/parseparam_copy.test new file mode 100644 index 000000000..ecf3b9648 --- /dev/null +++ b/lrpar/cttests/src/parseparam_copy.test @@ -0,0 +1,18 @@ +name: Test %parse-param copy +yacckind: Grmtools +grammar: | + %start S + %parse-param p: u64 + %% + S -> u64: + // Previously %parse-param required a `Copy` bounds. + // Since then we relaxed the bounds to require `Clone`. + // This tests backwards compatibility of actions that + // rely on the older copy bounds. + 'INT' { (move |_| {})(p); check_copy(p); p + $lexer.span_str($1.unwrap().span()).parse::().unwrap() } + ; + %% + fn check_copy(_: T){} +lexer: | + %% + [0-9]+ 'INT' diff --git a/lrpar/examples/clone_param/Cargo.toml b/lrpar/examples/clone_param/Cargo.toml new file mode 100644 index 000000000..0a30f3581 --- /dev/null +++ b/lrpar/examples/clone_param/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mut_param" +version = "0.1.0" +authors = ["Matt Rice "] +edition = "2021" +license = "Apache-2.0/MIT" + +[[bin]] +doc = false +name = "mut_param" + +[build-dependencies] +cfgrammar = { path="../../../cfgrammar" } +lrlex = { path="../../../lrlex" } +lrpar = { path="../.." } + +[dependencies] +cfgrammar = { path="../../../cfgrammar" } +lrlex = { path="../../../lrlex" } +lrpar = { path="../.." } diff --git a/lrpar/examples/clone_param/README.md b/lrpar/examples/clone_param/README.md new file mode 100644 index 000000000..3be9d4bb7 --- /dev/null +++ b/lrpar/examples/clone_param/README.md @@ -0,0 +1,20 @@ +# `clone_param` + +## Description +Example which shows how to use interior mutability with the `%parse-param` directive. +As a parameter the parse function accepts a `Rc>`. + +## Input +For input the parser accepts a positive or negative integer e.g. `-1`, `42`, etc followed +by any sequence of `+`, or `-` characters. Except for the initial `-` on a negative integer, +`+` or `-` are treated as `Increment` and `Decrement` operators. + +## Evaluation +Rather than building an AST, the param is directly mutated by the actions. +As such an input sequence like `-3++-` will evalute to `-2`. + +## Example +``` +>>> -3++- +Evaluated: RefCell { value: -2 } +``` diff --git a/lrpar/examples/clone_param/build.rs b/lrpar/examples/clone_param/build.rs new file mode 100644 index 000000000..2bca2a146 --- /dev/null +++ b/lrpar/examples/clone_param/build.rs @@ -0,0 +1,20 @@ +#![deny(rust_2018_idioms)] +use cfgrammar::yacc::YaccKind; +use lrlex::CTLexerBuilder; + +fn main() { + // Since we're using both lrlex and lrpar, we use lrlex's `lrpar_config` convenience function + // that makes it easy to a) create a lexer and parser and b) link them together. + CTLexerBuilder::new() + .rust_edition(lrlex::RustEdition::Rust2021) + .lrpar_config(|ctp| { + ctp.yacckind(YaccKind::Grmtools) + .rust_edition(lrpar::RustEdition::Rust2021) + .grammar_in_src_dir("param.y") + .unwrap() + }) + .lexer_in_src_dir("param.l") + .unwrap() + .build() + .unwrap(); +} diff --git a/lrpar/examples/clone_param/src/main.rs b/lrpar/examples/clone_param/src/main.rs new file mode 100644 index 000000000..82aed5461 --- /dev/null +++ b/lrpar/examples/clone_param/src/main.rs @@ -0,0 +1,40 @@ +#![allow(clippy::unnecessary_wraps)] + +use std::io::{self, BufRead, Write}; +use std::{ rc::Rc, cell::RefCell }; +use lrlex::lrlex_mod; +use lrpar::lrpar_mod; + +// Using `lrlex_mod!` brings the lexer for `param.l` into scope. By default the module name will be +// `param_l` (i.e. the file name, minus any extensions, with a suffix of `_l`). +lrlex_mod!("param.l"); +// Using `lrpar_mod!` brings the parser for `param.y` into scope. By default the module name will be +// `param_y` (i.e. the file name, minus any extensions, with a suffix of `_y`). +lrpar_mod!("param.y"); + +fn main() { + // Get the `LexerDef` for the `param` language. + let lexerdef = param_l::lexerdef(); + let stdin = io::stdin(); + loop { + print!(">>> "); + io::stdout().flush().ok(); + match stdin.lock().lines().next() { + Some(Ok(ref l)) => { + if l.trim().is_empty() { + continue; + } + // Now we create a lexer with the `lexer` method with which we can lex an input. + let lexer = lexerdef.lexer(l); + let param = Rc::new(RefCell::new(0)); + // Pass the lexer to the parser and lex and parse the input. + let (_opt , errs) = param_y::parse(&lexer, param.clone()); + for e in errs { + println!("{}", e.pp(&lexer, ¶m_y::token_epp)); + } + println!("Evaluated: {:?}", ¶m); + } + _ => break, + } + } +} diff --git a/lrpar/examples/clone_param/src/param.l b/lrpar/examples/clone_param/src/param.l new file mode 100644 index 000000000..0b3447a79 --- /dev/null +++ b/lrpar/examples/clone_param/src/param.l @@ -0,0 +1,5 @@ +%% +(\-?)[0-9]+ "INT" +\- "Decr" +\+ "Incr" +[\n\t\ ] ; diff --git a/lrpar/examples/clone_param/src/param.y b/lrpar/examples/clone_param/src/param.y new file mode 100644 index 000000000..6dd21fc7d --- /dev/null +++ b/lrpar/examples/clone_param/src/param.y @@ -0,0 +1,21 @@ +%token Incr Decr +%parse-param val: Rc> +%% +Expr -> () : "INT" Ops { + *val.borrow_mut() += parse_int($lexer.span_str($1.map_err(|_| "").unwrap().span())).unwrap() + }; +Ops -> (): + %empty {} + | Ops Incr { *val.borrow_mut() += 1; } + | Ops Decr { *val.borrow_mut() -= 1; }; +%% +use std::{ rc::Rc, cell::RefCell, error::Error }; + +fn parse_int(s: &str) -> Result> { + match s.parse::() { + Ok(val) => Ok(val), + Err(_) => { + Err(Box::from(format!("{} cannot be represented as a i64", s))) + } + } +} diff --git a/lrpar/src/lib/cpctplus.rs b/lrpar/src/lib/cpctplus.rs index 86dc1e1ba..0ee626e8a 100644 --- a/lrpar/src/lib/cpctplus.rs +++ b/lrpar/src/lib/cpctplus.rs @@ -106,7 +106,7 @@ struct CPCTPlus< StorageT: 'static + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, > where usize: AsPrimitive, { @@ -118,7 +118,7 @@ pub(super) fn recoverer< StorageT: 'static + Debug + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, >( parser: &'a Parser, ) -> Box + 'a> @@ -135,7 +135,7 @@ impl< StorageT: 'static + Debug + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, > Recoverer for CPCTPlus<'a, 'b, 'input, StorageT, LexerTypesT, ActionT, ParamT> where @@ -270,7 +270,7 @@ impl< StorageT: 'static + Debug + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, > CPCTPlus<'a, 'b, 'input, StorageT, LexerTypesT, ActionT, ParamT> where usize: AsPrimitive, @@ -457,7 +457,7 @@ fn apply_repairs< StorageT: 'static + Debug + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, >( parser: &Parser, mut laidx: usize, @@ -496,7 +496,7 @@ fn simplify_repairs< StorageT: 'static + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT, - ParamT: Copy, + ParamT: Clone, >( parser: &Parser, all_rprs: &mut Vec>>, @@ -557,7 +557,7 @@ fn rank_cnds< StorageT: 'static + Debug + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, >( parser: &Parser, finish_by: Instant, diff --git a/lrpar/src/lib/parser.rs b/lrpar/src/lib/parser.rs index 5f38e2070..d6a1ca241 100644 --- a/lrpar/src/lib/parser.rs +++ b/lrpar/src/lib/parser.rs @@ -88,7 +88,7 @@ pub(super) struct Parser< StorageT: 'static + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, > where usize: AsPrimitive, { @@ -241,7 +241,7 @@ impl< StorageT: 'static + Debug + Eq + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT: 'a, - ParamT: Copy, + ParamT: Clone, > Parser<'a, 'b, 'input, StorageT, LexerTypesT, ActionT, ParamT> where usize: AsPrimitive, @@ -329,7 +329,7 @@ where self.lexer, span, astack.drain(pop_idx - 1..), - self.param, + self.param.clone(), )); astack.push(v); } @@ -447,7 +447,7 @@ where self.lexer, span, astack_uw.drain(pop_idx - 1..), - self.param, + self.param.clone(), )); astack_uw.push(v); } else { @@ -595,7 +595,7 @@ pub(super) trait Recoverer< StorageT: 'static + Debug + Hash + PrimInt + Unsigned, LexerTypesT: LexerTypes, ActionT, - ParamT: Copy, + ParamT: Clone, > where usize: AsPrimitive, { @@ -877,7 +877,7 @@ where /// (`None, [...]`), errors and a value (`Some(...), [...]`), as well as a value and no errors /// (`Some(...), []`). Errors are sorted by the position they were found in the input and can /// be a mix of lexing and parsing errors. - pub fn parse_actions<'b: 'a, 'input: 'b, ActionT: 'a, ParamT: Copy>( + pub fn parse_actions<'b: 'a, 'input: 'b, ActionT: 'a, ParamT: Clone>( &self, lexer: &'b dyn NonStreamingLexer<'input, LexerTypesT>, actions: &'a [ActionFn<'a, 'b, 'input, StorageT, LexerTypesT, ActionT, ParamT>],