moonrockz/krueger/printer does not have a README file

    FormatError

    pub suberror FormatError {
    ParseFailed(Array[
    Diagnostic
    ])
    Unprintable(PrintError)
    } derive(
    Debug
    )

    Why format gives no text.

    test {
    let failed = try @printer.format("module A exposing (a)\n\na = (\n") catch {
    ParseFailed(diagnostics) => diagnostics.length() > 0
    Unprintable(_) => false
    } noraise {
    _ => false
    }
    inspect(failed, content="true")
    }

    PrintError

    pub suberror PrintError {
    PrintError(path~ :
    NodePath
    , problem~ : PrintProblem)
    } derive(
    Debug
    )

    An AST that cannot print as valid Elm. path leads from the node given to the printer to the bad node, with elm-syntax JSON field names (see NodePath); for print_file it starts at the file.

    Layout

    pub(all) enum Layout {
    ElmFormat
    Width(Int)
    } derive(Eq,
    Debug
    )

    How the formatter chooses line breaks.

    • ElmFormat: as elm-format 0.8.7: a construct is multi-line when the source has a line break where elm-format looks, or when a part of it is multi-line. There is no line width. Literals keep the form of their source text: a triple-quoted string stays triple-quoted, and a float with an exponent keeps one (1E3 gives 1.0e3).
    • Width(n): a construct is multi-line when it does not fit in n columns (the layout of print_file). Literals print from their value.

    Both layouts print the comments of the source.

    test {
    let source = "module A exposing (a)\n\na = [ 1\n , 2 ]\n"
    inspect(
    @printer.format(source, layout=ElmFormat),
    content=(
    #|module A exposing (a)
    #|
    #|
    #|a =
    #| [ 1
    #| , 2
    #| ]
    #|
    ),
    )
    inspect(
    @printer.format(source, layout=Width(80)),
    content=(
    #|module A exposing (a)
    #|
    #|
    #|a =
    #| [ 1, 2 ]
    #|
    ),
    )
    }

    NameKind

    pub(all) enum NameKind {
    Lower
    Upper
    Operator
    } derive(Eq,
    Debug
    )

    The kind of name that a PrintProblem::InvalidName is about.

    PrintProblem

    pub(all) enum PrintProblem {
    InvalidName(NameKind, String)
    EmptyModuleName
    NonFiniteFloat(Double)
    UnrepresentableInt(Int64)
    NegativePattern
    ShortApplication
    ShortTuple
    NoFields
    EmptyExposing
    EmptyCase
    EmptyLet
    NoConstructors
    NoLambdaArguments
    UnknownOperator(String)
    InvalidPrecedence(Int)
    InvalidGlsl
    InvalidDocumentation
    LetDocumentation
    TooDeep
    UnplacedComment(
    Range
    )
    } derive(Eq,
    Debug
    )

    Why an AST cannot print as valid Elm.

    format

    fn format(source : String, layout? : Layout, dialect? :
    Dialect
    ) -> String raise FormatError

    Formats Elm source as elm-format 0.8.7 does (layout=ElmFormat, the default), or with the line-width layout of print_file (layout=Width(n)).

    • The output is the source of normalize_file(ast): exposed items and imports in elm-format's order, elm-format's parentheses and literal forms, and doc comments with their Markdown and Elm code formatted.
    • Every regular comment prints once, where elm-format puts it: a comment leads the node after it, trails the node before it on its line, or goes before the closing token of its container. A comment moves with an exposed item or an import that elm-format reorders. A block comment gets elm-format's form ({-a-} gives {- a -}). No comment is dropped: one that the printer cannot place raises Unprintable with UnplacedComment.
    • Raises ParseFailed when the source has a syntax error in dialect.

    format(format(s)) == format(s), except for the doc comments on which elm-format 0.8.7 is not idempotent (see normalize_file).

    test {
    let source = "module A exposing (a)\n\na = f x -- why\n y\n"
    inspect(
    @printer.format(source),
    content=(
    #|module A exposing (a)
    #|
    #|
    #|a =
    #| f x
    #| -- why
    #| y
    #|
    ),
    )
    }

    format_parsed

    Formats a parse result, as format formats source. Give the dialect that parsed it: the printer uses its operator table. Raises ParseFailed when the result has a diagnostic with severity Error. Use it when the parse result is already there, for example after a check of its diagnostics.

    test {
    let result = @parser.parse_module(
    @scanner.SourceText::new("module A exposing (a)\n\na = [1,2]\n"),
    @scanner.DefaultScanner::new(),
    )
    inspect(
    @printer.format_parsed(result, layout=Width(40)),
    content=(
    #|module A exposing (a)
    #|
    #|
    #|a =
    #| [ 1, 2 ]
    #|
    ),
    )
    }

    normalize_file

    The file in the order and with the parentheses that print_file gives it (elm-format's, and the ones that the parser needs). For a parsed file, parsing the output of print_file gives this AST (without ranges and regular comments). format gives this AST too, with two differences that come from elm-format: it keeps parentheses that have a comment inside them, and it keeps a chain of operators of the same precedence and different directions flat (a |> f <| g), where this AST has (a |> f) <| g, as print_file writes it (elm make rejects the flat chain). dialect gives the operator table.

    • The module's exposed items are a set: a duplicate is removed (T and T(..) give T(..)). They are sorted: operators, then types, then values, each by name. When the module documentation has @docs lines, the items that they name come first, in the order of those lines.
    • The imports are sorted by module name. The imports of one module are merged into the first: a later alias replaces an earlier one, and the exposing lists are joined ((..) wins). An alias equal to the module name is removed (import A as A). Each exposing list is a sorted set, as above.
    • Parentheses that neither the parser nor elm-format needs go (elm-format formatExpression, syntaxParens): case (f x) of gives case f x of, a - (f b) gives a - f b, f (-x) gives f -x. Parentheses around an operator chain in a chain stay.
    • Parentheses are added where print_file writes them: elm-format's, around a lambda, if, case or let at the end of an operator chain, except after <| (x |> (\y -> y)), and around a constructor pattern with arguments before or after :: and in an as ((Just a) :: rest); and the ones that the parser needs, for example around an operator chain that cannot join its parent's chain ((a + b) * c).

    • Doc comments (documentation and the doc comments in File.comments) are written as elm-format writes them: Markdown through @markdown.format_doc, Elm code in them formatted (see format). As elm-format, this is not idempotent for some doc comments: @docsT(..) (it names no exposed item, then it names T(..) as T), emphasis that starts after a letter (a*b c* gives a_b c_, then text), two bullet lists in a row (one list the next time), [a] [a] (one link), and a fenced Elm code block that starts with a blank line (the line goes the next time).

    Nodes keep their ranges; an expression that replaces its parentheses takes their range. Nothing else changes.

    test {
    let text = "module A exposing (b, a)\n\nimport C\nimport B as B\n\n\na =\n 1\n"
    let file = @parser.parse_module(
    @scanner.SourceText::new(text),
    @scanner.DefaultScanner::new(),
    ).ast.unwrap()
    inspect(
    @printer.print_file(@printer.normalize_file(file)),
    content=(
    #|module A exposing (a, b)
    #|
    #|import B
    #|import C
    #|
    #|
    #|a =
    #| 1
    #|
    ),
    )
    }

    The Elm source of a declaration, without a trailing line feed. A port's doc comment is not part of its declaration (elm-syntax keeps it in File.comments), so only print_file prints it.

    test {
    let r : @ast.Range = {
    start: { row: 0, column: 0, },
    end: { row: 0, column: 0, },
    }
    let int : @ast.Node[@ast.TypeAnnotation] = {
    range: r,
    value: Typed({ range: r, value: ([][:], "Int"), }, [][:]),
    }
    let decl : @ast.Node[@ast.Declaration] = {
    range: r,
    value: AliasDeclaration({
    documentation: None,
    name: { range: r, value: "Age", },
    generics: [][:],
    type_annotation: int,
    }),
    }
    inspect(
    @printer.print_declaration(decl),
    content=(
    #|type alias Age =
    #| Int
    ),
    )
    }

    The Elm source of an expression, with elm-format's parentheses: parentheses are added where precedence needs them and where elm-format writes them (a lambda, if, case or let at the end of an operator chain, except after <|); parentheses in the AST that neither needs go (see normalize_file). if, case and let are always multi-line (elm-format). Raises PrintError for an expression that cannot print as valid Elm; the error's path starts at e.

    test {
    let r : @ast.Range = {
    start: { row: 0, column: 0, },
    end: { row: 0, column: 0, },
    }
    fn v(name : String) -> @ast.Node[@ast.Expression] {
    { range: r, value: FunctionOrValue([][:], name), }
    }
    let sum : @ast.Node[@ast.Expression] = {
    range: r,
    value: OperatorApplication("+", Left, v("a"), v("b")),
    }
    let product : @ast.Node[@ast.Expression] = {
    range: r,
    value: OperatorApplication("*", Left, sum, v("c")),
    }
    inspect(@printer.print_expression(product), content="(a + b) * c")
    }
    fn print_file(file :
    File
    , width? : Int, dialect? :
    Dialect
    ) -> String raise PrintError

    The Elm source of a file in the elm-format layout, ending with one line feed. Fixed shapes (declaration bodies, custom types, if, case, let) are always multi-line; other constructs stay on one line when they fit in width columns. Prints documentation and the doc comments of File.comments (module and port documentation), not regular comments. It prints normalize_file(file): the exposing lists and the imports in elm-format's order, with a module's exposing list grouped by the @docs lines of its documentation. Doc comments are written as elm-format writes them (Markdown and Elm code formatted, LF line ends; see @markdown.format_doc). As elm-format, this is not idempotent for some doc comments (see normalize_file). Text inside GLSL is written as it is, line ends included. Raises PrintError for an AST that cannot print as valid Elm; the error's path starts at the file.

    UnConsPattern(a, AsPattern(b, c)) prints as a :: b as c, which elm make 0.19.1 and elm-format read as (a :: b) as c (see print_pattern).

    test {
    let text = "module Main exposing (main)\n\n\nmain =\n 1 + 2\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(text),
    @scanner.DefaultScanner::new(),
    )
    inspect(@printer.print_file(result.ast.unwrap()) == text, content="true")
    }

    The Elm source of a pattern. Raises PrintError for a pattern that Elm cannot write (a negative number, a bad name, a tuple with one item); the error's path starts at p.

    UnConsPattern(a, AsPattern(b, c)) prints as a :: b as c, because krueger and elm-syntax read that text back as the same AST. elm make 0.19.1 and elm-format read a :: b as c as (a :: b) as c. To bind only the tail, use ParenthesizedPattern: a :: (b as c).

    test {
    let r : @ast.Range = {
    start: { row: 0, column: 0, },
    end: { row: 0, column: 0, },
    }
    fn pvar(name : String) -> @ast.Node[@ast.Pattern] {
    { range: r, value: VarPattern(name), }
    }
    let inner : @ast.Node[@ast.Pattern] = {
    range: r,
    value: UnConsPattern(pvar("a"), pvar("b")),
    }
    let outer : @ast.Node[@ast.Pattern] = {
    range: r,
    value: UnConsPattern(inner, pvar("rest")),
    }
    // `::` is right-associative, so an `::` on its left gets parentheses.
    inspect(@printer.print_pattern(outer), content="(a :: b) :: rest")
    // elm-format writes a constructor with arguments before `::` in
    // parentheses.
    let just : @ast.Node[@ast.Pattern] = {
    range: r,
    value: NamedPattern({ module_name: [][:], name: "Just", }, [pvar("a")][:]),
    }
    inspect(
    @printer.print_pattern({
    range: r,
    value: UnConsPattern(just, pvar("rest")),
    }),
    content="(Just a) :: rest",
    )
    }

    The Elm source of a type annotation, laid out in width columns where elm-format allows a choice. Parentheses are added where precedence needs them. Raises PrintError when the type cannot print as valid Elm; the error's path starts at t.

    test {
    let r : @ast.Range = {
    start: { row: 0, column: 0, },
    end: { row: 0, column: 0, },
    }
    let a : @ast.Node[@ast.TypeAnnotation] = {
    range: r,
    value: GenericType("a"),
    }
    let maybe : @ast.Node[@ast.TypeAnnotation] = {
    range: r,
    value: Typed({ range: r, value: ([][:], "Maybe"), }, [a][:]),
    }
    let list : @ast.Node[@ast.TypeAnnotation] = {
    range: r,
    value: Typed({ range: r, value: ([][:], "List"), }, [maybe][:]),
    }
    inspect(@printer.print_type_annotation(list), content="List (Maybe a)")
    }

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io