Skip to content
00-jcPublic

About

AST analysis tool (mirror)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

ast2md

ast2md (“AST to Metrics Data”) is a multi-language static-analysis engine designed to extract coupling, stability, and structural dependency metrics from compiler ASTs.

The system is architected to support multiple language frontends. Swift and C are currently implemented, sharing the same analysis engine, graph model, and metric computation logic.

ast2md is suited for architecture evaluation, dependency audits, CI enforcement, and large-scale structural analysis.

What It Measures

Using compiler-emitted ASTs, ast2md derives a wide set of semantic and structural metrics:

  • Afferent couplings (incoming references)
  • Efferent couplings (outgoing references)
  • Stability index
  • Transitive reachability (incoming and outgoing)
  • Categorized dependencies explicit types / abstract types / nested types / modules / methods
  • Deep stability
  • Ripple and impact effects
  • Locality, pressure, skew
  • Abstractness
  • Longest dependency chains per unit

All metrics are computed using the same algorithms and formulas for every language backend. Differences between languages only affect how semantic units are discovered, not how they are analyzed.

Language Backends

Swift

  • Frontend: swiftc JSON AST
  • Nested, abstract, and protocol-aware
  • File-based modules
  • Rich semantic signal

C

  • Frontend: clang -Xclang -ast-dump=json
  • Flat semantic model
  • No language-level nesting or abstraction
  • No clustering, since C has no native namespace/module hierarchy

Despite this, all metrics are still computed identically. C units simply do not participate in module clustering.

These frontends rely on the ast dumps of the compilers, but there is an internal messaging protocol: UnitDeps

pub struct UnitDeps {
    pub(crate) name: String,
    pub(crate) kind: Kind,
    pub(crate) deps: Vec<(String, Kind)>,
}

If a frontend emits these to the consumer thread via walk(), you can parse from src, or other dumps, the engine is totally decoupled from the frontend.

Semantic Units

All backends emit the same semantic vocabulary:

  • Module
  • ExplicitType, ExplicitNestedType
  • AbstractType, AbstractNestedType
  • Method
  • NonInferred

Backends may omit kinds the language cannot express, but the engine does not special-case metrics.

Stable IDs are computed as:

xxh3_64_with_seed(name.bytes(), rotated(kind))

Metrics

For each semantic unit:

  • transitive afferent reachability
  • transitive efferent reachability
  • per-kind dependency counts
  • total reachability
  • locality & skew
  • ripple (downstream influence)
  • impact (upstream influence)
  • deep stability
  • abstractness
  • stability index Ca / (Ca + Ce)

Units with zero afferent and efferent edges produce NaN stability (intentional isolation marker).

Design Scope

ast2md is shaped for:

  • CI dependency validation
  • architecture drift detection
  • large-scale static analysis
  • mixed Swift / C codebases
  • research on dependency metrics
  • fast incremental re-analysis

Swift and C differ only in semantic expressiveness, not in analysis rigor.


CLI Usage

ast2md - AST Analyzer

Usage: ast2md [OPTIONS] --input <FILES>... --target <TARGET> --lang <LANG> [-- <ADDITIONAL_ARGS>...]

Arguments:
  [ADDITIONAL_ARGS]...
          Arguments passed after `--` are forwarded verbatim to the driver's compiler.

Options:
  -i, --input <FILES>...
          List of files to analyze

  -o, --target <TARGET>
          The target of the pipeline

          Possible values:
          - report:    Produce a human-readable, color-coded report
          - json:      Emit all metrics and dependency graphs as JSON
          - dot:       Emit a graphviz dot representation of the full semantic context
          - validator: Validate heuristics; return non-zero on excessive bad coupling.
                       Always returns a dump of all the offending units to stdout as a json.
          - tui:       Run as a interactive tui

  -L, --lang <LANG>
          The target of the pipeline

          Possible values:
          - swift: Emit JSON AST's for swift (needs .swiftmodules precompiled)
          - c:     Emits JSON AST for each C file (n translation units)

  -c, --cap-bad-coupling <N>
          Cap the maximum of "bad" coupling occurrences the tool will tolerate before returning a non-zero code.
          This only works with the `validator` target and counts all bad efferent, afferent, and instable dependencies.

  -t, --threshold <N>
          Threshold for bad coupling metrics.
          Stability uses it directly.
          Afferent/efferent counts are normalized by their maximum.
          Default: 0.75% (higher = more strict).

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Compiler flags can be forwarded after --:

# Swift
ast2md -i File.swift -o report -L swift -- -I .ast2md_cache

# C
ast2md -i main.c util.c -o json -L c -- -I include/

Input Modes

ast2md uses a generic, language-agnostic driver.

Driver Modes

There are two execution strategies, selected by language:

driver<L: Lang>

Used for single-source ASTs

  • Each file is compiled independently
  • Incremental recompilation is decided per file
  • ASTs are processed in parallel
  • Cached JSON ASTs are reused when valid

Note

Single translation unit AST's were discarded due to being worse in both performance and size.

Beware of C ast's, if the codebase is too large, i.e jemalloc, sqlite,... AST dumps become so large that can completely flood the ram in modern systems, when analyzing C files at a scale, it is better to do it by submodules.

This is because C preprocessors inject ALL libc boilerplate into every single tranlation unit.

Incremental Behavior

Recompilation happens per-file if:

  • the cache file is missing,
  • the source file is newer than it's cache,
  • metadata cannot be read.
  • any additional flags passed to the compiler have changed

The system always errs toward recomputing.

Output Targets

1. json

Stable, deterministic (under the deterministic flag) JSON including:

  • unit metadata
  • computed metrics
  • afferent & efferent edges

2. report

Human-readable, color-coded report sorted by stability (under deterministic flag), you can expect it to be like this:

ExplicitType::t_u8 (0xC7C77BF56FF93144)
[ FULL: 10 afferent | 1 efferent ]   stability=0.91
 ├─ total-reachability:    196 afferent |    2 efferent
 ├─ transitive:            186 afferent |    1 efferent
 ├─ abstract:                0 afferent |    0 efferent
 ├─ explicit:                0 afferent |    1 efferent
 ├─ nested-explicit:         0 afferent |    0 efferent
 ├─ nested-abstract:         0 afferent |    0 efferent
 ├─ methods:                10 afferent |    0 efferent
 ├─ modules:                 0 afferent |    0 efferent
 ├─ visibility:              0.0996
 ├─ abstractness:            0.0000
 ├─ deep-stability:          0.9899
 ├─ pressure:                196
 ├─ skew:                    -9
 ├─ locality:                11
 ├─ 10 afferent dependencies
 │   ├─ Method::_write_u8_fwd
 │   ├─ Method::__populate
 │   ├─ Method::ft_memctz_u32
 │   ├─ Method::ft_memctz_u64
 │   ├─ Method::ft_memctz_u128
 │   ├─ Method::__maxu8
 │   ├─ Method::_look4_u8_fwd_unsafe
 │   ├─ Method::ft_q_sqrt_round
 │   ├─ Method::ft_roundf
 │   └─ Method::ft_pow_u8
 └─ 1 efferent dependencies
      └─ ExplicitType::unsigned char

3. dot

GraphViz DOT graph.

  • Swift: clustered by module
  • C: flat graph (no clustering)

4. validator

CI-oriented validation mode.

Returns:

  • 0 if within limits (using the --threshold and --cap-bad-coupling)
  • 1 otherwise

Prints offending units to stdout (json-serialized).

5. TUI (Interactive Explorer)

ast2md also ships with an interactive terminal UI for exploring the computed semantic graph after analysis. Built on top of ratatui its read-only: no mutation, just navigation across units, metrics, and dependencies.

All TUI query results ARE SORTED, turning off the flag does nothing for this backend.

Invoke it with:

ast2md -L <lang> -o tui -i <files...>

Layout

The UI is split into two zones:

  • Left panel (Detail): the full coupling report for the currently selected unit (same content as the report formatter, but rendered as plain text for the TUI).
  • Right sidebar
    • Top (Kind filter): select which kind of semantic units to browse (types, modules, methods, etc.).
    • Bottom (Results list): units of the selected kind, sorted by stability index, optionally filtered by search.

Units are color-coded by stability and sorted in the following order:

  • Gray: isolated (NaN stability: zero afferent + zero efferent)
  • Red: unstable
  • Yellow: medium stability
  • Green: stable

Navigation & Keybindings

Focus is explicit: the UI always has one “active” pane.

  • Mouse scrolling integration
  • Tab cycles focus: Kind → Results → Detail → Kind
  • Ctrl+d, Ctrl+u, j and k work the same as in Neovim
  • / to search for an entry of the semantic kind you've selected
  • q quits at any time
  • Esc quits search, not the TUI

Searching

  • / enters search mode (typing goes into the search field shown in the Results title)
  • Backspace deletes a character
  • Enter exits search mode back to Results
  • Esc exits search mode back to Results
  • When querying nested types, : -> internally stores \x1F (displayed as ::)

Search is a case-insensitive substring filter over unit names within the current kind.

Naming & Nesting

Internally, ast2md uses 0x1F (ASCII Unit Separator) as the nesting delimiter. The UI lets you type nested names naturally:

  • When typing in search mode, : is normalized into the internal delimiter.
  • Names are displayed using :: for readability.

So you can search like:

  • MyModule:MyType
  • MyModule:MyType:doWork

And the engine will match the canonical internal form.

Scrolling the Detail View

When focus is on Detail, Up/Down scrolls the report text (useful for large dependency lists).

Notes

  • The TUI operates over the already-computed semantic context (same as report/json/dot outputs).
  • It’s intended for quickly finding hotspots (low-stability units), scanning dependency pressure, and drilling into specific units without rerunning the pipeline.

Canonicalization & Naming Model

ast2md normalizes all semantic units across all languages into a single canonical string representation.

Canonical Separator

The engine uses 0x1F (ASCII Unit Separator) as an internal nesting delimiter.

Example canonical form:

MyMod\x1FMyType\x1FInner\x1FdoThing

Why 0x1F

  • Cannot appear in identifiers
  • UTF-8 safe
  • Language-agnostic
  • Allows fast byte-level hashing and comparison
  • Avoids ambiguity across languages (., ::, _, etc.)

This representation is engine-internal and stable.

How Canonical Names Are Used

Graph Construction

The engine never splits on . or language-specific separators. All hierarchy reconstruction is driven exclusively by 0x1F.

Example:

A␟B␟C

Produces:

  • A
  • A␟B
  • A␟B␟C

Swift uses this to infer module clusters. C does not, but still benefits from stable hashing and identity.

JSON Output

Canonical strings are emitted as-is:

{
  "unit": {
    "str": "MyMod\u001FMyType\u001FInner",
    "kind": "ExplicitNestedType",
    "hash": 123...
  }
}

This is intentional:

  • lossless round-tripping
  • consumer-controlled parsing
  • cross-version stability

Human-Facing Output

Only the following targets rewrite the separator:

  • --target report
  • --target dot

0x1F → ::

Building the CLI

cargo build --release

Or alternatively:

./compile.sh

compile.sh will strip metadata from your filesystem and other personal information cargo embeds in your executable by default.


Future expansions

The only thing implementers have to conform is this trait, where UnitDeps are sent to the main consumer.

pub trait Lang {
    fn compile_ast(file: &str, ctx: &Option<Vec<String>>) -> Result<Vec<u8>, Ast2MdError>;
    fn walk<'a>(
        ast: &'a mut [u8],
        sx: Sender<UnitDeps>,
        arena: Option<&'a Bump>,
    ) -> Result<(), Ast2MdError>;
}

Using as a Library

use ast2md::{SemanticCtx, Ast2MdError, langs::swift::SwiftLang};
use bumpalo::Bump;

let arena = Bump::new();
let mut ctx = SemanticCtx::new(&arena);

ctx.driver<SwiftLang>(&["MyFile.swift".into()], &None)?;
let out = ctx.serialize()?;
let q = ctx.query_top_down("Hello\x1FWorld");
// queries expect normalized names! suggestion: emit normalized in Lang::walk(...) directly

The library API is language-agnostic; callers provide the desired frontend.

Feature Flags

Feature Description
hide_std Remove stdlib types from metrics
emit_noninferred Include semantic units marked NonInferred
deduplicate-edges Collapse repeated dependency edges
pretty_print Pretty-formatted JSON outputs
hide_synthesided_conformances Hide synthesized protocol conformances
deterministic Always sort elements before dumping data

Performance

You can find this very bench in benches/driver.rs (+30 concurrent files overall), you can run it with:

cargo bench
Swift driver pipeline/golden
                        time:   [388.86 µs 392.56 µs 395.90 µs]
Swift driver pipeline/lineal
                        time:   [179.73 µs 180.51 µs 181.35 µs]
Swift driver pipeline/dag
                        time:   [243.75 µs 244.95 µs 246.25 µs]
Swift driver pipeline/pathological
                        time:   [1.2253 ms 1.2312 ms 1.2373 ms]
Swift driver pipeline/all
                        time:   [1.7287 ms 1.7324 ms 1.7363 ms]
C minishell driver pipeline/minishell
                        time:   [423.09 ms 423.64 ms 424.24 ms]
C tcc driver pipeline/tcc
                        time:   [540.60 ms 543.11 ms 545.60 ms]

Testing

Tests live under src/engine/test (integration) and the swift/C src files live in test/.

cargo test

Authors

  • Jaime Castro Enrique - castro.jaime [at] proton.me

LICENSE

Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.

About

AST analysis tool (mirror)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages