diff options
author | robojumper <[email protected]> | 2019-04-05 21:34:45 +0100 |
---|---|---|
committer | robojumper <[email protected]> | 2019-04-06 00:07:35 +0100 |
commit | ca40ca93a55ffa08d3e699fc877e7e189b526c66 (patch) | |
tree | 8b56a9250db5c713da3fc14758c0583bbb029638 /crates | |
parent | 0372eca5b2e6dade5132a08db46992ca73a25188 (diff) |
Parse and infer tuple indices
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_hir/src/expr.rs | 5 | ||||
-rw-r--r-- | crates/ra_hir/src/name.rs | 9 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests.rs | 59 | ||||
-rw-r--r-- | crates/ra_parser/src/grammar/expressions.rs | 13 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/extensions.rs | 34 | ||||
-rw-r--r-- | crates/ra_syntax/src/syntax_error.rs | 4 | ||||
-rw-r--r-- | crates/ra_syntax/src/validation.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/validation/field_expr.rs | 12 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs | 5 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt | 51 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs | 1 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt | 27 |
13 files changed, 210 insertions, 14 deletions
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index b2a237ece..cfa824458 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs | |||
@@ -671,7 +671,10 @@ impl ExprCollector { | |||
671 | } | 671 | } |
672 | ast::ExprKind::FieldExpr(e) => { | 672 | ast::ExprKind::FieldExpr(e) => { |
673 | let expr = self.collect_expr_opt(e.expr()); | 673 | let expr = self.collect_expr_opt(e.expr()); |
674 | let name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); | 674 | let name = match e.field_access() { |
675 | Some(kind) => kind.as_name(), | ||
676 | _ => Name::missing(), | ||
677 | }; | ||
675 | self.alloc_expr(Expr::Field { expr, name }, syntax_ptr) | 678 | self.alloc_expr(Expr::Field { expr, name }, syntax_ptr) |
676 | } | 679 | } |
677 | ast::ExprKind::TryExpr(e) => { | 680 | ast::ExprKind::TryExpr(e) => { |
diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs index 677d18efc..283f37845 100644 --- a/crates/ra_hir/src/name.rs +++ b/crates/ra_hir/src/name.rs | |||
@@ -90,6 +90,15 @@ impl AsName for ast::Name { | |||
90 | } | 90 | } |
91 | } | 91 | } |
92 | 92 | ||
93 | impl<'a> AsName for ast::FieldKind<'a> { | ||
94 | fn as_name(&self) -> Name { | ||
95 | match self { | ||
96 | ast::FieldKind::Name(nr) => nr.as_name(), | ||
97 | ast::FieldKind::Index(idx) => Name::new(idx.text().clone()), | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
93 | impl AsName for ra_db::Dependency { | 102 | impl AsName for ra_db::Dependency { |
94 | fn as_name(&self) -> Name { | 103 | fn as_name(&self) -> Name { |
95 | Name::new(self.name.clone()) | 104 | Name::new(self.name.clone()) |
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 0b7c841df..f0164f7ea 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs | |||
@@ -2242,6 +2242,65 @@ static B: u64 = { let x = 1; x }; | |||
2242 | ); | 2242 | ); |
2243 | } | 2243 | } |
2244 | 2244 | ||
2245 | #[test] | ||
2246 | fn tuple_struct_fields() { | ||
2247 | assert_snapshot_matches!( | ||
2248 | infer(r#" | ||
2249 | struct S(i32, u64); | ||
2250 | fn test() -> u64 { | ||
2251 | let a = S(4, 6); | ||
2252 | let b = a.0; | ||
2253 | a.1 | ||
2254 | } | ||
2255 | "#), | ||
2256 | @r###" | ||
2257 | [38; 87) '{ ... a.1 }': u64 | ||
2258 | [48; 49) 'a': S | ||
2259 | [52; 53) 'S': S(i32, u64) -> S | ||
2260 | [52; 59) 'S(4, 6)': S | ||
2261 | [54; 55) '4': i32 | ||
2262 | [57; 58) '6': u64 | ||
2263 | [69; 70) 'b': i32 | ||
2264 | [73; 74) 'a': S | ||
2265 | [73; 76) 'a.0': i32 | ||
2266 | [82; 83) 'a': S | ||
2267 | [82; 85) 'a.1': u64"### | ||
2268 | ); | ||
2269 | } | ||
2270 | |||
2271 | #[test] | ||
2272 | fn tuple_struct_with_fn() { | ||
2273 | assert_snapshot_matches!( | ||
2274 | infer(r#" | ||
2275 | struct S(fn(u32) -> u64); | ||
2276 | fn test() -> u64 { | ||
2277 | let a = S(|i| 2*i); | ||
2278 | let b = a.0(4); | ||
2279 | a.0(2) | ||
2280 | } | ||
2281 | "#), | ||
2282 | @r###" | ||
2283 | [44; 102) '{ ...0(2) }': u64 | ||
2284 | [54; 55) 'a': S | ||
2285 | [58; 59) 'S': S(fn(u32) -> u64) -> S | ||
2286 | [58; 68) 'S(|i| 2*i)': S | ||
2287 | [60; 67) '|i| 2*i': fn(u32) -> u64 | ||
2288 | [61; 62) 'i': i32 | ||
2289 | [64; 65) '2': i32 | ||
2290 | [64; 67) '2*i': i32 | ||
2291 | [66; 67) 'i': i32 | ||
2292 | [78; 79) 'b': u64 | ||
2293 | [82; 83) 'a': S | ||
2294 | [82; 85) 'a.0': fn(u32) -> u64 | ||
2295 | [82; 88) 'a.0(4)': u64 | ||
2296 | [86; 87) '4': u32 | ||
2297 | [94; 95) 'a': S | ||
2298 | [94; 97) 'a.0': fn(u32) -> u64 | ||
2299 | [94; 100) 'a.0(2)': u64 | ||
2300 | [98; 99) '2': u32"### | ||
2301 | ); | ||
2302 | } | ||
2303 | |||
2245 | fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String { | 2304 | fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String { |
2246 | let func = source_binder::function_from_position(db, pos).unwrap(); | 2305 | let func = source_binder::function_from_position(db, pos).unwrap(); |
2247 | let body_source_map = func.body_source_map(db); | 2306 | let body_source_map = func.body_source_map(db); |
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs index bf5d6544d..9b38b0a31 100644 --- a/crates/ra_parser/src/grammar/expressions.rs +++ b/crates/ra_parser/src/grammar/expressions.rs | |||
@@ -379,6 +379,14 @@ fn method_call_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { | |||
379 | // fn foo() { | 379 | // fn foo() { |
380 | // x.foo; | 380 | // x.foo; |
381 | // x.0.bar; | 381 | // x.0.bar; |
382 | // x.0(); | ||
383 | // } | ||
384 | |||
385 | // test_err bad_tuple_index_expr | ||
386 | // fn foo() { | ||
387 | // x.0.; | ||
388 | // x.1i32; | ||
389 | // x.0x01; | ||
382 | // } | 390 | // } |
383 | fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { | 391 | fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { |
384 | assert!(p.at(DOT)); | 392 | assert!(p.at(DOT)); |
@@ -387,7 +395,10 @@ fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { | |||
387 | if p.at(IDENT) { | 395 | if p.at(IDENT) { |
388 | name_ref(p) | 396 | name_ref(p) |
389 | } else if p.at(INT_NUMBER) { | 397 | } else if p.at(INT_NUMBER) { |
390 | p.bump() | 398 | p.bump(); |
399 | } else if p.at(FLOAT_NUMBER) { | ||
400 | // FIXME: How to recover and instead parse INT + DOT? | ||
401 | p.bump(); | ||
391 | } else { | 402 | } else { |
392 | p.error("expected field name or number") | 403 | p.error("expected field name or number") |
393 | } | 404 | } |
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 9f5c41b0c..a06a6375d 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs | |||
@@ -17,7 +17,7 @@ pub use self::{ | |||
17 | generated::*, | 17 | generated::*, |
18 | traits::*, | 18 | traits::*, |
19 | tokens::*, | 19 | tokens::*, |
20 | extensions::{PathSegmentKind, StructKind, SelfParamKind}, | 20 | extensions::{PathSegmentKind, StructKind, FieldKind, SelfParamKind}, |
21 | expr_extensions::{ElseBranch, PrefixOp, BinOp, LiteralKind}, | 21 | expr_extensions::{ElseBranch, PrefixOp, BinOp, LiteralKind}, |
22 | }; | 22 | }; |
23 | 23 | ||
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index aec57c380..ca33b43e7 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs | |||
@@ -3,11 +3,8 @@ | |||
3 | 3 | ||
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | 5 | ||
6 | use crate::{ | 6 | use crate::{SmolStr, SyntaxToken, ast::{self, AstNode, children, child_opt}, SyntaxKind::*, SyntaxElement}; |
7 | SmolStr, SyntaxToken, | 7 | use ra_parser::SyntaxKind; |
8 | ast::{self, AstNode, children, child_opt}, | ||
9 | SyntaxKind::*, | ||
10 | }; | ||
11 | 8 | ||
12 | impl ast::Name { | 9 | impl ast::Name { |
13 | pub fn text(&self) -> &SmolStr { | 10 | pub fn text(&self) -> &SmolStr { |
@@ -217,6 +214,33 @@ impl ast::ExprStmt { | |||
217 | } | 214 | } |
218 | } | 215 | } |
219 | 216 | ||
217 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
218 | pub enum FieldKind<'a> { | ||
219 | Name(&'a ast::NameRef), | ||
220 | Index(SyntaxToken<'a>), | ||
221 | } | ||
222 | |||
223 | impl ast::FieldExpr { | ||
224 | pub fn index_token(&self) -> Option<SyntaxToken> { | ||
225 | self.syntax | ||
226 | .children_with_tokens() | ||
227 | // FIXME: Accepting floats here to reject them in validation later | ||
228 | .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER) | ||
229 | .as_ref() | ||
230 | .and_then(SyntaxElement::as_token) | ||
231 | } | ||
232 | |||
233 | pub fn field_access(&self) -> Option<FieldKind> { | ||
234 | if let Some(nr) = self.name_ref() { | ||
235 | Some(FieldKind::Name(nr)) | ||
236 | } else if let Some(tok) = self.index_token() { | ||
237 | Some(FieldKind::Index(tok)) | ||
238 | } else { | ||
239 | None | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | |||
220 | impl ast::RefPat { | 244 | impl ast::RefPat { |
221 | pub fn is_mut(&self) -> bool { | 245 | pub fn is_mut(&self) -> bool { |
222 | self.syntax().children_with_tokens().any(|n| n.kind() == MUT_KW) | 246 | self.syntax().children_with_tokens().any(|n| n.kind() == MUT_KW) |
diff --git a/crates/ra_syntax/src/syntax_error.rs b/crates/ra_syntax/src/syntax_error.rs index 4b8c22a57..4198eefdb 100644 --- a/crates/ra_syntax/src/syntax_error.rs +++ b/crates/ra_syntax/src/syntax_error.rs | |||
@@ -95,6 +95,7 @@ pub enum SyntaxErrorKind { | |||
95 | InvalidSuffix, | 95 | InvalidSuffix, |
96 | InvalidBlockAttr, | 96 | InvalidBlockAttr, |
97 | InvalidMatchInnerAttr, | 97 | InvalidMatchInnerAttr, |
98 | InvalidTupleIndexFormat, | ||
98 | } | 99 | } |
99 | 100 | ||
100 | impl fmt::Display for SyntaxErrorKind { | 101 | impl fmt::Display for SyntaxErrorKind { |
@@ -139,6 +140,9 @@ impl fmt::Display for SyntaxErrorKind { | |||
139 | InvalidMatchInnerAttr => { | 140 | InvalidMatchInnerAttr => { |
140 | write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression") | 141 | write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression") |
141 | } | 142 | } |
143 | InvalidTupleIndexFormat => { | ||
144 | write!(f, "Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix") | ||
145 | } | ||
142 | ParseError(msg) => write!(f, "{}", msg.0), | 146 | ParseError(msg) => write!(f, "{}", msg.0), |
143 | } | 147 | } |
144 | } | 148 | } |
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index fc534df83..c2f545173 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs | |||
@@ -3,6 +3,7 @@ mod byte_string; | |||
3 | mod char; | 3 | mod char; |
4 | mod string; | 4 | mod string; |
5 | mod block; | 5 | mod block; |
6 | mod field_expr; | ||
6 | 7 | ||
7 | use crate::{ | 8 | use crate::{ |
8 | SourceFile, SyntaxError, AstNode, SyntaxNode, | 9 | SourceFile, SyntaxError, AstNode, SyntaxNode, |
@@ -17,6 +18,7 @@ pub(crate) fn validate(file: &SourceFile) -> Vec<SyntaxError> { | |||
17 | let _ = visitor_ctx(&mut errors) | 18 | let _ = visitor_ctx(&mut errors) |
18 | .visit::<ast::Literal, _>(validate_literal) | 19 | .visit::<ast::Literal, _>(validate_literal) |
19 | .visit::<ast::Block, _>(block::validate_block_node) | 20 | .visit::<ast::Block, _>(block::validate_block_node) |
21 | .visit::<ast::FieldExpr, _>(field_expr::validate_field_expr_node) | ||
20 | .accept(node); | 22 | .accept(node); |
21 | } | 23 | } |
22 | errors | 24 | errors |
diff --git a/crates/ra_syntax/src/validation/field_expr.rs b/crates/ra_syntax/src/validation/field_expr.rs new file mode 100644 index 000000000..2b405062e --- /dev/null +++ b/crates/ra_syntax/src/validation/field_expr.rs | |||
@@ -0,0 +1,12 @@ | |||
1 | use crate::{ast::{self, FieldKind}, | ||
2 | SyntaxError, | ||
3 | SyntaxErrorKind::*, | ||
4 | }; | ||
5 | |||
6 | pub(crate) fn validate_field_expr_node(node: &ast::FieldExpr, errors: &mut Vec<SyntaxError>) { | ||
7 | if let Some(FieldKind::Index(idx)) = node.field_access() { | ||
8 | if idx.text().chars().any(|c| c < '0' || c > '9') { | ||
9 | errors.push(SyntaxError::new(InvalidTupleIndexFormat, idx.range())); | ||
10 | } | ||
11 | } | ||
12 | } | ||
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs new file mode 100644 index 000000000..30cc49138 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.rs | |||
@@ -0,0 +1,5 @@ | |||
1 | fn foo() { | ||
2 | x.0.; | ||
3 | x.1i32; | ||
4 | x.0x01; | ||
5 | } | ||
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt new file mode 100644 index 000000000..c111f60ea --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/err/0010_bad_tuple_index_expr.txt | |||
@@ -0,0 +1,51 @@ | |||
1 | SOURCE_FILE@[0; 47) | ||
2 | FN_DEF@[0; 46) | ||
3 | FN_KW@[0; 2) "fn" | ||
4 | WHITESPACE@[2; 3) " " | ||
5 | NAME@[3; 6) | ||
6 | IDENT@[3; 6) "foo" | ||
7 | PARAM_LIST@[6; 8) | ||
8 | L_PAREN@[6; 7) "(" | ||
9 | R_PAREN@[7; 8) ")" | ||
10 | WHITESPACE@[8; 9) " " | ||
11 | BLOCK@[9; 46) | ||
12 | L_CURLY@[9; 10) "{" | ||
13 | WHITESPACE@[10; 15) "\n " | ||
14 | EXPR_STMT@[15; 20) | ||
15 | FIELD_EXPR@[15; 19) | ||
16 | PATH_EXPR@[15; 16) | ||
17 | PATH@[15; 16) | ||
18 | PATH_SEGMENT@[15; 16) | ||
19 | NAME_REF@[15; 16) | ||
20 | IDENT@[15; 16) "x" | ||
21 | DOT@[16; 17) "." | ||
22 | err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix` | ||
23 | FLOAT_NUMBER@[17; 19) "0." | ||
24 | SEMI@[19; 20) ";" | ||
25 | WHITESPACE@[20; 25) "\n " | ||
26 | EXPR_STMT@[25; 32) | ||
27 | FIELD_EXPR@[25; 31) | ||
28 | PATH_EXPR@[25; 26) | ||
29 | PATH@[25; 26) | ||
30 | PATH_SEGMENT@[25; 26) | ||
31 | NAME_REF@[25; 26) | ||
32 | IDENT@[25; 26) "x" | ||
33 | DOT@[26; 27) "." | ||
34 | err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix` | ||
35 | INT_NUMBER@[27; 31) "1i32" | ||
36 | SEMI@[31; 32) ";" | ||
37 | WHITESPACE@[32; 37) "\n " | ||
38 | EXPR_STMT@[37; 44) | ||
39 | FIELD_EXPR@[37; 43) | ||
40 | PATH_EXPR@[37; 38) | ||
41 | PATH@[37; 38) | ||
42 | PATH_SEGMENT@[37; 38) | ||
43 | NAME_REF@[37; 38) | ||
44 | IDENT@[37; 38) "x" | ||
45 | DOT@[38; 39) "." | ||
46 | err: `Tuple (struct) field access is only allowed through decimal integers with no underscores or suffix` | ||
47 | INT_NUMBER@[39; 43) "0x01" | ||
48 | SEMI@[43; 44) ";" | ||
49 | WHITESPACE@[44; 45) "\n" | ||
50 | R_CURLY@[45; 46) "}" | ||
51 | WHITESPACE@[46; 47) "\n" | ||
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs index 3e69538e5..b8da2ddc3 100644 --- a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs +++ b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | fn foo() { | 1 | fn foo() { |
2 | x.foo; | 2 | x.foo; |
3 | x.0.bar; | 3 | x.0.bar; |
4 | x.0(); | ||
4 | } | 5 | } |
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt index a86702843..78054ec5a 100644 --- a/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt +++ b/crates/ra_syntax/tests/data/parser/inline/ok/0011_field_expr.txt | |||
@@ -1,5 +1,5 @@ | |||
1 | SOURCE_FILE@[0; 37) | 1 | SOURCE_FILE@[0; 48) |
2 | FN_DEF@[0; 36) | 2 | FN_DEF@[0; 47) |
3 | FN_KW@[0; 2) "fn" | 3 | FN_KW@[0; 2) "fn" |
4 | WHITESPACE@[2; 3) " " | 4 | WHITESPACE@[2; 3) " " |
5 | NAME@[3; 6) | 5 | NAME@[3; 6) |
@@ -8,7 +8,7 @@ SOURCE_FILE@[0; 37) | |||
8 | L_PAREN@[6; 7) "(" | 8 | L_PAREN@[6; 7) "(" |
9 | R_PAREN@[7; 8) ")" | 9 | R_PAREN@[7; 8) ")" |
10 | WHITESPACE@[8; 9) " " | 10 | WHITESPACE@[8; 9) " " |
11 | BLOCK@[9; 36) | 11 | BLOCK@[9; 47) |
12 | L_CURLY@[9; 10) "{" | 12 | L_CURLY@[9; 10) "{" |
13 | WHITESPACE@[10; 15) "\n " | 13 | WHITESPACE@[10; 15) "\n " |
14 | EXPR_STMT@[15; 21) | 14 | EXPR_STMT@[15; 21) |
@@ -37,6 +37,21 @@ SOURCE_FILE@[0; 37) | |||
37 | NAME_REF@[30; 33) | 37 | NAME_REF@[30; 33) |
38 | IDENT@[30; 33) "bar" | 38 | IDENT@[30; 33) "bar" |
39 | SEMI@[33; 34) ";" | 39 | SEMI@[33; 34) ";" |
40 | WHITESPACE@[34; 35) "\n" | 40 | WHITESPACE@[34; 39) "\n " |
41 | R_CURLY@[35; 36) "}" | 41 | EXPR_STMT@[39; 45) |
42 | WHITESPACE@[36; 37) "\n" | 42 | CALL_EXPR@[39; 44) |
43 | FIELD_EXPR@[39; 42) | ||
44 | PATH_EXPR@[39; 40) | ||
45 | PATH@[39; 40) | ||
46 | PATH_SEGMENT@[39; 40) | ||
47 | NAME_REF@[39; 40) | ||
48 | IDENT@[39; 40) "x" | ||
49 | DOT@[40; 41) "." | ||
50 | INT_NUMBER@[41; 42) "0" | ||
51 | ARG_LIST@[42; 44) | ||
52 | L_PAREN@[42; 43) "(" | ||
53 | R_PAREN@[43; 44) ")" | ||
54 | SEMI@[44; 45) ";" | ||
55 | WHITESPACE@[45; 46) "\n" | ||
56 | R_CURLY@[46; 47) "}" | ||
57 | WHITESPACE@[47; 48) "\n" | ||