Formatter
The Formatter package takes a parsed Gren source file and produces a
canonically formatted string. The parsing is done by the
gren-lang/compiler-common package.
Calling it
There is one function to call, prettyPrint. It takes the two things the
compiler-common parser gives you
for a source file — the syntax tree and the parse context (the comments) — and
returns the formatted text, or an error string:
prettyPrint : Src.Module -> Ctx.Context -> Result String String
So your code runs the parser first, then passes both of its results to the
formatter. This is what gren-format itself does, after reading the file from
disk:
module FormatFile exposing (format)
import Compiler.Parse.Context as Context
import Compiler.Parse.Module as PM
import Formatter
import String.Parser.Advanced as Parser
-- Format the contents of one Gren source file.
format : String -> Result String String
format source =
let
parser =
Parser.succeed (\ast context -> { ast = ast, context = context })
|> Parser.keep PM.parser
|> Parser.keep Parser.getPayload
in
when Parser.run parser Context.empty source is
Err errs ->
-- the source isn't valid Gren
Err (PM.errorsToString source errs)
Ok { ast, context } ->
-- the AST says what the code means; the context holds every
-- comment and blank line
Formatter.prettyPrint ast context
The parser is run with Context.empty as its starting payload; it fills that
payload in as it goes, and Parser.getPayload retrieves the finished context
once the module is parsed. Both results are needed — the AST alone has no
comments in it.
Input: a syntax tree (Compiler.Ast.Source.Module) and a comment table
(Compiler.Parse.Context.Context) produced by the Gren parser.
Output: a formatted source string.
The work happens in three stages:
-
Build a Logical Printing Tree (MakeLogical, InsertExpressions, Comments, VerticalSpace). The syntax tree is walked and converted into an intermediate tree of layout shapes — nodes that say things like "these pieces go across one line, or each on its own line" or "this block is indented under its header." Comments are woven in from the comment table, and blank lines are inserted between top-level declarations according to the spacing rules.
-
Render to boxes (Formatter.Render.MakeRenderBox). Each layout shape is translated into a
Formatter.Render.Box— the geometry IR ported from elm-format, which knows about tab stops and prefix padding rather than about Gren syntax. Layout is author-driven, not fit-driven: there is no page-width search. Each shape already knows whether it renders inline or vertical (decided from the author's original source rows), so this stage just builds the box that says so. -
Serialize to a string.
Formatter.Renderrenders each top-level declaration's box, right-trims it, and joins the results with newlines.
The key design principle is that layout decisions (where line breaks go, how much indentation) are separated from content (the actual tokens and comments). Stage 1 captures the logical structure and the author's inline/vertical choices; stages 2 and 3 render them.