API — ppbcc.code_complexity

Contents

API — ppbcc.code_complexity#

Module Overview#

Halstead complexity and LOC metrics for C++ and GPU-enriched C++.

The package analyses C++ sources (and the C-like kernel/shading languages used by GPU paradigms) and computes the Halstead complexity measures as well as line-based size metrics. Constructs of GPU programming models such as OpenMP, OpenACC, Kokkos, RAJA, Alpaka, CUDA, HIP, SYCL, OpenCL, Vulkan, Boost.Compute, WebGPU/WGSL, GLSL, Slang and Metal are recognised and counted as dialect operators, which allows separating the baseline C++ complexity from the share added by a paradigm.

Typical usage:

>>> from ppbcc.code_complexity import evaluate
>>> frame = evaluate([Path("src/")], language_dialect="kokkos",
...                  metrics=["halstead", "loc"], diff=True)

Analysis Pipeline#

Top-level analysis pipeline: from source paths to the metric DataFrame.

ppbcc.code_complexity.evaluate.AGGREGATE_ROW_NAME: str = 'TOTAL'#

Name used in the file column of the aggregation row.

ppbcc.code_complexity.evaluate.collect_source_files(sources)[source]#

Expands the given paths into a flat list of source files.

Parameters:

sources (list[Path]) – Files and/or directories. Directories are searched recursively for files with an extension in ppbcc.code_complexity.config.SOURCE_EXTENSIONS; explicitly listed files are taken as-is.

Returns:

Sorted list of unique source files.

Raises:

FileNotFoundError – If one of the paths does not exist.

Return type:

list[Path]

ppbcc.code_complexity.evaluate.analyze_source(code, path, keywords, registry, dialects)[source]#

Analyses one source text.

Parameters:
  • code (str) – Raw source text.

  • path (Path | None) – Path of the file (used for logging and auto-detection); may be None for in-memory analysis.

  • keywords (CppKeywords) – Baseline C++ keyword sets.

  • registry (DialectRegistry) – The dialect registry.

  • dialects (list[DialectSpec] | None) – Active dialects, or None to auto-detect them per file.

Returns:

Tuple of the classified token counts, the line metrics and the names of the active dialects.

Return type:

tuple[TokenCounts, LineMetrics, list[str]]

ppbcc.code_complexity.evaluate.evaluate(sources, language_dialect='auto', metrics=None, *, diff=False, aggregate=False, output=None, csv_separator=',', keywords_path=None, dialects_path=None, exclude_macros=None, exclude_headers=None)[source]#

Runs the complexity analysis - the library’s top-level entry point.

Parameters:
  • sources (list[Path]) – Source files and/or directories to analyse.

  • language_dialect (str) – Dialect selection: "auto" (default) detects the dialects per file, "cpp"/"none" analyses plain C++, and any dialect name/alias (optionally comma-separated, e.g. "kokkos,openmp") forces those dialects for all files.

  • metrics (list[str] | None) – Metric names/groups to include as columns (e.g. ["halstead_effort", "loc"]); None selects all metrics.

  • diff (bool) – If True, add baseline_/delta_ columns comparing the full metrics against the metrics without dialect tokens.

  • aggregate (bool) – If True, append a TOTAL row aggregating all files (operator/operand multisets are merged before recomputing the Halstead measures, so distinct counts are program-wide).

  • output (Path | None) – Optional CSV destination; written when given.

  • csv_separator (str) – Field separator for the CSV output.

  • keywords_path (Path | None) – Optional override for the packaged cpp_keywords.toml.

  • dialects_path (Path | None) – Optional override for the packaged dialects.toml.

  • exclude_macros (list[str] | None) – Regular expressions for macro names whose invocations and conditionals are removed before the analysis, e.g. ["PPB_MARKER_\w+"] (see ppbcc.code_complexity.exclude).

  • exclude_headers (list[str] | None) – Glob patterns for headers which are neither analysed nor counted where they are included, e.g. ["common/Marker.h"].

Returns:

DataFrame with one row per source file (plus the optional TOTAL row) and the selected metric columns.

Raises:
  • FileNotFoundError – If a source path does not exist.

  • KeyError – If language_dialect names an unknown dialect.

  • ValueError – If metrics contains an unknown metric name, or a macro pattern is not a valid regular expression.

Return type:

DataFrame

Exclusions#

Removal of code that should not count towards an implementation’s complexity.

A benchmark carries code that is not part of the algorithm it implements, most prominently profiler instrumentation: region markers around the kernels, and headers which define them. Such code would be counted as if every implementation had written it, so it is removed from the source text before the analysis, which then sees the program as if the instrumentation had never been added.

Two kinds of exclusion are supported:

  • Macros, given as regular expressions matched against the whole macro name.

    • An invocation of such a macro is removed together with its argument list and a directly following ;, e.g. PPB_MARKER_GPU_SCOPE("evaluate");.

    • A conditional on such a macro (#ifdef, #ifndef, #if defined(...), #if !defined(...), #if NAME) is resolved as if the macro was undefined: the branch the preprocessor would drop is removed with the directives, the branch it would keep stays. A conditional which combines the macro with other conditions is left untouched, with a warning.

  • Headers, given as glob patterns (e.g. common/Marker.h) matched against the spelling of an #include and against the tail of a file path. The #include directive is removed, and is_excluded_file() tells callers which files to leave out altogether.

A line which held nothing but removed code (and possibly a comment) is removed entirely, so the line metrics shrink along with the Halstead counts.

class ppbcc.code_complexity.exclude.Exclusions(macros=(), headers=())[source]#

Bases: object

What to remove from the analysed code.

Variables:
  • macros (tuple[re.Pattern[str], ...]) – Compiled patterns; a macro is excluded if one matches its whole name.

  • headers (tuple[str, ...]) – Glob patterns for excluded headers.

Parameters:
macros: tuple[Pattern[str], ...] = ()#
headers: tuple[str, ...] = ()#
classmethod create(macros=None, headers=None)[source]#

Builds the exclusions from their textual patterns.

Parameters:
  • macros (Iterable[str] | None) – Regular expressions for macro names, e.g. PPB_MARKER_\w+.

  • headers (Iterable[str] | None) – Glob patterns for headers, e.g. common/Marker.h.

Returns:

The exclusions.

Raises:

ValueError – If a macro pattern is not a valid regular expression.

Return type:

Exclusions

is_excluded_macro(name)[source]#

Whether name is one of the excluded macros.

Parameters:

name (str)

Return type:

bool

is_excluded_header(spelling)[source]#

Whether an #include of spelling includes an excluded header.

Parameters:

spelling (str)

Return type:

bool

ppbcc.code_complexity.exclude.is_excluded_file(path, exclusions)[source]#

Whether path is an excluded header.

A pattern matches the whole path or any trailing part of it, so common/Marker.h matches /repo/src/common/Marker.h.

Parameters:
  • path (Path) – The file in question.

  • exclusions (Exclusions) – The exclusions in effect.

Returns:

True if the file is to be left out of the analysis.

Return type:

bool

ppbcc.code_complexity.exclude.strip_excluded(code, exclusions, name='<string>')[source]#

Removes the excluded macros and headers from a source text.

Parameters:
  • code (str) – Raw source text.

  • exclusions (Exclusions) – What to remove.

  • name (str) – Name of the source, used in warnings.

Returns:

The source text without the excluded code; the text itself if there is nothing to exclude.

Return type:

str

Tokenizer#

A lightweight lexer for C++ and C-like GPU/shading languages.

The lexer splits source text into Token objects (identifiers, numbers, literals, punctuation, comments and preprocessor directives). It is deliberately not a full C++ parser: it is precise enough for Halstead counting and line classification, and it degrades gracefully on the C-like shading languages (OpenCL C, GLSL, WGSL, Slang, Metal).

Special handling:

  • Comments and string/character literals are matched first, so their content never produces spurious tokens.

  • #include <header> is normalised so that the header name becomes a single (operand) token.

  • #pragma lines are tracked: every token of a pragma line carries the pragma’s first word (e.g. "omp") in Token.pragma, which lets the classifier attribute OpenMP/OpenACC directives to their dialect.

  • Backslash line continuations inside pragma lines are honoured.

class ppbcc.code_complexity.tokenizer.TokenKind(*values)[source]#

Bases: Enum

Lexical category of a token.

COMMENT = 1#
STRING = 2#
CHAR = 3#
NUMBER = 4#
IDENT = 5#
PUNCT = 6#
DIRECTIVE = 7#
class ppbcc.code_complexity.tokenizer.Token(kind, text, line, end_line=0, pragma=None, start=-1, end=-1)[source]#

Bases: object

A single lexical token.

Variables:
  • kind (ppbcc.code_complexity.tokenizer.TokenKind) – Lexical category.

  • text (str) – Exact source text (directives are normalised to #name).

  • line (int) – 1-based line number of the token’s first character.

  • end_line (int) – 1-based line number of the token’s last character (differs from line only for multi-line comments/raw strings).

  • pragma (str | None) – First word of the surrounding #pragma line (e.g. "omp") if the token is part of one, otherwise None.

  • start (int) – Offset of the token’s first character in the source text (for a directive, the offset of its #), or -1 for a token built by hand. Not part of the token’s equality.

  • end (int) – Offset one past the token’s last character, or -1. Not part of the token’s equality.

Parameters:
kind: TokenKind#
text: str#
line: int#
end_line: int#
pragma: str | None#
start: int#
end: int#
ppbcc.code_complexity.tokenizer.CLOSING_BRACKETS: frozenset[str] = frozenset({')', ']', '}'})#

lexed, but skipped by the classifier because they pair with their (counted) opening bracket.

Type:

Closing brackets

ppbcc.code_complexity.tokenizer.KERNEL_LAUNCH_SPLIT: dict[str, tuple[str, ...]] = {'<<<': ('<<', '<'), '>>>': ('>>', '>')}#

Kernel-launch punctuation with its baseline C++ re-interpretation, used when no CUDA/HIP dialect is active.

ppbcc.code_complexity.tokenizer.normalize_includes(code)[source]#

Rewrites #include <header> to #include "header".

The angle-bracket form would otherwise be lexed as comparison operators and identifiers; the string form yields a single operand token for the header name. The replacement preserves the text length and line numbers.

Parameters:

code (str) – Raw source text.

Returns:

The source text with angle-bracket includes converted to the quoted form.

Return type:

str

ppbcc.code_complexity.tokenizer.tokenize(code)[source]#

Lexes source text into tokens.

Parameters:

code (str) – Raw source text of a C++/C-like translation unit.

Returns:

All tokens including comments (whitespace is dropped). Tokens inside #pragma lines carry the pragma’s first word in Token.pragma.

Return type:

list[Token]

Token Classification#

Classification of lexer tokens into Halstead operators and operands.

The classifier consumes the token stream produced by ppbcc.code_complexity.tokenizer and sorts every countable token into one of four multisets:

  • operators – baseline C++ operators: keywords, punctuation, preprocessor directives.

  • operands – baseline C++ operands: identifiers, literals.

  • dialect operators – constructs contributed by an active GPU dialect: dialect keywords (__global__), qualified names under a dialect namespace (Kokkos::parallel_for counts as one operator), dialect pragmas and their clauses, kernel-launch punctuation, …

  • dialect operands – values that only occur inside dialect constructs (e.g. the variables listed in an OpenMP map(...) clause).

Counting conventions (documented here once, applied consistently):

  • Closing brackets ), ], } pair with their opening bracket and are not counted separately.

  • A qualified name that does not belong to a dialect is counted classically as its identifier segments (operands) joined by :: operators.

  • true, false, nullptr and this count as operands.

  • Namespace aliases (namespace bc = boost::compute;) are resolved before dialect matching, so bc::vector is recognised as Boost.Compute.

class ppbcc.code_complexity.classification.TokenCounts(operators=<factory>, operands=<factory>, dialect_operators=<factory>, dialect_operands=<factory>)[source]#

Bases: object

Multisets of classified tokens of one translation unit.

Variables:
Parameters:
operators: Counter[str]#
operands: Counter[str]#
dialect_operators: Counter[str]#
dialect_operands: Counter[str]#
merge(other)[source]#

Adds another unit’s counts in place (used for aggregation).

Parameters:

other (TokenCounts) – Counts to merge into this instance.

Return type:

None

property full_operators: Counter[str]#

Baseline plus dialect operators (the “full” program view).

property full_operands: Counter[str]#

Baseline plus dialect operands (the “full” program view).

ppbcc.code_complexity.classification.extract_namespace_aliases(code)[source]#

Extracts C++ namespace alias definitions from source text.

Parameters:

code (str) – Raw source text.

Returns:

Mapping from alias name to the target namespace split into segments, e.g. {"bc": ("boost", "compute")}.

Return type:

dict[str, tuple[str, …]]

class ppbcc.code_complexity.classification.TokenClassifier(keywords, dialects, namespace_aliases=None)[source]#

Bases: object

Classifies a token stream for a fixed set of active dialects.

Parameters:
  • keywords (CppKeywords) – Baseline C++ keyword sets.

  • dialects (list[DialectSpec]) – Active dialect specifications; their constructs are counted as dialect operators. Pass an empty list for plain C++.

  • namespace_aliases (dict[str, tuple[str, ...]] | None) – Namespace aliases of the translation unit, as returned by extract_namespace_aliases().

classify(tokens)[source]#

Classifies all tokens into operator/operand multisets.

Parameters:

tokens (list[Token]) – Token stream of one translation unit (comments included; they are skipped here).

Returns:

The resulting TokenCounts.

Return type:

TokenCounts

Dialect Detection#

Automatic detection of the GPU dialects used by a source file.

When language_dialect="auto" is requested, every file is scanned for strong dialect signals; a dialect is activated for the file if any of the following matches:

  • the file extension is dialect-specific (.cu, .hip, .cl, …),

  • an #include matches one of the dialect’s header patterns,

  • a #pragma line starts with one of the dialect’s pragma prefixes,

  • one of the dialect’s namespaces is used (Kokkos::, also via a namespace alias), or

  • at least ppbcc.code_complexity.config.DETECTION_MIN_HITS occurrences of the dialect’s weaker detect_patterns are found.

A dialect detected only via detect_patterns is suppressed when a sibling dialect sharing its keywords (e.g. CUDA and HIP both define threadIdx) was detected with one of the stronger signals.

ppbcc.code_complexity.detection.extract_includes(code)[source]#

Extracts all included header names from source text.

Parameters:

code (str) – Raw source text.

Returns:

Header names as written between <> or "".

Return type:

list[str]

ppbcc.code_complexity.detection.detect_dialects(code, path, registry)[source]#

Detects the dialects used by one source file.

Parameters:
  • code (str) – Raw source text.

  • path (Path | None) – Path of the file (used for extension matching); may be None.

  • registry (DialectRegistry) – The dialect registry to match against.

Returns:

The detected dialect specifications, in registry order (possibly empty for plain C++ code).

Return type:

list[DialectSpec]

Halstead Metrics#

Computation of the Halstead complexity measures.

Given the operator/operand multisets produced by ppbcc.code_complexity.classification, this module derives the classic Halstead metrics (Maurice Halstead, Elements of Software Science, 1977):

Measure

Formula

Vocabulary n

n1 + n2

Length N

N1 + N2

Calculated length N^

n1*log2(n1) + n2*log2(n2)

Volume V

N * log2(n)

Difficulty D

(n1 / 2) * (N2 / n2)

Effort E

D * V

Time T

E / 18 seconds

Delivered bugs B

V / 3000

Program level L

1 / D

Language level lambda

L^2 * V

with n1/n2 the number of distinct operators/operands and N1/N2 their total number of occurrences.

class ppbcc.code_complexity.halstead.HalsteadMetrics(distinct_operators, distinct_operands, total_operators, total_operands)[source]#

Bases: object

The Halstead base counts and derived measures of one program.

Variables:
  • distinct_operators (int) – Number of distinct operators n1.

  • distinct_operands (int) – Number of distinct operands n2.

  • total_operators (int) – Total operator occurrences N1.

  • total_operands (int) – Total operand occurrences N2.

Parameters:
  • distinct_operators (int)

  • distinct_operands (int)

  • total_operators (int)

  • total_operands (int)

distinct_operators: int#
distinct_operands: int#
total_operators: int#
total_operands: int#
classmethod from_counts(operators, operands)[source]#

Builds the metrics from operator/operand multisets.

Parameters:
  • operators (Counter[str]) – Operator occurrences keyed by operator text.

  • operands (Counter[str]) – Operand occurrences keyed by operand text.

Returns:

The corresponding HalsteadMetrics.

Return type:

HalsteadMetrics

property vocabulary: int#

Program vocabulary n = n1 + n2.

property length: int#

Program length N = N1 + N2.

property calculated_length: float#

Estimated program length N^ = n1*log2(n1) + n2*log2(n2).

property volume: float#

Program volume V = N * log2(n) in bits.

property difficulty: float#

Program difficulty D = (n1 / 2) * (N2 / n2).

property effort: float#

Programming effort E = D * V in elementary mental discriminations.

property time_seconds: float#

Estimated implementation time T = E / 18 in seconds.

property delivered_bugs: float#

Estimated number of delivered bugs B = V / 3000.

property program_level: float#

Program level L = 1 / D (1 is the most abstract program).

property language_level: float#

Language level lambda = L^2 * V.

as_dict()[source]#

Serialises all base counts and derived measures.

Returns:

Mapping from metric name (matching the CSV column names) to its value, in a stable order.

Return type:

dict[str, int | float]

Line Metrics#

Line-based size metrics (LOC/SLOC/comment/blank lines).

The classification is derived from the token stream so that multi-line comments, comments behind code and string literals containing // are all handled correctly:

  • LOC – total number of physical lines.

  • SLOC – lines containing at least one code token (a line with code and a trailing comment counts as source, not comment).

  • comment lines – lines whose only content is comment text.

  • blank lines – lines with neither code nor comments.

class ppbcc.code_complexity.loc.LineMetrics(loc, sloc, comment_lines, blank_lines)[source]#

Bases: object

Line counts of one source file.

Variables:
  • loc (int) – Total number of physical lines (Lines of Code).

  • sloc (int) – Number of lines containing code (Source Lines of Code).

  • comment_lines (int) – Number of comment-only lines.

  • blank_lines (int) – Number of lines without code or comments.

Parameters:
  • loc (int)

  • sloc (int)

  • comment_lines (int)

  • blank_lines (int)

loc: int#
sloc: int#
comment_lines: int#
blank_lines: int#
as_dict()[source]#

Serialises the line counts.

Returns:

Mapping from metric name (matching the CSV column names) to its value, in a stable order.

Return type:

dict[str, int]

combine(other)[source]#

Adds another file’s line counts (used for aggregation).

Parameters:

other (LineMetrics) – Line counts to add.

Returns:

A new LineMetrics with the summed counts.

Return type:

LineMetrics

ppbcc.code_complexity.loc.count_lines(code, tokens)[source]#

Computes the line metrics of one source file.

Parameters:
Returns:

The LineMetrics of the file.

Return type:

LineMetrics

Configuration#

Loading and representation of the analysis configuration.

The configuration consists of two TOML resources shipped with the package in ppbcc/code_complexity/share:

  • cpp_keywords.toml – the baseline C++ keyword sets used to distinguish operators from operands.

  • dialects.toml – the registry of GPU/parallel programming dialects (Kokkos, CUDA, OpenMP, …) whose constructs are counted as dialect operators.

Both files can be overridden with user-supplied paths, e.g. via the CLI.

ppbcc.code_complexity.config.DETECTION_MIN_HITS: int = 3#

Number of detect_patterns hits in a source file required to activate a dialect during automatic detection (headers, file extensions, pragmas and namespace usage activate a dialect with a single hit instead).

ppbcc.code_complexity.config.SOURCE_EXTENSIONS: frozenset[str] = frozenset({'.c', '.c++', '.cc', '.cl', '.comp', '.cpp', '.cu', '.cuh', '.cxx', '.frag', '.geom', '.glsl', '.h', '.h++', '.hh', '.hip', '.hlsl', '.hpp', '.hxx', '.inc', '.inl', '.ipp', '.metal', '.slang', '.tesc', '.tese', '.vert', '.wgsl'})#

File extensions collected when a directory is passed as a source.

ppbcc.code_complexity.config.BASELINE_DIALECT_NAMES: frozenset[str] = frozenset({'baseline', 'c++', 'cpp', 'none'})#

Names of language_dialect that mean “plain C++, no dialect”.

ppbcc.code_complexity.config.AUTO_DIALECT_NAME: str = 'auto'#

Name of the pseudo dialect that triggers automatic per-file detection.

class ppbcc.code_complexity.config.CppKeywords(keywords, operand_keywords)[source]#

Bases: object

Baseline C++ keyword sets.

Variables:
  • keywords (frozenset[str]) – Keywords counted as operators (e.g. for, const).

  • operand_keywords (frozenset[str]) – Keywords that denote values and therefore count as operands (e.g. true, nullptr, this).

Parameters:
keywords: frozenset[str]#
operand_keywords: frozenset[str]#
class ppbcc.code_complexity.config.DialectSpec(name, aliases, namespace_prefixes=(), identifier_pattern=None, keywords=frozenset({}), pragma_prefixes=frozenset({}), pragma_clauses=frozenset({}), punctuation=frozenset({}), header_patterns=(), detect_patterns=(), extensions=frozenset({}))[source]#

Bases: object

Description of a single GPU/parallel-programming dialect.

All regular expressions are pre-compiled; identifier patterns are matched with fullmatch against single identifiers, header and detection patterns with search.

Variables:
  • name (str) – Canonical dialect name (the table name in dialects.toml).

  • aliases (frozenset[str]) – Lower-case names accepted for language_dialect.

  • namespace_prefixes (tuple[tuple[str, ...], ...]) – Namespace prefixes (as tuples of segments) that turn a whole qualified name into a single dialect operator.

  • identifier_pattern (re.Pattern[str] | None) – Combined regex for dialect identifiers, or None if the dialect defines no identifier patterns.

  • keywords (frozenset[str]) – Exact identifiers belonging to the dialect.

  • pragma_prefixes (frozenset[str]) – First pragma tokens claiming a #pragma line.

  • pragma_clauses (frozenset[str]) – Identifiers counted as operators inside a claimed pragma line (all other pragma identifiers count as operands).

  • punctuation (frozenset[str]) – Extra punctuation operators (e.g. CUDA <<<).

  • header_patterns (tuple[re.Pattern[str], ...]) – Regexes matched against included header names for automatic detection.

  • detect_patterns (tuple[re.Pattern[str], ...]) – Regexes searched in the raw source for automatic detection (requires DETECTION_MIN_HITS total hits).

  • extensions (frozenset[str]) – File extensions that imply the dialect.

Parameters:
name: str#
aliases: frozenset[str]#
namespace_prefixes: tuple[tuple[str, ...], ...] = ()#
identifier_pattern: Pattern[str] | None = None#
keywords: frozenset[str] = frozenset({})#
pragma_prefixes: frozenset[str] = frozenset({})#
pragma_clauses: frozenset[str] = frozenset({})#
punctuation: frozenset[str] = frozenset({})#
header_patterns: tuple[Pattern[str], ...] = ()#
detect_patterns: tuple[Pattern[str], ...] = ()#
extensions: frozenset[str] = frozenset({})#
matches_identifier(identifier)[source]#

Checks whether a single identifier belongs to this dialect.

Parameters:

identifier (str) – The identifier text, e.g. "cudaMalloc".

Returns:

True if the identifier is a dialect keyword or fully matches one of the dialect’s identifier patterns.

Return type:

bool

matches_qualified(segments)[source]#

Checks whether a qualified name belongs to this dialect.

Parameters:

segments (tuple[str, ...]) – The ::-separated name split into segments, e.g. ("Kokkos", "parallel_for").

Returns:

True if the name starts with one of the dialect’s namespace prefixes.

Return type:

bool

class ppbcc.code_complexity.config.DialectRegistry(dialects, alias_map=<factory>)[source]#

Bases: object

All known dialects plus lookup helpers.

Variables:
Parameters:
dialects: dict[str, DialectSpec]#
alias_map: dict[str, str]#
resolve(name)[source]#

Resolves a dialect name or alias to its specification.

Parameters:

name (str) – Dialect name or alias, case-insensitive (e.g. "Kokkos").

Returns:

The matching DialectSpec.

Raises:

KeyError – If the name is not a known dialect or alias.

Return type:

DialectSpec

resolve_all(names)[source]#

Resolves a dialect selection string or list to specifications.

Parameters:

names (str | list[str]) – Either a single (possibly comma-separated) string such as "kokkos,openmp" or a list of dialect names. Baseline names ("cpp", "none", …) resolve to an empty selection.

Returns:

List of resolved dialect specifications (without duplicates).

Raises:

KeyError – If any name is neither a dialect alias nor a baseline name. "auto" must be handled by the caller and also raises here.

Return type:

list[DialectSpec]

ppbcc.code_complexity.config.load_cpp_keywords(path=None)[source]#

Loads the baseline C++ keyword sets.

Parameters:

path (Path | None) – Optional path to a TOML file overriding the packaged cpp_keywords.toml.

Returns:

The loaded CppKeywords.

Return type:

CppKeywords

ppbcc.code_complexity.config.load_dialects(path=None)[source]#

Loads the dialect registry.

Parameters:

path (Path | None) – Optional path to a TOML file overriding the packaged dialects.toml.

Returns:

The loaded DialectRegistry with all regexes compiled.

Return type:

DialectRegistry

Reporting#

Result assembly: metric selection, DataFrame construction, CSV export.

ppbcc.code_complexity.report.ID_COLUMNS: tuple[str, ...] = ('file', 'dialect')#

Identifier columns always present in the report.

ppbcc.code_complexity.report.LOC_COLUMNS: tuple[str, ...] = ('loc', 'sloc', 'comment_lines', 'blank_lines')#

Line-metric columns (see ppbcc.code_complexity.loc).

ppbcc.code_complexity.report.HALSTEAD_COLUMNS: tuple[str, ...] = ('distinct_operators', 'distinct_operands', 'total_operators', 'total_operands', 'vocabulary', 'length', 'calculated_length', 'volume', 'difficulty', 'effort', 'time_seconds', 'delivered_bugs', 'program_level', 'language_level')#

Halstead columns (see ppbcc.code_complexity.halstead), in report order.

ppbcc.code_complexity.report.DIALECT_COLUMNS: tuple[str, ...] = ('dialect_distinct_operators', 'dialect_total_operators', 'dialect_distinct_operands', 'dialect_total_operands')#

Columns counting the tokens contributed by the GPU dialect(s).

ppbcc.code_complexity.report.DIFF_COLUMNS: tuple[str, ...] = ('distinct_operators', 'distinct_operands', 'total_operators', 'total_operands', 'vocabulary', 'length', 'volume', 'difficulty', 'effort')#

Columns that additionally get baseline_/delta_ variants in diff mode (baseline = metrics of the code with all dialect tokens removed).

ppbcc.code_complexity.report.METRIC_GROUPS: dict[str, tuple[str, ...]] = {'all': ('loc', 'sloc', 'comment_lines', 'blank_lines', 'distinct_operators', 'distinct_operands', 'total_operators', 'total_operands', 'vocabulary', 'length', 'calculated_length', 'volume', 'difficulty', 'effort', 'time_seconds', 'delivered_bugs', 'program_level', 'language_level', 'dialect_distinct_operators', 'dialect_total_operators', 'dialect_distinct_operands', 'dialect_total_operands'), 'dialect': ('dialect_distinct_operators', 'dialect_total_operators', 'dialect_distinct_operands', 'dialect_total_operands'), 'dialect_operators': ('dialect_distinct_operators', 'dialect_total_operators', 'dialect_distinct_operands', 'dialect_total_operands'), 'halstead': ('distinct_operators', 'distinct_operands', 'total_operators', 'total_operands', 'vocabulary', 'length', 'calculated_length', 'volume', 'difficulty', 'effort', 'time_seconds', 'delivered_bugs', 'program_level', 'language_level'), 'lines_of_code': ('loc', 'sloc', 'comment_lines', 'blank_lines'), 'loc': ('loc', 'sloc', 'comment_lines', 'blank_lines'), 'sloc': ('sloc',), 'source_lines_of_code': ('sloc',)}#

Metric-group names accepted by evaluate(metrics=...) and the CLI.

ppbcc.code_complexity.report.resolve_metric_columns(metrics)[source]#

Resolves metric names/groups to the report columns they select.

Accepted names are the group names in METRIC_GROUPS, any single column name (e.g. "effort", "sloc") and Halstead columns with a halstead_ prefix (e.g. "halstead_effort"). Matching is case-insensitive; spaces and dashes are treated as underscores.

Parameters:

metrics (list[str] | None) – Requested metric names, or None/empty for all metrics.

Returns:

The selected column names in canonical report order (without the identifier columns).

Raises:

ValueError – If a metric name is unknown.

Return type:

list[str]

ppbcc.code_complexity.report.report_columns(metrics, diff)[source]#

Builds the full column list of the report.

Parameters:
Returns:

The ordered column names, starting with ID_COLUMNS.

Return type:

list[str]

ppbcc.code_complexity.report.to_dataframe(rows, metrics, diff)[source]#

Builds the report DataFrame from per-file result rows.

Parameters:
  • rows (list[dict]) – One dictionary per analysed file containing all metric values.

  • metrics (list[str] | None) – Requested metric names (see resolve_metric_columns()).

  • diff (bool) – Whether to include baseline_/delta_ columns.

Returns:

DataFrame with one row per file and the selected metric columns.

Return type:

DataFrame

ppbcc.code_complexity.report.format_table(frame, table_format='rounded_outline')[source]#

Pretty-prints the report DataFrame as a text table.

Parameters:
  • frame (DataFrame) – The report DataFrame.

  • table_format (str) – Any table format supported by tabulate (e.g. "rounded_outline", "github", "psql", "simple").

Returns:

The rendered table; floats are shown with six significant digits.

Return type:

str

ppbcc.code_complexity.report.save_csv(frame, path, separator=',')[source]#

Writes the report DataFrame to a CSV file.

Parameters:
  • frame (DataFrame) – The report DataFrame.

  • path (Path) – Destination file; parent directories are created as needed.

  • separator (str) – CSV field separator.

Return type:

None

Command Line Interface#

Command-line interface for code-complexity analysis.

Examples

Analyse a directory with automatic dialect detection and save a CSV:

python -m ppbcc.code_complexity path/to/src -o report.csv

Force the Kokkos dialect, restrict the metrics and show the difference to the plain-C++ baseline:

python -m ppbcc.code_complexity src/kokkos -d kokkos \
    -m halstead_effort halstead_volume loc --diff -v
ppbcc.code_complexity.cli.configure_logging(verbosity)[source]#

Configures the loguru sink according to the CLI verbosity.

Parameters:

verbosity (int) – Number of -v flags: 0 = INFO, 1 = DEBUG, >= 2 = TRACE.

Return type:

None

ppbcc.code_complexity.cli.build_parser()[source]#

Builds the CLI argument parser.

Returns:

The configured argparse.ArgumentParser.

Return type:

ArgumentParser

ppbcc.code_complexity.cli.list_dialects(dialects_config)[source]#

Prints all known dialects with their aliases to stdout.

Parameters:

dialects_config (Path | None) – Optional override for the packaged dialects.toml.

Return type:

None

ppbcc.code_complexity.cli.main(argv=None)[source]#

CLI entry point.

Parameters:

argv (list[str] | None) – Command line arguments (defaults to sys.argv[1:]).

Returns:

0 on success, 1 on error.

Return type:

Process exit code