Fix in & ternary precedence (#260)

Fixes https://github.com/SpaceManiac/SpacemanDMM/issues/122
Fixes `x ?  y : z = 0` being treated as `x ? y : (z = 0)` instead of 
`(x ? y : z) = 0`.

The issue happened with any assignment operator, hopefully this change 
is reasonable.
This commit is contained in:
William Wallace 2021-05-18 07:43:01 +01:00 committed by GitHub
parent f81223ba17
commit 80f40fe9f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 10 deletions

View file

@ -6,8 +6,7 @@ pub const IN_AMBIG_ERRORS: &[(u32, u16, &str)] = &[
(2, 7, "ambiguous `!` on left side of an `in`"),
(6, 7, "ambiguous `&&` on left side of an `in`"),
(11, 7, "ambiguous `=` on left side of an `in`"),
// TODO: Fix this, https://github.com/SpaceManiac/SpacemanDMM/issues/122
//(13, 7, "ambiguous ternary on left side of an `in`"),
(15, 7, "ambiguous ternary on left side of an `in`"),
];
#[test]
@ -35,6 +34,20 @@ fn in_ambig() {
check_errors_match(code, IN_AMBIG_ERRORS);
}
pub const TERNARY_IN_AMBIG_ERRORS: &[(u32, u16, &str)] = &[
(2, 14, "got \'in\', expected one of: operator, field access, \':\'"),
];
#[test]
fn ambig_in_ternary_cond() {
let code = r##"
/proc/test()
if(i ? 1 in list() : 2)
return
"##.trim();
check_errors_match(code, TERNARY_IN_AMBIG_ERRORS);
}
pub const OP_OVERLOAD_ERRORS: &[(u32, u16, &str)] = &[
(6, 5, "Attempting operator++ on a /mob which does not overload operator++"),
];

View file

@ -1713,10 +1713,10 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> {
}
fn expression(&mut self) -> Status<Expression> {
self.expression_ex(false)
self.expression_ex(None, false)
}
fn expression_ex(&mut self, in_ternary: bool) -> Status<Expression> {
fn expression_ex(&mut self, strength: Option<Strength>, in_ternary: bool) -> Status<Expression> {
let mut expr = leading!(self.group(in_ternary));
loop {
// try to read the next operator
@ -1729,14 +1729,22 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> {
}
};
// If we're a sub-expression within a ternary expression, don't try to read further than our parent's precedence would allow
if let Some(strength) = strength {
if info.strength > strength {
self.put_back(Token::Punct(info.token));
break;
}
}
// trampoline high-strength expression parts as the lhs of the newly found op
expr = require!(self.expression_part(expr, info,
expr = require!(self.expression_part(expr, info, strength,
in_ternary || info.strength == Strength::Conditional));
}
success(expr)
}
fn expression_part(&mut self, lhs: Expression, prev_op: OpInfo, in_ternary: bool) -> Status<Expression> {
fn expression_part(&mut self, lhs: Expression, prev_op: OpInfo, strength: Option<Strength>, in_ternary: bool) -> Status<Expression> {
use std::cmp::Ordering;
let mut bits = vec![lhs];
@ -1757,7 +1765,7 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> {
match info.strength.cmp(&prev_op.strength) {
Ordering::Less => {
// the operator is stronger than us... recurse down
rhs = require!(self.expression_part(rhs, info,
rhs = require!(self.expression_part(rhs, info, strength,
in_ternary || info.strength == Strength::Conditional));
}
Ordering::Greater => {
@ -1781,7 +1789,7 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> {
rhs = Expression::BinaryOp {
op: BinaryOp::To,
lhs: Box::new(rhs),
rhs: Box::new(require!(self.expression_ex(in_ternary))),
rhs: Box::new(require!(self.expression_ex(Some(Strength::In), in_ternary))),
};
// "step" could appear here but doesn't actually do anything.
// In for statements it is parsed by `for_range`.
@ -1794,10 +1802,13 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> {
match self.next("':'")? {
Token::Punct(Punctuation::Colon) |
Token::Punct(Punctuation::CloseColon) => {}
_ => return self.parse_error(),
other => {
self.put_back(other);
return self.parse_error()
}
}
// Read the else branch.
let else_ = match self.expression()? {
let else_ = match self.expression_ex(Some(Strength::Conditional), true)? {
Some(else_) => else_,
None => {
self.error("missing else arm of conditional operator should be replaced with 'null'")

View file

@ -41,6 +41,27 @@ fn ternary_precedence() {
)
}
#[test]
fn in_after_ternary() {
// ((foo = (1 ? 2 : 3)) in 4)
assert_eq!(
parse_expr("foo = 1 ? 2 : 3 in 4"),
Expression::BinaryOp {
op: BinaryOp::In,
lhs: Box::new(Expression::AssignOp {
op: AssignOp::Assign,
lhs: Box::new(Expression::from(Term::Ident("foo".to_owned()))),
rhs: Box::new(Expression::TernaryOp {
cond: Box::new(Expression::from(Term::Int(1))),
if_: Box::new(Expression::from(Term::Int(2))),
else_: Box::new(Expression::from(Term::Int(3))),
}),
}),
rhs: Box::new(Expression::from(Term::Int(4))),
}
)
}
#[test]
fn ternary_nesting() {
// 1 ? 2 : (3 ? 4 : 5)