tweag / topiary

https://topiary.tweag.io/
MIT License
575 stars 29 forks source link

Uniformity of handling of operators #657

Open Niols opened 11 months ago

Niols commented 11 months ago

Is your feature request related to a problem? Please describe.

I would like operators in OCaml to be handled in a consistent and predictable way. For now, this does not seem to be the case. I don't think the way they should be handled has been clearly defined, though, so maybe we should have a first discussion about this. As an example, here are how a bunch of a <op> b <op> c multiline expressions are handled by Topiary:

a#b#c
;;

a * b *
  c
;;

a = b
= c
;;

a **
  b **
    c
;;

a :: b :: c
;;

a @
b @
c

(Note that some of them are not even output in a multi-line way; cf https://github.com/tweag/topiary/issues/658 for the specific case of ::.)

Describe the solution you'd like

I would like there to be clear, simple and syntactic rules on how to format operators. Precedence and priorities are known statically in OCaml so I think we could come up with something pretty good with that. I believe in indentation to show that kind of expressions. For the example above, I would go for:

Left associative:

a
  # b
  # c
;;

a
  * b
  * c
;;

a
  = b
  = c
;;

Right associative on the right, maybe? Although for :: and @ that just looks weird, but I think I'd get used to that pretty easily:

a **
  b **
  c
;;

a ::
  b ::
  c
;;

a @
  b @
  c

Or otherwise on the left if we believe that makes more sense (but it would be equally disturbing for things such as @@):

a
  ** b
  ** c
;;

a
  :: b
  :: c
;;

a
  @ b
  @ c

Additional context

Related to https://github.com/tweag/topiary/issues/261 and https://github.com/tweag/topiary/pull/259#discussion_r1099027655.

Here is a tiny script to generate and call Topiary on various OCaml operators, for quick comparison:

## For an operator `op`, output a sequence `a op b op c` in multiline to
## explicit Topiary's behaviour. For instance, for +, produce:
##
##     let () = a + b +
##              c
##     ;;

g () {
    for op; do
      echo "
        let () = a $op b $op
                 c
      "
    done
}

## Based on table from OCaml manual, section 7.1 Precedence and associativity.
## https://v2.ocaml.org/manual/expr.html#ss%3Aprecedence-and-associativity

{
    echo '(* Left associative *)'

    g '#' '##'
    g '*' '*+' '*.' '/' '/.' '%' 'mod' 'land' 'lor' 'lxor'
    g '+' '+.' '-' '-.'
    g '=' '==' '=>' '<' '<$>' '>' '>>=' '|=' '&=' '$' '!='

    echo '(* Right associative *)'

    g '**' 'lsl' 'lsr' 'asr'
    g '::'
    g '@' '@@' '^' '^='
    g '&' '&&'
    g 'or' '||'
    g '<-' ':='
    g ';'

} | topiary -- format -l ocaml

Here is the output as of https://github.com/tweag/topiary/commit/8cc9aa40d99c89b448190bb1693777128fdcd6c0:

(* Left associative *)

let () =
  a#b#c

let () =
  a##b##c

let () =
  a * b *
    c

let () =
  a *+ b *+
    c

let () =
  a *. b *.
    c

let () =
  a / b /
    c

let () =
  a /. b /.
    c

let () =
  a % b %
    c

let () =
  a mod b mod
    c

let () =
  a land b land
    c

let () =
  a lor b lor
    c

let () =
  a lxor b lxor
    c

let () =
  a + b +
    c

let () =
  a +. b +.
    c

let () =
  a - b -
    c

let () =
  a -. b -.
    c

let () =
  a = b
  = c

let () =
  a == b
  == c

let () =
  a => b
  => c

let () =
  a < b
  < c

let () =
  a <$> b
  <$> c

let () =
  a > b
  > c

let () =
  a >>= b
  >>= c

let () =
  a |= b
  |= c

let () =
  a &= b
  &= c

let () =
  a $ b
  $ c

let () =
  a != b
  != c

(* Right associative *)

let () =
  a **
    b **
      c

let () =
  a lsl
    b lsl
      c

let () =
  a lsr
    b lsr
      c

let () =
  a asr
    b asr
      c

let () =
  a :: b :: c

let () =
  a @
  b @
  c

let () =
  a @@
  b @@
  c

let () =
  a ^
  b ^
  c

let () =
  a ^=
  b ^=
  c

let () =
  a
  & b
  & c

let () =
  a
  && b
  && c

let () =
  a
  or b
  or c

let () =
  a
  || b
  || c

let () =
  a <-
    b <-
      c

let () =
  a :=
    b :=
      c

let () =
  a;
  b;
  c
Niols commented 11 months ago

Note that we could generate the file without let () = ..., using ;; instead, but Topiary currently handles this separator quite poorly. See https://github.com/tweag/topiary/issues/659.

Niols commented 11 months ago

@aspiwack You might have an opinion on this, especially on the interaction of formatting of left-/right-associativity. I remember us discussing this somewhere else as well, with the maintainer of the Tree Sitter grammar in particular.

aspiwack commented 11 months ago

This is the issue you're thinking of, I believe: https://github.com/tweag/topiary/issues/541#issuecomment-1626460317 .

Niols commented 11 months ago

Exactly, thank you! It also links to https://github.com/tweag/topiary/issues/546.