Skip to content

Commit 6b2fd7a

Browse files
committed
basic work for safe nav
1 parent 63043ae commit 6b2fd7a

5 files changed

Lines changed: 141 additions & 5 deletions

File tree

crates/emmylua_formatter/src/formatter/spacing.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ pub(crate) fn space_around_binary_op(op: BinaryOperator, config: &LuaFormatConfi
5252
| BinaryOperator::OpBXor
5353
| BinaryOperator::OpShl
5454
| BinaryOperator::OpShr
55-
| BinaryOperator::OpNop => SpaceRule::Space,
55+
| BinaryOperator::OpNop
56+
| BinaryOperator::OpNilCoalescing => SpaceRule::Space,
5657
BinaryOperator::OpConcat => {
5758
if config.spacing.space_around_concat_operator {
5859
SpaceRule::Space

crates/emmylua_parser/src/grammar/lua/expr.rs

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,47 @@ fn parse_sub_expr(p: &mut LuaParser, limit: i32) -> ParseResult {
3636
parse_simple_expr(p)?
3737
};
3838

39-
let mut bop = LuaOpKind::to_binary_operator(p.current_token());
40-
while bop != BinaryOperator::OpNop && bop.get_priority().left > limit {
39+
const TERNARY_LEFT: i32 = 1;
40+
41+
loop {
42+
if p.current_token() == LuaTokenKind::TkTernary && TERNARY_LEFT > limit {
43+
let m = cm.precede(p, LuaSyntaxKind::TernaryExpr);
44+
p.bump(); // consume '?'
45+
match parse_sub_expr(p, 0) {
46+
Ok(_) => {}
47+
Err(_) => {
48+
p.push_error(LuaParseError::syntax_error_from(
49+
&t!("expected expression after '?'"),
50+
p.current_token_range(),
51+
));
52+
}
53+
}
54+
match expect_token(p, LuaTokenKind::TkColon) {
55+
Ok(_) => {}
56+
Err(_) => {
57+
p.push_error(LuaParseError::syntax_error_from(
58+
&t!("expected ':' in ternary expression"),
59+
p.current_token_range(),
60+
));
61+
}
62+
}
63+
match parse_sub_expr(p, 0) {
64+
Ok(_) => {}
65+
Err(_) => {
66+
p.push_error(LuaParseError::syntax_error_from(
67+
&t!("expected expression after ':'"),
68+
p.current_token_range(),
69+
));
70+
}
71+
}
72+
cm = m.complete(p);
73+
continue;
74+
}
75+
76+
let bop = LuaOpKind::to_binary_operator(p.current_token());
77+
if bop == BinaryOperator::OpNop || bop.get_priority().left <= limit {
78+
break;
79+
}
4180
let op_range = p.current_token_range();
4281
let op_token = p.current_token();
4382
let m = cm.precede(p, LuaSyntaxKind::BinaryExpr);
@@ -58,7 +97,6 @@ fn parse_sub_expr(p: &mut LuaParser, limit: i32) -> ParseResult {
5897
}
5998

6099
cm = m.complete(p);
61-
bop = LuaOpKind::to_binary_operator(p.current_token());
62100
}
63101

64102
Ok(cm)
@@ -526,6 +564,15 @@ fn parse_suffixed_expr(p: &mut LuaParser) -> ParseResult {
526564
}
527565
cm = m.complete(p);
528566
}
567+
LuaTokenKind::TkSafeNavigation => {
568+
let m = cm.precede(p, LuaSyntaxKind::SafeIndexExpr);
569+
p.bump(); // consume '?.'
570+
if let Err(err) = parse_safe_index_struct(p) {
571+
m.complete(p);
572+
return Err(err);
573+
}
574+
cm = m.complete(p);
575+
}
529576
LuaTokenKind::TkLeftParen
530577
| LuaTokenKind::TkLongString
531578
| LuaTokenKind::TkString
@@ -660,6 +707,87 @@ fn parse_index_struct(p: &mut LuaParser) -> Result<(), ParseFailReason> {
660707
Ok(())
661708
}
662709

710+
/// Parses a safe index structure, which is used in safe navigation expressions (e.g., `?.`).
711+
/// like `?.[expr]`, `?.name`, or `?.:method()`.
712+
/// call like `?.(`, `?. ""`, or `?. {}`.
713+
fn parse_safe_index_struct(p: &mut LuaParser) -> Result<(), ParseFailReason> {
714+
let index_op_range = p.current_token_range();
715+
match p.current_token() {
716+
LuaTokenKind::TkLeftBracket => {
717+
p.bump();
718+
match parse_expr(p) {
719+
Ok(_) => {}
720+
Err(err) => {
721+
p.push_error(LuaParseError::syntax_error_from(
722+
&t!("expected expression inside table index brackets"),
723+
index_op_range,
724+
));
725+
return Err(err);
726+
}
727+
}
728+
match expect_token(p, LuaTokenKind::TkRightBracket) {
729+
Ok(_) => {}
730+
Err(err) => {
731+
p.push_error(LuaParseError::syntax_error_from(
732+
&t!("expected ']' to close table index"),
733+
index_op_range,
734+
));
735+
return Err(err);
736+
}
737+
}
738+
}
739+
LuaTokenKind::TkName => {
740+
p.bump();
741+
}
742+
LuaTokenKind::TkColon => {
743+
p.bump();
744+
let name_token_range = p.current_token_range();
745+
match expect_token(p, LuaTokenKind::TkName) {
746+
Ok(_) => {}
747+
Err(err) => {
748+
p.push_error(LuaParseError::syntax_error_from(
749+
&t!("expected method name after ':'"),
750+
index_op_range,
751+
));
752+
return Err(err);
753+
}
754+
}
755+
if !matches!(
756+
p.current_token(),
757+
LuaTokenKind::TkLeftParen
758+
| LuaTokenKind::TkLeftBrace
759+
| LuaTokenKind::TkString
760+
| LuaTokenKind::TkLongString
761+
) {
762+
p.push_error(LuaParseError::syntax_error_from(
763+
&t!(
764+
"colon accessor must be followed by a function call or table constructor or string literal"
765+
),
766+
name_token_range,
767+
));
768+
769+
return Err(ParseFailReason::UnexpectedToken);
770+
}
771+
}
772+
LuaTokenKind::TkString
773+
| LuaTokenKind::TkLongString
774+
| LuaTokenKind::TkLeftParen
775+
| LuaTokenKind::TkLeftBrace => {
776+
// allow these tokens to be part of a safe navigation call expression
777+
}
778+
_ => {
779+
p.push_error(LuaParseError::syntax_error_from(
780+
&t!("expect safe index struct after '?.'"),
781+
p.current_token_range(),
782+
));
783+
784+
return Err(ParseFailReason::UnexpectedToken);
785+
}
786+
}
787+
788+
Ok(())
789+
}
790+
663791
fn parse_args(p: &mut LuaParser) -> ParseResult {
664792
let m = p.mark(LuaSyntaxKind::CallArgList);
665793
match p.current_token() {

crates/emmylua_parser/src/kind/lua_operator_kind.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ pub enum BinaryOperator {
3232
OpNe, // ~=
3333
OpAnd, // and
3434
OpOr, // or
35+
OpNilCoalescing, // ??
3536
OpNop, // (empty)
3637
}
3738

38-
pub const PRIORITY: [PriorityTable; 21] = [
39+
pub const PRIORITY: [PriorityTable; 23] = [
3940
PriorityTable {
4041
left: 10,
4142
right: 10,
@@ -78,6 +79,8 @@ pub const PRIORITY: [PriorityTable; 21] = [
7879
PriorityTable { left: 3, right: 3 }, // OPR_GE
7980
PriorityTable { left: 2, right: 2 }, // OPR_AND
8081
PriorityTable { left: 1, right: 1 }, // OPR_OR
82+
PriorityTable { left: 1, right: 1 }, // OPR_NIL_COALESCING
83+
PriorityTable { left: 0, right: 0 }, // OPR_NOP (unreachable)
8184
];
8285

8386
impl BinaryOperator {

crates/emmylua_parser/src/kind/lua_syntax_kind.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ pub enum LuaSyntaxKind {
4747
SetmetatableCallExpr, // setmetatable(a, b)
4848
IndexExpr,
4949
NameExpr,
50+
TernaryExpr,
51+
SafeIndexExpr,
52+
NilCoalescingExpr,
5053

5154
// other
5255
LocalName,

crates/emmylua_parser/src/kind/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ impl LuaOpKind {
174174
LuaTokenKind::TkNe => BinaryOperator::OpNe,
175175
LuaTokenKind::TkAnd => BinaryOperator::OpAnd,
176176
LuaTokenKind::TkOr => BinaryOperator::OpOr,
177+
LuaTokenKind::TkNilCoalescing => BinaryOperator::OpNilCoalescing,
177178
_ => BinaryOperator::OpNop,
178179
}
179180
}

0 commit comments

Comments
 (0)