Implement a formatter for the Dart programming language as a Haskell program. Byte-for-byte output fidelity against the reference Dart formatter (dart_style 3.1.4) is what is graded.
There is no Dart SDK in this environment or in the verifier — you are building the formatter itself, from scratch, in Haskell.
Deliverable
A buildable cabal project at /app/formatter whose executable target is
named dartfmt. The verifier builds it with
cabal build --offline exe:dartfmt
locates the binary via cabal list-bin dartfmt, and runs it once per test
case. If the offline build fails, the score is 0 — keep the project
buildable at all times.
CLI contract
dartfmt reads Dart source on stdin and writes the formatted result to
stdout, exit code 0. Flags (every invocation passes them explicitly):
--statement|--compilation-unit— parse the input as a single statement (function-body context) or as a whole compilation unit.--page-width N— target line width in columns.--indent N— number of leading spaces of extra indentation to apply to every line of output.--language-version M.m— Dart language version governing both grammar and style (see below).--trailing-commas automate|preserve— trailing-comma handling mode.--enable-experiment <name>— may be passed zero or more times (experiment flags a Dart parser may need to accept).
Output contract: with --compilation-unit the output ends with exactly one
trailing newline; with --statement it ends with no trailing newline.
A non-zero exit code fails the case.
Style semantics
The reference formatter implements two styles selected by
--language-version:
- >= 3.7 — the "tall" style (the modern formatting style),
- <= 3.6 — the "short" style (the older formatting style).
The authoritative behavioral specification is the visible test battery at
/app/visible — real reference-formatter test fixtures with golden
outputs produced by dart_style 3.1.4. Fixture file format: an optional
first line whose | marks the page width; >>> starts a case (options and
description may follow on the header line); <<< starts the expected
output (optionally version-tagged <<< M.m); ### lines are comments;
×hh escapes a Unicode code point. .stmt files hold statements, .unit
files whole compilation units. /app/harness.py (the same runner the
hidden pipeline uses) parses all of this for you — you do not need to
parse fixtures yourself.
Scoring
After your run ends, the verifier scores your binary against a hidden pipeline that is a strict superset of the visible battery: every visible case is in it (at roughly 10% of the total weight), plus corner-case fixtures, a real-world Dart package corpus, and generator-produced corpus covering modern language features (records, patterns, switch expressions, cascades, null-aware elements, multiline strings with interpolation, tight page-width geometries, comment placements, and more). Expect roughly ten times the visible case count.
Each case contributes two scoring units:
- match — your output equals the golden output byte-for-byte;
- idempotence — counted only on top of a match: feeding your own output back through your formatter (same options) must return it unchanged. A formatter whose output is not a fixed point of itself loses this unit even when the first pass matched.
Score = passed units / total units — a raw, continuous pass rate. Feedback is aggregate only (overall, per-bucket and per bucket/layout subgroup rates — enough to see WHERE misses concentrate); no per-case identities or goldens are returned.
Grading budget
Declared so you can size your implementation; all values are the verifier's.
| Stage | Verifier budget | Your selfcheck.py |
|---|---|---|
offline cabal build --offline -j8 exe:dartfmt |
2400 s (exceeded ⇒ score 0) | no time limit at all |
per invocation of your dartfmt (one case = 1–2 invocations) |
30 s, then SIGKILL | 30 s |
| whole scoring phase over the hidden pipeline | 5400 s; cases that never run count as failed | n/a (945 visible cases) |
| whole verifier stage | 12600 s wall-clock, 8 CPUs / 4 GiB | your container is also 8 CPUs / 4 GiB |
The per-invocation cap is now the same 30 s locally and at grading, so the self-check no longer flatters a slow formatter case by case. One asymmetry remains: the self-check puts no limit on the build, while grading caps it at 2400 s. A case killed at the per-invocation cap simply scores as a failed unit (it is never reported as a grader error), and so does every case that never ran because the whole scoring phase hit its budget.
python3 /app/selfcheck.py builds your project offline and scores it on
the visible battery with exactly these semantics. The visible battery is
deliberately the basic-functionality slice: matching it is necessary but
far from sufficient — the hidden mass is dominated by edge cases. Handling
what the visible spec implies (width-splitting decisions, comments,
strings, the full expression and declaration grammar) is your job even
where no visible case shows it.
What you can use
- GHC 9.6 and cabal (offline; the package store is pre-warmed).
- Haskell packages, pinned in
/app/formatter/cabal.project.freeze: megaparsec, parsec, attoparsec, text, bytestring, containers, unordered-containers, hashable, vector, mtl, transformers, optparse-applicative, prettyprinter, split, extra, array, deepseq, directory, filepath, process (plus the GHC boot libraries), and thealex/happyparser generators. Keep the freeze file — only these versions exist in the offline store. /app/visible,/app/harness.py,/app/selfcheck.py.
What you cannot use
- Any existing Dart formatter or Dart SDK, in any form: no FFI, no embedding, no shelling out (none exists in either environment — the rule is enforced physically).
- Network access (there is none, in either environment).
- Anything other than Haskell for the deliverable: the graded artifact is
the
dartfmtbinary built by cabal from your Haskell sources.
Work autonomously; do not stop to ask for input.