packages feed

anatomy-0.3: lib/pretty.atomo

-- root URL for links
define: docs-root as: ""

-- current pretty-printer indentation level
define: indent-level as: 0

-- allow multiline pretty-printing
define: multiline-pretty? as: False

-- context for a pretty-print
define: context as: @top

-- output as values? (i.e. <expression 1 + 1> instead of 1 + 1)
define: value-output? as: False


-- indent s up to the current indentation level
indented: (s: String) :=
  ($  repeat: (indent-level * 2)) .. s

-- an empty line, followed by (indentation - 1) indentation
unindent :=
  "\n" .. ($  repeat: ((indent-level - 1) * 2))


-- default pretty-printer; just do @show
A pretty: o :=
  { s = o show

    if: (s starts-with?: "<")
      then: { s escape type: "internal" }
      else: { s escape }
  } call

-- primitive pretty-printers
A pretty: (c: Char) := c show escape type: "char"
A pretty: (b: Boolean) := b show escape type: "keyword boolean"
A pretty: (d: Double) := d show escape type: "number double"
A pretty: (i: Integer) := i show escape type: "number integer"
A pretty: (r: Rational) := r show escape type: "number rational"
A pretty: (c: String) := c show escape type: "string"

-- pattern pretty-printing; lop off "<pattern [...]>" normally
A pretty: (p: Pattern) :=
  if: value-output?
    then: { p show escape type: "internal" }
    else: {
      p show (drop: "<pattern " length) init escape
        type: "pattern"
    }

-- pretty-print both sides of an Association
(a: A) pretty: (as: Association) :=
  (a pretty: as from) .. " -> " escape .. (a pretty: as to)

(a: A) pretty: (p: Particle) :=
  p type match: {
    @single -> ("@" .. p name escape (type: "name")) type: "particle"
    @keyword -> a pretty-particle: p
  }

(a: A) pretty: (l: List) :=
  ("[" .. l (map: { l | a pretty: l }) (join: ", ") .. "]") type: "list"

(a: A) pretty: (b: Block) :=
  with: [
    context -> @top
    value-output? -> False
    indent-level -> indent-level + 1
  ] do: {
    arguments = b arguments (map: { p | a pretty: p }) (join: " ")

    pretty-contents = b contents (map: { e | a pretty: e })

    contents =
      if: multiline-pretty?
        then: {
          exprs = pretty-contents (map: { c | "\n" .. (indented: c) }) join strip-end
          exprs .. unindent
        }
        else: { " " .. pretty-contents (join: "; ") .. " " }

    condition: {
      contents strip empty? && b arguments empty? ->
        "{ }" type: "block"

      contents strip empty? ->
        ("{ " .. arguments .. " | }") type: "block"

      b arguments empty? ->
        ("{" .. contents .. "}") type: "block"

      otherwise ->
        ("{ " .. arguments .. " |" .. contents .. "}") type: "block"
    }
  }

(a: A) pretty: (e: Expression) :=
  { pretty = e type match: {
      @(dispatch: type) ->
        with: value-output? as: False do: {
          a pretty-dispatch: e type: type
        }

      @define ->
        with: [
          value-output? -> False
          context -> @define
        ] do: {
          pat = a pretty: e pattern

          if: multiline-pretty?
            then: {
              with: [
                indent-level -> indent-level + 1
                context -> @define
              ] do: {
                expr = a pretty: e expression
                pat .. " :=\n" .. (indented: expr) .. "\n"
              }
            }
            else: {
              with: context as: @define do: {
                expr = a pretty: e expression
                pat .. " := " .. expr
              }
            }
        }

      @set ->
        with: [
          value-output? -> False
          context -> @set
        ] do: {
          (a pretty: e pattern) .. " = " .. (a pretty: e expression)
        }

      @list ->
        with: [
          value-output? -> False
          context -> @list
        ] do: {
          "[" .. e contents (map: { l | a pretty: l }) (join: ", ") .. "]"
        }

      @(particle: @single) -> a pretty: (a evaluate: e)

      @(particle: @keyword) ->
        with: context as: @particle do: {
          a pretty-particle: e
        }

      @primitive ->
        with: value-output? as: True do: {
          a pretty: (a evaluate: e)
        }

      @block ->
        with: context as: @block do: {
          a pretty: (a evaluate: e)
        }

      @top -> "this" type: "keyword top"

      @macro ->
        [ "macro" type: "keyword macro"
          "(" .. (a pretty: e pattern) .. ")"
          a pretty: e expression
        ] unwords

      @operator ->
        [ "operator" type: "keyword operator"
          e associativity name
          a pretty: e precedence
          e operators
            (map: { o | a linked: `~o as: o names head })
            unwords
        ] unwords

      @for-macro ->
        [ "for-macro" type: "keyword for-macro"
          a pretty: e expression
        ] unwords

      @quote ->
        with: [
          value-output? -> False
          context -> @quote
        ] do: {
          "`" .. (a pretty-segment: e expression)
        }

      @unquote ->
        with: [
          value-output? -> False
          context -> @unquote
        ] do: {
          "~" .. (a pretty-segment: e expression)
        }

      x -> e show (drop: 2) init escape -- remove '( and )
    }

    condition: {
      value-output? not -> pretty

      e needs-parens? ->
        "'(" .. pretty .. ")"

      otherwise -> "'" .. pretty
    }
  } call

(e: Expression) needs-parens? :=
  e type match: {
    @(dispatch: @single) ->
      e target type /= @top

    x -> x in?: [@define, @set, @(dispatch: @keyword)]
  }

(a: A) pretty-segment: (e: Expression) :=
  if: e needs-parens?
    then: { "(" .. (a pretty: e) .. ")" }
    else: { a pretty: e }

(a: A) pretty-dispatch: e type: @single :=
  with: context as: @single do: {
    msg = (a linked: e as: e name) type: "dispatch single"

    if: (e target type == @top)
      then: { msg }
      else: { (a pretty: e target) .. " " .. msg }
  }

(a: A) pretty-dispatch: e type: @keyword :=
  condition: {
    e targets empty? ->
      (a linked: e as: e names (map: @keywordfy) join)
        type: "dispatch keyword"

    e names == [":="] ->
      with: context as: @define do: {
        pat = a pretty: e targets head
        exp = e targets (at: 1)
        key = a linked: e as: ":="

        if: multiline-pretty?
          then: {
            modify: indent-level as: @(+ 1) do: {
              expr = a pretty: exp
              pat .. " " .. key .. "\n" .. (indented: expr) .. "\n"
            }
          } else: {
            pat .. " " .. key .. " " .. (a pretty: exp)
          }
      }

    e names == ["="] ->
      with: context as: @set do: {
        pat = a pretty: e targets head
        exp = e targets (at: 1)
        key = a linked: e as: "="

        pat .. " " .. key .. " " .. (a pretty: exp)
      }

    otherwise ->
      { from = context

        with: context as: @keyword do: {
          initial =
            if: (e targets head type == @top)
              then: { "" }
              else: { (a pretty: e targets head) .. " " }

          rest =
            (0 ... e names length) (map: { n |
              name = e names (at: n)
              value = a pretty: e targets (at: (n + 1))

              (a linked: e as: name keywordfy) .. " " .. value
            }) join: " "

          (if: (from == @single || from == @keyword)
            then: { "(" .. initial .. rest .. ")" }
            else: { initial .. rest })
            type: "dispatch keyword"
        }
      } call
  }

(a: A) pretty-particle: p :=
  if: p values (all?: @(== @none))
    then: {
      ("@" ..
        p names
          (map: { n | n keywordfy escape (type: "names") })
          join
      ) type: "particle"
    }
    else: {
      initial =
        p values head match: {
          @none -> ""
          @(ok: v) -> a pretty: v
        }

      ks = (0 ... p names length) map:
        { n |
          name = p names (at: n)
          value =
            p values (at: (n + 1)) match: {
              @none -> "_"
              @(ok: v) -> a pretty: v
            }

          name keywordfy escape (type: "names") .. " " .. value
        }

      ("@(" .. initial .. ks (join: " ") .. ")") type: "particle"
    }


-- find a url for an expression, and link it with t as the text
(a: A) linked: (e: Expression) as: (t: String) :=
  a (url-for: e) match: {
    @none -> t escape
    @(ok: u) ->
      t escape in-tag: ("a href=\"" .. docs-root .. u .. "\"")
  }