Skip to content

Commit

Permalink
: operator is left-associative (#11671)
Browse files Browse the repository at this point in the history
Adjust operator parsing to allow chained conversions, like `3.14 : Integer : Text`.

Change the precedence and associativity of the `:` operator, when used as a binary operator in an expression:
- It is now **left-associative**
- It now has **lower** precedence than `->` (previously they were equal)

# Important Notes
One previously-reasonable syntax has **changed interpretation**: `x->x:Type` is no longer a valid way to write a casting function, and would likely result in a type error. There was 1 instance of this syntax in our .enso sources.
  • Loading branch information
kazcw authored Nov 27, 2024
1 parent d2fb4a2 commit 7eca04a
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 64 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- [Visualizations on components are slightly transparent when not
focused][11582].
- [New design for vector-editing widget][11620]
- [The `:` type operator can now be chained][11671]

[11151]: https://github.com/enso-org/enso/pull/11151
[11271]: https://github.com/enso-org/enso/pull/11271
Expand All @@ -60,6 +61,7 @@
[11612]: https://github.com/enso-org/enso/pull/11612
[11582]: https://github.com/enso-org/enso/pull/11582
[11620]: https://github.com/enso-org/enso/pull/11620
[11671]: https://github.com/enso-org/enso/pull/11671

#### Enso Standard Library

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ type Table_Column_Helper
specified types will also be selected (i.e. ignore size, precision).
select_by_type : Vector Value_Type -> Boolean -> Vector
select_by_type self types:Vector strict:Boolean=False =
parsed_types = types.map t->t:Value_Type
parsed_types = types.map t-> t:Value_Type
selected_columns = if strict then self.columns.filter c-> parsed_types.contains c.value_type else
self.columns.filter c-> parsed_types.any t-> c.value_type.is_same_type t
if selected_columns.length == 0 then Error.throw (No_Output_Columns.Error "No columns of the specified types were found.") else
Expand Down
8 changes: 8 additions & 0 deletions lib/rust/parser/debug/tests/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,14 @@ fn type_signatures() {
fn type_annotations() {
test_block!("val = x : Int",
,(Assignment::new("val", sexp![(TypeAnnotated (Ident x) ":" (Ident Int))])));
test_block!("val = x : A : B : C",
,(Assignment::new("val", sexp![
(TypeAnnotated
(TypeAnnotated
(TypeAnnotated (Ident x)
":" (Ident A))
":" (Ident B))
":" (Ident C))])));
test_block!("val = foo (x : Int)",
,(Assignment::new("val", sexp![
(App (Ident foo)
Expand Down
42 changes: 24 additions & 18 deletions lib/rust/parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -686,16 +686,22 @@ fn analyze_operator(token: &str) -> token::Variant {
pub fn analyze_non_syntactic_operator(token: &str) -> OperatorProperties {
match token {
"-" => OperatorProperties::value()
.with_unary_prefix_mode(token::Precedence::unary_minus())
.with_binary_infix_precedence(15),
"!" => OperatorProperties::value().with_binary_infix_precedence(3),
"||" | "\\\\" | "&&" => OperatorProperties::value().with_binary_infix_precedence(4),
">>" | "<<" => OperatorProperties::functional().with_binary_infix_precedence(5),
"|>" | "|>>" => OperatorProperties::functional().with_binary_infix_precedence(6),
"<|" | "<<|" =>
OperatorProperties::functional().with_binary_infix_precedence(6).as_right_associative(),
"<=" | ">=" => OperatorProperties::value().with_binary_infix_precedence(14),
"==" | "!=" => OperatorProperties::value().with_binary_infix_precedence(5),
.with_unary_prefix_mode(token::Precedence::Negation)
.with_binary_infix_precedence(token::Precedence::Addition),
"!" => OperatorProperties::value().with_binary_infix_precedence(token::Precedence::Not),
"||" | "\\\\" | "&&" =>
OperatorProperties::value().with_binary_infix_precedence(token::Precedence::Logical),
">>" | "<<" => OperatorProperties::functional()
.with_binary_infix_precedence(token::Precedence::Equality),
"|>" | "|>>" => OperatorProperties::functional()
.with_binary_infix_precedence(token::Precedence::Functional),
"<|" | "<<|" => OperatorProperties::functional()
.with_binary_infix_precedence(token::Precedence::Functional)
.as_right_associative(),
"<=" | ">=" =>
OperatorProperties::value().with_binary_infix_precedence(token::Precedence::Inequality),
"==" | "!=" =>
OperatorProperties::value().with_binary_infix_precedence(token::Precedence::Equality),
_ => analyze_user_operator(token),
}
}
Expand Down Expand Up @@ -725,14 +731,14 @@ fn analyze_user_operator(token: &str) -> OperatorProperties {
}
}
let binary = match precedence_char.unwrap() {
'!' => 10,
'|' => 11,
'&' => 13,
'<' | '>' => 14,
'+' | '-' => 15,
'*' | '/' | '%' => 16,
'^' => 17,
_ => 18,
'!' => token::Precedence::Not,
'|' => token::Precedence::BitwiseOr,
'&' => token::Precedence::BitwiseAnd,
'<' | '>' => token::Precedence::Inequality,
'+' | '-' => token::Precedence::Addition,
'*' | '/' | '%' => token::Precedence::Multiplication,
'^' => token::Precedence::Exponentiation,
_ => token::Precedence::OtherUserOperator,
};
operator = operator.with_binary_infix_precedence(binary);
if !has_right_arrow && !has_left_arrow {
Expand Down
2 changes: 1 addition & 1 deletion lib/rust/parser/src/syntax/operator/annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ where Inner:
left_precedence: None,
right_precedence: ModifiedPrecedence::new(
following_spacing.unwrap_or_default(),
Precedence::annotation(),
Precedence::Negation,
false,
),
associativity: Associativity::Left,
Expand Down
6 changes: 3 additions & 3 deletions lib/rust/parser/src/syntax/operator/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ where Inner: OperatorConsumer<'s> + OperandConsumer<'s>
Spacing::of_token(&name)
};
let precedence =
ModifiedPrecedence::new(spacing, token::Precedence::application(), false);
ModifiedPrecedence::new(spacing, token::Precedence::Application, false);
let right_precedence = ModifiedPrecedence::new(
// Named applications always have unspaced right-precedence; if it reads
// from left to right as a named application, a following operator can't
// cause the interpretation to change.
Spacing::Unspaced,
token::Precedence::application(),
token::Precedence::Application,
false,
);
let operator = Operator {
Expand Down Expand Up @@ -105,7 +105,7 @@ impl<Inner: Finish> Finish for InsertApps<Inner> {
}

fn application<'s>(spacing: Spacing) -> Operator<'s> {
let precedence = ModifiedPrecedence::new(spacing, token::Precedence::application(), false);
let precedence = ModifiedPrecedence::new(spacing, token::Precedence::Application, false);
Operator {
left_precedence: Some(precedence),
right_precedence: precedence,
Expand Down
83 changes: 42 additions & 41 deletions lib/rust/parser/src/syntax/token/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,7 @@ impl OperatorProperties {
}

/// Return a copy of this operator, with the given binary infix precedence.
pub fn with_binary_infix_precedence(self, value: u8) -> Self {
let precedence = Precedence { value };
debug_assert!(precedence > Precedence::min());
debug_assert!(value & 0x80 == 0);
debug_assert!((value + 1) & 0x80 == 0);
pub fn with_binary_infix_precedence(self, precedence: Precedence) -> Self {
Self { binary_infix_precedence: Some(precedence), ..self }
}

Expand Down Expand Up @@ -159,7 +155,7 @@ impl<'s> TokenOperatorProperties for Token<'s> {
impl HasOperatorProperties for variant::AssignmentOperator {
fn operator_properties(&self) -> OperatorProperties {
OperatorProperties {
binary_infix_precedence: Some(Precedence { value: 1 }),
binary_infix_precedence: Some(Precedence::Assignment),
lhs_section_termination: Some(SectionTermination::Unwrap),
is_right_associative: true,
is_compile_time: true,
Expand All @@ -171,9 +167,8 @@ impl HasOperatorProperties for variant::AssignmentOperator {
impl HasOperatorProperties for variant::TypeAnnotationOperator {
fn operator_properties(&self) -> OperatorProperties {
OperatorProperties {
binary_infix_precedence: Some(Precedence { value: 2 }),
binary_infix_precedence: Some(Precedence::TypeAnnotation),
lhs_section_termination: Some(SectionTermination::Reify),
is_right_associative: true,
is_compile_time: true,
rhs_is_non_expression: true,
..default()
Expand All @@ -184,7 +179,7 @@ impl HasOperatorProperties for variant::TypeAnnotationOperator {
impl HasOperatorProperties for variant::ArrowOperator {
fn operator_properties(&self) -> OperatorProperties {
OperatorProperties {
binary_infix_precedence: Some(Precedence { value: 2 }),
binary_infix_precedence: Some(Precedence::Arrow),
lhs_section_termination: Some(SectionTermination::Unwrap),
is_right_associative: true,
is_compile_time: true,
Expand All @@ -196,7 +191,7 @@ impl HasOperatorProperties for variant::ArrowOperator {
impl HasOperatorProperties for variant::AnnotationOperator {
fn operator_properties(&self) -> OperatorProperties {
OperatorProperties {
unary_prefix_precedence: Some(Precedence::max()),
unary_prefix_precedence: Some(Precedence::Annotation),
is_right_associative: true,
is_compile_time: true,
rhs_is_non_expression: true,
Expand All @@ -220,7 +215,7 @@ impl HasOperatorProperties for variant::NegationOperator {
fn operator_properties(&self) -> OperatorProperties {
OperatorProperties {
is_value_operation: true,
unary_prefix_precedence: Some(Precedence::unary_minus()),
unary_prefix_precedence: Some(Precedence::Negation),
..default()
}
}
Expand All @@ -238,7 +233,7 @@ impl HasOperatorProperties for variant::LambdaOperator {

impl HasOperatorProperties for variant::DotOperator {
fn operator_properties(&self) -> OperatorProperties {
OperatorProperties { binary_infix_precedence: Some(Precedence { value: 80 }), ..default() }
OperatorProperties { binary_infix_precedence: Some(Precedence::Application), ..default() }
}
}

Expand All @@ -256,7 +251,7 @@ impl HasOperatorProperties for variant::SuspensionOperator {
impl HasOperatorProperties for variant::CommaOperator {
fn operator_properties(&self) -> OperatorProperties {
OperatorProperties {
binary_infix_precedence: Some(Precedence { value: 1 }),
binary_infix_precedence: Some(Precedence::Assignment),
is_compile_time: true,
rhs_is_non_expression: true,
..default()
Expand All @@ -267,50 +262,56 @@ impl HasOperatorProperties for variant::CommaOperator {
/// Value that can be compared to determine which operator will bind more tightly within an
/// expression.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Precedence {
/// A numeric value determining precedence order.
pub(super) value: u8,
#[repr(u8)]
#[allow(missing_docs)]
pub enum Precedence {
/// A value that is lower than the precedence of any operator.
Min = 0,
Assignment,
TypeAnnotation,
Arrow,
Not,
Logical,
Equality,
Functional,
BitwiseOr,
BitwiseAnd,
Inequality,
Addition,
Multiplication,
Exponentiation,
OtherUserOperator,
Negation,
Application,
Annotation,
/// A value that is higher than the precedence of any operator.
Max,
}

impl Precedence {
/// Return a precedence that is lower than the precedence of any operator.
pub fn min() -> Self {
Precedence { value: 0 }
Precedence::Min
}

/// Return the precedence for any operator.
/// Return the lowest precedence for any operator.
pub fn min_valid() -> Self {
Precedence { value: 1 }
debug_assert_eq!(Precedence::Assignment as u8, Precedence::Min as u8 + 1);
Precedence::Assignment
}

/// Return a precedence that is not lower than any other precedence.
pub fn max() -> Self {
Precedence { value: 100 }
}

/// Return the precedence of application.
pub fn application() -> Self {
Precedence { value: 80 }
}

/// Return the precedence of @annotations.
pub fn annotation() -> Self {
Precedence { value: 79 }
}

/// Return the precedence of unary minus.
pub fn unary_minus() -> Self {
Precedence { value: 79 }
}

/// Return the precedence of unary minus when applied to a numeric literal.
pub fn unary_minus_numeric_literal() -> Self {
Precedence { value: 80 }
Precedence::Max
}

/// Return the value as a number.
pub fn into_u8(self) -> u8 {
self.value
let value = self as u8;
debug_assert!(value > Precedence::Min as u8);
debug_assert!(value & 0x80 == 0);
debug_assert!((value + 1) & 0x80 == 0);
value
}
}

Expand Down

0 comments on commit 7eca04a

Please sign in to comment.