1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
//! Perl: telling the `and` and `not` operators, and the file tests,
//! from an auto-quoted key that the grammar lexes as one of them.
use cratePerl;
use crate;
/// Whether `and`, a Perl `and` token whose parent is `parent`, is the
/// low-precedence logical operator.
///
/// FIXME(#1539 upstream): tree-sitter-perl 1.1.2 lexes an auto-quoted
/// bareword `and` as the keyword. Perl quotes a bareword before `=>`
/// and alone inside a hash subscript, so `(and => 1)` and `$h{and}`
/// hold a string key and no operator, yet the grammar emits two
/// shapes that read as one:
///
/// - a subscript (`$h{and}`, `$r->{and}`, `$h{-and}`) recovers into an
/// `ERROR` node holding the token;
/// - a fat-comma key (`(and => 1)`, `{ and => 1 }`, `f(x => 1, and =>
/// 2)`) becomes a `unary_expression` whose *first* child is the
/// token — the kind a real `$a and $b` also gets, with `$a` first.
///
/// `and` is an infix operator, so in valid Perl the operator always has
/// a left operand. That is the whole test: the parent is one of the two
/// expression kinds and the token is not its first child, an `extra`
/// aside.
/// `or` and `xor` are unaffected — the grammar reads either as an
/// identifier in both positions. Remove this, and every call site,
/// once the pin carries the upstream fix.
/// Whether `expr` is the `unary_expression` tree-sitter-perl builds
/// around an auto-quoted `and` key (`(and => 1)`): its first child,
/// comments aside, is the `and` token itself. See
/// [`perl_and_is_operator`].
/// Whether `not`, a Perl `not` token whose parent is `parent`, is an
/// auto-quoted hash key rather than the prefix operator.
///
/// FIXME(#1541 upstream): the `not` sibling of #1539 — see
/// [`perl_and_is_operator`]. tree-sitter-perl 1.1.2 lexes a bareword
/// `not` before `=>` or alone in a hash subscript as the keyword, and
/// recovers into one of two shapes:
///
/// - a subscript (`$h{not}`, `$r->{not}`) gives an `ERROR` node holding
/// the token;
/// - a fat-comma key (`(not => 1)`, `{ not => 1 }`) gives the
/// `unary_expression` a real `not 1` gets, with an `ERROR` wrapping
/// the `=>` as the token's next sibling;
/// - a fat-comma key after an earlier keyword key in the same list
/// (`f(and => 1, not => 2)`) gives a `not` applied to a zero-width
/// bareword the recovery invents, with the `=>` beside the whole
/// `unary_expression`. A real `not` always has a written operand.
///
/// `and`'s test does not carry over: `not` is a prefix operator, so it
/// is the first child of its expression whether it is a key or not. The
/// recovery node is the only signal, so this reads it directly. A real
/// `not` that lands inside an `ERROR` for an unrelated reason is billed
/// as a key too; that takes input the grammar already failed to parse.
/// `or` is unaffected — the grammar reads it as an identifier in both
/// positions. Remove this, and every call site, once the pin carries the
/// upstream fix.
/// Whether `test`, a Perl `file_handle_operator` whose ancestor chain is
/// `ancestors`, is an auto-quoted `-bareword` key rather than a file
/// test.
///
/// FIXME(#1545 upstream): tree-sitter-perl 1.1.2 lexes the leading `-X`
/// of a dash-prefixed bareword key as a file test. Perl auto-quotes
/// `-foo` before `=>` and alone in a hash subscript — the Tk, CGI and
/// Getopt option-hash idiom — so `$h{-foo}` and `(-text => 1)` hold the
/// strings `"-foo"` and `"-text"`, and no operator. The grammar emits
/// two file-test shapes, which this recognises:
///
/// - `-` and a letter glued to more word characters (`-foo`) becomes a
/// file test on the bareword `oo`. Perl never reads that as a file
/// test, since one needs a non-word character after its letter, so
/// only the position decides: the test is the whole `key` of a hash
/// subscript, or the left of a `=>`. Elsewhere `-foo` is a negated
/// call or a string, and stays as the grammar reads it.
/// - a key that *is* a file test (`(-x => 1)`) swallows the `=>` and
/// the value, wrapping the `=>` in an `ERROR`. A real file test
/// cannot take a `=>` as its operand, so the `ERROR` decides — when
/// the `=>` is on the test's own line. Perl quotes `-x` only when
/// nothing but spaces and tabs separate it from the `=>`: across a
/// newline or a comment (`(-f # c⏎ => 7)`) it stays the file test on
/// `$_`, the list `(-f, 7)`, and the grammar recovers the same
/// `ERROR` regardless. A comment runs to the end of its line, so the
/// row alone tells the two apart. The `-and` / `-not` and `-foo`
/// keys are quoted across either, as Perl does.
///
/// The subscript form of the second shape (`$h{-x}`) recovers into an
/// `ERROR` that drops the `-x` token altogether, so nothing is left to
/// bill. A key whose letter is no file-test letter (`-name`, `-height`)
/// is a third shape, a `-` applied to the bareword, which this does not
/// cover: it still bills the `-` and the word, and ABC counts the word
/// as a call, as every bareword key does. Remove this, and every call
/// site, once the pin carries the upstream fix.
pub
/// Whether `node` is the word of a `-bareword` key that the enclosing
/// file test bills whole, so it bills nothing of its own. `ancestors`
/// is `node`'s chain.
/// Whether `dash`, a Perl `-` token whose parent is `parent`, is the
/// sign of an auto-quoted `-and` or `-not` key (`$h{-not}`,
/// `(-and => 1)`), which the grammar splits into the `-` and the keyword
/// token. [`perl_and_is_operator`] and [`perl_not_is_key`] decide
/// whether the keyword is a key, and a key's sign is part of the string
/// whether or not a space separates the two.
/// Whether `key`, an `and` / `not` key token whose ancestor chain is
/// `ancestors`, is signed by a `-` (`$h{-not}`, `(- and => 1)`), read off
/// the tree as [`perl_dash_signs_keyword_key`] reads it: the `-` sits
/// beside the key, or is the operator of the expression the key leads.
/// The bytes cannot say it: an infix `-` (`$x - and => 2`) or a comment
/// ending in one also precedes a key, and a comment can sit between a
/// sign and its key.
// The width of a file-test operator token (`-e`, `-x`, …): the grammar
// lexes it as one hidden anonymous `/-[rwxo…]/` token, a `-` and one
// letter, so no node carries its end byte.
const FILE_TEST_TOKEN_LEN: usize = 2;
// Whether `word` is the bareword the grammar split off a `-bareword`
// key: a word glued to the file-test token starts right after it.
// Whether the `unary_expression` around `test` is the whole key of a
// hash subscript, which the grammar wraps in a `binary_expression` with
// no other child but comments, or is followed by a `=>`.
// The sibling after `child` in `parent`, comments aside. Every key of
// a wide `(-a => 1, -b => 2, …)` list asks, so it is found without a
// scan from the list's start (#1545).