Skip to content

Commit 165cb7c

Browse files
committed
introduce Precedence struct with associativity
1 parent afa8d89 commit 165cb7c

File tree

5 files changed

+181
-31
lines changed

5 files changed

+181
-31
lines changed

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

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,81 @@ where
3535
failures: Set<ParseError<'t>>,
3636
}
3737

38-
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
39-
pub struct Precedence(usize);
38+
/// The *precedence* of a variant determines how to manage
39+
/// recursive invocations.
40+
///
41+
/// The general rule is that an expression
42+
/// with lower-precedence cannot be embedded
43+
/// into an expression of higher-precedence.
44+
/// So given `1 + 2 * 3`, the `+` cannot be a
45+
/// (direct) child of the `*`, because `+` is
46+
/// lower precedence.
47+
///
48+
/// The tricky bit is what happens with *equal*
49+
/// precedence. In that case, we have to consider
50+
/// the [`Associativity`][] (see enum for details).
51+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
52+
pub struct Precedence {
53+
level: usize,
54+
associativity: Associativity,
55+
}
56+
57+
/// Determines what happens when you have equal precedence.
58+
/// The result is dependent on whether you are embedding
59+
/// in left position (i.e., a recurrence before having parsed any
60+
/// tokens) or right position (a recurrence after parsed tokens).
61+
/// So given `1 + 2 + 3`, `1 + 2` is a *left* occurrence of the second `+`.
62+
/// And `2 + 3` is a *right* occurence of the first `+`.
63+
///
64+
/// With `Associativity::Left`, equal precedence is allowed in left matches
65+
/// but not right. So `1 + 2 + 3` parses as `(1 + 2) + 3`, as you would expect.
66+
///
67+
/// With `Associativity::Right`, equal precedence is allowed in right matches
68+
/// but not left. So `1 + 2 + 3` parses as `1 + (2 + 3)`. That's probably not what you wanted
69+
/// for arithemetic expressions, but could be useful for (say) curried function types,
70+
/// where `1 -> 2 -> 3` should parse as `1 -> (2 -> 3)`.
71+
///
72+
/// With `Associativity::None`, equal precedence is not allowed anywhere, so
73+
/// `1 + 2 + 3` is just an error and you have to explicitly add parentheses.
74+
///
75+
/// Use `Precedence::default` for cases where precedence is not relevant.
76+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
77+
pub enum Associativity {
78+
Left,
79+
Right,
80+
None,
81+
}
82+
83+
impl Precedence {
84+
/// Left associative with the given precedence level
85+
pub fn left(level: usize) -> Self {
86+
Self::new(level, Associativity::Left)
87+
}
88+
89+
/// Right associative with the given precedence level
90+
pub fn right(level: usize) -> Self {
91+
Self::new(level, Associativity::Right)
92+
}
93+
94+
/// Non-associative with the given precedence level
95+
pub fn none(level: usize) -> Self {
96+
Self::new(level, Associativity::None)
97+
}
98+
99+
/// Construct a new precedence.
100+
fn new(level: usize, associativity: Associativity) -> Self {
101+
Self {
102+
level,
103+
associativity,
104+
}
105+
}
106+
}
107+
108+
impl Default for Precedence {
109+
fn default() -> Self {
110+
Self::new(0, Associativity::None)
111+
}
112+
}
40113

41114
/// The "active variant" struct is the main struct
42115
/// you will use if you are writing custom parsing logic.
@@ -81,7 +154,7 @@ where
81154
mut op: impl FnMut(&mut ActiveVariant<'s, 't, L>) -> Result<T, Set<ParseError<'t>>>,
82155
) -> ParseResult<'t, T> {
83156
Parser::multi_variant(scope, text, nonterminal_name, |parser| {
84-
parser.parse_variant(nonterminal_name, 0, &mut op);
157+
parser.parse_variant(nonterminal_name, Precedence::default(), &mut op);
85158
})
86159
}
87160

@@ -126,7 +199,7 @@ where
126199
/// Shorthand for `parse_variant` where the parsing operation is to
127200
/// parse the type `V` and then upcast it to the desired result type.
128201
/// Also marks the variant as a cast variant.
129-
pub fn parse_variant_cast<V>(&mut self, variant_precedence: usize)
202+
pub fn parse_variant_cast<V>(&mut self, variant_precedence: Precedence)
130203
where
131204
V: CoreParse<L> + Upcast<T>,
132205
{
@@ -146,19 +219,17 @@ where
146219
pub fn parse_variant(
147220
&mut self,
148221
variant_name: &'static str,
149-
variant_precedence: usize,
222+
variant_precedence: Precedence,
150223
op: impl FnOnce(&mut ActiveVariant<'s, 't, L>) -> Result<T, Set<ParseError<'t>>>,
151224
) {
152225
let span = tracing::span!(
153226
tracing::Level::TRACE,
154227
"variant",
155228
name = variant_name,
156-
variant_precedence = variant_precedence
229+
?variant_precedence,
157230
);
158231
let guard = span.enter();
159232

160-
let variant_precedence = Precedence(variant_precedence);
161-
162233
let mut active_variant =
163234
ActiveVariant::new(variant_precedence, self.scope, self.start_text);
164235
let result = op(&mut active_variant);
@@ -263,8 +334,9 @@ where
263334
l1.len() > l2.len() && (0..l2.len()).all(|i| l1[i] == l2[i])
264335
}
265336

266-
s_i.precedence > s_j.precedence
267-
|| (s_i.precedence == s_j.precedence && has_prefix(&s_i.reductions, &s_j.reductions))
337+
s_i.precedence.level > s_j.precedence.level
338+
|| (s_i.precedence.level == s_j.precedence.level
339+
&& has_prefix(&s_i.reductions, &s_j.reductions))
268340
}
269341
}
270342

@@ -608,11 +680,21 @@ where
608680
pub fn nonterminal<T>(&mut self) -> Result<T, Set<ParseError<'t>>>
609681
where
610682
T: CoreParse<L>,
611-
L: Language,
612683
{
613684
self.nonterminal_with(T::parse)
614685
}
615686

687+
/// Gives an error if `T` is parsable here.
688+
pub fn reject_nonterminal<T>(&mut self) -> Result<(), Set<ParseError<'t>>>
689+
where
690+
T: CoreParse<L>,
691+
{
692+
self.reject(
693+
|p| p.nonterminal::<T>(),
694+
|value| ParseError::at(self.text(), format!("unexpected `{value:?}`")),
695+
)
696+
}
697+
616698
/// Try to parse the current point as `T` and return `None` if there is nothing there.
617699
///
618700
/// **NB:** If the parse partially succeeds, i.e., we are able to consume some tokens

crates/formality-macros/src/parse.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub(crate) fn derive_parse_with_spec(
4848
for variant in s.variants() {
4949
let variant_name = Literal::string(&format!("{}::{}", s.ast().ident, variant.ast().ident));
5050
let v = parse_variant(variant, external_spec, any_variable_variant)?;
51-
let precedence = precedence(&variant.ast().attrs)?.literal();
51+
let precedence = precedence(&variant.ast().attrs)?.expr();
5252
parse_variants.extend(quote_spanned!(
5353
variant.ast().ident.span() =>
5454
__parser.parse_variant(#variant_name, #precedence, |__p| { #v });

crates/formality-macros/src/precedence.rs

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,47 @@
11
use std::str::FromStr;
22

3-
use proc_macro2::{Literal, TokenStream};
3+
use proc_macro2::{Ident, Literal, Span, TokenStream};
4+
use quote::quote;
45
use syn::spanned::Spanned;
56

6-
#[derive(Default, Debug)]
7-
pub(crate) struct Precedence {
8-
pub level: usize,
7+
#[derive(Debug)]
8+
pub(crate) enum Precedence {
9+
Defaulted,
10+
Parsed {
11+
level: usize,
12+
13+
/// Will be either the name of a construct method on the
14+
/// `Parser::Associativity` type: `left``, `right``, or `none`.
15+
associativity: Ident,
16+
},
917
}
1018

1119
impl Precedence {
12-
pub fn literal(&self) -> Literal {
13-
Literal::usize_unsuffixed(self.level)
20+
pub fn expr(&self) -> TokenStream {
21+
match self {
22+
Precedence::Defaulted => quote!(formality_core::parse::Precedence::default()),
23+
Precedence::Parsed {
24+
level,
25+
associativity,
26+
} => {
27+
let level = Literal::usize_unsuffixed(*level);
28+
quote!(formality_core::parse::Precedence::#associativity(#level))
29+
}
30+
}
31+
}
32+
}
33+
34+
impl Default for Precedence {
35+
fn default() -> Self {
36+
Precedence::Defaulted
1437
}
1538
}
1639

1740
impl syn::parse::Parse for Precedence {
1841
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1942
let token_stream: TokenStream = input.parse()?;
2043
let span = token_stream.span();
21-
let mut tokens = token_stream.into_iter();
44+
let mut tokens = token_stream.into_iter().peekable();
2245

2346
let Some(token) = tokens.next() else {
2447
return Err(syn::Error::new(span, "precedence expected"));
@@ -42,10 +65,49 @@ impl syn::parse::Parse for Precedence {
4265
}
4366
}
4467

68+
const VALID_ASSOCIATIVITIES: &[&str] = &["left", "right", "none"];
69+
let associativity = if let Some(comma_token) = tokens.next() {
70+
match &comma_token {
71+
proc_macro2::TokenTree::Punct(punct) if punct.as_char() == ',' => {
72+
match tokens.next() {
73+
Some(proc_macro2::TokenTree::Ident(ident))
74+
if VALID_ASSOCIATIVITIES
75+
.iter()
76+
.any(|a| *a == ident.to_string()) =>
77+
{
78+
ident
79+
}
80+
81+
_ => {
82+
return Err(syn::Error::new(
83+
comma_token.span(),
84+
&format!(
85+
"expected valid associativity after comma, one of `{:?}`",
86+
VALID_ASSOCIATIVITIES
87+
),
88+
));
89+
}
90+
}
91+
}
92+
93+
_ => {
94+
return Err(syn::Error::new(
95+
comma_token.span(),
96+
"extra `,` followed by associativity",
97+
));
98+
}
99+
}
100+
} else {
101+
Ident::new("left", Span::call_site())
102+
};
103+
45104
if let Some(token) = tokens.next() {
46105
return Err(syn::Error::new(token.span(), "extra tokens"));
47106
}
48107

49-
Ok(Precedence { level })
108+
Ok(Precedence::Parsed {
109+
level,
110+
associativity,
111+
})
50112
}
51113
}

crates/formality-types/src/grammar/ty/parse_impls.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Handwritten parser impls.
22
3-
use formality_core::parse::{ActiveVariant, CoreParse, ParseError, ParseResult, Parser, Scope};
3+
use formality_core::parse::{
4+
ActiveVariant, CoreParse, ParseError, ParseResult, Parser, Precedence, Scope,
5+
};
46
use formality_core::Upcast;
57
use formality_core::{seq, Set};
68

@@ -20,10 +22,13 @@ impl CoreParse<Rust> for RigidTy {
2022
// Parse a `ScalarId` (and upcast it to `RigidTy`) with the highest
2123
// precedence. If someone writes `u8`, we always interpret it as a
2224
// scalar-id.
23-
parser.parse_variant_cast::<ScalarId>(1);
25+
parser.parse_variant_cast::<ScalarId>(Precedence::default());
2426

2527
// Parse something like `Id<...>` as an ADT.
26-
parser.parse_variant("Adt", 0, |p| {
28+
parser.parse_variant("Adt", Precedence::default(), |p| {
29+
// Don't accept scalar-ids as Adt names.
30+
p.reject_nonterminal::<ScalarId>()?;
31+
2732
let name: AdtId = p.nonterminal()?;
2833
let parameters: Vec<Parameter> = parse_parameters(p)?;
2934
Ok(RigidTy {
@@ -33,7 +38,7 @@ impl CoreParse<Rust> for RigidTy {
3338
});
3439

3540
// Parse `&`
36-
parser.parse_variant("Ref", 0, |p| {
41+
parser.parse_variant("Ref", Precedence::default(), |p| {
3742
p.expect_char('&')?;
3843
let lt: Lt = p.nonterminal()?;
3944
let ty: Ty = p.nonterminal()?;
@@ -44,7 +49,7 @@ impl CoreParse<Rust> for RigidTy {
4449
.upcast())
4550
});
4651

47-
parser.parse_variant("RefMut", 0, |p| {
52+
parser.parse_variant("RefMut", Precedence::default(), |p| {
4853
p.expect_char('&')?;
4954
p.expect_keyword("mut")?;
5055
let lt: Lt = p.nonterminal()?;
@@ -55,7 +60,7 @@ impl CoreParse<Rust> for RigidTy {
5560
})
5661
});
5762

58-
parser.parse_variant("Tuple", 0, |p| {
63+
parser.parse_variant("Tuple", Precedence::default(), |p| {
5964
p.expect_char('(')?;
6065
p.reject_custom_keywords(&["alias", "rigid", "predicate"])?;
6166
let types: Vec<Ty> = p.comma_nonterminal()?;
@@ -74,7 +79,7 @@ impl CoreParse<Rust> for RigidTy {
7479
impl CoreParse<Rust> for AliasTy {
7580
fn parse<'t>(scope: &Scope<Rust>, text: &'t str) -> ParseResult<'t, Self> {
7681
Parser::multi_variant(scope, text, "AliasTy", |parser| {
77-
parser.parse_variant("associated type", 0, |p| {
82+
parser.parse_variant("associated type", Precedence::default(), |p| {
7883
p.expect_char('<')?;
7984
let ty0: Ty = p.nonterminal()?;
8085
let () = p.expect_keyword("as")?;
@@ -119,11 +124,11 @@ fn parse_parameters<'t>(
119124
impl CoreParse<Rust> for ConstData {
120125
fn parse<'t>(scope: &Scope<Rust>, text: &'t str) -> ParseResult<'t, Self> {
121126
Parser::multi_variant(scope, text, "ConstData", |parser| {
122-
parser.parse_variant("Variable", 1, |p| p.variable());
127+
parser.parse_variant("Variable", Precedence::default(), |p| p.variable());
123128

124-
parser.parse_variant_cast::<Bool>(1);
129+
parser.parse_variant_cast::<Bool>(Precedence::default());
125130

126-
parser.parse_variant("Int", 0, |p| {
131+
parser.parse_variant("Int", Precedence::default(), |p| {
127132
let n: u128 = p.number()?;
128133
p.expect_char('_')?;
129134
let ty: Ty = p.nonterminal()?;

tests/parser-torture-tests/precedence.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ pub enum Expr {
77
Id(Id),
88

99
#[grammar($v0 + $v1)]
10+
#[precedence(1)]
1011
Add(Arc<Expr>, Arc<Expr>),
1112

1213
#[grammar($v0 * $v1)]
13-
#[precedence(1)]
14+
#[precedence(2)]
1415
Mul(Arc<Expr>, Arc<Expr>),
1516
}
1617

0 commit comments

Comments
 (0)