Skip to content

Commit 0b1120a

Browse files
committed
introduce proper precedence, associativity
1 parent 61226f9 commit 0b1120a

File tree

7 files changed

+310
-77
lines changed

7 files changed

+310
-77
lines changed

crates/formality-core/src/parse/parser.rs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ where
3333
nonterminal_name: &'static str,
3434
successes: Vec<SuccessfulParse<'t, T>>,
3535
failures: Set<ParseError<'t>>,
36+
min_precedence_level: usize,
3637
}
3738

3839
/// The *precedence* of a variant determines how to manage
@@ -78,6 +79,7 @@ pub enum Associativity {
7879
Left,
7980
Right,
8081
None,
82+
Both,
8183
}
8284

8385
impl Precedence {
@@ -98,16 +100,31 @@ impl Precedence {
98100

99101
/// Construct a new precedence.
100102
fn new(level: usize, associativity: Associativity) -> Self {
103+
// We require level to be STRICTLY LESS than usize::MAX
104+
// so that we can always add 1.
105+
assert!(level < std::usize::MAX);
101106
Self {
102107
level,
103-
associativity,
108+
associativity: associativity,
104109
}
105110
}
106111
}
107112

108113
impl Default for Precedence {
109114
fn default() -> Self {
110-
Self::new(std::usize::MAX, Associativity::None)
115+
// Default precedence:
116+
//
117+
// Use MAX-1 because we sometimes try to add 1 when we detect recursion, and we don't want overflow.
118+
//
119+
// Use Right associativity because if you have a variant like `T = [T]`
120+
// then you want to be able to (by default) embed arbitrary T in that recursive location.
121+
// If you use LEFT or NONE, then this recursive T would have a minimum level of std::usize::MAX
122+
// (and hence never be satisfied).
123+
//
124+
// Using RIGHT feels a bit weird here but seems to behave correctly all the time.
125+
// It's tempting to add something like "Both" or "N/A" that would just not set a min
126+
// prec level when there's recursion.
127+
Self::new(std::usize::MAX - 1, Associativity::Both)
111128
}
112129
}
113130

@@ -172,7 +189,7 @@ where
172189
) -> ParseResult<'t, T> {
173190
let text = skip_whitespace(text);
174191

175-
left_recursion::enter(scope, text, || {
192+
left_recursion::enter(scope, text, |min_precedence_level| {
176193
let tracing_span = tracing::span!(
177194
tracing::Level::TRACE,
178195
"nonterminal",
@@ -188,6 +205,7 @@ where
188205
nonterminal_name,
189206
successes: vec![],
190207
failures: set![],
208+
min_precedence_level,
191209
};
192210

193211
op(&mut parser);
@@ -230,6 +248,15 @@ where
230248
);
231249
let guard = span.enter();
232250

251+
if variant_precedence.level < self.min_precedence_level {
252+
tracing::trace!(
253+
"variant has precedence level {} which is below parser minimum of {}",
254+
variant_precedence.level,
255+
self.min_precedence_level,
256+
);
257+
return;
258+
}
259+
233260
let mut active_variant =
234261
ActiveVariant::new(variant_precedence, self.scope, self.start_text);
235262
let result = op(&mut active_variant);
@@ -480,7 +507,7 @@ where
480507
}
481508

482509
/// Accepts any of the given keywords.
483-
#[tracing::instrument(level = "trace", ret)]
510+
#[tracing::instrument(level = "trace", skip(self), ret)]
484511
pub fn expect_keyword_in(&mut self, expected: &[&str]) -> Result<String, Set<ParseError<'t>>> {
485512
let text0 = self.current_text;
486513
match self.identifier_like_string() {
@@ -495,6 +522,7 @@ where
495522
/// Attempts to execute `op` and, if it successfully parses, then returns an error
496523
/// with the value (which is meant to be incorporated into a par)
497524
/// If `op` fails to parse then the result is `Ok`.
525+
#[tracing::instrument(level = "trace", skip(self, op, err), ret)]
498526
pub fn reject<T>(
499527
&self,
500528
op: impl Fn(&mut ActiveVariant<'s, 't, L>) -> Result<T, Set<ParseError<'t>>>,
@@ -519,7 +547,6 @@ where
519547
/// Does not consume any input.
520548
/// You can this to implement positional keywords -- just before parsing an identifier or variable,
521549
/// you can invoke `reject_custom_keywords` to reject anything that you don't want to permit in this position.
522-
#[tracing::instrument(level = "trace", ret)]
523550
pub fn reject_custom_keywords(&self, keywords: &[&str]) -> Result<(), Set<ParseError<'t>>> {
524551
self.reject(
525552
|p| p.expect_keyword_in(keywords),
@@ -534,7 +561,7 @@ where
534561

535562
/// Extracts a string that meets the regex for an identifier
536563
/// (but it could also be a keyword).
537-
#[tracing::instrument(level = "trace", ret)]
564+
#[tracing::instrument(level = "trace", skip(self), ret)]
538565
pub fn identifier_like_string(&mut self) -> Result<String, Set<ParseError<'t>>> {
539566
self.string(
540567
|ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '_'),
@@ -547,7 +574,7 @@ where
547574
/// following the usual rules. **Disallows language keywords.**
548575
/// If you want to disallow additional keywords,
549576
/// see the `reject_custom_keywords` method.
550-
#[tracing::instrument(level = "trace", ret)]
577+
#[tracing::instrument(level = "trace", skip(self), ret)]
551578
pub fn identifier(&mut self) -> Result<String, Set<ParseError<'t>>> {
552579
self.reject_custom_keywords(L::KEYWORDS)?;
553580
self.identifier_like_string()
@@ -610,7 +637,7 @@ where
610637
/// It also allows parsing where you use variables to stand for
611638
/// more complex parameters, which is kind of combining parsing
612639
/// and substitution and can be convenient in tests.
613-
#[tracing::instrument(level = "trace", ret)]
640+
#[tracing::instrument(level = "trace", skip(self), ret)]
614641
pub fn variable<R>(&mut self) -> Result<R, Set<ParseError<'t>>>
615642
where
616643
R: Debug + DowncastFrom<CoreParameter<L>>,
@@ -636,7 +663,7 @@ where
636663
}
637664

638665
/// Extract a number from the input, erroring if the input does not start with a number.
639-
#[tracing::instrument(level = "trace", ret)]
666+
#[tracing::instrument(level = "trace", skip(self), ret)]
640667
pub fn number<T>(&mut self) -> Result<T, Set<ParseError<'t>>>
641668
where
642669
T: FromStr + std::fmt::Debug,
@@ -702,7 +729,7 @@ where
702729
/// because we consumed the open paren `(` from `T` but then encountered an error
703730
/// looking for the closing paren `)`.
704731
#[track_caller]
705-
#[tracing::instrument(level = "Trace", ret)]
732+
#[tracing::instrument(level = "Trace", skip(self), ret)]
706733
pub fn opt_nonterminal<T>(&mut self) -> Result<Option<T>, Set<ParseError<'t>>>
707734
where
708735
T: CoreParse<L>,
@@ -726,6 +753,7 @@ where
726753

727754
/// Continue parsing instances of `T` while we can.
728755
/// This is a greedy parse.
756+
#[tracing::instrument(level = "Trace", skip(self), ret)]
729757
pub fn many_nonterminal<T>(&mut self) -> Result<Vec<T>, Set<ParseError<'t>>>
730758
where
731759
T: CoreParse<L>,
@@ -737,7 +765,7 @@ where
737765
Ok(result)
738766
}
739767

740-
#[tracing::instrument(level = "Trace", ret)]
768+
#[tracing::instrument(level = "Trace", skip(self), ret)]
741769
pub fn delimited_nonterminal<T>(
742770
&mut self,
743771
open: char,

0 commit comments

Comments
 (0)