aboutsummaryrefslogtreecommitdiffstats
open Core
open Types
open PPrint

let rec escape_chlist = function
  | [] -> []
  | '$' :: '{' :: l' -> '\\' :: '$' :: '{' :: escape_chlist l'
  | '\n' :: l' -> '\\' :: 'n' :: escape_chlist l'
  | '\r' :: l' -> '\\' :: 'r' :: escape_chlist l'
  | '\t' :: l' -> '\\' :: 't' :: escape_chlist l'
  | '\\' :: l' -> '\\' :: '\\' :: escape_chlist l'
  | '"' :: l' -> '\\' :: '"' :: escape_chlist l'
  | c :: l' -> c :: escape_chlist l'

let escape_string s = s |> String.to_list |> escape_chlist |> String.of_list
let out_width = ref 80
let set_width i = out_width := i
let indent = ref 2
let set_indent i = indent := i

let rec doc_of_expr = function
  | BinaryOp (op, lhs, rhs) ->
      let lhs_doc = maybe_parens_bop op `Left lhs
      and rhs_doc = maybe_parens_bop op `Right rhs in
      infix !indent 1 (doc_of_bop op) lhs_doc rhs_doc
  | UnaryOp (op, e) -> precede (doc_of_uop op) (maybe_parens (prec_of_uop op) e)
  | Cond (e1, e2, e3) ->
      surround !indent 1
        (soft_surround !indent 1 (string "if") (doc_of_expr e1) (string "then"))
        (doc_of_expr e2)
        (string "else" ^^ nest !indent (break 1 ^^ doc_of_expr e3))
  | With (e1, e2) ->
      flow (break 1) [ string "with"; doc_of_expr e1 ^^ semi; doc_of_expr e2 ]
  | Assert (e1, e2) ->
      flow (break 1) [ string "assert"; doc_of_expr e1 ^^ semi; doc_of_expr e2 ]
  | Test (e, path) ->
      maybe_parens 4 e ^^ space ^^ string "?"
      ^^ group (break 1 ^^ separate_map dot doc_of_expr path)
  | SetLet bs ->
      surround !indent 1
        (string "let " ^^ lbrace)
        (group (separate_map (break 1) doc_of_binding bs))
        rbrace
  | Let (bs, e) ->
      surround !indent 1 (string "let")
        (separate_map (break 1) doc_of_binding bs)
        (prefix !indent 1 (string "in") (doc_of_expr e))
  | Val v -> doc_of_val v
  | Id id -> string id
  | Select (e, path, oe) ->
      maybe_parens 1 e ^^ dot ^^ doc_of_attpath path
      ^^ optional
           (fun e ->
             space ^^ string "or" ^^ nest !indent (break 1 ^^ maybe_parens 1 e))
           oe
  | Apply (e1, e2) -> prefix !indent 1 (maybe_parens 2 e1) (maybe_parens 2 e2)
  | Aquote e -> surround !indent 0 (string "${") (doc_of_expr e) (string "}")

and maybe_parens lvl e =
  if prec_of_expr e >= lvl then surround !indent 0 lparen (doc_of_expr e) rparen
  else doc_of_expr e

and maybe_parens_bop op (loc : [ `Left | `Right ]) e =
  match (loc, assoc_of_bop op) with
  | (`Left, Some Left | `Right, Some Right)
    when prec_of_expr e >= prec_of_bop op ->
      doc_of_expr e
  | _, _ -> maybe_parens (prec_of_bop op) e

and doc_of_attpath path = separate_map dot doc_of_expr path

and doc_of_paramset (params, kind) =
  let ps =
    List.map ~f:doc_of_param params
    @ if Poly.(kind = Loose) then [ string "..." ] else []
  in
  surround !indent 0 lbrace (separate (comma ^^ break 1) ps) rbrace

and doc_of_param (id, oe) =
  string id ^^ optional (fun e -> qmark ^^ space ^^ doc_of_expr e) oe

and doc_of_binding = function
  | AttrPath (path, e) ->
      doc_of_attpath path ^^ space ^^ equals ^^ space ^^ doc_of_expr e ^^ semi
  | Inherit (oe, ids) ->
      let id_docs =
        List.map
          ~f:(function
            | Id x | Val (Str (x, [])) -> string x | _ -> assert false)
          ids
      in
      let xs =
        flow (break 1)
          (match oe with
          | Some e -> parens (doc_of_expr e) :: id_docs
          | None -> id_docs)
      in
      soft_surround !indent 0 (string "inherit" ^^ space) xs semi

and doc_of_bop = function
  | Plus -> plus
  | Minus -> minus
  | Mult -> star
  | Div -> slash
  | Gt -> rangle
  | Lt -> langle
  | Lte -> string "<="
  | Gte -> string ">="
  | Eq -> string "=="
  | Neq -> string "!="
  | Or -> string "||"
  | And -> string "&&"
  | Impl -> string "->"
  | Merge -> string "//"
  | Concat -> string "++"

and doc_of_uop = function Negate -> minus | Not -> bang

and doc_of_val = function
  | Str (start, xs) ->
      dquotes
        (string (escape_string start)
        ^^ concat
             (List.map
                ~f:(fun (e, s) ->
                  surround !indent 0 (string "${") (doc_of_expr e)
                    (string "}" ^^ string (escape_string s)))
                xs))
  | IStr (i, start, xs) ->
      let qq = string "''" in
      let str s =
        String.split ~on:'\n' s
        |> List.map ~f:(fun s ->
               let len = String.length s in
               let s' =
                 if len >= i then String.sub s ~pos:i ~len:(len - i) else s
               in
               string s')
        |> separate hardline
      in
      enclose qq qq
        (str start
        ^^ concat
             (List.map
                ~f:(fun (e, s) ->
                  enclose (string "${") rbrace (doc_of_expr e) ^^ str s)
                xs))
  | Int x | Float x | Path x | SPath x | HPath x | Uri x -> string x
  | Lambda (pattern, body) ->
      let pat =
        match pattern with
        | Alias id -> string id
        | ParamSet (None, ps) -> doc_of_paramset ps
        | ParamSet (Some id, ps) ->
            doc_of_paramset ps ^^ group (break 1 ^^ at ^^ break 1 ^^ string id)
      in
      flow (break 1) [ pat ^^ colon; doc_of_expr body ]
  | List [] -> lbracket ^^ space ^^ rbracket
  | List es ->
      surround !indent 1 lbracket
        (separate_map (break 1) (maybe_parens 2) es)
        rbracket
  | AttSet (Nonrec, []) -> lbrace ^^ space ^^ rbrace
  | AttSet (Nonrec, bs) ->
      surround !indent 1 lbrace
        (group (separate_map (break 1) doc_of_binding bs))
        rbrace
  | AttSet (Rec, bs) ->
      string "rec" ^^ space ^^ doc_of_val (AttSet (Nonrec, bs))

let print chan expr = ToChannel.pretty 0.7 !out_width chan (doc_of_expr expr)

let to_string expr =
  let buf = Stdlib.Buffer.create 0 in
  ToBuffer.pretty 0.7 !out_width buf (doc_of_expr expr);
  Stdlib.Buffer.contents buf