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
//! `Getter` implementation for Tcl.
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use super::*;
/// The braced-word kinds `Getter::is_subsumed_braced_word` is
/// instantiated with (#1354): the literal *value* form the guard keys
/// on, the *script* form it gates on holding a command, and the comment
/// kind that gate must not mistake for one.
const BRACED_WORD_KINDS: BracedWordKinds = BracedWordKinds {
value: Tcl::BracedWordSimple as u16,
script: Tcl::BracedWord as u16,
comment: Tcl::Comment as u16,
};
impl Getter for TclCode {
fn get_space_kind(node: &Node) -> SpaceKind {
match node.kind_id().into() {
Tcl::Procedure => SpaceKind::Function,
Tcl::SourceFile => SpaceKind::Unit,
_ => SpaceKind::Unknown,
}
}
fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> HalsteadType {
match node.kind_id().into() {
// The braced-word rule (#1354 / #1317), whose two halves
// `Getter::is_subsumed_braced_word` states once for both
// dialects. This arm is only its Tcl instantiation, plus
// the derivation and the caveats.
//
// A braced *word* is one literal value: Tcl evaluates
// nothing between the braces, so `{$x}` is the
// two-character string `$x` and not a variable reference.
// The grammar models the interior structurally all the
// same — `braced_word_simple` (89) admits `simple_word`,
// `escaped_character`, `quoted_word`,
// `variable_substitution`, `command_substitution` and a
// nested `braced_word_simple` — so the word and every part
// of it were billed together: `set x {literal here}` scored
// n2 4 / N2 4 for one value, and `set a {braced word}`
// reported four operands where its synonym
// `set a "quoted word"` reported two.
//
// The wrapper keeps the operand and its children are
// suppressed — the opposite polarity from `QuotedWord`
// below, and for the reason that decides it: a quoted word
// *does* interpolate, so its `$var` children are real
// references the walk has to count, while a braced word's
// are letters. Billing the whole word is also what keeps
// the two spellings of one literal scoring alike, the
// spelling insensitivity #695, #1312 and #1314 each
// restored elsewhere — and it is the contract the book
// already documents: "a Tcl braced word … contributes *one
// operand* — the literal" (`metrics.md`, Halstead).
//
// The value half is keyed on the parent alone rather than
// on a list of child kinds (the #1355 shape): the invariant
// is positional — a wrapper's span already contains its
// children's — and an enumerated list would be a coverage
// claim to re-derive on every grammar bump.
// `tcl_braced_word_bills_its_content_once_1354` witnesses all six
// child kinds and fails on a seventh.
//
// It subsumes #1314's narrower `LBRACE` guard: that `{` is
// one more child of the word, and reporting it as a `{}`
// operator fabricated a block the source does not contain.
// A *script* is a `BracedWord` (88) — verified across
// `proc`, `if`, `catch`, `eval`, `after` and `uplevel` — an
// `if`/`while` condition is an `Expr` (97), and a `proc`
// parameter list is `Arguments` (94). All three keep their
// braces as operators, which is what makes keying the value
// half on `BracedWordSimple` safe.
//
// Two limits of the value half, both deliberate and both
// pinned rather than left implied.
//
// It is *parent*-scoped, so "Tcl evaluates nothing between
// the braces" holds for what sits directly inside the word
// and not for what sits deeper: the grammar parses a `[…]`
// command substitution and a `"…"` quoted word inside a
// braced word, and the operands *they* contain still count
// (`set a {x "$q" v}` bills `$q`). Widening to an ancestor
// scan would also swallow the `{` of every `Expr` and
// `BracedWord` nested under a value word, which is the
// fabrication #1314 removed in the other direction —
// `tcl_braced_word_guard_is_parent_scoped_not_ancestor_scoped`
// pins the operators, `tcl_braced_word_bills_its_content_once_1354`
// the surviving operands. The residual is the same one
// #1314 recorded: the grammar models a substitution Tcl
// will not perform, and what the classifier sees is what
// the metric reports.
//
// And the grammar emits `BracedWordSimple` only in the
// value slot of the commands it special-cases, so
// `lappend x {a b}` parses its literal as a `BracedWord`
// script and still fabricates a `{}`. Guarding that kind
// too would drop every real block, so closing it needs
// command-name recognition (grammar-dispatch §9) rather
// than another kind arm — FIXME(#1318). iRules carries the
// twin of all of this.
//
// Cross-walked against the sibling predicates
// (grammar-dispatch §7) and left as it was:
// `Checker::is_string` and `Alterator::alterate` both list
// `BracedWord` beside the two literal forms, so
// `bca find -f string` reports a `proc` body as a string
// literal while this arm gives it no operand. That
// disagreement predates #1354 in shape — an interpolating
// `QuotedWord` is already `Unknown` here and a string
// there — and calling a script body a string is #1318's
// role conflation rather than this arm's, so changing
// `find`'s answer is left to that issue.
_ if Self::is_subsumed_braced_word(node, ancestors, &BRACED_WORD_KINDS) => {
HalsteadType::Unknown
}
// Anonymous keyword tokens (control-flow and declaration keywords).
Tcl::Proc
| Tcl::If2
| Tcl::Elseif2
| Tcl::Else2
| Tcl::While2
| Tcl::Foreach2
| Tcl::Set2
| Tcl::Global2
| Tcl::Namespace2
| Tcl::Try2
| Tcl::Catch2
| Tcl::Finally2
| Tcl::Regexp2
| Tcl::Expr2
// String comparison operators.
| Tcl::Eq
| Tcl::Ne
| Tcl::In
| Tcl::Ni
// Structural punctuation. Only the *opening* delimiter is an
// operator (the pair folds to one glyph in
// `get_operator_id_as_str`); counting the matching closer too
// (former `RBRACE`/`RBRACK`/`RPAREN` arms) double-counted every
// balanced pair, inflating n1/N1 (#695).
// `LPAREN2` is defensive — the runtime collapses it to `LPAREN`
// before `kind_id()`, so it never fires (#768; see the Cpp note).
| Tcl::LBRACE
| Tcl::LBRACK
| Tcl::LPAREN
| Tcl::LPAREN2
| Tcl::SEMI
| Tcl::COLON
| Tcl::COLONCOLON
| Tcl::COLONCOLON2
// Arithmetic / exponent operators.
| Tcl::PLUS
| Tcl::DASH
| Tcl::STAR
| Tcl::SLASH
| Tcl::PERCENT
| Tcl::STARSTAR
// Bitwise operators.
| Tcl::AMP
| Tcl::PIPE
| Tcl::CARET
| Tcl::TILDE
| Tcl::LTLT
| Tcl::GTGT
// Comparison operators.
| Tcl::EQEQ
| Tcl::BANGEQ
| Tcl::LT
| Tcl::GT
| Tcl::LTEQ
| Tcl::GTEQ
// Logical operators.
| Tcl::BANG
| Tcl::AMPAMP
| Tcl::PIPEPIPE
// Ternary conditional operator.
| Tcl::QMARK => HalsteadType::Operator,
// The anonymous `id` token (`Id2`) is the kind the parser
// actually emits, in *both* of its positions: as a standalone
// `set` target (`set s …` → `s`, a real operand) and as the
// inner leaf of every `variable_substitution` (`$s` →
// `(variable_substitution (id "s"))`), whose wrapper is already
// the operand for the reference. A blanket `Id2` exclusion
// therefore dropped every `set` target from n2/N2 (#1294); the
// correct guard is a parent check, the same shape as iRules'
// `Id` arm. The named `Id` never surfaces at the pinned grammar
// (drift marker: `tcl_named_id_variant_is_unreachable`) and is
// listed defensively so a grammar bump that starts emitting it
// classifies it identically.
Tcl::Id | Tcl::Id2 => {
if ancestors.parent_has_kind(node, Tcl::VariableSubstitution as u16) {
HalsteadType::Unknown
} else {
HalsteadType::Operand
}
}
// Operands: identifiers and literals.
//
// `BracedWord` reaches this arm only when the guard above
// let it through, which since #1354 means only when it
// holds no command. A script kind holding commands —
// a `proc` body, an `if` body, a `catch` or `eval` script —
// was billed as one operand spanning its entire text
// *beside* the commands the walk descends into, which made
// `n2` grow with the size and uniqueness of the code rather
// than with its vocabulary; nested bodies compounded it,
// one vocabulary entry per block, each containing the next.
//
// Gated rather than deleted (grammar-dispatch §6): the same
// kind is the value slot of every command the grammar does
// not special-case, so `lappend l {}` is an empty list
// whose brace pair is its only carrier, and deleting the
// kind scored it zero where its `lappend l ""` synonym
// scores one. An empty or comment-only `proc` body is
// spelled identically and so also counts one — accepted
// rather than argued away, since no kind-scoped arm can
// separate the two roles (the same conflation FIXME(#1318)
// tracks on the operator side).
Tcl::SimpleWord
| Tcl::Number
| Tcl::BracedWord
| Tcl::BracedWordSimple
| Tcl::VariableSubstitution => HalsteadType::Operand,
// Double-quoted strings count as a single operand when inert
// (`"hello world"`). When they carry a `$var` or `[cmd]`
// interpolation child, the inner `variable_substitution` /
// `command_substitution` nodes are walked separately and
// contribute their own operands; counting the wrapping
// `QuotedWord` too would double-count `N2` (issue #277, same
// pattern as #180/#183/#184 for Bash/C#/PHP).
Tcl::QuotedWord => Self::string_operand_type(
node,
&[
Tcl::VariableSubstitution as u16,
Tcl::CommandSubstitution as u16,
],
),
_ => HalsteadType::Unknown,
}
}
get_operator!(Tcl);
}