mirror of
https://github.com/SpaceManiac/SpacemanDMM.git
synced 2025-12-23 05:36:47 +00:00
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:
parent
f81223ba17
commit
80f40fe9f7
3 changed files with 55 additions and 10 deletions
|
|
@ -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++"),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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'")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue