Skip to main content

big_code_analysis_ast/getter/
perl.rs

1//! `Getter` implementation for Perl.
2#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
3
4use super::*;
5
6impl Getter for PerlCode {
7    fn get_space_kind(node: &Node) -> SpaceKind {
8        match node.kind_id().into() {
9            Perl::FunctionDefinition
10            | Perl::FunctionDefinitionWithoutSub
11            | Perl::AnonymousFunction => SpaceKind::Function,
12            Perl::SourceFile => SpaceKind::Unit,
13            _ => SpaceKind::Unknown,
14        }
15    }
16
17    fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
18        use Perl as P;
19
20        match node.kind_id().into() {
21            // Regex delimiter punctuation. The bare match form is the
22            // only one of Perl's five regex literals that spells its
23            // delimiters with an operator token kind: `bca dump` at the
24            // pinned grammar shows `/abc/` emitting two `SLASH` under
25            // `PatternMatcher`, while `m//`, `qr//`, `s///` and `tr///`
26            // use the dedicated `StartDelimiter` / `SeparatorDelimiter`
27            // / `EndDelimiter` kinds that no arm here classifies. So
28            // `$s =~ /abc/` fabricated two division operators (#1312,
29            // the Perl sibling of Elixir #1256). Suppress them exactly
30            // when their parent is the literal — the compound-leaf
31            // guard of grammar-dispatch §5 — which leaves the bare form
32            // contributing what its three synonyms already contribute
33            // (#1314 made that "one operand" rather than "nothing";
34            // see the pattern-value arm below).
35            P::SLASH
36                if ancestors.parent_has_kind(node, P::PatternMatcher as u16) =>
37            {
38                TokenRole::Unknown
39            }
40            // Control-flow and declaration keywords. `Perl::Sub` is the
41            // `sub` keyword (token id 16); `Perl::SUB` is the `__SUB__`
42            // literal (token id 7) — that one is an operand, not an
43            // operator. Same split for `Package` (keyword) vs `PACKAGE`
44            // (`__PACKAGE__` literal).
45            P::If | P::Unless | P::Else | P::Elsif | P::While | P::Until | P::For
46            | P::Foreach | P::When | P::Continue | P::Next | P::Last | P::Redo | P::Goto
47            | P::Return | P::Sub | P::Package | P::My | P::Our
48            | P::Local | P::State | P::Use | P::No | P::Require | P::Bless | P::And | P::Or
49            | P::Xor | P::Not | P::Eq | P::Ne | P::Lt | P::Gt | P::Le | P::Ge | P::Cmp
50            // Punctuation acting as operators
51            | P::SEMI | P::COMMA | P::COLON | P::COLONCOLON | P::LBRACE | P::LBRACK
52            | P::LPAREN | P::DOT | P::DOTDOT | P::DOTDOTDOT | P::FatComma | P::DASHGT
53            | P::QMARK | P::BSLASH | P::DOLLAR | P::DOLLARHASH | P::AT | P::PERCENT | P::HASH
54            // Arithmetic / comparison / logical / bitwise / assignment operators
55            | P::EQ | P::PLUS | P::DASH | P::STAR | P::SLASH | P::STARSTAR | P::BANG
56            | P::TILDE | P::EQTILDE | P::BANGTILDE | P::EQEQ | P::BANGEQ | P::LT | P::GT
57            | P::LTEQ | P::GTEQ | P::AMPAMP | P::PIPEPIPE | P::SLASHSLASH | P::PIPE
58            | P::CARET | P::LTLT | P::GTGT | P::TILDETILDE | P::PLUSPLUS | P::DASHDASH
59            | P::PLUSEQ | P::DASHEQ | P::STAREQ | P::SLASHEQ | P::PERCENTEQ | P::STARSTAREQ
60            | P::AMPEQ | P::PIPEEQ | P::CARETEQ | P::LTLTEQ | P::GTGTEQ | P::AMPAMPEQ
61            | P::PIPEPIPEEQ | P::SLASHSLASHEQ | P::DOTEQ | P::XEQ
62            | P::LTEQGT | P::AMPDOTEQ | P::PIPEDOTEQ | P::CARETDOTEQ | P::Isa
63            // Substitution and transliteration. These are *operations
64            // applied to a target*, not pattern values, so they are
65            // operators while the three pattern spellings below are
66            // operands (#1314). `y///` is a synonym of `tr///` and
67            // shares `TransliterationTrOrY`, so the two fold to one
68            // entry, which is what synonyms should do.
69            //
70            // Their *literal* pattern and replacement text is
71            // invisible either way: `bca dump` shows `s/a/b/` emitting
72            // only the `s` keyword and its `start` / `separator` /
73            // `end` delimiters, with no content node, so classifying
74            // them as operators loses nothing that treating them as
75            // values would have kept. An *interpolated* `s/$x/$y/` does
76            // emit `Interpolation` children, and those count as
77            // operands under either choice — so the reason for the
78            // split is the semantic one above, not this. `get_operator_id_as_str` below renders them
79            // `s///` and `tr///` rather than as raw kind names.
80            | P::SubstitutionPatternS | P::TransliterationTrOrY
81                => TokenRole::Operator,
82            // `package_name`, `package_variable` and `typeglob` each
83            // spell a whole name — `Data::Dumper`, `$Foo::count`,
84            // `*STDOUT` — and are operands in their own right below, so
85            // an operand-classified node they *contain* is already
86            // billed by the wrapper. Before #1355 the wrapper and its
87            // contents both counted: `use strict;` scored N2 2 for one
88            // name, `require Data::Dumper;` n2 3 / N2 3 for one, and
89            // `our $Foo::count = 3;` n2 5 / N2 6 for two — the
90            // vocabulary even growing a bare `::` entry, because a
91            // `package_variable` spells its separator as a second,
92            // childless `package_name` beside the qualifier.
93            //
94            // Keyed on the parent alone rather than on a list of child
95            // kinds, because the invariant is positional: a wrapper's
96            // span contains its children's, so whatever sits directly
97            // inside one is already paid for. node-types.json admits ten
98            // (wrapper, child) pairings across eight operand kinds, all
99            // of them reachable — `package_name` ⊃ `typeglob` is
100            // `*Foo::glob`, `package_name` ⊃ `package_variable` the
101            // `$Foo::Bar::baz` nesting — and enumerating them here would
102            // be a coverage claim to re-derive on every grammar bump for
103            // no gain, the trade grammar-dispatch §1 recommends taking.
104            // `perl_name_wrappers_bill_the_name_once_1355` witnesses all
105            // ten and fails on an eleventh. Naming the three wrappers as
106            // *parents* also resolves arbitrary qualifier depth to the
107            // outermost, the way PHP handles `$$$x` (#1259).
108            //
109            // It sits below the operator arm on purpose: the `::` token
110            // inside a bareword `package_name` (`Data::Dumper`), and `*`
111            // and the opening `{` inside a `typeglob`, are matched there
112            // and keep their operator reading. A sigil-qualified name
113            // has no such token — its separator is the childless
114            // `package_name` above, which the guard drops with the rest
115            // of the wrapper — so `$Foo::count` bills no `::` where
116            // `Data::Dumper` bills one. That asymmetry is the grammar's:
117            // `main` billed that node as a bogus `::` operand and never
118            // as an operator, so n1/N1 are unchanged here. The typeglob
119            // `*` stays an operator because this getter already bills
120            // the `$` sigil on every scalar reference (`my $x` → n1
121            // {my, $, =, ;}), so the typeglob sigil follows the sigil
122            // convention rather than #1312's delimiter suppression; the
123            // cost is that it shares `Perl::STAR` with multiplication,
124            // so both fold into one n1 entry, and `*Foo` and `*{Foo}`
125            // differ by one `{}` operator. (The closing `}` has never
126            // been classified — `get_operator_id_as_str` folds the pair
127            // to one `{}` glyph — so the guard changes nothing for it.)
128            //
129            // Guarded, not deleted, per grammar-dispatch §6: these
130            // leaves are ordinary operands everywhere else (`my $x`,
131            // `foo()`, the `bar` of `Foo::bar()`, which is a *sibling*
132            // of the `package_name` rather than a child).
133            //
134            // Keeping the wrapper's whole span is the opposite call from
135            // PHP's `qualified_name` (#1293) and Groovy's
136            // `QualifiedName` (#1263 / #1352), which bill the leaves and
137            // drop the wrapper. Three reasons it goes the other way in
138            // Perl. `package_variable` and `typeglob` are sigil-bearing
139            // *variable references*, the shape #1259 settled by keeping
140            // the wrapper — the leaves-only reading detaches the sigil
141            // from the variable and glues it to the qualifier, billing
142            // `$Foo::Bar::baz` as `$Foo` + `Bar` + `baz`, three operands
143            // for one variable. The three kinds nest into each other, so
144            // they must be decided together; splitting `package_name`
145            // off would need a grandparent-aware guard. And the
146            // vocabulary-sharing argument of #1293 / #1352 — that
147            // `java.util.List` and `List` name the same class — has no
148            // Perl analogue, since a package is always spelled in full.
149            //
150            // `module_name` is not here, though #1355 expected it to
151            // be: it is the quoted `use 'Foo.pm'` form, and it holds
152            // nothing but its two quote tokens — not the `identifier`
153            // the issue thought it shared. It stays an operand, pinned
154            // by `perl_qualified_name_leaves_still_count_elsewhere_1355`.
155            _ if matches!(
156                ancestors.parent(node).map(|p| p.kind_id().into()),
157                Some(P::PackageName | P::PackageVariable | P::Typeglob)
158            ) =>
159            {
160                TokenRole::Unknown
161            }
162            // Operands: identifiers and literals. Non-interpolating
163            // string literals (`'…'`, `q{…}`) are leaf operands; the
164            // interpolating kinds (`"…"`, `qq{…}`, `` `…` ``, `qx{…}`)
165            // are handled separately below so their inner
166            // scalar/array/hash variables are not double-counted.
167            //
168            // `ListItem` is one element of a `qw(a b c)` list — a leaf
169            // the grammar emits under `word_list_qw` and nowhere else,
170            // so it cannot double-bill another construct. Until it was
171            // listed the whole `qw()` was invisible to Halstead: neither
172            // the elements nor the wrapper nor the `qw` keyword had an
173            // arm, so `my @a = qw(a b c)` billed `@a` alone while its
174            // synonym `("a", "b", "c")` billed four operands. The
175            // wrapper is gated below, the Ruby `%w[]` shape of #1353.
176            P::Identifier | P::ScalarVariable | P::ArrayVariable | P::HashVariable
177            | P::PackageVariable | P::SpecialScalarVariable | P::PackageName | P::ModuleName
178            | P::BarewordImport | P::Typeglob | P::FileHandle | P::ListItem
179            | P::Integer | P::FloatingPoint | P::ScientificNotation | P::Hexadecimal | P::Octal
180            | P::True | P::False | P::SpecialLiteral
181            | P::StringSingleQuoted | P::StringQQuoted
182            | P::FILE | P::LINE | P::SUB | P::PACKAGE
183                => TokenRole::Operand,
184            // Perl's interpolating string-like literals count as one
185            // operand when inert. When they carry an `Interpolation`
186            // child the inner scalar / array / hash variables are
187            // already walked and classified as operands; counting the
188            // wrapping literal too would double-count the inner
189            // variables' contribution to `N2` (issue #199, same
190            // pattern as #180 for Elixir/Bash, #183 for C#, #184 for
191            // PHP, #191 for Kotlin). `HeredocBodyStatement` is the
192            // visible body of `<<TAG ... TAG` heredocs (issue #287);
193            // it is interpolation-capable, so it joins the same
194            // dispatch — inert heredocs are one operand, interpolating
195            // heredocs let the inner variables carry the count.
196            // The pattern *values* — `/abc/`, `m/abc/`, `qr/abc/` —
197            // join the same dispatch (#1314). `m/abc/` is exactly
198            // `/abc/` and `qr/abc/` is the same pattern as a value, so
199            // all three must score alike; that equality is what
200            // `perl_every_pattern_value_spelling_scores_alike` protects
201            // and why #1312 declined to promote the bare form on its
202            // own. Promoting the three together closes the gap without
203            // reintroducing the spelling sensitivity: each contributes
204            // one operand, matching Ruby's `Regex` and Elixir's
205            // `Sigil`. `s///` and `tr///` are *not* here — they are
206            // operations applied to a target and sit in the operator
207            // arm above.
208            //
209            // Sharing the string dispatch is not incidental, which is
210            // why the arms are merged rather than duplicated: the bare
211            // form keeps an interpolated variable inside an
212            // unclassified `regex_pattern_content`, but `m/$x/` and
213            // `qr/$x/` emit a real `Interpolation` whose
214            // `scalar_variable` is already counted. A plain operand arm
215            // would score `/$x/` at one operand and `m/$x/` at two —
216            // the very divergence the equality above exists to prevent.
217            //
218            // `WordListQw` holds classified `ListItem` operands rather
219            // than interpolation, so the same guard gives it "one
220            // operand per element, or one for the empty `qw()`" — the
221            // rule #1353 settled for Ruby's `%w[]`. One list serves
222            // both groups because no string kind admits a `list_item`
223            // and `word_list_qw` admits no `interpolation`.
224            P::StringDoubleQuoted | P::StringQqQuoted | P::BacktickQuoted
225            | P::CommandQxQuoted | P::HeredocBodyStatement
226            | P::PatternMatcher | P::PatternMatcherM | P::RegexPatternQr | P::WordListQw => {
227                Self::string_operand_type(node, &[P::Interpolation as u16, P::ListItem as u16])
228            }
229            _ => TokenRole::Unknown,
230        }
231    }
232
233    /// Folds paired delimiters to one glyph, and names the two
234    /// pattern *operations* after their source spelling.
235    ///
236    /// Hand-written rather than `get_operator!(Perl)` because
237    /// `SubstitutionPatternS` and `TransliterationTrOrY` are named
238    /// nodes rather than punctuation tokens (#1314): the macro's
239    /// fallback renders a kind's own name, so `bca ops` would list
240    /// `substitution_pattern_s` and `transliteration_tr_or_y` among
241    /// the operators, which reads as a bug rather than as `s///`.
242    /// `tr///` covers `y///` too — one kind, one glyph, because they
243    /// are synonyms.
244    #[inline]
245    fn get_operator_id_as_str(id: u16) -> &'static str {
246        let typ = id.into();
247        match typ {
248            Perl::LPAREN => "()",
249            Perl::LBRACK => "[]",
250            Perl::LBRACE => "{}",
251            Perl::SubstitutionPatternS => "s///",
252            Perl::TransliterationTrOrY => "tr///",
253            _ => typ.into(),
254        }
255    }
256}