A Pascal/Delphi source code formatter, written in Delphi 13. Reformats .pas
files according to a configurable rule set: line-length budgeting, structural
re-indentation, capitalization, and column alignment.
YADF ships in three flavours, built from the same engine:
YADF.exe??? a stand-alone CLI for formatting files, folders, and whole.dpr/.dprojprojects (see CLI quick start).YADFSetup.exe??? a visual settings editor / format playground (see YADFSetup).YADFOT.bpl??? a Delphi IDE design-time package ("YADF Open Tools") that adds aHelp ??? Tools ??? YADFOT: Format Current Buffermenu item and aCtrl+Shift+Alt+Fshortcut to format the current source-editor buffer (see YADFOT install).
All three artifacts are produced by this single project tree and share one
yadf.ini configuration. build_all.bat builds them together with a single
version stamp.
YADFSetup.exe is a three-column GUI ??? Settings | Source | Result. Load any
.pas on the left, watch it reformat live on the right, and tune every option
in between. It is the visual front-end for the whole family's configuration:
what you change here is what YADF.exe and YADFOT.bpl use.
- Get the binary. Download
YADFSetup.exefrom the release zip (alongsideYADF.exeandYADFOT.bpl), or build it yourself (see Building). - No installation required. It is a standalone Win64 executable ??? just run
it. The demo unit (
Demo\Sample.pas) is built into it: on first launch it is written to%APPDATA%\YADF\Sample.pasand loaded, so you immediately see formatting at work. Edit that file to preview your own code; delete it to get the shipped demo back. When a newer YADF ships a newer demo it replaces the file, saving an edited copy asSample.pas.bakfirst when Backup is on. - First run creates the config. If no
yadf.iniexists yet, YADFSetup writes a fully-commented one to%APPDATA%\YADF\yadf.iniand starts editing it (see Where the INI lives).
Heads-up: every change you make autosaves to the shared
yadf.ini, so it immediately changes how the CLI and IDE wizard format from then on. Use Save As??? to snapshot a profile before experimenting, and Reset to return to the shipped defaults.
YADFSetup reads and writes the shared per-user config:
%APPDATA%\YADF\yadf.ini
which expands to e.g. C:\Users\<you>\AppData\Roaming\YADF\yadf.ini. This is
the same file YADF.exe and YADFOT.bpl read, so all three tools stay in sync
(see the full INI lookup order). YADFSetup always edits the
active F profile and shows its path at the bottom of the Settings column.
Since 1.0.8 you can also edit these settings without leaving the IDE: open Tools ??? Options ??? Third Party ??? YADF. The page edits the same F profile as YADFSetup (with a live before/after preview), so a change you make there takes effect on the very next Ctrl+Shift+Alt+F ??? no restart, no stale settings. See Editing settings in the IDE.
To keep alternative option sets, use Save As??? to export a named .ini
anywhere, and Load Settings??? to bring one back later.
The window has three resizable columns (drag the splitters):
- Settings (left) ??? every formatting option, grouped (Line length,
Reflow & whitespace, Casing, Alignment, Uses, Declarations, Labels, File &
CLI). Hover any control for a one-line explanation. Changing any option
re-formats the Result pane instantly and saves to the shared
yadf.ini.- Load Settings??? ??? import an option profile from any
.ini. - Save As??? ??? export the current options to an
.ini(does not touch the shared file). - Reset ??? restore the shipped defaults (this does overwrite the shared
yadf.ini; you'll be asked to confirm).
- Load Settings??? ??? import an option profile from any
- Source (middle) ??? paste Pascal here, or click Open File??? to load any
.pas/.dpr/.inc. Edits re-format after a brief pause. - Result (right) ??? the formatted output, read-only. Copy puts it on the clipboard.
The status line under Settings shows the active INI path and a (saved) marker after each change. The status by the Result pane shows OK or a [Format error] message if the input can't be parsed (the app never crashes on bad input).
Options in the File & CLI group (Backup, BackupDir, BackupKeep, ResultDir, Encoding,
Logging) configure the CLI's file handling and have no effect on the live
preview ??? they're saved for YADF.exe to use.
YADFOT is a design-time Delphi IDE package, built for both Win32 and Win64 (since 1.0.11). Install the BPL that matches your IDE's architecture:
- RAD Studio 12+ / Delphi 13 ??? the IDE is a 64-bit application, so use
YADFOT-Win64.bpl(fromWin64\Release\EXE\YADFOT.bpl). - Older 32-bit IDEs ??? use
YADFOT-Win32.bpl(fromWin32\Release\EXE\YADFOT.bpl).
The release zip ships both, renamed YADFOT-Win32.bpl / YADFOT-Win64.bpl.
YADF builds from a plain clone -- nothing else to check out. The DelphiAST lexer
it depends on is vendored in vendor/DelphiAST/
together with the two local fixes it needs.
- Open
YADFOT.dprojin Delphi 13. - Active platform: Win64 (for the 64-bit IDE) or Win32; configuration Release (Debug works too).
- Project ??? Build YADFOT. The output is
<Platform>\Release\EXE\YADFOT.bpl. - Component ??? Install Packages ??? Add??? ??? browse to that
YADFOT.bpl??? OK.
The wizard registers itself on package load -- no IDE restart needed.
For a full release build (all platforms, all artifacts):
build_all.batOr to build a single artifact (e.g., YADFOT Win64 Release):
cmd.exe /c "call \"C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat\" ^
&& msbuild /t:Build /p:Config=Release /p:Platform=Win64 YADFOT.dproj"Then install via the IDE (step 4 above).
CLI exit codes. 0 = success; 1 = at least one file failed (a fixed code, not a count); 2 = usage error or unhandled exception; 3 = the content guard declined at least one file and nothing failed (a failure outranks it). --stdout writes the exact bytes --o would write for a single input; with several inputs each result follows a --- <file> --- line. (the file is left unchanged - safe to ignore, but not silently 0).
Two entry points appear in the IDE after install:
- Menu ???
Help???Tools???YADFOT: Format Current Buffer(this is the standard location for IOTAMenuWizard items in the Delphi IDE ??? installed wizards live under Help ??? Tools, not directly under Tools). - Keyboard shortcut ???
Ctrl + Shift + Alt + F
A third entry point configures the formatter rather than running it:
-
Settings page ???
Tools ??? Options ??? Third Party ??? YADF.You no longer need to leave the IDE to change how YADF formats ??? or to manage profiles. This page lets you edit every formatting option (with a live before/after preview) and, since 1.0.9, manage your profiles ??? the full
YADFSetup.exeworkflow, inside Delphi. Every control is generated from the sameOptionTablethatYADFSetup.exeand the CLI use, so the editors never drift.- Profiles panel (top of the page): lists every
%APPDATA%\YADF\*.ini, badges which is [F] (Ctrl+Shift+Alt+F/ the CLI default) and which is [R] (Ctrl+Shift+Alt+R), and gives you Set F, Set R, Unassign, and New... buttons. Click a profile to edit that one. - Editing the F profile also mirrors it onto the shared
%APPDATA%\YADF\yadf.ini, so YADFSetup, the CLI, and the IDE all agree; editing R (or any other profile) leaves the F file untouched. - Profile actions (assign F/R, new, switch) save immediately; option-value
edits commit when you click OK, and ??? since 1.0.8 ??? take effect
immediately: the very next Ctrl+Shift+Alt+F formats with your new
settings. No IDE restart, and no stale-settings surprise from a stray
project-local
yadf.ini(those are no longer auto-detected). - The page is added when the package loads and removed when you uncheck YADFOT in Component ??? Install Packages.
- Profiles panel (top of the page): lists every
Since 1.0.10, YADFOT also paints a splash-screen entry (the YADF icon +
YADFOT (MIT) <version>) while the IDE starts up, and registers a plugin
entry in the About dialog. To see the About entry, open Help ??? About
Embarcadero RAD Studio and scroll to the YADFOT entry in the installed-
plugins list ??? it shows the icon, MIT licence, version, and a short
description. (It lives inside the IDE's own About dialog; there is intentionally
no separate "About YADFOT" menu item.)
The shortcut is registered as a Delphi keyboard binding named
YADFOT. If the chord clashes with something on your machine you
can rebind it under Tools ??? Options ??? User Interface ??? Editor ??? Key Mappings (look for the YADFOT entry), or change it in source
at the call to BindingServices.AddKeyBinding in
YADFOT.Wizard.pas and rebuild.
YADFOT verifies the active edit view is a code buffer before doing anything.
Supported extensions: .pas .dpr .dpk .inc. If the active view is a
form designer, project options page, welcome page, or any other non-code
editor, the wizard reports it and exits without changes -- it deliberately
does not auto-jump to a sibling .pas behind a form. Click into the
code editor first, then invoke the wizard.
The buffer does not need to be saved -- the in-memory contents are read, formatted, and rewritten through an undoable IDE edit (so Ctrl+Z reverts the format).
Encoding. Formatting the editor BUFFER goes through the IDE's UTF-8
edit reader/writer, so Encoding does not apply there. When YADFOT has
to format the FILE on disk instead (a form unit), it honours Encoding,
Backup and BackupDir exactly like the CLI. ResultDir and Logging
are CLI-only.
INI lookup ??? profile-based (since 1.0.8).
YADF.exe and YADFOT.bpl resolve their config the same way YADFSetup and the
IDE Options page do: through the per-user profile map at
%APPDATA%\YADF\profiles.ini
which names the active INI files:
[Profiles]
F=yadf.ini ; the default profile (Ctrl+Shift+Alt+F in the IDE, and the CLI default)
R=yadf-D10-Test.ini ; optional second profile (Ctrl+Shift+Alt+R in the IDE only)Each value is an INI name resolved under %APPDATA%\YADF\ (an absolute path
passes through unchanged). Resolution:
- CLI (
YADF.exe) ??? with no--ini, uses the F profile.--ini <path>overrides it explicitly;--no-inireads no INI at all (and creates none): compiled-in defaults plus CLI flags.--iniand--no-inicannot be combined. - YADFOT (IDE) ??? Ctrl+Shift+Alt+F and the Tools-menu item use the F profile; Ctrl+Shift+Alt+R uses the R profile (if assigned).
- YADFSetup / IDE Options page ??? always edit the F profile, and mirror it
onto
%APPDATA%\YADF\yadf.iniso all three front-ends converge on one file.
There is no project-tree walk and no in-memory caching: the file you edit
is the file that formats, every time. To use a different config for a project,
assign it as your F profile in YADFSetup or pass --ini <path> on the CLI.
Changed in 1.0.8: earlier versions walked up from the source file / project / working directory looking for the nearest
yadf.ini, which could silently shadow your saved settings (a stray project-localyadf.iniwould win over the profile you edited). Project-localyadf.inifiles are no longer auto-detected ??? use--inior the F profile instead.
First-run experience. No INI ships in the release zip. The first time you run
YADF.exe (or save a file with YADFOT registered), the F profile
(%APPDATA%\YADF\yadf.ini) is created with every option, its default, and a
one-line explanation, and profiles.ini is pinned to F=yadf.ini. Edit values
in YADFSetup / the IDE Options page and re-run.
The CLI prints Created default config: <path> on the first run that
materialises the template, so you know where to find it.
The [Format] section keys match the CLI keys exactly. The IDE wizard
ignores ResultDir and Logging (CLI-only); Encoding applies only when it
formats a form unit's file on disk -- buffer formatting is always UTF-8.
Behaviour notes (by design): the wizard keeps your caret and scroll position across a format (clamped to the new line count). The profile is read live from its INI on every format, so cancelling the Options page after switching profiles has no cached state to roll back. LF-only buffers are returned with CRLF line endings.
Component ??? Install Packages ??? select YADFOT ??? Remove.
yadf MyUnit.pasFormat a single .pas file in place ??? the source is overwritten with the
formatted output, no backup. This is the default; if you want a copy of the
original kept, opt in:
yadf MyUnit.pas --bIn-place format with a sibling backup MyUnit.pas.BCK1 (then BCK2, BCK3,
...) next to the source. To send backups to a central folder with timestamped
.bak names (MyUnit.pas.<folder hash>.<stamp>.bak) instead, give --b a folder argument:
yadf MyUnit.pas --b D:\YADF-backupsTo send the formatted output somewhere else and leave the source untouched:
yadf MyUnit.pas --o D:\out\MyUnit.formatted.pas :: write to this exact file
yadf C:\src\*.pas --of D:\formatted :: every result into this folderyadf MyProject.dprFormat the .dpr and every local .pas named in its uses clause. *.pas
wildcards (yadf C:\src\*.pas) and .dproj project files work the same way
??? .dproj is parsed for its <DCCReference Include="...pas"/> entries.
yadf -hShows the full option list, every CLI flag, and the current effective
values pulled from yadf.ini.
Layout
- Configurable max line length (default 180), indent step (default 2), tab width (default 4)
- Full reflow: drops user line breaks where structurally non-essential, joins lines that fit, breaks lines that overflow
- Block-aware breaking:
usesclauses, parenthesized argument lists, bracket literals all break to one item per line when needed - Operator-chain breaking: long arithmetic/logical expressions break with the operator leading each new line
- Hanging indent for parens-broken statements: items at
LineWS + 2*Indent, closing paren atLineWS
Indentation
- Re-indents every line based on structural depth, ignoring source whitespace
- Tracks
type/var/constsections,class/record/object/interfacedeclarations,begin/endblocks,case/try/asm - Visibility keyword bonus: members inside
private/public/protected/publishedindent one level deeper if/while/forbody bonus: single-statement bodies afterthen/do/elseindent one level deeper, persisting across multi-line bodies- Case-alternative bonus: bodies after
LABEL:indent one level deeper elsealigns with itsif;else ifchains stay on one line
Capitalization
- Lowercase reserved keywords (
begin,end,if,else,var, ...) - Uppercase hex digits in
$NNnumeric literals and exponent letterE - Uppercase compiler-directive names (
{$IFDEF},{$DEFINE}, ...) - First-occurrence identifier-case normalization (consistent casing throughout the file)
Pass-2 alignment
- Plain align of
:in declarations and=inconstblocks across consecutive lines - Smart align of
:=assignments: when adjacent lines share the same token shape (same sequence of operators/punctuation, identifier names may differ), every common anchor (., :=,(,,,),;) is padded so the anchors line up - Aligned columns are kept tight: an anchor's spacing rule wins
(member-access
.is glued on both sides, no space before;,:=spacing perAssignNoSpaceBefore/AssignSpaceAfter), and a column is never pushed right by padding that every line in the run shares ??? a space appears before an anchor only on the shorter lines that need it to reach the shared column
Other
- Always emits CRLF line endings - including inside multi-line strings, block comments and asm bodies of an LF source (normalised before lexing). An
Eol=preserveoption and LF editor buffers in YADFOT are deliberately not supported (declined: the engine, guard and aligners assume CRLF). - Reads and writes file bytes in the encoding chosen by
Encoding=inyadf.ini(defaultANSI;UTF-8-BOMandUTF-16-BOMalso supported). Existing BOM in input is auto-detected on load. (YADFOT bypasses this setting and always exchanges UTF-8 with the IDE buffer.) - Backups are opt-in: in-place formatting overwrites the source unless
you set
Backup=trueinyadf.inior pass--bon the command line. When backups are on, they go to a sibling<source>.BCK<N>(default) or to a folder you specify with--b <folder>(<name>.<folder hash>.<yyyymmdd-hhnnss>.bak; the hash identifies the source folder, so same-named files from different folders keep separate backups). - Trailing whitespace stripped on every line
- Consecutive blank lines collapsed to a configurable cap (default 1)
- Optional blank-line insertion before sections, methods, type sections
- Block-end labels: long
begin..endblocks get// while,// procedure, etc. trailing comments - Optional unclosed-block markers: when source has unmatched
begin/record, emit// TODO -oYADF : 'begin' on line N has no matching 'end'so they show up in the IDE's To Do List
Robustness
- The format pipeline is idempotent:
format(format(x)) == format(x) --check <file>verifies byte-faithful round-trip (token stream emits identical bytes back)- Tolerates structurally-broken Pascal source (missing
ends) without crashing
Delphi10Compat=true in yadf.ini (or --d10 on the command line; default off)
rewrites inline var declarations into a classic top-of-routine var block, so
code written on Delphi 11/12/13 can build on Delphi 10.2.3 (Tokyo) and earlier,
which predate inline variables.
yadf MyUnit.pas --d10What it does:
- Explicit-typed inline vars and typed
for-loop vars hoist mechanically:var N: Integer := 5;becomesN: Integer;in avarblock plusN := 5;in place;for var L: Integer := 0 to 9 dobecomesfor L := 0 to 9 do. Avarblock is created if the routine has none. Multi-name declarations (var A, B: Integer;) hoist each name. - Inferred initializers with a literal RHS get a best-effort type from the
literal (
Integer/Cardinal/Int64by magnitude,Extended,string,Boolean, andT.Create->T), with a// TODO -oYADF -cd10 : inferred type, verifycomment so you can confirm the guess (a run without--d10removes it). - Anything whose type cannot be determined, a name collision with a
different type, or an inline var inside an anonymous method is left in place
with a
// TODO -oYADF : ...marker (visible in the IDE To-Do list) quoting the original line, so you can finish it by hand and re-run. A name declared inline with different types in one routine is never hoisted at all (every declaration of it is flagged), so the unit still compiles in Delphi 13.
This is not full Delphi 10 compliance. Only inline variables are downgraded.
Other modern constructs are not rewritten -- in particular the if ternary
expression (A := if C then X else Y), inline constants, and managed records have
no Delphi 10 equivalent a formatter can produce mechanically, so they remain and must
be reworked by hand. Inline-var hoisting still removes the most common blocker.
Limits. We do not have Delphi 10 (or any pre-10.3 Delphi) available, so this transform is best-effort and unverified on a real Tokyo toolchain, and the YADFOT IDE wizard / older builds are untested for it. The prebuilt
YADF.exeneeds no Delphi installed, so anyone can run the downgrade. Please report cases it gets wrong.
YADF (CLI) and YADFOT (IDE wizard) share a unified, profile-based config
documented in detail under YADFOT install ??? INI lookup
above. Summary: the per-user %APPDATA%\YADF\profiles.ini names the active
F profile (CLI default and Ctrl+Shift+Alt+F) and optional R profile
(Ctrl+Shift+Alt+R); --ini <path> overrides on the CLI. There is no
project-tree walk. No INI ships in the release zip ??? first run creates a
fully-commented %APPDATA%\YADF\yadf.ini and pins F=yadf.ini. Edit it from
YADFSetup or in-IDE via Tools ??? Options ??? Third Party ??? YADF. CLI
flags override INI values; INI values override compiled-in defaults (--no-ini: CLI flags override defaults).
Short bodies. PackShortBodies (CLI --pack-bodies, default off) is a preset over four independent rules (BreakIfBody, BreakLoopBody, BreakWithBody = false, CaseArmOnLabelLine = true), shown together in the Options page group Short bodies. A rule you set explicitly is kept over the preset within the same source (INI or CLI); the CLI beats the INI. --no-pack-bodies only clears the Pack flag: rules an INI sets explicitly keep their values, and an INI saved by YADF (Options page, YADFSetup) names all four, so with such an INI change them with the --break-* / --case-arm-on-label switches. See docs/help-site/options/pack-short-bodies.md.
YADFOT (IDE wizard) reads the same [Format] keys with a different search
order ??? see YADFOT install above.
Requires Delphi 13 (RAD Studio 37.0). Depends on
DelphiAST for its lexer only, which
is vendored in vendor\DelphiAST\ (the three lexer files + its MPL 2.0
license) -- a fresh clone builds with no sibling checkout.
The vendored copy carries two fixes that are not in upstream master yet:
- PR #337 (Uwe Raabe): Delphi 12+ triple-quoted multi-line strings were mis-lexed.
- The
unsafefix from PR #346:unsafeis a directive, not a reserved word, so an identifier namedUnsafekeeps its casing.
Provenance (upstream base, local commits) and how to refresh the copy are in
vendor/DelphiAST/VENDORED.md.
Build the CLI:
cmd /c "call \"C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat\" ^
&& msbuild /t:Build /p:Config=Release /p:Platform=Win64 YADF.dproj"Build the IDE wizard package:
cmd /c "call \"C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat\" ^
&& msbuild /t:Build /p:Config=Release /p:Platform=Win32 YADFOT.dproj"Build the settings editor:
cmd /c "call \"C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat\" ^
&& msbuild /t:Build /p:Config=Release /p:Platform=Win32 YADFSetup.dproj"Or build all three at once with one shared version stamp:
build_all.batbuild_all.bat is the single source of truth for the release version: it sets
YADF_MAJOR/MINOR/RELEASE/BUILD once and stamps the identical FileVersion into
YADF.exe, YADFSetup.exe, and YADFOT.bpl (keep YADF.Version.inc in sync
for the in-app version string). All outputs land in Win32\Release\EXE\
(plus Win64\Release\EXE\ for the CLI); YADFOT.bpl is Win32-only, since the
Delphi IDE is 32-bit, while YADF.exe builds for Win32 and Win64.
YADF\
YADF.Tokens.pas lexer wrapper + token stream
YADF.Options.pas TYadfOptions + YADF_OPTIONS table + INI load/save
YADF.Groups.pas structural group parser (begin/end nesting, etc.)
YADF.Layout.pas FormatSource -- the engine entry point
YADF.Debug.pas debug-tree dump (CLI --debug-tree)
YADF.Version.inc single-source version string (display)
YadfMain.pas CLI argument parser + driver
YADF.dpr / .dproj CLI project
uYADFSetupMain.pas YADFSetup form (3-column playground)
YADFSetup.dpr/.dproj settings-editor GUI project
YADFOT.Wizard.pas IDE wizard (ToolsAPI + buffer I/O + INI loader)
YADFOT.dpk / .dproj IDE design-time package
Demo\Sample.pas preview demo unit (embedded via YADFSample.rc -> .RES)
build_all.bat builds all 3 artifacts with one version stamp
yadf.ini shared CLI + wizard + GUI config (optional)
Three Ways to Read Delphi -- an article on why YADF links DelphiAST's lexer and never its parser, and how that choice compares with a full AST (DelphiAST) or an error-tolerant CST (tree-sitter-delphi13). It covers conditional compilation, byte-exact round-trip fidelity, and which layer is the right foundation for a formatter, an indexer, or a one-shot analyser.
The same article is also published as a styled HTML page and a Word document.
YADF is built on top of DelphiAST
by Roman Yankovsky and contributors. The
lexer (SimpleParser.Lexer) handles all token-level details including modern
Delphi syntax (multi-line strings, inline vars, generic constraints, etc.) and
made this project possible.
Its lexer is vendored in vendor\DelphiAST\, so building YADF from source needs
no separate DelphiAST checkout.
DelphiAST is Copyright (c) 2014-2020 Roman Yankovsky (roman@yankovsky.me) et al, released under the Mozilla Public License v2.0.
YADF is released under the Mozilla Public License v2.0. See LICENSE for the
full text. This is the same license as DelphiAST.
Copyright (c) 2026 Alexander Liberov
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/.