Skip to content

Parser Backends ​

Flude can read C++ sources through three interchangeable backends. They produce the same intermediate model, so everything downstream (renderers, coverage gates, diffs) works the same whichever one you pick. The backend is chosen by the parser_backend key (global config, SDK config or doc-config; the usual cascade applies) and defaults to doxygen.

parser_backendHow it reads the codeNeedsSpeedBest for
doxygenRuns Doxygen and reads its XMLthe Doxygen binaryfast, results cachedprojects whose public API is plain declarations
nativeCompiles each file with libclanga working build: compile_commands.json or a .vcxprojslowest (minutes to tens of minutes)the most exact result, macro-heavy code that you can build
syntactictree-sitter plus the pcpp preprocessor, no compilernothing but the sources (a build file only improves the result)fast (seconds to a minute)code you cannot or do not want to build, large trees, quick feedback

syntactic is C and C++ only (collector.language of cpp); any other language raises an error at start-up.


🧭 Choosing a backend ​

  • No build available, or a build that takes hours — use syntactic. It expands the project's own macros with a real preprocessor, so entities generated by property/factory macros are found, which Doxygen does not do by default.
  • A working compile_commands.json or .vcxproj, and exactness matters more than time — use native. It sees the program the compiler sees.
  • Plain declarations, no macro tricks — doxygen is enough.

On a large real-world codebase (microsoft/terminal, 388 files, 3.8 MB, 4 CPUs) native takes about 15 minutes and 3 GB of memory and syntactic about 35 seconds. Against Doxygen the syntactic result has full recall; the entities it adds are the macro-generated ones Doxygen cannot see (see What the numbers mean below).


⚙️ Syntactic backend settings ​

All keys live under collector in the doc-config. Paths are resolved relative to the doc-config's own directory.

  • file_patterns, exclude, exclude_patterns, min_visibility — which files are parsed and which members are reported, with the same meaning as for the other backends. min_visibility defaults to protected: private members are not reported.

  • include_paths — directories the preprocessor searches to resolve #include. Following the include graph is what lets the backend see macros defined in headers, and it is also about 93% of the run time, so this is the main speed/accuracy lever (see Profiles). An include that cannot be found is skipped, never an error.

  • macros — extra definitions, NAME, NAME=VALUE or NAME(args)=VALUE. An empty value (EXPORT_API=) removes an annotation macro from the code before parsing.

  • expand_as_defined — names of project macros whose real expansion should be used. For this backend they are appended to macros.

  • vcxproj_path, vcxproj_configuration, vcxproj_platform, bazel_build_path, bazel_target_name, cmake_lists_path — read macros (and, for the thorough profile or when build_file_include_dirs is enabled, include directories) from an existing build file, statically, without running the build. Without build_file_include_dirs only what the file states plainly is read: a .vcxproj's own settings, a CMakeLists.txt's top-level include_directories() and add_definitions(), a Bazel target's includes, defines and copts.

  • build_file_include_dirs — boolean, default false. Turns on the extended reading of the build files above and adds the include directories they declare, only those that exist and lie inside the project tree (below the deepest common ancestor of the build file's directory and the configured include_paths). balanced is otherwise unchanged, fast ignores the key, and thorough already adds those directories (it does not turn on the extended reading by itself). Directories from compile_commands.json are not covered by it. The extended reading adds:

    • .vcxproj — <Import>ed .props files are followed (up to 8 levels, only files inside the project tree), simple Conditions are evaluated (==, !=, Exists(), And, Or), and the properties $(MSBuildThisFileDirectory), $(ProjectDir), $(Configuration), $(Platform), those set in the files read, and $(SolutionDir) (the directory of the nearest .sln above the project) are resolved. An implicit Directory.Build.props is read too.
    • CMake — commands at the top level of the file (not inside if, foreach, function or macro): set() variables, include_directories, add_definitions, add_compile_definitions, target_include_directories, target_compile_definitions and the -D and -I items of target_compile_options. The definitions and directories of all targets in the file are merged (there is no target selector). $<BUILD_INTERFACE:…> is unwrapped; other generator expressions are ignored.
    • Bazel — a relative -I in copts is resolved from the workspace root (the nearest directory above the BUILD file that holds WORKSPACE or MODULE.bazel).

    Limits: no file outside the project tree is ever read, and project-specific CMake helper macros and conditional blocks are not followed, so for some projects the extended reading finds nothing more (on nlohmann/json, googletest, protobuf and Abseil the CMakeLists.txt yields no include directories or macros). Then list include_paths and macros yourself. Measured on microsoft/terminal in October 2026: with only vcxproj_path, build_file_include_dirs: true and the repository root as include_paths, the backend finds the same 7,032 entities as a hand-written configuration (nine include directories and the macros listed by hand); without the key it finds 6,119.

  • compile_commands_path — a compile_commands.json. Each file gets the -D flags of its own entry; headers get the union. compile_commands_macros_exclude lists macro names to drop from that union (for example ABI-test switches that are never part of one real build).

  • target_triple — set to a Windows target (x86_64-pc-windows-msvc, a mingw triple) or give a .vcxproj, and the backend also defines the Windows SDK function spellings (STDMETHODIMP, STDMETHODCALLTYPE, STDMETHOD, IFACEMETHOD and friends) and ignores calling-convention keywords and __declspec(uuid(...)) class attributes, which the parser cannot read. A Linux or macOS project is unaffected.

  • precompiled_header — a file name, a list of names, or false. A header parsed on its own does not see the macros its project's precompiled header brings in, so its macro-generated members stay unexpanded. With this key each header starts from the macro state of the nearest pch.h up the directory tree, and each source file whose first real line is #include "pch.h" is seeded the same way instead of re-reading the chain. When the key is absent it is read from the .vcxproj's <PrecompiledHeaderFile>; with no .vcxproj it is off.

  • max_workers — number of worker processes for the preprocessing step. The default follows the container's CPU quota (cgroup-aware), capped at 8; 1 runs everything in one process. The result is identical for any value; only the time changes.

  • profile — fast, balanced (default) or thorough, see below.


⏱️ Profiles ​

A profile states how much of the include graph the backend follows. It changes nothing else: max_workers and the macro settings are untouched. It takes precedence over include_paths and precompiled_header (fast ignores them, thorough extends them), because otherwise it would do nothing for a project that already lists its include paths.

  • balanced (default) — the include paths, precompiled header and macros exactly as configured (with build_file_include_dirs enabled, also the include directories and macros the build files declare, see above).
  • fast — follows no #include at all: include_paths and precompiled_header are ignored. Use it for a quick structural scan, a size estimate or a smoke test.
  • thorough — balanced plus the include directories the build itself declares: the -I, -isystem and -iquote directories of compile_commands.json that lie inside the project tree (system directories are skipped), and the include directories of a .vcxproj, Bazel or CMake file. Use it when you have a build file and want the most complete macro-generated entities.

Measured on three real projects (4 CPUs, October 2026, reference = Doxygen configured to expand the project's entity-generating macros; missing is what Doxygen found and the backend did not, extra what the backend found and Doxygen did not and that is not a verified macro-generated entity):

ProjectProfileTimeEntities foundMissingExtraVerified macro-generated extras
microsoft/terminal (C++/WinRT, .vcxproj)fast27 s5,962235201,459
balanced35 s6,7280162,174
thorough36 s6,7280162,174
dmlc/xgboost (CMake)fast33 s5,03913210654
balanced61 s5,16700156
thorough68 s5,30100332
nlohmann/json (header-only)fast4 s9539339460
balanced11 s93883810
thorough10 s93883810

How to read it:

  • fast is 1.3 to 3 times faster and loses what lives in headers. On terminal and xgboost its worst category misses 4 to 10% of what Doxygen finds, and it loses a third to two thirds of the macro-generated entities. On nlohmann/json it finds nothing that matches: that library's namespace is spelled by a macro defined in its own headers, so without following #include every qualified name comes out different. Use it for a structural scan or a size estimate, not for documentation.
  • balanced is the reference. It is the profile every measurement in this project is made with. The remaining differences on nlohmann/json are known gaps of the syntactic backend on heavily templated header-only code; use native there if you can build it.
  • thorough costs about 10% more time and pays off when the build declares include directories you did not list. On xgboost it adds 134 entities, 176 more of them verified macro-generated ones (the library's parameter-registration macros), with the same recall and precision; on terminal and nlohmann/json there is nothing to add.

🔎 What the numbers mean ​

The reference for these measurements is Doxygen, configured to expand the project's entity-generating macros. A syntactic entity that Doxygen does not report is not necessarily wrong: the macro-generated members (properties, factories, X-list settings) are real code that Doxygen's simplified preprocessor does not expand even when asked. Each such entity is verified against the sources before it is excluded from the precision figure; the few that could not be verified stay in the figures. Recall of 100% means every entity Doxygen found, the syntactic backend found too.

🩺 Timing a run ​

Set the environment variable FLUDE_PROFILE=1 and the catalog metadata gets a per-stage timing (discovery, prefix_baseline, preprocess, normalize, tree_sitter_parse, extract, merge_finalize), wall and CPU seconds, plus the number and size of the parsed files. Without the variable nothing is measured and nothing changes.


🧭 Where This Fits ​

The backend key and the other doc-config keys are described in Target Settings; the global cascade in Global Settings.