mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 04:55:15 +03:00
Fix precedence of -1.x
(#5830)
Fixes #5826. # Important Notes - Change frontend representation of negation. - Fix a precedence issue: The `.` operators in -1.x and -1.2 must have different precedences. - Remove a no-longer-needed special case from backend translation. - Add tests for this case after all translations.
This commit is contained in:
parent
c696bf1d87
commit
75fda33837
@ -5,6 +5,7 @@
|
||||
#![feature(extend_one)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(assert_matches)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
@ -160,6 +161,7 @@ impl Parser {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ast::HasRepr;
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn test_group_repr() {
|
||||
@ -220,4 +222,10 @@ main =
|
||||
let ast = Parser::new().parse_line_ast("a->4").unwrap();
|
||||
assert!(ast::macros::as_lambda(&ast).is_some(), "{ast:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_number() {
|
||||
let ast = Parser::new().parse_line_ast("-23").unwrap();
|
||||
assert_matches!(ast.shape(), ast::Shape::Number(_));
|
||||
}
|
||||
}
|
||||
|
@ -174,8 +174,12 @@ impl Translate {
|
||||
if let Some(arg) = rhs {
|
||||
let non_block_operand = "Unary operator cannot be applied to an (empty) block.";
|
||||
let arg = self.translate(arg).expect(non_block_operand);
|
||||
let section = section_right(opr, arg).expect_unspaced();
|
||||
self.finish_ast(section, builder)
|
||||
let value = match arg.body.shape() {
|
||||
ast::Shape::Number(ast::Number { base, int }) =>
|
||||
(ast::Number { base: base.clone(), int: format!("-{int}") }).into(),
|
||||
_ => prefix(opr, arg).expect_unspaced(),
|
||||
};
|
||||
self.finish_ast(value, builder)
|
||||
} else {
|
||||
let opr = opr.expect_unspaced();
|
||||
self.finish_ast(ast::SectionSides { opr }, builder)
|
||||
|
@ -936,9 +936,7 @@ final class TreeToIr {
|
||||
case Tree.OprSectionBoundary bound -> translateExpression(bound.getAst(), false);
|
||||
case Tree.UnaryOprApp un when "-".equals(un.getOpr().codeRepr()) ->
|
||||
switch (translateExpression(un.getRhs(), false)) {
|
||||
// AstToIr doesn't construct negative floating-point literals.
|
||||
// Match that behavior for testing during the transition.
|
||||
case IR$Literal$Number n when !n.copy$default$2().contains(".") -> n.copy(
|
||||
case IR$Literal$Number n -> n.copy(
|
||||
n.copy$default$1(),
|
||||
"-" + n.copy$default$2(),
|
||||
n.copy$default$3(),
|
||||
|
@ -1208,6 +1208,11 @@ public class EnsoCompilerTest {
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDotPrecedence() throws Exception {
|
||||
equivalenceTest("x = -1.up_to 100", "x = (-1).up_to 100");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreeze() throws Exception {
|
||||
equivalenceTest("a = x", "a = FREEZE x");
|
||||
|
@ -748,8 +748,25 @@ fn minus_unary() {
|
||||
test!("x=-x", (Assignment (Ident x) "=" (UnaryOprApp "-" (Ident x))));
|
||||
test!("-x+x", (OprApp (UnaryOprApp "-" (Ident x)) (Ok "+") (Ident x)));
|
||||
test!("-x*x", (OprApp (UnaryOprApp "-" (Ident x)) (Ok "*") (Ident x)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_unary_decimal() {
|
||||
test!("-2.1", (UnaryOprApp "-" (Number () "2" ("." "1"))));
|
||||
//test!("-1.x", (OprApp (UnaryOprApp "-" (Number () "1" ())) (Ok ".") (Ident x)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_unary_in_method_app() {
|
||||
test!("-1.x", (OprApp (UnaryOprApp "-" (Number () "1" ())) (Ok ".") (Ident x)));
|
||||
test!("-1.up_to 100",
|
||||
(App (OprApp (UnaryOprApp "-" (Number () "1" ())) (Ok ".") (Ident up_to))
|
||||
(Number () "100" ())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_app_in_minus_unary() {
|
||||
test!("-Number.positive_infinity",
|
||||
(UnaryOprApp "-" (OprApp (Ident Number) (Ok ".") (Ident positive_infinity))));
|
||||
}
|
||||
|
||||
|
||||
@ -1197,6 +1214,7 @@ fn numbers() {
|
||||
test!("0o122137", (Number "0o" "122137" ()));
|
||||
test!("0xAE2F14", (Number "0x" "AE2F14" ()));
|
||||
test!("pi = 3.14", (Assignment (Ident pi) "=" (Number () "3" ("." "14"))));
|
||||
test!("0.0.x", (OprApp (Number () "0" ("." "0")) (Ok ".") (Ident x)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1211,6 +1229,7 @@ fn new_delimited_numbers() {
|
||||
#[test]
|
||||
fn old_nondecimal_numbers() {
|
||||
test!("2_01101101", (Number "2_" "01101101" ()));
|
||||
test!("-2_01101101", (UnaryOprApp "-" (Number "2_" "01101101" ())));
|
||||
test!("16_17ffffffffffffffa", (Number "16_" "17ffffffffffffffa" ()));
|
||||
}
|
||||
|
||||
|
@ -619,25 +619,51 @@ impl<'s> Lexer<'s> {
|
||||
}
|
||||
});
|
||||
if let Some(token) = token {
|
||||
if token.code == "+-" {
|
||||
match token.code.as_ref() {
|
||||
// Special-case: Split into multiple operators.
|
||||
"+-" => {
|
||||
let (left, right) = token.split_at_(Bytes(1));
|
||||
let lhs = analyze_operator(&left.code);
|
||||
self.submit_token(left.with_variant(token::Variant::operator(lhs)));
|
||||
let rhs = analyze_operator(&right.code);
|
||||
self.submit_token(right.with_variant(token::Variant::operator(rhs)));
|
||||
return;
|
||||
}
|
||||
if token.code == "..." {
|
||||
// Composed of operator characters, but not an operator node.
|
||||
"..." => {
|
||||
let token = token.with_variant(token::Variant::auto_scope());
|
||||
self.submit_token(token);
|
||||
return;
|
||||
}
|
||||
// Decimal vs. method-application must be distinguished before parsing because they
|
||||
// have different precedences; this is a special case here because the distinction
|
||||
// requires lookahead.
|
||||
"." if self.last_spaces_visible_offset.width_in_spaces == 0
|
||||
&& let Some(char) = self.current_char && char.is_ascii_digit() => {
|
||||
let opr = token::OperatorProperties::new()
|
||||
.with_binary_infix_precedence(81)
|
||||
.as_decimal();
|
||||
let token = token.with_variant(token::Variant::operator(opr));
|
||||
self.submit_token(token);
|
||||
}
|
||||
// The unary-negation operator binds tighter to numeric literals than other
|
||||
// expressions.
|
||||
"-" if self.last_spaces_visible_offset.width_in_spaces == 0
|
||||
&& let Some(char) = self.current_char && char.is_ascii_digit() => {
|
||||
let opr = token::OperatorProperties::new()
|
||||
.with_unary_prefix_mode(token::Precedence::unary_minus_numeric_literal())
|
||||
.with_binary_infix_precedence(15);
|
||||
let token = token.with_variant(token::Variant::operator(opr));
|
||||
self.submit_token(token);
|
||||
}
|
||||
// Normally-structured operator.
|
||||
_ => {
|
||||
let tp = token::Variant::operator(analyze_operator(&token.code));
|
||||
let token = token.with_variant(tp);
|
||||
self.submit_token(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Precedence ===
|
||||
@ -703,8 +729,7 @@ fn analyze_operator(token: &str) -> token::OperatorProperties {
|
||||
.as_compile_time_operation()
|
||||
.as_special()
|
||||
.as_sequence(),
|
||||
"." =>
|
||||
return operator.with_binary_infix_precedence(80).with_decimal_interpretation().as_dot(),
|
||||
"." => return operator.with_binary_infix_precedence(80).as_dot(),
|
||||
_ => (),
|
||||
}
|
||||
// "The precedence of all other operators is determined by the operator's Precedence Character:"
|
||||
@ -821,9 +846,13 @@ impl<'s> Lexer<'s> {
|
||||
token::Base::Hexadecimal =>
|
||||
self.token(|this| this.take_while(is_hexadecimal_digit)),
|
||||
};
|
||||
if let Some(token) = token {
|
||||
let joiner = token::OperatorProperties::new()
|
||||
.with_binary_infix_precedence(usize::MAX)
|
||||
.as_token_joiner();
|
||||
self.submit_token(Token("", "", token::Variant::operator(joiner)));
|
||||
// Every number has a digits-token, even if it's zero-length.
|
||||
let token = token.unwrap_or_default();
|
||||
self.submit_token(token.with_variant(token::Variant::digits(Some(base))));
|
||||
}
|
||||
} else {
|
||||
self.submit_token(token.with_variant(token::Variant::digits(None)));
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ impl<'s> ExpressionBuilder<'s> {
|
||||
SectionTermination::Unwrap => lhs.map(|op| op.value),
|
||||
};
|
||||
let rhs = rhs_.map(syntax::Tree::from);
|
||||
let ast = syntax::tree::apply_operator(lhs, tokens, rhs, self.nospace);
|
||||
let ast = syntax::tree::apply_operator(lhs, tokens, rhs);
|
||||
Operand::from(ast)
|
||||
} else {
|
||||
let rhs = rhs_.map(syntax::Tree::from);
|
||||
@ -307,9 +307,8 @@ impl<'s> ExpressionBuilder<'s> {
|
||||
if tokens.len() != 1 || tokens[0].properties.can_form_section() {
|
||||
elided += lhs.is_none() as u32 + rhs.is_none() as u32;
|
||||
}
|
||||
let mut operand = Operand::from(lhs).map(|lhs| {
|
||||
syntax::tree::apply_operator(lhs, tokens, rhs, self.nospace)
|
||||
});
|
||||
let mut operand = Operand::from(lhs)
|
||||
.map(|lhs| syntax::tree::apply_operator(lhs, tokens, rhs));
|
||||
operand.elided += elided;
|
||||
operand
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ pub struct OperatorProperties {
|
||||
is_compile_time_operation: bool,
|
||||
is_right_associative: bool,
|
||||
// Unique operators
|
||||
can_be_decimal_operator: bool,
|
||||
is_decimal: bool,
|
||||
is_type_annotation: bool,
|
||||
is_assignment: bool,
|
||||
is_arrow: bool,
|
||||
@ -343,6 +343,7 @@ pub struct OperatorProperties {
|
||||
is_annotation: bool,
|
||||
is_dot: bool,
|
||||
is_special: bool,
|
||||
is_token_joiner: bool,
|
||||
}
|
||||
|
||||
impl OperatorProperties {
|
||||
@ -379,6 +380,11 @@ impl OperatorProperties {
|
||||
Self { is_special: true, ..self }
|
||||
}
|
||||
|
||||
/// Return a copy of this operator, modified to be flagged as the token-joiner operator.
|
||||
pub fn as_token_joiner(self) -> Self {
|
||||
Self { is_token_joiner: true, ..self }
|
||||
}
|
||||
|
||||
/// Return a copy of this operator, modified to have the specified LHS operator-section/
|
||||
/// template-function behavior.
|
||||
pub fn with_lhs_section_termination<T>(self, lhs_section_termination: T) -> Self
|
||||
@ -421,9 +427,9 @@ impl OperatorProperties {
|
||||
Self { is_dot: true, ..self }
|
||||
}
|
||||
|
||||
/// Return a copy of this operator, modified to allow an interpretion as a decmial point.
|
||||
pub fn with_decimal_interpretation(self) -> Self {
|
||||
Self { can_be_decimal_operator: true, ..self }
|
||||
/// Return a copy of this operator, modified to be interpreted as a decimal point.
|
||||
pub fn as_decimal(self) -> Self {
|
||||
Self { is_decimal: true, ..self }
|
||||
}
|
||||
|
||||
/// Return this operator's binary infix precedence, if it has one.
|
||||
@ -486,6 +492,11 @@ impl OperatorProperties {
|
||||
self.is_dot
|
||||
}
|
||||
|
||||
/// Return whether this operator is the token-joiner operator.
|
||||
pub fn is_token_joiner(&self) -> bool {
|
||||
self.is_token_joiner
|
||||
}
|
||||
|
||||
/// Return this operator's associativity.
|
||||
pub fn associativity(&self) -> Associativity {
|
||||
match self.is_right_associative {
|
||||
@ -494,9 +505,9 @@ impl OperatorProperties {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return whether this operator can be interpreted as a decimal point.
|
||||
pub fn can_be_decimal_operator(&self) -> bool {
|
||||
self.can_be_decimal_operator
|
||||
/// Return whether this operator is a decimal point.
|
||||
pub fn is_decimal(&self) -> bool {
|
||||
self.is_decimal
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,6 +544,11 @@ impl Precedence {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Associativity (left or right).
|
||||
|
@ -762,12 +762,6 @@ impl<'s> span::Builder<'s> for OperatorDelimitedTree<'s> {
|
||||
/// application has special semantics.
|
||||
pub fn apply<'s>(mut func: Tree<'s>, mut arg: Tree<'s>) -> Tree<'s> {
|
||||
match (&mut *func.variant, &mut *arg.variant) {
|
||||
(Variant::Number(func_ @ Number { base: _, integer: None, fractional_digits: None }),
|
||||
Variant::Number(Number { base: None, integer, fractional_digits })) => {
|
||||
func_.integer = mem::take(integer);
|
||||
func_.fractional_digits = mem::take(fractional_digits);
|
||||
func
|
||||
}
|
||||
(Variant::Annotated(func_ @ Annotated { argument: None, .. }), _) => {
|
||||
func_.argument = maybe_apply(mem::take(&mut func_.argument), arg).into();
|
||||
func
|
||||
@ -862,13 +856,29 @@ pub fn apply_operator<'s>(
|
||||
mut lhs: Option<Tree<'s>>,
|
||||
opr: Vec<token::Operator<'s>>,
|
||||
mut rhs: Option<Tree<'s>>,
|
||||
nospace: bool,
|
||||
) -> Tree<'s> {
|
||||
let opr = match opr.len() {
|
||||
0 => return apply(lhs.unwrap(), rhs.unwrap()),
|
||||
1 => Ok(opr.into_iter().next().unwrap()),
|
||||
_ => Err(MultipleOperatorError { operators: NonEmptyVec::try_from(opr).unwrap() }),
|
||||
};
|
||||
if let Ok(opr_) = &opr
|
||||
&& opr_.properties.is_token_joiner()
|
||||
&& let Some(lhs_) = lhs.as_mut()
|
||||
&& let Some(rhs_) = rhs.as_mut() {
|
||||
return match (&mut *lhs_.variant, &mut *rhs_.variant) {
|
||||
(Variant::Number(func_ @ Number { base: _, integer: None, fractional_digits: None }),
|
||||
Variant::Number(Number { base: None, integer, fractional_digits })) => {
|
||||
func_.integer = mem::take(integer);
|
||||
func_.fractional_digits = mem::take(fractional_digits);
|
||||
lhs.take().unwrap()
|
||||
}
|
||||
_ => {
|
||||
debug_assert!(false, "Unexpected use of token-joiner operator!");
|
||||
apply(lhs.take().unwrap(), rhs.take().unwrap())
|
||||
},
|
||||
}
|
||||
}
|
||||
if let Ok(opr_) = &opr && opr_.properties.is_special() {
|
||||
let tree = Tree::opr_app(lhs, opr, rhs);
|
||||
return tree.with_error("Invalid use of special operator.");
|
||||
@ -890,8 +900,7 @@ pub fn apply_operator<'s>(
|
||||
let invalid = Tree::opr_app(lhs, opr, rhs);
|
||||
return invalid.with_error(error);
|
||||
}
|
||||
if nospace
|
||||
&& let Ok(opr) = &opr && opr.properties.can_be_decimal_operator()
|
||||
if let Ok(opr) = &opr && opr.properties.is_decimal()
|
||||
&& let Some(lhs) = lhs.as_mut()
|
||||
&& let box Variant::Number(lhs_) = &mut lhs.variant
|
||||
&& lhs_.fractional_digits.is_none()
|
||||
|
Loading…
Reference in New Issue
Block a user