Skip to content

Commit 5b90a76

Browse files
Add operator overloading for building grammars.
1 parent 54399b0 commit 5b90a76

File tree

5 files changed

+175
-108
lines changed

5 files changed

+175
-108
lines changed

Diff for: fuzzcheck/src/mutators/grammar/grammar.rs

+104-33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::ops::{Range, RangeBounds, RangeInclusive};
1+
use std::ops::{Add, BitOr, Range, RangeBounds, RangeInclusive};
22
use std::rc::{Rc, Weak};
33

44
#[cfg(feature = "regex_grammar")]
@@ -8,46 +8,90 @@ use crate::mutators::grammar::regex::grammar_from_regex;
88
/// A grammar which can be used for fuzzing.
99
///
1010
/// See [the module documentation](crate::mutators::grammar) for advice on how to create a grammar.
11-
pub enum Grammar {
11+
pub enum GrammarInner {
1212
Literal(Vec<RangeInclusive<char>>),
13-
Alternation(Vec<Rc<Grammar>>),
14-
Concatenation(Vec<Rc<Grammar>>),
15-
Repetition(Rc<Grammar>, Range<usize>),
16-
Recurse(Weak<Grammar>),
17-
Recursive(Rc<Grammar>),
13+
Alternation(Vec<Grammar>),
14+
Concatenation(Vec<Grammar>),
15+
Repetition(Grammar, Range<usize>),
16+
Recurse(Weak<GrammarInner>),
17+
Recursive(Grammar),
18+
}
19+
20+
#[derive(Debug, Clone)]
21+
/// A [`Grammar`] can be transformed into an [`ASTMutator`] (which generates
22+
/// [`String`]s corresponding to the grammar in question) or combined with other
23+
/// grammars to produce a more complicated grammar.
24+
///
25+
/// For examples on how to use this struct, see the crate documentation
26+
/// ([`super`]).
27+
///
28+
/// [`ASTMutator`]: crate::mutators::grammar::ASTMutator
29+
pub struct Grammar(pub(crate) Rc<GrammarInner>);
30+
31+
impl From<Rc<GrammarInner>> for Grammar {
32+
fn from(inner: Rc<GrammarInner>) -> Self {
33+
Grammar(inner)
34+
}
35+
}
36+
37+
impl AsRef<GrammarInner> for Grammar {
38+
fn as_ref(&self) -> &GrammarInner {
39+
&self.0
40+
}
41+
}
42+
43+
impl Add for Grammar {
44+
type Output = Grammar;
45+
46+
/// Calls [`concatenation`] on the two provided grammars.
47+
fn add(self, rhs: Self) -> Self::Output {
48+
concatenation([self, rhs])
49+
}
50+
}
51+
52+
impl BitOr for Grammar {
53+
type Output = Grammar;
54+
55+
/// Calls [`alternation`] on the two provided grammars.
56+
fn bitor(self, rhs: Self) -> Self::Output {
57+
alternation([self, rhs])
58+
}
1859
}
1960

2061
#[cfg(feature = "regex_grammar")]
2162
#[doc(cfg(feature = "regex_grammar"))]
2263
#[no_coverage]
23-
pub fn regex(s: &str) -> Rc<Grammar> {
64+
pub fn regex(s: &str) -> Grammar {
2465
grammar_from_regex(s)
2566
}
2667

2768
#[no_coverage]
28-
/// Creates an [`Rc<Grammar>`] which outputs characters in the given range.
69+
/// Creates a [`Grammar`] which outputs characters in the given range.
2970
///
3071
/// For example, to generate characters in the range 'a' to 'z' (inclusive), one
3172
/// could use this code
3273
///
3374
/// ```
34-
/// let a_to_z = literal_ranges('a'..='z');
75+
/// # use fuzzcheck::mutators::grammar::literal_ranges;
76+
/// let a_to_z = literal_ranges(vec!['a'..='b', 'q'..='s', 't'..='w']);
3577
/// ```
36-
pub fn literal_ranges(ranges: Vec<RangeInclusive<char>>) -> Rc<Grammar> {
37-
Rc::new(Grammar::Literal(ranges))
78+
pub fn literal_ranges(ranges: Vec<RangeInclusive<char>>) -> Grammar {
79+
Rc::new(GrammarInner::Literal(ranges)).into()
3880
}
3981

4082
#[no_coverage]
41-
/// Creates an [`Rc<Grammar>`] which matches a single character literal.
83+
/// Creates a [`Grammar`] which matches a single character literal.
4284
///
4385
/// ```
86+
/// # use fuzzcheck::mutators::grammar::literal;
4487
/// let l = literal('l');
4588
/// ```
46-
pub fn literal(l: char) -> Rc<Grammar> {
47-
Rc::new(Grammar::Literal(vec![l..=l]))
89+
pub fn literal(l: char) -> Grammar {
90+
Rc::new(GrammarInner::Literal(vec![l..=l])).into()
4891
}
92+
4993
#[no_coverage]
50-
pub fn literal_range<R>(range: R) -> Rc<Grammar>
94+
pub fn literal_range<R>(range: R) -> Grammar
5195
where
5296
R: RangeBounds<char>,
5397
{
@@ -61,34 +105,60 @@ where
61105
std::ops::Bound::Excluded(x) => unsafe { char::from_u32_unchecked(*x as u32 - 1) },
62106
std::ops::Bound::Unbounded => panic!("The range must have an upper bound"),
63107
};
64-
Rc::new(Grammar::Literal(vec![start..=end]))
108+
Rc::new(GrammarInner::Literal(vec![start..=end])).into()
65109
}
66110

67111
/// Produces a grammar which will choose between the provided grammars.
112+
///
113+
/// For example, this grammar
114+
/// ```
115+
/// # use fuzzcheck::mutators::grammar::{Grammar, alternation, regex};
116+
/// let fuzz_or_check: Grammar = alternation([
117+
/// regex("fuzz"),
118+
/// regex("check")
119+
/// ]);
120+
/// ```
121+
/// would output either "fuzz" or "check".
122+
///
123+
/// It is also possible to use the `|` operator to write alternation grammars.
124+
/// For example, the [`Grammar`] above could be equivalently written as
125+
///
126+
/// ```
127+
/// let fuzz_or_check: Grammar = regex("fuzz") | regex("check");
128+
/// ```
68129
#[no_coverage]
69-
pub fn alternation(gs: impl IntoIterator<Item = Rc<Grammar>>) -> Rc<Grammar> {
70-
Rc::new(Grammar::Alternation(gs.into_iter().collect()))
130+
pub fn alternation(gs: impl IntoIterator<Item = Grammar>) -> Grammar {
131+
Rc::new(GrammarInner::Alternation(gs.into_iter().collect())).into()
71132
}
72133

73134
/// Produces a grammar which will concatenate the output of all the provided
74135
/// grammars together, in order.
75136
///
76137
/// For example, the grammar
77138
/// ```
78-
/// concatenation([
139+
/// # use fuzzcheck::mutators::grammar::{concatenation, Grammar, regex};
140+
/// let fuzzcheck: Grammar = concatenation([
79141
/// regex("fuzz"),
80142
/// regex("check")
81-
/// ])
143+
/// ]);
82144
/// ```
83145
/// would output "fuzzcheck".
146+
///
147+
/// It is also possible to use the `+` operator to concatenate separate grammars
148+
/// together. For example, the grammar above could be equivalently written as
149+
///
150+
/// ```
151+
/// # use fuzzcheck::mutators::grammar::{Grammar, regex};
152+
/// let fuzzcheck: Grammar = regex("fuzz") + regex("check");
153+
/// ```
84154
#[no_coverage]
85-
pub fn concatenation(gs: impl IntoIterator<Item = Rc<Grammar>>) -> Rc<Grammar> {
86-
Rc::new(Grammar::Concatenation(gs.into_iter().collect()))
155+
pub fn concatenation(gs: impl IntoIterator<Item = Grammar>) -> Grammar {
156+
Rc::new(GrammarInner::Concatenation(gs.into_iter().collect())).into()
87157
}
88158

89159
#[no_coverage]
90-
/// Repeats the provided grammar some number of times in the given range.
91-
pub fn repetition<R>(gs: Rc<Grammar>, range: R) -> Rc<Grammar>
160+
/// Repeats the provided [`Grammar`] some number of times in the given range.
161+
pub fn repetition<R>(gs: Grammar, range: R) -> Grammar
92162
where
93163
R: RangeBounds<usize>,
94164
{
@@ -102,25 +172,26 @@ where
102172
std::ops::Bound::Excluded(x) => *x,
103173
std::ops::Bound::Unbounded => usize::MAX,
104174
};
105-
Rc::new(Grammar::Repetition(gs, start..end))
175+
Rc::new(GrammarInner::Repetition(gs, start..end)).into()
106176
}
107177

108178
#[no_coverage]
109179
/// Used to indicate a point of recursion to Fuzzcheck. Should be combined with
110180
/// [`recursive`].
111181
///
112182
/// See the module documentation ([`super`]) for an example on how to use it.
113-
pub fn recurse(g: &Weak<Grammar>) -> Rc<Grammar> {
114-
Rc::new(Grammar::Recurse(g.clone()))
183+
pub fn recurse(g: &Weak<GrammarInner>) -> Grammar {
184+
Rc::new(GrammarInner::Recurse(g.clone())).into()
115185
}
116186

117187
#[no_coverage]
118-
/// Creates a recursive grammar. This function should be combined with
188+
/// Creates a recursive [`Grammar`]. This function should be combined with
119189
/// [`recurse`] to make recursive calls.
120190
///
121191
/// See the module documentation ([`super`]) for an example on how to use it.
122-
pub fn recursive(data_fn: impl Fn(&Weak<Grammar>) -> Rc<Grammar>) -> Rc<Grammar> {
123-
Rc::new(Grammar::Recursive(Rc::new_cyclic(|g| {
124-
Rc::try_unwrap(data_fn(g)).unwrap()
125-
})))
192+
pub fn recursive(data_fn: impl Fn(&Weak<GrammarInner>) -> Grammar) -> Grammar {
193+
Rc::new(GrammarInner::Recursive(
194+
Rc::new_cyclic(|g| Rc::try_unwrap(data_fn(g).0).unwrap()).into(),
195+
))
196+
.into()
126197
}

Diff for: fuzzcheck/src/mutators/grammar/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ pub use ast::AST;
7676
#[doc(cfg(feature = "regex_grammar"))]
7777
pub use grammar::regex;
7878
#[doc(inline)]
79-
pub use grammar::Grammar;
80-
#[doc(inline)]
8179
pub use grammar::{alternation, concatenation, literal, literal_range, literal_ranges, recurse, recursive, repetition};
8280
#[doc(inline)]
81+
pub use grammar::{Grammar, GrammarInner};
82+
#[doc(inline)]
8383
pub use mutators::grammar_based_ast_mutator;
8484
#[doc(inline)]
8585
pub use mutators::ASTMutator;

Diff for: fuzzcheck/src/mutators/grammar/mutators.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::rc::{Rc, Weak};
66

77
use fuzzcheck_mutators_derive::make_single_variant_mutator;
88

9-
use super::grammar::Grammar;
9+
use super::grammar::{Grammar, GrammarInner};
1010
use crate::mutators::alternation::AlternationMutator;
1111
use crate::mutators::character_classes::CharacterMutator;
1212
use crate::mutators::either::Either3;
@@ -233,7 +233,7 @@ impl Mutator<AST> for ASTMutator {
233233
}
234234

235235
#[no_coverage]
236-
pub fn grammar_based_ast_mutator(grammar: Rc<Grammar>) -> ASTMutator {
236+
pub fn grammar_based_ast_mutator(grammar: Grammar) -> ASTMutator {
237237
ASTMutator::from_grammar(grammar)
238238
}
239239

@@ -282,19 +282,19 @@ impl ASTMutator {
282282
}
283283

284284
#[no_coverage]
285-
pub(crate) fn from_grammar(grammar: Rc<Grammar>) -> Self {
285+
pub(crate) fn from_grammar(grammar: Grammar) -> Self {
286286
let mut others = HashMap::new();
287287
Self::from_grammar_rec(grammar, &mut others)
288288
}
289289

290290
#[no_coverage]
291291
pub(crate) fn from_grammar_rec(
292-
grammar: Rc<Grammar>,
293-
others: &mut HashMap<*const Grammar, Weak<ASTMutator>>,
292+
grammar: Grammar,
293+
others: &mut HashMap<*const GrammarInner, Weak<ASTMutator>>,
294294
) -> Self {
295295
match grammar.as_ref() {
296-
Grammar::Literal(l) => Self::token(CharacterMutator::new(l.clone())),
297-
Grammar::Alternation(gs) => Self::alternation(AlternationMutator::new(
296+
GrammarInner::Literal(l) => Self::token(CharacterMutator::new(l.clone())),
297+
GrammarInner::Alternation(gs) => Self::alternation(AlternationMutator::new(
298298
gs.iter()
299299
.map(
300300
#[no_coverage]
@@ -303,29 +303,29 @@ impl ASTMutator {
303303
.collect(),
304304
0.0,
305305
)),
306-
Grammar::Concatenation(gs) => {
306+
GrammarInner::Concatenation(gs) => {
307307
let mut ms = Vec::<ASTMutator>::new();
308308
for g in gs {
309309
let m = Self::from_grammar_rec(g.clone(), others);
310310
ms.push(m);
311311
}
312312
Self::concatenation(FixedLenVecMutator::new_without_inherent_complexity(ms))
313313
}
314-
Grammar::Repetition(g, range) => Self::repetition(VecMutator::new_without_inherent_complexity(
314+
GrammarInner::Repetition(g, range) => Self::repetition(VecMutator::new_without_inherent_complexity(
315315
Self::from_grammar_rec(g.clone(), others),
316316
range.start..=range.end - 1,
317317
)),
318-
Grammar::Recurse(g) => {
318+
GrammarInner::Recurse(g) => {
319319
if let Some(m) = others.get(&g.as_ptr()) {
320320
Self::recur(RecurToMutator::from(m))
321321
} else {
322322
panic!()
323323
}
324324
}
325-
Grammar::Recursive(g) => Self::recursive(
325+
GrammarInner::Recursive(g) => Self::recursive(
326326
#[no_coverage]
327327
|m| {
328-
let weak_g = Rc::downgrade(g);
328+
let weak_g = Rc::downgrade(&g.0);
329329
others.insert(weak_g.as_ptr(), m.clone());
330330
Self::from_grammar_rec(g.clone(), others)
331331
},

Diff for: fuzzcheck/src/mutators/grammar/regex.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
use std::rc::Rc;
2-
31
use regex_syntax::hir::{Class, HirKind, Literal, RepetitionKind, RepetitionRange};
42

5-
use crate::mutators::grammar::{alternation, concatenation, literal, literal_ranges, repetition, Grammar};
3+
use super::grammar::Grammar;
4+
use crate::mutators::grammar::{alternation, concatenation, literal, literal_ranges, repetition};
65

76
#[no_coverage]
8-
pub(crate) fn grammar_from_regex(regex: &str) -> Rc<Grammar> {
7+
pub(crate) fn grammar_from_regex(regex: &str) -> Grammar {
98
let mut parser = regex_syntax::Parser::new();
109
let hir = parser.parse(regex).unwrap();
1110
grammar_from_regex_hir_kind(hir.kind())
1211
}
1312
#[no_coverage]
14-
pub fn grammar_from_regex_hir_kind(hir: &HirKind) -> Rc<Grammar> {
13+
pub fn grammar_from_regex_hir_kind(hir: &HirKind) -> Grammar {
1514
match hir {
1615
HirKind::Empty => panic!("empty regexes are not supported"),
1716
HirKind::Literal(l) => match l {

0 commit comments

Comments
 (0)