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
#![allow(
clippy::enum_glob_use,
clippy::too_many_lines,
clippy::wildcard_imports
)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use super::cpp::{
cpp_count_arguments, cpp_count_chain_operands, cpp_count_condition_slot, cpp_count_return,
cpp_walk_for_statement, cpp_walk_ternary,
};
use super::{Abc, Stats};
use crate::*;
impl Abc for CCode {
fn compute<'a>(
node: &Node<'a>,
_code: &'a [u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) {
// bca: suppress(cyclomatic)
// Exhaustive one-arm-per-grammar-kind dispatch table; see the
// rationale on `CppCode::compute`, of which this is the
// C-grammar sibling.
use C::*;
match node.kind_id().into() {
// `assignment_expression` covers both plain `=` and every
// compound form (`+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`,
// `^=`, `<<=`, `>>=`); the grammar lifts them all into a
// single named node so we count once per
// `assignment_expression`. `update_expression` covers both
// prefix and postfix `++` / `--`.
AssignmentExpression | UpdateExpression => {
stats.assignments += 1.;
}
// `int x = expr;` parses as a `declaration` carrying an
// `init_declarator` of the form `declarator = value`. Per
// Fitzpatrick (1997), every `=` operator increments A, so we
// count every `init_declarator` whose body contains an
// explicit `=` token (`const int x = 5;` counts like
// `int x = 5;`). `int x;` parses as a plain declarator inside
// the `declaration`, not an `init_declarator`, so this arm
// never fires for un-initialised declarations.
InitDeclarator if node.first_child(|id| id == EQ as u16).is_some() => {
stats.assignments += 1.;
}
// Every call counts. The C grammar exposes two aliased
// `call_expression` ids. C has no `new` allocations, so
// (unlike the C++ impl) there is no `NewExpression` branch.
CallExpression | CallExpression2 => {
stats.branches += 1.;
}
// Comparison operators emitted as token children of a
// `binary_expression`. `else` opens an alternative branch
// path; `case` (non-default) adds one per switch arm; `?`
// opens a ternary. C has no exceptions and no `<=>`
// spaceship, so — unlike the C++ impl — there are no
// `try` / `catch` / `LTEQGT` condition arms.
//
// `&&` / `||` are deliberately NOT counted (Fitzpatrick
// Rule 7 in Figure 3; the unary-conditional
// counterpart is Rule 9). See the module-level `Stats`
// doc-comment for the cross-language policy (issue
// #395, walker tracked in #403).
//
// Unlike C++ (#1448), `<=` `>=` `==` `!=` need no parent
// gate: tree-sitter-c 0.24.2's `grammar.json` emits them
// only from `binary_expression` and
// `preproc_binary_expression`, both comparisons, and C has
// no operator overloading to declare them elsewhere.
LTEQ | GTEQ | EQEQ | BANGEQ | Else | Case | QMARK => {
stats.conditions += 1.;
}
// Plain `<` / `>` are comparison operators (C has no
// templates, so there is no template-delimiter ambiguity to
// resolve). The `binary_expression` parent check ensures only
// comparison uses count. Both kind-id aliases
// (`BinaryExpression`, `BinaryExpression2`) are accepted
// because the grammar emits the node under two
// production-rule paths.
LT | GT
if ancestors.parent(node).is_some_and(|p| {
matches!(p.kind_id().into(), BinaryExpression | BinaryExpression2)
}) =>
{
stats.conditions += 1.;
}
// Fitzpatrick Rule 9 (Figure 3): each operand of a
// `&&` / `||` chain is one condition (issue #403).
AMPAMP | PIPEPIPE => {
if let Some(parent) = ancestors.parent(node) {
cpp_count_chain_operands(&parent, &mut stats.conditions);
}
}
// Phase-2B (issue #403): condition slots, read by grammar field
// in `cpp_count_condition_slot`. The slot pays for any
// predicate no other arm counts (#1526), so `if (true)`,
// `if (-x)` and `return !x` each count one condition; bare
// `return x` reports zero.
IfStatement | WhileStatement | DoStatement => {
cpp_count_condition_slot(node, &mut stats.conditions);
}
ReturnStatement => {
cpp_count_return(node, &mut stats.conditions);
}
// `f(!a, !b)` — argument list walker. Two aliases —
// `argument_list` is emitted as ArgumentList or
// ArgumentList2 depending on production rule path.
ArgumentList | ArgumentList2 => {
cpp_count_arguments(node, &mut stats.conditions);
}
// `a ? !b : !c` — the ternary's own `?` token is already
// counted by the condition arm above; this walks the three
// operand slots (issue #1102).
ConditionalExpression => {
cpp_walk_ternary(node, &mut stats.conditions);
}
// `for (init; cond; update)` — the condition slot, read by
// grammar field (issue #1276). `for (;;)` has no condition
// field and counts nothing.
ForStatement => {
cpp_walk_for_statement(node, &mut stats.conditions);
}
_ => {}
}
}
}