diff --git a/src/main/java/net/hydromatic/morel/compile/Pretty.java b/src/main/java/net/hydromatic/morel/compile/Pretty.java index 8c9eebb7..2aa3081e 100644 --- a/src/main/java/net/hydromatic/morel/compile/Pretty.java +++ b/src/main/java/net/hydromatic/morel/compile/Pretty.java @@ -21,8 +21,8 @@ import net.hydromatic.morel.ast.Op; import net.hydromatic.morel.eval.Codes; import net.hydromatic.morel.foreign.RelList; -import net.hydromatic.morel.parse.Parsers; import net.hydromatic.morel.type.DataType; +import net.hydromatic.morel.type.FnType; import net.hydromatic.morel.type.ForallType; import net.hydromatic.morel.type.ListType; import net.hydromatic.morel.type.PrimitiveType; @@ -32,12 +32,12 @@ import net.hydromatic.morel.type.TypeSystem; import net.hydromatic.morel.util.Ord; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.util.List; import javax.annotation.Nonnull; +import static net.hydromatic.morel.parse.Parsers.appendId; import static net.hydromatic.morel.util.Pair.forEachIndexed; import static java.util.Objects.requireNonNull; @@ -64,17 +64,18 @@ class Pretty { StringBuilder pretty(@Nonnull StringBuilder buf, @Nonnull Type type, @Nonnull Object value) { int lineEnd = lineWidth < 0 ? -1 : (buf.length() + lineWidth); - return pretty1(buf, 0, new int[] {lineEnd}, 0, type, value); + return pretty1(buf, 0, new int[] {lineEnd}, 0, type, value, 0, 0); } /** Prints a value to a buffer. If the first attempt goes beyond * {@code lineEnd}, back-tracks, adds a newline and indent, and * tries again one time. */ private StringBuilder pretty1(@Nonnull StringBuilder buf, int indent, - int[] lineEnd, int depth, @Nonnull Type type, @Nonnull Object value) { + int[] lineEnd, int depth, @Nonnull Type type, @Nonnull Object value, + int leftPrec, int rightPrec) { final int start = buf.length(); final int end = lineEnd[0]; - pretty2(buf, indent, lineEnd, depth, type, ImmutableList.of(), value); + pretty2(buf, indent, lineEnd, depth, type, value, leftPrec, rightPrec); if (end >= 0 && buf.length() > end) { // Reset to start, remove trailing whitespace, add newline buf.setLength(start); @@ -89,7 +90,7 @@ private StringBuilder pretty1(@Nonnull StringBuilder buf, int indent, lineEnd[0] = lineWidth < 0 ? -1 : (buf.length() + lineWidth); indent(buf, indent); - pretty2(buf, indent, lineEnd, depth, type, ImmutableList.of(), value); + pretty2(buf, indent, lineEnd, depth, type, value, leftPrec, rightPrec); } return buf; } @@ -101,29 +102,127 @@ private static void indent(@Nonnull StringBuilder buf, int indent) { } private StringBuilder pretty2(@Nonnull StringBuilder buf, - int indent, int[] lineEnd, int depth, - @Nonnull Type type, List argTypes, - @Nonnull Object value) { + int indent, int[] lineEnd, int depth, @Nonnull Type type, + @Nonnull Object value, int leftPrec, int rightPrec) { if (value instanceof TypedVal) { final TypedVal typedVal = (TypedVal) value; final StringBuilder buf2 = new StringBuilder("val "); - Parsers.appendId(buf2, typedVal.name) + appendId(buf2, typedVal.name) .append(" = "); pretty1(buf, indent, lineEnd, depth, PrimitiveType.BOOL, - buf2.toString()); - pretty1(buf, indent + 2, lineEnd, depth + 1, typedVal.type, typedVal.o); + buf2.toString(), 0, 0); + pretty1(buf, indent + 2, lineEnd, depth + 1, typedVal.type, typedVal.o, + 0, 0); buf.append(' '); pretty1(buf, indent + 2, lineEnd, depth, PrimitiveType.BOOL, - ": " + unqualified(typedVal.type).moniker()); + new TypeVal(": ", unqualified(typedVal.type)), 0, 0); return buf; } + if (value instanceof NamedVal) { final NamedVal namedVal = (NamedVal) value; - Parsers.appendId(buf, namedVal.name) + appendId(buf, namedVal.name) .append('='); - pretty1(buf, indent, lineEnd, depth, type, namedVal.o); + pretty1(buf, indent, lineEnd, depth, type, namedVal.o, 0, 0); + return buf; + } + + if (value instanceof LabelVal) { + final LabelVal labelVal = (LabelVal) value; + final String prefix = + appendId(new StringBuilder(), labelVal.label) + .append(':') + .toString(); + pretty1(buf, indent, lineEnd, depth, type, + new TypeVal(prefix, labelVal.type), 0, 0); return buf; } + + if (value instanceof TypeVal) { + final TypeVal typeVal = (TypeVal) value; + final int start; + buf.append(typeVal.prefix); + int indent2 = indent + typeVal.prefix.length(); + switch (typeVal.type.op()) { + case DATA_TYPE: + case ID: + case TY_VAR: + return pretty1(buf, indent2, lineEnd, depth, type, + typeVal.type.moniker(), 0, 0); + + case LIST: + if (leftPrec > Op.LIST.left + || rightPrec > Op.LIST.right) { + pretty1(buf, indent2, lineEnd, depth, type, "(", 0, 0); + pretty1(buf, indent2, lineEnd, depth, type, typeVal, 0, 0); + pretty1(buf, indent2, lineEnd, depth, type, ")", 0, 0); + return buf; + } + final ListType listType = (ListType) typeVal.type; + pretty1(buf, indent2, lineEnd, depth, type, + new TypeVal("", listType.elementType), leftPrec, Op.LIST.left); + return buf.append(" list"); + + case TUPLE_TYPE: + if (leftPrec > Op.TUPLE_TYPE.left + || rightPrec > Op.TUPLE_TYPE.right) { + pretty1(buf, indent2, lineEnd, depth, type, "(", 0, 0); + pretty1(buf, indent2, lineEnd, depth, type, typeVal, 0, 0); + pretty1(buf, indent2, lineEnd, depth, type, ")", 0, 0); + return buf; + } + final TupleType tupleType = (TupleType) typeVal.type; + start = buf.length(); + List argTypes = tupleType.argTypes; + for (int i = 0; i < argTypes.size(); i++) { + Type argType = argTypes.get(i); + if (buf.length() > start) { + pretty1(buf, indent2, lineEnd, depth, type, + " * ", 0, 0); + } + pretty1(buf, indent2, lineEnd, depth, type, + new TypeVal("", argType), + i == 0 ? leftPrec : Op.TUPLE_TYPE.right, + i == argTypes.size() - 1 ? rightPrec : Op.TUPLE_TYPE.left); + } + return buf; + + case RECORD_TYPE: + final RecordType recordType = (RecordType) typeVal.type; + buf.append("{"); + start = buf.length(); + recordType.argNameTypes.forEach((name, elementType) -> { + if (buf.length() > start) { + buf.append(", "); + } + pretty1(buf, indent2 + 1, lineEnd, depth, type, + new LabelVal(name, elementType), 0, 0); + }); + return buf.append("}"); + + case FUNCTION_TYPE: + if (leftPrec > Op.FUNCTION_TYPE.left + || rightPrec > Op.FUNCTION_TYPE.right) { + pretty1(buf, indent2, lineEnd, depth, type, "(", 0, 0); + pretty1(buf, indent2, lineEnd, depth, type, typeVal, 0, 0); + pretty1(buf, indent2, lineEnd, depth, type, ")", 0, 0); + return buf; + } + final FnType fnType = (FnType) typeVal.type; + pretty1(buf, indent2 + 1, 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), + Op.FUNCTION_TYPE.right, rightPrec); + return buf; + + default: + throw new AssertionError("unknown type " + typeVal.type); + } + } + if (printDepth >= 0 && depth > printDepth) { buf.append('#'); return buf; @@ -196,7 +295,7 @@ private StringBuilder pretty2(@Nonnull StringBuilder buf, buf.append(","); } pretty1(buf, indent + 1, lineEnd, depth + 1, nameType.getValue(), - new NamedVal(nameType.getKey(), o)); + new NamedVal(nameType.getKey(), o), 0, 0); }); return buf.append("}"); @@ -210,13 +309,13 @@ private StringBuilder pretty2(@Nonnull StringBuilder buf, if (buf.length() > start) { buf.append(","); } - pretty1(buf, indent + 1, lineEnd, depth + 1, elementType, o); + pretty1(buf, indent + 1, lineEnd, depth + 1, elementType, o, 0, 0); }); return buf.append(")"); case FORALL_TYPE: return pretty2(buf, indent, lineEnd, depth + 1, ((ForallType) type).type, - ImmutableList.of(), value); + value, 0, 0); case DATA_TYPE: final DataType dataType = (DataType) type; @@ -239,8 +338,7 @@ private StringBuilder pretty2(@Nonnull StringBuilder buf, if (needParentheses) { buf.append('('); } - pretty2(buf, indent, lineEnd, depth + 1, typeConArgType, - ImmutableList.of(), arg); + pretty2(buf, indent, lineEnd, depth + 1, typeConArgType, arg, 0, 0); if (needParentheses) { buf.append(')'); } @@ -277,10 +375,10 @@ private StringBuilder printList(@Nonnull StringBuilder buf, } if (printLength >= 0 && o.i >= printLength) { pretty1(buf, indent + 1, lineEnd, depth + 1, PrimitiveType.BOOL, - "..."); + "...", 0, 0); break; } else { - pretty1(buf, indent + 1, lineEnd, depth + 1, elementType, o.e); + pretty1(buf, indent + 1, lineEnd, depth + 1, elementType, o.e, 0, 0); } } return buf.append("]"); @@ -310,6 +408,28 @@ private static class NamedVal { this.o = o; } } + + /** Wrapper that indicates that a value should be printed "label:type". */ + private static class LabelVal { + final String label; + final Type type; + + LabelVal(String label, Type type) { + this.label = label; + this.type = type; + } + } + + /** Wrapper around a type value. */ + private static class TypeVal { + final String prefix; + final Type type; + + TypeVal(String prefix, Type type) { + this.prefix = prefix; + this.type = type; + } + } } // End Pretty.java diff --git a/src/test/java/net/hydromatic/morel/ShellTest.java b/src/test/java/net/hydromatic/morel/ShellTest.java index dac23479..74463b2a 100644 --- a/src/test/java/net/hydromatic/morel/ShellTest.java +++ b/src/test/java/net/hydromatic/morel/ShellTest.java @@ -509,83 +509,143 @@ private Matcher is2(String expected) { @Test void testLineWidth() { String inputString = "Sys.set (\"lineWidth\", 100);\n" + + "100;" + "val x = [[1,2,3], [4,5], [6], []];\n" + "val y = ([1,2,3], [4,5], [6], []);\n" + "val z = {a=[1,2,3], b=[4,5], c=[6], d=()};\n" + + "val r = {x=x,y=y,z=z};\n" + + "val f = fn s => String.size s;\n" + "Sys.set (\"lineWidth\", 40);\n" - + "x;" - + "y;" - + "z;" + + "40;\n" + + "x;\n" + + "y;\n" + + "z;\n" + + "r;\n" + "Sys.set (\"lineWidth\", 20);\n" - + "x;" - + "y;" - + "z;" + + "20;\n" + + "x;\n" + + "y;\n" + + "z;\n" + + "f;\n" + "Sys.set (\"lineWidth\", 1);\n" - + "x;" + + "1;\n" + + "x;\n" + "Sys.set (\"lineWidth\", 0);\n" - + "x;" + + "0;\n" + + "x;\n" + "Sys.set (\"lineWidth\", ~1);\n" + + "~1;\n" + "x;\n"; - String expected = "val it = () : unit\n" - + "val x = [[1,2,3],[4,5],[6],[]] : int list list\n" - + "val y = ([1,2,3],[4,5],[6],[])" - + " : int list * int list * int list * 'a list\n" - + "val z = {a=[1,2,3],b=[4,5],c=[6],d=()}" - + " : {a:int list, b:int list, c:int list, d:unit}\n" - + "val it = () : unit\n" - + "val it = [[1,2,3],[4,5],[6],[]]\n" - + " : int list list\n" - + "val it = ([1,2,3],[4,5],[6],[])\n" - + " : int list * int list * int list * 'a list\n" - + "val it = {a=[1,2,3],b=[4,5],c=[6],d=()}\n" - + " : {a:int list, b:int list, c:int list, d:unit}\n" - + "val it = () : unit\n" - + "val it =\n" - + " [[1,2,3],[4,5],[6],\n" - + " []]\n" - + " : int list list\n" - + "val it =\n" - + " ([1,2,3],[4,5],[6],\n" - + " [])\n" - + " : int list * int list * int list * 'a list\n" - + "val it =\n" - + " {a=[1,2,3],b=[4,5],\n" - + " c=[6],d=()}\n" - + " : {a:int list, b:int list, c:int list, d:unit}\n" - + "val it =\n" - + " ()\n" - + " : unit\n" - + "val it =\n" - + " [\n" - + " [\n" - + " 1,\n" - + " 2,\n" - + " 3],\n" - + " [\n" - + " 4,\n" - + " 5],\n" - + " [\n" - + " 6],\n" - + " []]\n" - + " : int list list\n" - + "val it =\n" - + " ()\n" - + " : unit\n" - + "val it =\n" - + " [\n" - + " [\n" - + " 1,\n" - + " 2,\n" - + " 3],\n" - + " [\n" - + " 4,\n" - + " 5],\n" - + " [\n" - + " 6],\n" - + " []]\n" - + " : int list list\n" - + "val it = () : unit\n" - + "val it = [[1,2,3],[4,5],[6],[]] : int list list\n"; + String[] lines = { + // width 100 + "val it = () : unit", + "val it = 100 : int", + "val x = [[1,2,3],[4,5],[6],[]] : int list list", + "val y = ([1,2,3],[4,5],[6],[])" + + " : int list * int list * int list * 'a list", + "val z = {a=[1,2,3],b=[4,5],c=[6],d=()}" + + " : {a:int list, b:int list, c:int list, d:unit}", + "val r = {x=[[1,2,3],[4,5],[6],[]],y=([1,2,3],[4,5],[6],[])," + + "z={a=[1,2,3],b=[4,5],c=[6],d=()}}", + " : {x:int list list, y:int list * int list * int list * 'a list,", + " z:{a:int list, b:int list, c:int list, d:unit}}", + "val f = fn : string -> int", + // width 40 + "val it = () : unit", + "val it = 40 : int", + "val it = [[1,2,3],[4,5],[6],[]]", + " : int list list", + "val it = ([1,2,3],[4,5],[6],[])", + " : int list * int list * int list *", + " 'a list", + "val it = {a=[1,2,3],b=[4,5],c=[6],d=()}", + " : {a:int list, b:int list, c:int list,", + " d:unit}", + "val it =", + " {x=[[1,2,3],[4,5],[6],[]],", + " y=([1,2,3],[4,5],[6],[]),", + " z={a=[1,2,3],b=[4,5],c=[6],d=()}}", + " : {x:int list list,", + " y:int list * int list * int list *", + " 'a list,", + " z:{a:int list, b:int list,", + " c:int list, d:unit}}", + // width 20 + "val it = () : unit", + "val it = 20 : int", + "val it =", + " [[1,2,3],[4,5],[6],", + " []]", + " : int list list", + "val it =", + " ([1,2,3],[4,5],[6],", + " [])", + " : int list *", + " int list *", + " int list *", + " 'a list", + "val it =", + " {a=[1,2,3],b=[4,5],", + " c=[6],d=()}", + " : {a:int list,", + " b:int list,", + " c:int list,", + " d:unit}", + "val it = fn", + " : string -> int", + // width 1 + "val it =", + " ()", + " :", + " unit", + "val it =", + " 1", + " :", + " int", + "val it =", + " [", + " [", + " 1,", + " 2,", + " 3],", + " [", + " 4,", + " 5],", + " [", + " 6],", + " []]", + " :", + " int list list", + // width 0 + "val it =", + " ()", + " :", + " unit", + "val it =", + " 0", + " :", + " int", + "val it =", + " [", + " [", + " 1,", + " 2,", + " 3],", + " [", + " 4,", + " 5],", + " [", + " 6],", + " []]", + " :", + " int list list", + // width ~1 + "val it = () : unit", + "val it = ~1 : int", + "val it = [[1,2,3],[4,5],[6],[]] : int list list", + "", + }; + String expected = String.join("\n", lines); fixture() .withRaw(true) .withInputString(inputString)