Skip to content

Commit

Permalink
Improve formatting of function types
Browse files Browse the repository at this point in the history
When a function type is split over several lines, we previously
put the '->' at the end of the line, but now put '->' at the
start of the next line, like this:

  val mapReduce = fn
    : ('a -> ('b * 'c) list)
      -> ('b * 'c list -> 'd) -> 'a list -> ('b * 'd) list

Also add a test that after 'z, type variables are named 'ba,
'bb, etc.
  • Loading branch information
julianhyde committed Dec 13, 2024
1 parent 3c73f2f commit da52b1e
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 17 deletions.
20 changes: 14 additions & 6 deletions src/main/java/net/hydromatic/morel/compile/Pretty.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

import static net.hydromatic.morel.parse.Parsers.appendId;
import static net.hydromatic.morel.util.Pair.forEachIndexed;
import static net.hydromatic.morel.util.Static.endsWith;

import static java.util.Objects.requireNonNull;

Expand Down Expand Up @@ -273,8 +274,16 @@ private StringBuilder pretty2(@NonNull StringBuilder buf,

private StringBuilder prettyType(StringBuilder buf, int indent, int[] lineEnd,
int depth, Type type, TypeVal typeVal, int leftPrec, int rightPrec) {
buf.append(typeVal.prefix);
final int indent2 = indent + typeVal.prefix.length();
String prefix = typeVal.prefix;
if (endsWith(buf, " ")) {
// If the buffer ends with space, don't print any spaces at the start of
// the prefix.
while (prefix.startsWith(" ")) {
prefix = prefix.substring(1);
}
}
buf.append(prefix);
final int indent2 = indent + prefix.length();
final int start;
switch (typeVal.type.op()) {
case DATA_TYPE:
Expand Down Expand Up @@ -350,12 +359,11 @@ private StringBuilder prettyType(StringBuilder buf, int indent, int[] lineEnd,
return buf;
}
final FnType fnType = (FnType) typeVal.type;
pretty1(buf, indent2 + 1, lineEnd, depth, type,
pretty1(buf, indent2, lineEnd, depth, type,
new TypeVal("", fnType.paramType),
leftPrec, Op.FUNCTION_TYPE.left);
pretty1(buf, indent2 + 1, lineEnd, depth, type, " -> ", 0, 0);
pretty1(buf, indent2 + 1, lineEnd, depth, type,
new TypeVal("", fnType.resultType),
pretty1(buf, indent2, lineEnd, depth, type,
new TypeVal(" -> ", fnType.resultType),
Op.FUNCTION_TYPE.right, rightPrec);
return buf;

Expand Down
6 changes: 5 additions & 1 deletion src/main/java/net/hydromatic/morel/parse/Parsers.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,14 @@ public static char unquoteCharLiteral(String s) {

/** Appends an identifier. Encloses it in back-ticks if necessary. */
public static StringBuilder appendId(StringBuilder buf, String id) {
if (id.contains("`") || id.contains(" ")) {
if (id.contains("`")) {
return buf.append("`")
.append(id.replaceAll("`", "``"))
.append("`");
} else if (id.contains(" ")) {
return buf.append("`")
.append(id)
.append("`");
} else {
return buf.append(id);
}
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/net/hydromatic/morel/util/Static.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ public static <E> int find(List<? extends E> list, Predicate<E> predicate) {
return -1;
}

/** Returns a list containing the elements of {@code list0} that are also in
* {@code list1}. The result preserves the order of {@code list1}, and any
* duplicates it may contain. */
public static <E> List<E> intersect(List<E> list0,
Iterable<? extends E> list1) {
final ImmutableList.Builder<E> list2 = ImmutableList.builder();
Expand All @@ -232,6 +235,12 @@ public static String str(StringBuilder b) {
b.setLength(0);
return s;
}

/** Returns whether a builder ends with a given string. */
public static boolean endsWith(StringBuilder buf, String s) {
final int i = buf.length() - s.length();
return i >= 0 && buf.indexOf(s, i) == i;
}
}

// End Static.java
17 changes: 17 additions & 0 deletions src/test/java/net/hydromatic/morel/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import static net.hydromatic.morel.ast.AstBuilder.ast;
import static net.hydromatic.morel.eval.Codes.isNegative;
import static net.hydromatic.morel.util.Ord.forEachIndexed;
import static net.hydromatic.morel.util.Static.endsWith;
import static net.hydromatic.morel.util.Static.nextPowerOfTwo;
import static net.hydromatic.morel.util.Static.transform;

Expand Down Expand Up @@ -426,6 +427,22 @@ private <E> void checkShorterThan(Iterable<E> iterable, int size) {
hasToString("[[[NONE], 4], [[NONE], 5], [[NONE], 6],"
+ " [[SOME, true], 4], [[SOME, true], 5], [[SOME, true], 6]]"));
}

/** Tests {@link Static#endsWith(StringBuilder, String)}. */
@Test void testEndsWith() {
final StringBuilder yzxyz = new StringBuilder("yzxyz");
assertThat(endsWith(yzxyz, ""), is(true));
assertThat(endsWith(yzxyz, "z"), is(true));
assertThat(endsWith(yzxyz, "yz"), is(true));
assertThat(endsWith(yzxyz, "xy"), is(false));
assertThat(endsWith(yzxyz, "yzx"), is(false));
assertThat(endsWith(yzxyz, "yzxyz"), is(true));
assertThat(endsWith(yzxyz, "wyzxyz"), is(false));

final StringBuilder empty = new StringBuilder();
assertThat(endsWith(empty, "z"), is(false));
assertThat(endsWith(empty, ""), is(true));
}
}

// End UtilTest.java
8 changes: 4 additions & 4 deletions src/test/resources/script/blog.smli
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,8 @@ fun mapReduce mapper reducer list =
List.map (fn (key, values) => (key, reducer (key, values))) keyValuesList
end;
> val mapReduce = fn
> : ('a -> ('b * 'c) list) ->
> ('b * 'c list -> 'd) -> 'a list -> ('b * 'd) list
> : ('a -> ('b * 'c) list)
> -> ('b * 'c list -> 'd) -> 'a list -> ('b * 'd) list

fun wc_mapper line =
let
Expand Down Expand Up @@ -1245,8 +1245,8 @@ fun mapReduce mapper reducer list =
(k, v) in mapper e
group k compute c = (fn vs => reducer (k, vs)) of v;
> val mapReduce = fn
> : ('a -> ('b * 'c) list) ->
> ('b * 'c list -> 'd) -> 'a list -> {c:'d, k:'b} list
> : ('a -> ('b * 'c) list)
> -> ('b * 'c list -> 'd) -> 'a list -> {c:'d, k:'b} list
fun wc_mapper line =
List.map (fn w => (w, 1)) (split line);
> val wc_mapper = fn : string -> (string * int) list
Expand Down
4 changes: 2 additions & 2 deletions src/test/resources/script/fixedPoint.smli
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ fun shortest_path edges =
from p in sp (edges0, vertices) order p.source, p.target
end;
> val shortest_path = fn
> : {source:'a, target:'a, weight:int} list ->
> {source:'a, target:'a, weight:int} list
> : {source:'a, target:'a, weight:int} list
> -> {source:'a, target:'a, weight:int} list
shortest_path edges;
> val it =
> [{source="a",target="a",weight=0},{source="a",target="b",weight=~1},
Expand Down
4 changes: 2 additions & 2 deletions src/test/resources/script/regex-example.smli
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ fun seq r1 r2 s =
where j = j2 + 0
yield (i + 0, k + 0);
> val seq = fn
> : ('a -> (int * int) list) ->
> ('a -> (int * int) list) -> 'a -> (int * int) list
> : ('a -> (int * int) list)
> -> ('a -> (int * int) list) -> 'a -> (int * int) list
(*) match "l" followed by "o" in "hello"
seq (sym #"l") (sym #"o") "hello";
> val it = [(3,5)] : (int * int) list
Expand Down
4 changes: 2 additions & 2 deletions src/test/resources/script/suchThat.smli
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ fun isEmp e =
e elem scott.emp;
> val isEmp = fn
> :
> {comm:real, deptno:int, empno:int, ename:string, hiredate:string,
> job:string, mgr:int, sal:real} -> bool
> {comm:real, deptno:int, empno:int, ename:string, hiredate:string,
> job:string, mgr:int, sal:real} -> bool
(*
from e
where isEmp e andalso e.deptno = 20
Expand Down
45 changes: 45 additions & 0 deletions src/test/resources/script/type.smli
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,49 @@ end;
from b in [SOME true, NONE];
> val it = [SOME true,NONE] : bool option list

(*) If a function type is spread over several lines, put '->' at the start of
(*) the next line.
val f : ('a -> (int * int) list) -> ('a -> (int * int) list) -> 'a -> (int * int) list = fn a => fn b => fn c => [];
> val f = fn
> : ('a -> (int * int) list)
> -> ('b -> (int * int) list) -> 'c -> (int * int) list

(* SML/NJ output for previous is as follows. TODO match SML/NJ type variables.
> val f = fn
> : ('a -> (int * int) list)
> -> ('a -> (int * int) list) -> 'a -> (int * int) list
*)

(*) Function with tuple type and lots of type variables returns record type.
(*) Note that after 'z, type variables are 'ba, 'bb, etc.
(*) Also note that 'o' needs to be quoted.
fun r0 (a, b, c, d, e, f, g, h, i, j, k, l, m, n, `o`, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac) = {a=a,b=b,c=c};
> val r0 = fn
> :
> 'a * 'b * 'c * 'd * 'e * 'f * 'g * 'h * 'i * 'j * 'k * 'l * 'm * 'n * 'o *
> 'p * 'q * 'r * 's * 't * 'u * 'v * 'w * 'x * 'y * 'z * 'ba * 'bb * 'bc
> -> {a:'a, b:'b, c:'c}

(*) Highly curried function with lots of type variables returns record type.
fun r0 a b c d e f g h i j k l m n p q r s = {a=a,b=b,c=c};
> val r0 = fn
> : 'a
> -> 'b
> -> 'c
> -> 'd
> -> 'e
> -> 'f
> -> 'g
> -> 'h
> -> 'i
> -> 'j
> -> 'k
> -> 'l
> -> 'm
> -> 'n
> -> 'o
> -> 'p
> -> 'q
> -> 'r -> {a:'a, b:'b, c:'c}

(*) End type.smli

0 comments on commit da52b1e

Please sign in to comment.