diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 8a4428e0..1b70b776 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -35,8 +35,81 @@ where failures: Set>, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -pub struct Precedence(usize); +/// The *precedence* of a variant determines how to manage +/// recursive invocations. +/// +/// The general rule is that an expression +/// with lower-precedence cannot be embedded +/// into an expression of higher-precedence. +/// So given `1 + 2 * 3`, the `+` cannot be a +/// (direct) child of the `*`, because `+` is +/// lower precedence. +/// +/// The tricky bit is what happens with *equal* +/// precedence. In that case, we have to consider +/// the [`Associativity`][] (see enum for details). +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Precedence { + level: usize, + associativity: Associativity, +} + +/// Determines what happens when you have equal precedence. +/// The result is dependent on whether you are embedding +/// in left position (i.e., a recurrence before having parsed any +/// tokens) or right position (a recurrence after parsed tokens). +/// So given `1 + 2 + 3`, `1 + 2` is a *left* occurrence of the second `+`. +/// And `2 + 3` is a *right* occurence of the first `+`. +/// +/// With `Associativity::Left`, equal precedence is allowed in left matches +/// but not right. So `1 + 2 + 3` parses as `(1 + 2) + 3`, as you would expect. +/// +/// With `Associativity::Right`, equal precedence is allowed in right matches +/// but not left. So `1 + 2 + 3` parses as `1 + (2 + 3)`. That's probably not what you wanted +/// for arithemetic expressions, but could be useful for (say) curried function types, +/// where `1 -> 2 -> 3` should parse as `1 -> (2 -> 3)`. +/// +/// With `Associativity::None`, equal precedence is not allowed anywhere, so +/// `1 + 2 + 3` is just an error and you have to explicitly add parentheses. +/// +/// Use `Precedence::default` for cases where precedence is not relevant. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Associativity { + Left, + Right, + None, +} + +impl Precedence { + /// Left associative with the given precedence level + pub fn left(level: usize) -> Self { + Self::new(level, Associativity::Left) + } + + /// Right associative with the given precedence level + pub fn right(level: usize) -> Self { + Self::new(level, Associativity::Right) + } + + /// Non-associative with the given precedence level + pub fn none(level: usize) -> Self { + Self::new(level, Associativity::None) + } + + /// Construct a new precedence. + fn new(level: usize, associativity: Associativity) -> Self { + Self { + level, + associativity, + } + } +} + +impl Default for Precedence { + fn default() -> Self { + Self::new(0, Associativity::None) + } +} /// The "active variant" struct is the main struct /// you will use if you are writing custom parsing logic. @@ -81,7 +154,7 @@ where mut op: impl FnMut(&mut ActiveVariant<'s, 't, L>) -> Result>>, ) -> ParseResult<'t, T> { Parser::multi_variant(scope, text, nonterminal_name, |parser| { - parser.parse_variant(nonterminal_name, 0, &mut op); + parser.parse_variant(nonterminal_name, Precedence::default(), &mut op); }) } @@ -126,7 +199,7 @@ where /// Shorthand for `parse_variant` where the parsing operation is to /// parse the type `V` and then upcast it to the desired result type. /// Also marks the variant as a cast variant. - pub fn parse_variant_cast(&mut self, variant_precedence: usize) + pub fn parse_variant_cast(&mut self, variant_precedence: Precedence) where V: CoreParse + Upcast, { @@ -146,19 +219,17 @@ where pub fn parse_variant( &mut self, variant_name: &'static str, - variant_precedence: usize, + variant_precedence: Precedence, op: impl FnOnce(&mut ActiveVariant<'s, 't, L>) -> Result>>, ) { let span = tracing::span!( tracing::Level::TRACE, "variant", name = variant_name, - variant_precedence = variant_precedence + ?variant_precedence, ); let guard = span.enter(); - let variant_precedence = Precedence(variant_precedence); - let mut active_variant = ActiveVariant::new(variant_precedence, self.scope, self.start_text); let result = op(&mut active_variant); @@ -263,8 +334,9 @@ where l1.len() > l2.len() && (0..l2.len()).all(|i| l1[i] == l2[i]) } - s_i.precedence > s_j.precedence - || (s_i.precedence == s_j.precedence && has_prefix(&s_i.reductions, &s_j.reductions)) + s_i.precedence.level > s_j.precedence.level + || (s_i.precedence.level == s_j.precedence.level + && has_prefix(&s_i.reductions, &s_j.reductions)) } } @@ -608,11 +680,21 @@ where pub fn nonterminal(&mut self) -> Result>> where T: CoreParse, - L: Language, { self.nonterminal_with(T::parse) } + /// Gives an error if `T` is parsable here. + pub fn reject_nonterminal(&mut self) -> Result<(), Set>> + where + T: CoreParse, + { + self.reject( + |p| p.nonterminal::(), + |value| ParseError::at(self.text(), format!("unexpected `{value:?}`")), + ) + } + /// Try to parse the current point as `T` and return `None` if there is nothing there. /// /// **NB:** If the parse partially succeeds, i.e., we are able to consume some tokens diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index f920d63a..65c45651 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -48,7 +48,7 @@ pub(crate) fn derive_parse_with_spec( for variant in s.variants() { let variant_name = Literal::string(&format!("{}::{}", s.ast().ident, variant.ast().ident)); let v = parse_variant(variant, external_spec, any_variable_variant)?; - let precedence = precedence(&variant.ast().attrs)?.literal(); + let precedence = precedence(&variant.ast().attrs)?.expr(); parse_variants.extend(quote_spanned!( variant.ast().ident.span() => __parser.parse_variant(#variant_name, #precedence, |__p| { #v }); diff --git a/crates/formality-macros/src/precedence.rs b/crates/formality-macros/src/precedence.rs index 4eee2f66..0350016f 100644 --- a/crates/formality-macros/src/precedence.rs +++ b/crates/formality-macros/src/precedence.rs @@ -1,16 +1,39 @@ use std::str::FromStr; -use proc_macro2::{Literal, TokenStream}; +use proc_macro2::{Ident, Literal, Span, TokenStream}; +use quote::quote; use syn::spanned::Spanned; -#[derive(Default, Debug)] -pub(crate) struct Precedence { - pub level: usize, +#[derive(Debug)] +pub(crate) enum Precedence { + Defaulted, + Parsed { + level: usize, + + /// Will be either the name of a construct method on the + /// `Parser::Associativity` type: `left``, `right``, or `none`. + associativity: Ident, + }, } impl Precedence { - pub fn literal(&self) -> Literal { - Literal::usize_unsuffixed(self.level) + pub fn expr(&self) -> TokenStream { + match self { + Precedence::Defaulted => quote!(formality_core::parse::Precedence::default()), + Precedence::Parsed { + level, + associativity, + } => { + let level = Literal::usize_unsuffixed(*level); + quote!(formality_core::parse::Precedence::#associativity(#level)) + } + } + } +} + +impl Default for Precedence { + fn default() -> Self { + Precedence::Defaulted } } @@ -18,7 +41,7 @@ impl syn::parse::Parse for Precedence { fn parse(input: syn::parse::ParseStream) -> syn::Result { let token_stream: TokenStream = input.parse()?; let span = token_stream.span(); - let mut tokens = token_stream.into_iter(); + let mut tokens = token_stream.into_iter().peekable(); let Some(token) = tokens.next() else { return Err(syn::Error::new(span, "precedence expected")); @@ -42,10 +65,49 @@ impl syn::parse::Parse for Precedence { } } + const VALID_ASSOCIATIVITIES: &[&str] = &["left", "right", "none"]; + let associativity = if let Some(comma_token) = tokens.next() { + match &comma_token { + proc_macro2::TokenTree::Punct(punct) if punct.as_char() == ',' => { + match tokens.next() { + Some(proc_macro2::TokenTree::Ident(ident)) + if VALID_ASSOCIATIVITIES + .iter() + .any(|a| *a == ident.to_string()) => + { + ident + } + + _ => { + return Err(syn::Error::new( + comma_token.span(), + &format!( + "expected valid associativity after comma, one of `{:?}`", + VALID_ASSOCIATIVITIES + ), + )); + } + } + } + + _ => { + return Err(syn::Error::new( + comma_token.span(), + "extra `,` followed by associativity", + )); + } + } + } else { + Ident::new("left", Span::call_site()) + }; + if let Some(token) = tokens.next() { return Err(syn::Error::new(token.span(), "extra tokens")); } - Ok(Precedence { level }) + Ok(Precedence::Parsed { + level, + associativity, + }) } } diff --git a/crates/formality-types/src/grammar/ty/parse_impls.rs b/crates/formality-types/src/grammar/ty/parse_impls.rs index 24db70e3..82765eb7 100644 --- a/crates/formality-types/src/grammar/ty/parse_impls.rs +++ b/crates/formality-types/src/grammar/ty/parse_impls.rs @@ -1,6 +1,8 @@ //! Handwritten parser impls. -use formality_core::parse::{ActiveVariant, CoreParse, ParseError, ParseResult, Parser, Scope}; +use formality_core::parse::{ + ActiveVariant, CoreParse, ParseError, ParseResult, Parser, Precedence, Scope, +}; use formality_core::Upcast; use formality_core::{seq, Set}; @@ -20,10 +22,13 @@ impl CoreParse for RigidTy { // Parse a `ScalarId` (and upcast it to `RigidTy`) with the highest // precedence. If someone writes `u8`, we always interpret it as a // scalar-id. - parser.parse_variant_cast::(1); + parser.parse_variant_cast::(Precedence::default()); // Parse something like `Id<...>` as an ADT. - parser.parse_variant("Adt", 0, |p| { + parser.parse_variant("Adt", Precedence::default(), |p| { + // Don't accept scalar-ids as Adt names. + p.reject_nonterminal::()?; + let name: AdtId = p.nonterminal()?; let parameters: Vec = parse_parameters(p)?; Ok(RigidTy { @@ -33,7 +38,7 @@ impl CoreParse for RigidTy { }); // Parse `&` - parser.parse_variant("Ref", 0, |p| { + parser.parse_variant("Ref", Precedence::default(), |p| { p.expect_char('&')?; let lt: Lt = p.nonterminal()?; let ty: Ty = p.nonterminal()?; @@ -44,7 +49,7 @@ impl CoreParse for RigidTy { .upcast()) }); - parser.parse_variant("RefMut", 0, |p| { + parser.parse_variant("RefMut", Precedence::default(), |p| { p.expect_char('&')?; p.expect_keyword("mut")?; let lt: Lt = p.nonterminal()?; @@ -55,7 +60,7 @@ impl CoreParse for RigidTy { }) }); - parser.parse_variant("Tuple", 0, |p| { + parser.parse_variant("Tuple", Precedence::default(), |p| { p.expect_char('(')?; p.reject_custom_keywords(&["alias", "rigid", "predicate"])?; let types: Vec = p.comma_nonterminal()?; @@ -74,7 +79,7 @@ impl CoreParse for RigidTy { impl CoreParse for AliasTy { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { Parser::multi_variant(scope, text, "AliasTy", |parser| { - parser.parse_variant("associated type", 0, |p| { + parser.parse_variant("associated type", Precedence::default(), |p| { p.expect_char('<')?; let ty0: Ty = p.nonterminal()?; let () = p.expect_keyword("as")?; @@ -119,11 +124,11 @@ fn parse_parameters<'t>( impl CoreParse for ConstData { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { Parser::multi_variant(scope, text, "ConstData", |parser| { - parser.parse_variant("Variable", 1, |p| p.variable()); + parser.parse_variant("Variable", Precedence::default(), |p| p.variable()); - parser.parse_variant_cast::(1); + parser.parse_variant_cast::(Precedence::default()); - parser.parse_variant("Int", 0, |p| { + parser.parse_variant("Int", Precedence::default(), |p| { let n: u128 = p.number()?; p.expect_char('_')?; let ty: Ty = p.nonterminal()?; diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index 43ca5f02..36ceccce 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -7,10 +7,11 @@ pub enum Expr { Id(Id), #[grammar($v0 + $v1)] + #[precedence(1)] Add(Arc, Arc), #[grammar($v0 * $v1)] - #[precedence(1)] + #[precedence(2)] Mul(Arc, Arc), }