Skip to content

Commit

Permalink
[MOREL-25] Add o (function composition) and @ (list concatenation…
Browse files Browse the repository at this point in the history
…) operators
  • Loading branch information
julianhyde committed May 10, 2020
1 parent f3c2385 commit d5fb0ae
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 26 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@ Implemented:
* `val` (including `val rec`)
* `fun` (declare function)
* Operators: `=` `<>` `<` `>` `<=` `>=`
`~` `+` `-` `*` `/` `div` `mod` `^`
`~` `+` `-` `*` `/` `div` `mod` `^` `::` `o` `@`
`andalso` `orelse`
`::`
* Built-in constants and functions:
`it` `true` `false` `nil` `abs` `not` `ignore`
* Type derivation
Expand Down Expand Up @@ -121,7 +120,7 @@ Not implemented:
* `exception`
* `while`
* References, and operators `!` and `:=`
* Operators: `before` `o`
* Operators: `before`
* User-defined operators (`infix`, `infixr`)
* Type annotations in expressions and patterns

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/net/hydromatic/morel/ast/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ public Ast.Exp caret(Ast.Exp a0, Ast.Exp a1) {
return infix(Op.CARET, a0, a1);
}

public Ast.Exp o(Ast.Exp a0, Ast.Exp a1) {
return infix(Op.COMPOSE, a0, a1);
}

public Ast.Exp except(Ast.Exp a0, Ast.Exp a1) {
return infix(Op.EXCEPT, a0, a1);
}
Expand Down
23 changes: 22 additions & 1 deletion src/main/java/net/hydromatic/morel/compile/BuiltIn.java
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@ public enum BuiltIn {
ts.forallType(1, h ->
ts.fnType(ts.tupleType(h.get(0), h.get(0)), h.get(0)))),

/** Operator "General.op o", of type "(&beta; &rarr; &gamma;) *
* (&alpha; &rarr; &beta;) &rarr; &alpha; &rarr; &gamma;"
*
* <p>"f o g" is the function composition of "f" and "g". Thus, "(f o g) a"
* is equivalent to "f (g a)". */
GENERAL_OP_O("General", "op o", "op o", ts ->
ts.forallType(3, h ->
ts.fnType(
ts.tupleType(ts.fnType(h.get(1), h.get(2)),
ts.fnType(h.get(0), h.get(1))),
ts.fnType(h.get(0), h.get(2))))),

/** Constant "String.maxSize", of type "int".
*
* <p>"The longest allowed size of a string". */
Expand Down Expand Up @@ -311,11 +323,20 @@ public enum BuiltIn {
*
* <p>"l1 @ l2" returns the list that is the concatenation of l1 and l2.
*/
// TODO: make this infix "@" rather than prefix "at"
// TODO: remove
LIST_AT("List", "at", ts ->
ts.forallType(1, h ->
ts.fnType(ts.tupleType(h.list(0), h.list(0)), h.list(0)))),

/** Operator "List.op @", of type "&alpha; list * &alpha; list &rarr; &alpha;
* list".
*
* <p>"l1 @ l2" returns the list that is the concatenation of l1 and l2.
*/
LIST_OP_AT("List", "op @", "op @", ts ->
ts.forallType(1, h ->
ts.fnType(ts.tupleType(h.list(0), h.list(0)), h.list(0)))),

/** Function "List.hd", of type "&alpha; list &rarr; &alpha;".
*
* <p>"hd l" returns the first element of l. It raises {@code Empty} if l is
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/hydromatic/morel/compile/TypeResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,9 @@ private Ast.Exp deduceType(TypeEnv env, Ast.Exp node, Unifier.Variable v) {
}
return reg(apply.copy(fn2, arg2), null, v);

case AT:
case CARET:
case COMPOSE:
case PLUS:
case MINUS:
case TIMES:
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/net/hydromatic/morel/eval/Codes.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,17 @@ private static int div(EvalEnv env, Object arg) {
return Math.floorDiv((int) list.get(0), (int) list.get(1));
}

/** @see BuiltIn#GENERAL_OP_O */
private static final Applicable GENERAL_OP_O = Codes::compose;

/** Implements {@link #GENERAL_OP_O}. */
private static Applicable compose(EvalEnv evalEnv, Object arg) {
@SuppressWarnings("rawtypes") final List list = (List) arg;
final Applicable f = (Applicable) list.get(0);
final Applicable g = (Applicable) list.get(1);
return (evalEnv2, arg2) -> f.apply(evalEnv2, g.apply(evalEnv2, arg2));
}

/** @see BuiltIn#OP_CARET */
private static final Applicable OP_CARET = Codes::caret;

Expand Down Expand Up @@ -1049,6 +1060,7 @@ public static Applicable aggregate(Environment env, Code aggregateCode,
.put(BuiltIn.NOT, NOT)
.put(BuiltIn.ABS, ABS)
.put(BuiltIn.IGNORE, IGNORE)
.put(BuiltIn.GENERAL_OP_O, GENERAL_OP_O)
.put(BuiltIn.OP_CARET, OP_CARET)
.put(BuiltIn.OP_CONS, OP_CONS)
.put(BuiltIn.OP_DIV, OP_DIV)
Expand Down Expand Up @@ -1088,6 +1100,7 @@ public static Applicable aggregate(Environment env, Code aggregateCode,
.put(BuiltIn.LIST_NULL, LIST_NULL)
.put(BuiltIn.LIST_LENGTH, LIST_LENGTH)
.put(BuiltIn.LIST_AT, LIST_AT)
.put(BuiltIn.LIST_OP_AT, LIST_AT) // op @ == List.at
.put(BuiltIn.LIST_HD, LIST_HD)
.put(BuiltIn.LIST_TL, LIST_TL)
.put(BuiltIn.LIST_LAST, LIST_LAST)
Expand Down
113 changes: 113 additions & 0 deletions src/main/java/net/hydromatic/morel/util/Folder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Licensed to Julian Hyde under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Julian Hyde licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package net.hydromatic.morel.util;

import net.hydromatic.morel.ast.Ast;
import net.hydromatic.morel.ast.Op;

import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import static net.hydromatic.morel.ast.AstBuilder.ast;

/**
* Enable creating right-deep trees.
*
* @param <E> Element type
*/
public abstract class Folder<E> {
final E e;

Folder(E e) {
this.e = Objects.requireNonNull(e);
}

abstract E combine(List<Folder<E>> list);

public static <E> E combineAll(List<Folder<E>> list) {
if (list.size() == 0) {
throw new AssertionError();
}
final Folder<E> head = list.get(0);
final List<Folder<E>> tail = list.subList(1, list.size());
return head.combine(tail);
}

private static <E> Folder<E> end(E e) {
return new End<>(e);
}

/** Appends an element using "@". */
public static void at(List<Folder<Ast.Exp>> list, Ast.Exp e) {
append(list, e, e1 -> op(e1, Op.AT));
}

/** Appends an element using "::". */
public static void cons(List<Folder<Ast.Exp>> list, Ast.Exp e) {
append(list, e, e1 -> op(e1, Op.CONS));
}

/** Adds an element to an empty list. */
public static <E> void start(List<Folder<E>> list, E e) {
if (!list.isEmpty()) {
throw new AssertionError();
}
list.add(end(e));
}

/** Adds an element and operator to a non-empty list. */
private static <E> void append(List<Folder<E>> list, E e,
Function<E, Folder<E>> fn) {
if (list.isEmpty()) {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
final End<E> end = (End) list.get(list.size() - 1);
list.set(list.size() - 1, fn.apply(end.e));
list.add(end(e));
}

/** Creates a folder that combines an expression with whatever follows
* using an infix operator. */
private static Folder<Ast.Exp> op(Ast.Exp e, final Op at) {
return new Folder<Ast.Exp>(e) {
Ast.Exp combine(List<Folder<Ast.Exp>> list) {
final Ast.Exp rest = combineAll(list);
return ast.infixCall(e.pos.plus(rest.pos), at, e, rest);
}
};
}

/** Sub-class of {@code Folder} that marks the end of a list.
*
* @param <E> element type */
private static class End<E> extends Folder<E> {
End(E e) {
super(e);
}

E combine(List<Folder<E>> list) {
assert list.isEmpty();
return e;
}
}
}

// End Folder.java
34 changes: 27 additions & 7 deletions src/main/javacc/MorelParser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import net.hydromatic.morel.ast.Ast.*;
import net.hydromatic.morel.ast.AstBuilder;
import net.hydromatic.morel.ast.AstNode;
import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.util.Pair;
import net.hydromatic.morel.util.Folder;import net.hydromatic.morel.util.Pair;

import com.google.common.collect.ImmutableList;

Expand Down Expand Up @@ -581,14 +581,16 @@ Exp expression6() :
Exp expression5() :
{
Exp e;
final List<Exp> list = new ArrayList<>();
final List<Folder<Exp>> list = new ArrayList<>();
}
{
e = expression6() { list.add(e); }
e = expression6() { Folder.start(list, e); }
(
<CONS> e = expression6() { list.add(e); }
<AT> e = expression6() { Folder.at(list, e); }
|
<CONS> e = expression6() { Folder.cons(list, e); }
)*
{ return ast.foldCons(list); }
{ return Folder.combineAll(list); }
}

/** Parses an expression of precedence level 4 ({@code =}, {@code <>},
Expand Down Expand Up @@ -636,16 +638,32 @@ Exp expression4() :
{ return e; }
}

/** Parses an expression of precedence level 3 (o). */
Exp expression3() :
{
Exp e;
Exp e2;
}
{
e = expression4()
(
<O> e2 = expression4() {
e = ast.o(e, e2);
}
)*
{ return e; }
}

/** Parses an expression of precedence level 2 (andalso). */
Exp expression2() :
{
Exp e;
Exp e2;
}
{
e = expression4()
e = expression3()
(
<ANDALSO> e2 = expression4() {
<ANDALSO> e2 = expression3() {
e = ast.andAlso(e, e2);
}
)*
Expand Down Expand Up @@ -1257,6 +1275,7 @@ AstNode statementSemicolon() :
| < LET: "LET" >
| < MOD: "MOD" >
| < NOT_ELEM: "NOTELEM" >
| < O: "O" >
| < OF: "OF" >
| < ORELSE: "ORELSE" >
| < REC: "REC" >
Expand Down Expand Up @@ -1336,6 +1355,7 @@ AstNode statementSemicolon() :
| < SLASH: "/" >
| < TILDE: "~" >
| < CONS: "::" >
| < AT: "@" >
| < ELLIPSIS: "..." >
| < QUOTE: "'" >
| < DOUBLE_QUOTE: "\"" >
Expand Down
23 changes: 23 additions & 0 deletions src/test/java/net/hydromatic/morel/MainTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,29 @@ public void describeTo(Description description) {
.assertParse("1 :: 2 :: 3 :: []");
ml("1 + 2 :: 3 + 4 * 5 :: 6").assertParseSame();

// o is left-associative;
// lower precedence than "=" (4), higher than "andalso" (2)
ml("f o g").assertParseSame();
ml("f o g o h").assertParseSame();
ml("f o (g o h)").assertParseSame();
ml("(f o g) o h").assertParse("f o g o h");

ml("a = f o g andalso c = d").assertParseSame();
ml("a = (f o g) andalso (c = d)").assertParse("a = (f o g) andalso c = d");
ml("(a = f) o g andalso (c = d)").assertParse("a = f o g andalso c = d");

// @ is right-associative;
// lower precedence than "+" (6), higher than "=" (4)
ml("f @ g").assertParseSame();
ml("f @ g @ h").assertParseSame();
ml("f @ (g @ h)").assertParse("f @ g @ h");
ml("(f @ g) @ h").assertParseSame();

// ^ is left-associative;
// lower precedence than "*" (7), higher than "@" (5)
ml("a * f ^ g @ b").assertParseSame();
ml("(a * f) ^ (g @ b)").assertParse("a * f ^ (g @ b)");

ml("(1 + 2, 3, true, (5, 6), 7 = 8)").assertParseSame();

ml("let val x = 2 in x + (3 + x) + x end").assertParseSame();
Expand Down
18 changes: 18 additions & 0 deletions src/test/java/net/hydromatic/morel/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
package net.hydromatic.morel;

import net.hydromatic.morel.ast.Ast;
import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.util.Folder;
import net.hydromatic.morel.util.MapList;
import net.hydromatic.morel.util.Ord;
import net.hydromatic.morel.util.TailList;
Expand All @@ -28,6 +31,8 @@
import java.util.Arrays;
import java.util.List;

import static net.hydromatic.morel.ast.AstBuilder.ast;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

Expand Down Expand Up @@ -88,6 +93,19 @@ public class UtilTest {
assertThat(abc.get(2), is("c"));
assertThat(String.join(",", abc), is("a,b,c"));
}

@Test public void testFolder() {
final List<Folder<Ast.Exp>> list = new ArrayList<>();
Folder.start(list, ast.stringLiteral(Pos.ZERO, "a"));
Folder.at(list, ast.stringLiteral(Pos.ZERO, "b"));
Folder.at(list, ast.stringLiteral(Pos.ZERO, "c"));
assertThat(Folder.combineAll(list).toString(), is("\"a\" @ \"b\" @ \"c\""));

list.clear();
Folder.start(list, ast.stringLiteral(Pos.ZERO, "a"));
Folder.cons(list, ast.stringLiteral(Pos.ZERO, "b"));
assertThat(Folder.combineAll(list).toString(), is("\"a\" :: \"b\""));
}
}

// End UtilTest.java
Loading

0 comments on commit d5fb0ae

Please sign in to comment.