npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

pretty-fast-pretty-printer

v0.10.0

Published

A linear time pretty printing library

Downloads

296

Readme

Pretty Fast Pretty Printer

For an introduction to the Pretty Fast Pretty Printer and how to use it, please see the guide.

This readme is the reference manual.


Pretty printing is an approach to printing source code that can adapt how things are printed to fit within a maximum line width.

To use this library:

  1. Convert the source code you want to print into a document (instance of Doc), using the constructors below. The Doc will encode all possible ways that the source can be printed.
  2. Print the Doc using doc.display(width), where width is the maximum allowed line width. This will return a list of lines.

There are a variety of pretty-printing algorithms. The pretty-fast-pretty-printer uses a custom algorithm that displays a document in time linear in the number of distinct nodes in the document. (Note that this is better than linear in the size of the document: if a document contains multiple references to a single sub-document, that sub-document is only counted once. This can be an exponential improvement.)

The algorithm takes inspiration from:

  1. Wadler's Prettier Printer,
  2. Bernardy's Pretty but not Greedy Printer, and
  3. Ben Lerner's Pyret pretty printer

This pretty printer was developed as part of Codemirror Blocks. Don't let this stop you from using it for your own project, though: it's a general purpose library!

Kinds of Docs

Documents are constructed out of six basic combinators:

txt: Text

txt(string) simply displays string. The string cannot contain newlines. txt("") is an empty document.

For example,

    txt("Hello, world")
      .display(80);

produces:

Hello, world

All other combinators will automatically wrap string arguments in txt. As a result, you can almost always write "a string" instead of txt("a string").

vert: Vertical Concatenation

vert(doc1, doc2, ...) vertically concatenates documents, from top to bottom. (I.e., it joins them with newlines). The vertical concatenation of two documents looks like this:

Vertical concatenation image

For example,

    vert("Hello,", "world!")
      .display(80)

produces:

Vertical concatenation is associative. Thus:

  vert(X, Y, Z)
= vert(X, vert(Y, Z))
= vert(vert(X, Y), Z)

horz: Horizontal Concatenation

horz(doc1, doc2, ...) horizontally concatenates documents. The second document is indented to match the last line of the first document (and so forth for the third document, etc.). The horizontal concatention of two documents looks like this:

Horizontal concatenation image

For example,

    horz("BEGIN ", vert("first line", "second line"))
      .display(80)

produces:

Horizontal concatenation is associative. Thus:

  horz(X, Y, Z)
= horz(X, horz(Y, Z))
= horz(horz(X, Y), Z)

horzArray(docArray) is a variant of horz that takes a single argument that is an array of documents. It is equivalent to horz.apply(null, docArray).

concat: Simple Concatenation

concat(doc1, doc2, ...) naively concatenates documents from left to right. It is similar to horz, except that the indentation level is kept fixed for all of the documents. The simple concatenation of two documents looks like this:

Simple concatenation image

You should almost always prefer horz over concat.

As an example,

    concat("BEGIN ", vert("first line", "second line"))
      .display(80))

produces:

concatArray(docArray) is a variant of concat that takes a single argument that is an array of documents. It is equivalent to concat.apply(null, docArray).

ifFlat: Choose between two Layouts

ifFlat(doc1, doc2) chooses between two documents. It will use doc1 if it fits entirely on the current line, otherwise it will use doc2. More precisely, doc1 will be used iff:

  1. It can be rendered flat. A "flat" document has no newlines, i.e., no vert. And,
  2. When rendered flat, it fits on the current line without going over the pretty printing width. NOTE: this counts only the portion of the current line contained in the ifFlat and to its left; it does not count anything to its right. Mostly this doesn't matter much, but it can result in a document being printed over multiple lines when one would do. If this ends up being an issue, try moving more of the Doc into the ifFlat choice.

fullLine: Prevent More on the Same Line

Finally, fullLine(doc) ensures that nothing is placed after doc, if at all possible. More specifically, ifFlat choices will be made such that nothing appears to the right of the fullLine(doc), if at all possible. However, if something is displayed unconditionally to the right of the fullLine, this library will have no choice but to put it there.

This is helpful for line comments. For example, fullLine("// comment") will ensure that (if at all possible) nothing is placed after the comment.

Other Constructors

Besides the combinators, there are some other useful "utility" constructors. These constructors don't provide any extra power, as they are all defined in terms of the combinators described above. But they capture some useful patterns.

String Templates

There is also a string template shorthand for building a doc, called pretty. It accepts template strings that may contain newlines. It combines the lines with vert, and the parts of each line with horz, returning a Doc. For example, this template:

    let c = "a == b";
    let t = "a << 2";
    let e = "a + b";

    pretty`if (${c}) {\n  ${t}\n} else {\n  ${e}\n}`
      .display(80)

pretty prints an if statement across multiple lines:

if (a == b) {
  a << 2
} else {
  a + b
}

SepBy

sepBy(items, sep, vertSep="") will display either:

items[0] sep items[1] sep ... items[n]

if it fits on one line, or:

items[0] vertSep \n items[1] vertSep \n ... items[n]

otherwise. (Without the extra spaces; those are there for readability.)

Neither sep nor vertSep may contain newlines.

Wrap

wrap(words, sep=" ", vertSep="") does word wrapping. It combines the words with sep when they fit on the same line, or vertSep\n when they don't.

For simple word wrapping, you would use:

wrap(words, " ", "") // or just wrap(words)

For word-wrapping a comma-separated list, you would use:

wrap(words, ", ", ",")

Neither sep nor vertSep may contain newlines.

S-Expression Constructors

There are also some constructors for common kinds of s-expressions:

Standard

standardSexpr(func, args) is rendered like this:

 (func args ... args)

or like this:

 (func
  args
  ...
  args)

Lambda-like

lambdaLikeSexpr(keyword, defn, body) is rendered like this:

(keyword defn body)

or like this:

(keyword defn
  body)

Begin-like

beginLikeSexpr(keyword, bodies) is rendered like this:

(keyword
  bodies
  ...
  bodies)