pub trait Getter {
Show 19 methods
// Provided methods
fn get_func_name<'a, 'tree>(
node: &Node<'tree>,
code: &'a [u8],
ancestors: Ancestors<'tree, '_>,
) -> Option<&'a str> { ... }
fn get_func_space_name<'a, 'tree>(
node: &Node<'tree>,
code: &'a [u8],
ancestors: Ancestors<'tree, '_>,
) -> Option<&'a str> { ... }
fn get_space_kind(_node: &Node<'_>) -> SpaceKind { ... }
fn get_space_kind_with_code<'a>(
node: &Node<'a>,
_code: &[u8],
_ancestors: Ancestors<'a, '_>,
) -> SpaceKind { ... }
fn get_op_type<'a>(
_node: &Node<'a>,
_ancestors: Ancestors<'a, '_>,
) -> TokenRole { ... }
fn get_op_type_with_code<'a>(
node: &Node<'a>,
_code: &[u8],
ancestors: Ancestors<'a, '_>,
) -> TokenRole { ... }
fn get_operator_spelling<'a>(
_node: &Node<'a>,
_ancestors: Ancestors<'a, '_>,
) -> Option<&'static str> { ... }
fn get_operand_id<'a>(
node: &Node<'a>,
code: &'a [u8],
_ancestors: Ancestors<'a, '_>,
) -> &'a [u8] ⓘ { ... }
fn string_operand_type(node: &Node<'_>, interp_kinds: &[u16]) -> TokenRole { ... }
fn is_subsumed_braced_word<'a>(
node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool { ... }
fn generic_argument_command<'tree, 'chain>(
word: &Node<'tree>,
ancestors: Ancestors<'tree, 'chain>,
kinds: &BracedWordKinds,
) -> Option<(Node<'tree>, Ancestors<'tree, 'chain>)> { ... }
fn is_value_braced_word<'a>(
word: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool { ... }
fn is_braced_script_word<'a>(
node: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool { ... }
fn is_braced_literal_slot<'a>(
word: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool { ... }
fn is_switch_arm_body(word: &Node<'_>, command: &Node<'_>) -> bool { ... }
fn command_leading_word<'c>(
command: &Node<'_>,
code: &'c [u8],
kinds: &BracedWordKinds,
) -> Option<&'c str> { ... }
fn is_switch_arm<'a>(
command: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool { ... }
fn braced_word_op_type<'a>(
node: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> TokenRole { ... }
fn get_operator_id_as_str(_id: u16) -> &'static str { ... }
}Expand description
Per-language accessors that name and classify what a node is:
the function or space name, the SpaceKind a node opens, and the
TokenRole of a leaf.
Every method has a default that answers “nothing” (None,
SpaceKind::Unknown, TokenRole::Unknown); a language
overrides only the ones its grammar expresses.
Provided Methods§
Sourcefn get_func_name<'a, 'tree>(
node: &Node<'tree>,
code: &'a [u8],
ancestors: Ancestors<'tree, '_>,
) -> Option<&'a str>
fn get_func_name<'a, 'tree>( node: &Node<'tree>, code: &'a [u8], ancestors: Ancestors<'tree, '_>, ) -> Option<&'a str>
Names the function node declares. Defaults to
get_func_space_name.
Sourcefn get_func_space_name<'a, 'tree>(
node: &Node<'tree>,
code: &'a [u8],
ancestors: Ancestors<'tree, '_>,
) -> Option<&'a str>
fn get_func_space_name<'a, 'tree>( node: &Node<'tree>, code: &'a [u8], ancestors: Ancestors<'tree, '_>, ) -> Option<&'a str>
Names the space node opens.
ancestors is the chain the caller descended through. Elixir
needs it: its def / defmodule heads are ordinary Call
nodes, and one inside a quote template names no space at all,
which is a question about what encloses the call (#1088).
Sourcefn get_space_kind(_node: &Node<'_>) -> SpaceKind
fn get_space_kind(_node: &Node<'_>) -> SpaceKind
The kind of space node opens, or SpaceKind::Unknown when it
opens none.
Sourcefn get_space_kind_with_code<'a>(
node: &Node<'a>,
_code: &[u8],
_ancestors: Ancestors<'a, '_>,
) -> SpaceKind
fn get_space_kind_with_code<'a>( node: &Node<'a>, _code: &[u8], _ancestors: Ancestors<'a, '_>, ) -> SpaceKind
Source-aware variant of get_space_kind. The default
forwards to the byte-less classifier; languages whose space
kinds are encoded in macro identifier text (Elixir’s
defmodule / def / defp / defmacro / defmacrop Calls)
override this so the walker can attribute the correct
SpaceKind to each promoted func space (#275).
ancestors is the chain the caller descended through; Elixir
needs it to see whether the Call sits inside a quote
template without paying Node::parent’s O(depth) (#1084).
Sourcefn get_op_type<'a>(_node: &Node<'a>, _ancestors: Ancestors<'a, '_>) -> TokenRole
fn get_op_type<'a>(_node: &Node<'a>, _ancestors: Ancestors<'a, '_>) -> TokenRole
Classifies node as a Halstead operator, operand, or neither.
Punctuation and delimiters are vocabulary, so they classify as
operators whatever grammatical role they serve. Halstead
counts the alphabet a program is written in and has no notion of
a decision, so a < is the same vocabulary entry whether it
opens a JSX tag, brackets a Lua <const> attribute, names a C#
operator < overload, disambiguates a Kotlin super<A>, or
delimits a Perl <FH> readline. Every impl that classifies
punctuation at all bills (, {, [, ,, ;, . and : on
exactly that reading, for each glyph its language spells. Python
and Rust were the last two exceptions, closed by #1486.
A decision metric gates the same token by role, and is
supposed to disagree: ABC’s conditions counts a < only under
a binary_expression (#1274, #1275, #1280, #1297), so each of
the five constructs above scores zero there while bca ops
still reports the bracket. #1395 settled that the two answers
are both correct rather than a drift to be reconciled;
tests/parity/abc_halstead_bracket_parity.rs fails if a later
consistency pass flips either side.
The one suppression that reading admits is a literal’s own delimiter. There the punctuation is not vocabulary in its own right — the literal it wraps is already counted, so billing its quotes fabricates an arithmetic or comparison operator the source never spelled, and the score moves with the author’s choice of delimiter. It takes two shapes, and only the first is an arm of this method.
Nine arms suppress a delimiter whose enclosing literal node is
itself the operand, each guarded on the delimiter’s parent
being that literal: Elixir’s sigil (#1256), the regex / of the
JS family (#1314), of Perl and of Ruby (#1312), Ruby’s subshell
backtick (#1360), Groovy’s slashy string and the raw-string (
of both C++ grammars (#1314), and Objective-C’s @ before an
NSString literal.
The tenth is braced_word_op_type, which Tcl and iRules route
their { through. A braced value is a literal to
Checker::is_string_with_code and to the dump, so its opener
is withdrawn for the same reason — puts {c d} and
puts "c d" have to agree on the operator column — but the
operands are the words inside it rather than the braced word
itself, so the parent test above does not describe it. #1318
established that arm and #1382 widened it to the value slots of
a script-taking command.
A bracket delimiting syntax fits neither shape: no literal
would absorb its contribution, so it stays an operator. A
generic list’s < / > is billed like the other bracket pairs,
once as <> on the opener (Self::get_operator_spelling, #1559),
which leaves the comparison < and > their own entries.
ancestors is the chain the walker descended through. Most
impls read a parent from it to disambiguate a token whose role
depends on what encloses it — the nine literal-delimiter arms
above, plus, among others, Python’s not / in / is inside
the compound not in / is not, Rust’s || and ! inside a
binary expression rather than a doc-comment marker, the
namespace identifier in both C++ grammars, Bash’s $name, the
name inside a Tcl or iRules variable substitution, and Groovy’s
super in a wildcard bound. Reaching those parents with
Node::parent instead costs O(depth) per node (#1096).
Sourcefn get_op_type_with_code<'a>(
node: &Node<'a>,
_code: &[u8],
ancestors: Ancestors<'a, '_>,
) -> TokenRole
fn get_op_type_with_code<'a>( node: &Node<'a>, _code: &[u8], ancestors: Ancestors<'a, '_>, ) -> TokenRole
Source-aware variant of get_op_type. The default forwards
to the byte-less classifier; languages whose Halstead operand
classification depends on token text override this. Kotlin uses
it to recover the variable in a short-form string template
("Hi $name"), which the grammar emits as bare string_content
tokens with no structured interpolation node — the distinction
between an interpolated $name and a literal $5 is only
visible in the source bytes (#454).
Sourcefn get_operator_spelling<'a>(
_node: &Node<'a>,
_ancestors: Ancestors<'a, '_>,
) -> Option<&'static str>
fn get_operator_spelling<'a>( _node: &Node<'a>, _ancestors: Ancestors<'a, '_>, ) -> Option<&'static str>
The fixed spelling an operator node is keyed under in place of
its kind id, for a token whose kind the grammar shares with a
different operator; None keys it by kind. A generic list’s <
is the case (#1559): it has the comparison’s kind but is keyed as
the "<>" pair, as (), [] and {} are. Consulted only for a
node get_op_type_with_code calls
an operator.
Sourcefn get_operand_id<'a>(
node: &Node<'a>,
code: &'a [u8],
_ancestors: Ancestors<'a, '_>,
) -> &'a [u8] ⓘ
fn get_operand_id<'a>( node: &Node<'a>, code: &'a [u8], _ancestors: Ancestors<'a, '_>, ) -> &'a [u8] ⓘ
Returns the source-byte slice used to key a Halstead operand.
The default keys on the operand node’s full byte range. Kotlin
overrides this to narrow a short-interpolation name token
($name) to its leading identifier prefix, because the grammar
glues trailing inter-segment text onto the name token
("$a $b" → "a "); keying the raw bytes would record a
distinct "a " operand and break parity with the long ${a}
form (#454).
Sourcefn string_operand_type(node: &Node<'_>, interp_kinds: &[u16]) -> TokenRole
fn string_operand_type(node: &Node<'_>, interp_kinds: &[u16]) -> TokenRole
Classifies a string-literal node as a single Halstead
operand, unless it wraps an interpolation child drawn from
interp_kinds — in which case the wrapper yields
TokenRole::Unknown because the inner expressions are
walked and counted separately. Counting the wrapper too would
double-count their contribution to N2.
This declares the per-language interpolation skip once (issue #420), replacing nine independently-added regression fixes (#183 / #184 / #191 / #192 / #199 / #277, …). Each language supplies only its own grammar’s interpolation child-kind ids; the per-call rationale lives at each call site.
Sourcefn is_subsumed_braced_word<'a>(
node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool
fn is_subsumed_braced_word<'a>( node: &Node<'a>, ancestors: Ancestors<'a, '_>, kinds: &BracedWordKinds, ) -> bool
Whether node is a fragment of a Tcl-family braced word rather
than a token of its own, and so contributes no operand (#1354).
The two dialects are deliberate clones, so the rule is declared
once and instantiated with each grammar’s [BracedWordKinds];
the per-call rationale lives at each call site. It answers yes
in two cases:
nodesits directly inside a braced value (braced_word_simple). Tcl evaluates nothing between braces, so the value is its whole span and the parts inside it are letters — the wrapper carries the one operand. This subsumes #1314’s narrower guard on the opening{, which is one more such child.nodeis a braced script (braced_word) holding at least one command — a named child other than a comment. Commands are what the walk descends into and counts in their own right, so billing the block as well would count its whole text a second time. A comment is named too, but no arm classifies it, so a body holding only comments is billed like an empty one rather than like nothing at all.
A script with no command is deliberately not subsumed: the
same kind serves as the value slot of every command the grammar
does not special-case, where lappend l {} is an empty list and
the brace pair is its only carrier (grammar-dispatch §6). An
empty or comment-only proc body is spelled identically and so
also scores one operand, its whole text. #1318 can now tell the
two apart by the enclosing command, but it bills them alike, so
this arm needs no help from it.
This is the byte-less half of the braced-word rule and it is
not the whole of it. braced_word_op_type suppresses the {
of a script-kind word the enclosing command shows to be a plain
value, and both Tcl-family getters reach it through
get_op_type_with_code, which is what the walk calls
(grammar-dispatch §7). It revises no operand, so this arm’s
answers stand unchanged.
Sourcefn generic_argument_command<'tree, 'chain>(
word: &Node<'tree>,
ancestors: Ancestors<'tree, 'chain>,
kinds: &BracedWordKinds,
) -> Option<(Node<'tree>, Ancestors<'tree, 'chain>)>
fn generic_argument_command<'tree, 'chain>( word: &Node<'tree>, ancestors: Ancestors<'tree, 'chain>, kinds: &BracedWordKinds, ) -> Option<(Node<'tree>, Ancestors<'tree, 'chain>)>
The generic command node word is an argument of, paired with
that command’s own ancestry — or None when word instead
fills a slot of a construct the grammar models, and so is a
script by construction.
Both dialects hang a generic command’s arguments off a
word_list and a modelled construct’s slots off that
construct’s own node (procedure, if, else, elseif,
while, foreach, catch, try, finally, argument, plus
iRules’ when_event, for, switch_arm, on_handler,
trap_handler and the three dict loops). Asking the parent
kind therefore answers “is this a generic argument” without
enumerating the modelled parents — a list that would be a
coverage claim to re-derive on every grammar bump
(grammar-dispatch §1), and that a grammar gaining one more
modelled construct would silently invalidate.
A word_list whose own parent is not a command is
namespace’s argument list — the one modelled construct in
either grammar that reaches its body through a word_list. The
grandparent test answers None there, so namespace eval ns {…} keeps its body a script.
A braced word directly under a command is the command name
({puts} hi), not an argument, and also answers None: its
caller decides that case before asking, because a name is
always a literal and must not pick up the switch-arm rescue
below.
Sourcefn is_value_braced_word<'a>(
word: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool
fn is_value_braced_word<'a>( word: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, kinds: &BracedWordKinds, ) -> bool
Whether a braced script-kind word is really a plain value —
the literal {a b} of lappend x {a b} rather than the block
of eval {…} (#1318).
The two roles share one kind, so no kind-scoped arm can separate
them; recognition is out-of-band, by the enclosing command’s
leading word (grammar-dispatch §9). A word that fills a modelled
construct’s slot is a script; a word passed to a command in
SCRIPT_TAKING_COMMANDS is a script; everything else is a
value.
That default is the load-bearing choice, and it is deliberately the opposite of “keep today’s answer for anything unrecognised”:
- The script-taking set is closed and small — Tcl defines no
user-extensible control structures, so a command that
evaluates a braced argument is either a core command listed
above or a proc that forwards to
eval/uplevel. The value-taking set is open: every user proc and every package command that takes a list or a pattern is in it. An unrecognised name is therefore far likelier to be value-taking. - Defaulting to script fabricates — it reports a
{}operator for a block the source does not contain. Defaulting to value can only omit: oneN1occurrence per script handed to an unlisted command, and — when no modelled construct in the same space opens a block — the{}vocabulary entry with them. That last case is where the cost shows: a top-leveldict for {k v} $d { puts $k }with nothing around it has no operator left, and Halstead’s difficulty is a product withn1in it, soeffortreads0.0rather than a little low. Inside aproctheprockeyword and body brace keep the space non-zero, which is the scopehalstead.effortis gated at; and the same0.0was already the answer for a file ofset x {a b}lines, so it is Halstead’s shape on an operator-free space, not a new failure mode. The omission stays bounded only because the answer is scoped to the brace: seebraced_word_op_typefor the measurement that decided it, and for why suppressing the contents of a value would have made it unbounded instead. - It stops the score moving with the author’s choice of
delimiter, which is what #695, #1312 and #1314 each restored
elsewhere:
puts {c d}andputs "c d"now agree on the operator column.
The cost is a script passed to an unlisted command — a Tk
-command {…} callback, trace add variable v w {…}, a
subcommand dispatch (dict for, interp eval), a user-defined
with_lock {…} — losing the {} its block deserves. No
structural signal distinguishes those cases: the grammar parses
{a b} and {puts hi} into the same shape. The code inside
them is still counted, so the loss is one operator occurrence
per such block, plus the vocabulary entry in the case above.
Sourcefn is_braced_script_word<'a>(
node: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool
fn is_braced_script_word<'a>( node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, kinds: &BracedWordKinds, ) -> bool
Whether node is a Tcl-family braced script — a proc or if
body, an iRules when handler — rather than a braced literal
(#1381).
The positive form of is_value_braced_word, narrowed to the
script kind so it answers false for every node of every other
kind and for the two literal spellings, and narrowed again by the
value slots is_braced_literal_slot recognises. That makes it
the question the non-Halstead classifiers ask:
Checker::is_string_with_code must not call a proc body a
string literal, and Alterator::keeps_children must stop the dump
flattening one into a leaf, which dropped the body from it. Both
once listed braced_word beside quoted_word and
braced_word_simple, which is right for the literal role and
wrong for the script role the same kind also serves.
Stated here rather than in each of the five call sites so the
string, dump and Halstead classifiers cannot drift apart on the
same bytes (grammar-dispatch §7). Halstead was the deliberate
exception between #1318 and #1382: braced_word_op_type asked
is_value_braced_word alone, so the braces of the value slots
is_braced_literal_slot adds still billed a {} operator
while the other two called the same bytes a literal. #1382
closed it — that call site now asks this predicate — so a braced
value is a value to all three, and after {100} {puts hi} bills
the one {} its {puts hi} block earns rather than two.
The remaining §7 note is one of scope, not of disagreement: the
three differ only where Halstead’s operand half keys on
whether a body holds a command, which makes an empty or
comment-only proc body an operand there and not a string here.
Sourcefn is_braced_literal_slot<'a>(
word: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool
fn is_braced_literal_slot<'a>( word: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, kinds: &BracedWordKinds, ) -> bool
Whether word, a braced word is_value_braced_word calls a
script, fills a slot whose documented syntax takes a value.
is_value_braced_word answers by command name alone. Most of the
names it recognises have a mixed signature, though, so that answer
made a value — a millisecond count, a namespace name, a switch
subject — a script (#1381 review). This
is the per-argument half: proc has one value slot the grammar
names as a field, and every other construct declares its layout in
one of the two tables in lang_helpers::tcl_family, which
fills_script_slot applies.
Each value here was a string and a flat dump leaf before #1381, and
would otherwise have become a script under it: the dump rendered
{my proc} as a command named my and {100} as one named 100,
and namespace export {…} and namespace ensemble create -map {…}
both occur in the Tcl 8.6 standard library. Since #1382 Halstead
reads the same answer through is_braced_script_word, so the
braces of these values quote rather than open (after {100} {…}
scores the {} of its script and no other).
Two guards keep the construct-wide answer. A switch arm list parses
as commands, so an arm whose pattern is spelled proc,
namespace, after or switch builds one of these shapes around
what are really arm bodies — is_switch_arm recognises it first.
And an argument list whose own slot sequence is unreadable has no
positions worth trusting.
That second guard is tcl_family::argument_slots_are_readable,
which asks the slots rather than the owner — asking
Node::has_error of the command withdrew every value slot of a
command whose only error sat inside one of its script arguments.
Its doc carries the measurement. A list longer than the
signature admits is the second way to be unreadable, and
tcl_family::ScriptSlots::LastOf is where that one lives.
A try … trap clause is out of reach only where the grammar
wraps it: inside a proc or a namespace eval body the Tcl
grammar recovers into an ERROR node and no role signal
survives, but at the top level the same clause parses as a plain
trap command whose slots this does read. An earlier revision
of this paragraph said “the multi-line try … trap clause” was
out of reach outright, which is where the finally defect
LastOf now guards hid (#1382 review).
Sourcefn is_switch_arm_body(word: &Node<'_>, command: &Node<'_>) -> bool
fn is_switch_arm_body(word: &Node<'_>, command: &Node<'_>) -> bool
Whether word, an argument of a switch arm command
(is_switch_arm), sits in a body position rather than a
pattern position.
The arm list is a flat run of pattern body pattern body …,
and the grammar breaks it into commands at newlines. One arm per
line gives each command a pattern for its name and a body for
its sole argument; several arms on one line give the first
pattern’s command the whole run as arguments, so the arguments
alternate body pattern body …. The command-name test above
keeps the first braced pattern a literal, but without this
gate every later one was rescued as a body, and a one-line
switch -regexp $v { {^a} {…} {^b} {…} } fabricated a {}
around {^b} that the same arms written one per line did not —
the score moving with layout, which is what the rule exists to
stop.
Position is the only signal because it is what Tcl itself uses:
switch pairs the list up by index, with no marker on either
half. The parity holds across the words that can interpose — a
- fall-through body and a default pattern are both
simple_words and take a slot each — so an even index is a body
and an odd one a pattern. This is the one place the rule needs a
sibling index rather than a parent kind, and it runs once per
braced argument of such a command.
The index comes from Cursor::goto_first_child_for_byte, which
is O(log n) in the argument count. A children().position(..)
scan answers identically in O(n), and since every braced word
of a one-line arm list asks, that made the Halstead walk — and,
once #1381 routed them through this rule, find / count --type string and the Ast dump — quadratic in the width of
one line: seconds per request at tens of KB (#1381 review). The
id check keeps the scan’s answer for a word that is not one of
the command’s arguments at all.
Sourcefn command_leading_word<'c>(
command: &Node<'_>,
code: &'c [u8],
kinds: &BracedWordKinds,
) -> Option<&'c str>
fn command_leading_word<'c>( command: &Node<'_>, code: &'c [u8], kinds: &BracedWordKinds, ) -> Option<&'c str>
A command’s leading word, when it is a statically resolvable
simple_word.
Located by field rather than by index (grammar-dispatch §3): the
name is command’s name field in both grammars. A computed
name ($cmd {…}, [pick] {…}) parses as a substitution node and
is not resolvable at all, and a name spelled in quotes or braces
("eval" {…}) is legal Tcl that this deliberately leaves
unresolved — the same limitation tcl_command_name records for
the Cognitive and Cyclomatic walkers.
Sourcefn is_switch_arm<'a>(
command: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> bool
fn is_switch_arm<'a>( command: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, kinds: &BracedWordKinds, ) -> bool
Whether command is really one pattern body pair of a Tcl
switch arm list rather than a command of its own.
Tcl models no switch, so switch $v {a {…} b {…}} parses the
arm list as a braced word whose interior is a command named
after the first pattern — a here — with the arm bodies as
its arguments (grammar-dispatch §9; the Cognitive walker reads
the same shape through tcl_switch_arm_list). Without this
test the bodies would read as literals passed to a command
called a, which is exactly what the value default is for
everywhere else. iRules models switch with switch_arm
children, so its arm bodies never reach here.
Sourcefn braced_word_op_type<'a>(
node: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
kinds: &BracedWordKinds,
) -> TokenRole
fn braced_word_op_type<'a>( node: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>, kinds: &BracedWordKinds, ) -> TokenRole
Whether the { of node’s parent braced word opens a block,
or merely quotes a literal (#1318).
This is #1314’s guard with its kind test replaced by a role
test. #1314 suppressed the opener of a braced_word_simple,
the literal form the grammars emit only in the value slots they
special-case; everywhere else a literal is a braced_word, the
same kind a block uses, and its { still reported a {}
operator for a block the source does not contain —
lappend x {a b}, puts {c d}, and every user proc taking a
list.
The role is is_braced_script_word, the same predicate
Checker::is_string_with_code and Alterator::keeps_children
ask, so the three classifiers answer one question about one set
of bytes (grammar-dispatch §7). That was not so between #1318
and #1382: this call site asked is_value_braced_word alone
and billed a {} for the value slots of a script-taking
command, which the other two had called literals since #1381.
The exception, and what closing it moved, is recorded on
is_braced_script_word, which owns the §7 story for all five
call sites.
It revises the operator only, and deliberately leaves the
words inside a value alone. Suppressing them too would make
lappend x {a b} score like its set x {a b} synonym, which
is the tidier answer for that line — and the wrong one in
general, because the same braced argument of an unrecognised
command is just as often real code: an oo::class create C {…}
body, a tcltest -body {…}, an apply {{x} {…}} lambda.
Suppressing contents was built and measured on a file holding
one of each; it took n1 5 → 2, N1 8 → 2, n2 25 → 15 and
N2 32 → 15, the class body, the test body and the lambda each
collapsing into a single operand, and halstead.effort — a
gated threshold metric — collapsing with them. So the rule only
ever withdraws a claim the classifier cannot support; it never
discards code the walk has already read.
The residual is the asymmetry #1382 opens with, and it is the
contract, not an outstanding defect: a braced value scores
one operand where the grammar names the command (set x {a b})
and one per word where only the command name would
(lappend x {a b}). #1382 weighed the two ways of closing it
and took neither. Reading the contents — no substitution, no
nested block, therefore list-like — is the measurement above,
already taken and already rejected. Widening the one-operand
treatment to a value-taking command list needs the mirror of
SCRIPT_TAKING_COMMANDS, and that set is open where this one is
closed: every user proc is in it, so the list is incomplete by
construction and would leave three behaviours where there are
two. Both trade a bounded over-report of the operand vocabulary
for an unbounded loss of real code, which is the trade the
paragraph above declines.
Keeping to the operator also keeps the whole thing cheap: only
a braced word’s own opener can change answer, so the test is one
kind comparison and one parent lookup, the same scope #1354 and
#1314 use. Two lookups are O(log n) rather than O(1), both
cursor seeks by byte and both reached only from a command whose
name resolves: is_switch_arm_body needs a switch arm
word’s index among its command’s arguments, and — since #1382 —
is_braced_literal_slot needs the same index to place a word
against the layout lang_helpers::tcl_family records. An
ancestor scan would have been O(depth) per node and quadratic
on a deeply nested expr, the shape #1122 warns about.
Sourcefn get_operator_id_as_str(_id: u16) -> &'static str
fn get_operator_id_as_str(_id: u16) -> &'static str
The grammar’s name for the operator kind id, used to render a
Halstead operator; empty for languages that do not report them.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".