diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 66068027d..b89c79103 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -25,18 +25,31 @@ jobs:
strategy:
matrix:
- java-version: [ 8, 11, 17 ]
+ java-version: [ "11" ]
+ guava-version: [ "" ]
+ checkstyle-version: [ "" ]
javadoc: [ false ]
include:
- - java-version: 17
+ - java-version: "8"
+ guava-version: "19.0"
+ checkstyle-version: "9.3"
+ javadoc: false
+ - java-version: "17"
+ guava-version: "23.0"
javadoc: true
+ - java-version: "17"
+ guava-version: "31.1-jre"
+ javadoc: false
+ checkstyle-version: ""
+ - java-version: "18"
+ javadoc: false
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v1
with:
fetch-depth: 0
- name: Set up JDK
- uses: actions/setup-java@v2
+ uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java-version }}
distribution: 'adopt'
@@ -47,6 +60,15 @@ jobs:
then
GOALS="$GOALS javadoc:javadoc javadoc:test-javadoc"
fi
- mvn -Dmorel.ci --batch-mode --update-snapshots $GOALS
+ DEFS="-Dmorel.ci"
+ if [ "${{ matrix.checkstyle-version }}" ]
+ then
+ DEFS="$DEFS -Dcheckstyle.version=${{ matrix.checkstyle-version }}"
+ fi
+ if [ "${{ matrix.guava-version }}" ]
+ then
+ DEFS="$DEFS -Dguava.version=${{ matrix.guava-version }}"
+ fi
+ mvn $DEFS --batch-mode --update-snapshots $GOALS
# End main.yml
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ab9d43a0d..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-# 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.
-#
-# Configuration for Travis CI
-language: java
-matrix:
- fast_finish: true
- include:
- - env: IMAGE=maven:3-jdk-13
- - env: IMAGE=maven:3-jdk-12 JDOC=Y
- - env: IMAGE=maven:3-jdk-11
- - env: IMAGE=maven:3-jdk-10 SITE=Y
- - env: IMAGE=maven:3-jdk-9
- - env: IMAGE=maven:3-jdk-8 JDOC=Y
-env:
- global:
- - DOCKERRUN="docker run -it --rm -v $PWD:/src -v $HOME/.m2:/root/.m2 -w /src"
-services:
- - docker
-before_install:
- - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- - docker pull $IMAGE
-install: skip
-script:
- - if [ "$JDOC" = "Y" ]; then export JDOC=javadoc:javadoc; fi
- - if [ "$SITE" = "Y" ]; then export SITE="site"; fi
- - $DOCKERRUN $IMAGE ./mvnw -Dcheckstyle.skip -Dsurefire.useFile=false -Dsurefire.threadCount=1 -Dsurefire.perCoreThreadCount=false -Djavax.net.ssl.trustStorePassword=changeit test $JDOC $SITE
-cache:
- directories:
- - $HOME/.m2
-git:
- depth: 1000
-# End .travis.yml
diff --git a/README.md b/README.md
index abe257d19..e223ab4ea 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ until version 0.2.)
## Requirements
-Java version 8 or higher.
+Java version 11 or higher.
## Get Morel
@@ -61,6 +61,8 @@ On Windows, the last line is
> mvnw install
```
+If you are using Java 8, you should add parameters `-Dcheckstyle.version=9.3`.
+
### Run the shell
```bash
diff --git a/docs/reference.md b/docs/reference.md
index f71431e9c..66906760d 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -45,7 +45,6 @@ In Morel but not Standard ML:
In Standard ML but not in Morel:
* `word` constant
* `longid` identifier
-* type annotations ("`:` *typ*") (appears in expressions, patterns, and *funmatch*)
* references (`ref` and operators `!` and `:=`)
* exceptions (`raise`, `handle`, `exception`)
* `while` loop
@@ -131,6 +130,7 @@ In Standard ML but not in Morel:
| '(' exp1; ... ;expn ')' sequence (n ≥ 2)
| letdecinexp1 ; ... ; expnend
local declaration (n ≥ 1)
+ | exp:type type annotation
| exp1andalsoexp2 conjunction
| exp1orelseexp2 disjunction
| ifexp1thenexp2elseexp3
@@ -173,6 +173,7 @@ In Standard ML but not in Morel:
| '(' pat1 , ... , patn ')' tuple (n ≠ 1)
| { [ patrow ] } record
| '[' pat1, ... ,patn ']' list (n ≥ 0)
+ | pat:type type annotation
| idaspat layered
patrow → '...' wildcard
| lab=pat [,patrow] pattern
@@ -206,10 +207,10 @@ In Standard ML but not in Morel:
funbind → funmatch [ andfunmatch ]*
clausal function
funmatch → funmatchItem [ '|' funmatchItem ]*
-funmatchItem → [ op ] idpat1 ... patn=exp
+funmatchItem → [ op ] idpat1 ... patn [ :type ] =exp
nonfix (n ≥ 1)
- | pat1idpat2=exp infix
- | '(' pat1idpat2 ')' pat'1 ... pat'n = exp
+ | pat1idpat2 [ :type ] =exp infix
+ | '(' pat1idpat2 ')' pat'1 ... pat'n [ :type ] = exp
infix (n ≥ 0)
datbind → datbindItem [ anddatbindItem ]*
data type
@@ -470,11 +471,12 @@ Each property is set using the function `Sys.set (name, value)`,
displayed using `Sys.show name`,
and unset using `Sys.unset name`.
-| Name | Type | Default | Description |
-| ---------------- | ---- | ------- | ----------- |
-| hybrid | bool | false | Whether to try to create a hybrid execution plan that uses Apache Calcite relational algebra. |
-| inlinePassCount | int | 5 | Maximum number of inlining passes. |
-| lineWidth | int | 79 | When printing, the length at which lines are wrapped. |
-| printDepth | int | 5 | When printing, the depth of nesting of recursive data structure at which ellipsis begins. |
-| printLength | int | 12 | When printing, the length of lists at which ellipsis begins. |
-| stringDepth | int | 70 | When printing, the length of strings at which ellipsis begins. |
+| Name | Type | Default | Description |
+| -------------------- | ---- | ------- | ----------- |
+| hybrid | bool | false | Whether to try to create a hybrid execution plan that uses Apache Calcite relational algebra. |
+| inlinePassCount | int | 5 | Maximum number of inlining passes. |
+| lineWidth | int | 79 | When printing, the length at which lines are wrapped. |
+| matchCoverageEnabled | bool | true | Whether to check whether patterns are exhaustive and/or redundant. |
+| printDepth | int | 5 | When printing, the depth of nesting of recursive data structure at which ellipsis begins. |
+| printLength | int | 12 | When printing, the length of lists at which ellipsis begins. |
+| stringDepth | int | 70 | When printing, the length of strings at which ellipsis begins. |
diff --git a/pom.xml b/pom.xml
index ac421ecdc..76d07840b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,28 +76,34 @@ License.
${project.basedir}
- 1.29.0
- 7.8.2
- 1.3.9
- 0.4
-
- 21.0
+ 3.3.0
+ 1.30.0
+
+ 10.2
+ 3.0.2
+ 0.5
+ 4.9.10
+
+ 31.1-jre2.2
- 2.3.1
- 0.3
+ 2.5.11.1.2
- 3.0.0
- 7.0.5
- 3.16.0
- 5.7.2
- 3.0.0
- 3.0.0-M1
- 3.0.1
- 2.9
- 3.7.1
- 3.0.0-M3
- 0.1
- 1.7.25
+ 3.0.3
+ 7.0.11
+ 3.21.0
+ 5.8.2
+ 3.1.2
+ 3.10.1
+ 3.0.0
+ 3.4.0
+ 3.3.0
+ 3.12.0
+ 3.2.1
+ 3.0.0-M6
+ 0.2
+ 1.7.36-html5net.hydromatic.morel.parse
@@ -234,6 +240,7 @@ License.
org.apache.maven.pluginsmaven-source-plugin
+ ${maven-source-plugin.version}attach-sources
@@ -248,6 +255,7 @@ License.
org.apache.maven.pluginsmaven-compiler-plugin
+ ${maven-compiler-plugin.version}88
@@ -280,17 +288,6 @@ License.
checkstyle${checkstyle.version}
-
- net.hydromatic
- toolbox
- ${hydromatic-toolbox.version}
-
-
- com.google.guava
- guava
-
-
-
@@ -335,6 +332,7 @@ License.
org.codehaus.mojobuild-helper-maven-plugin
+ ${build-helper-maven-plugin.version}
@@ -397,6 +395,7 @@ License.
pl.project13.mavengit-commit-id-plugin
+ ${git-commit-id-plugin.version}false
diff --git a/src/main/config/checkstyle/checker.xml b/src/main/config/checkstyle/checker.xml
index 90d3e8b76..931b97024 100644
--- a/src/main/config/checkstyle/checker.xml
+++ b/src/main/config/checkstyle/checker.xml
@@ -57,9 +57,18 @@ License.
-
-
+
+
+
+
+
+
+
+
+
@@ -126,8 +135,10 @@ License.
+
+
@@ -150,9 +161,6 @@ License.
-
@@ -220,15 +228,6 @@ License.
-
-
-
-
-
-
-
-
@@ -256,28 +255,23 @@ License.
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
diff --git a/src/main/java/net/hydromatic/morel/Main.java b/src/main/java/net/hydromatic/morel/Main.java
index 58b4a3bfb..8aa70dadd 100644
--- a/src/main/java/net/hydromatic/morel/Main.java
+++ b/src/main/java/net/hydromatic/morel/Main.java
@@ -19,7 +19,7 @@
package net.hydromatic.morel;
import net.hydromatic.morel.ast.AstNode;
-import net.hydromatic.morel.compile.CompileException;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.compile.CompiledStatement;
import net.hydromatic.morel.compile.Compiles;
import net.hydromatic.morel.compile.Environment;
@@ -31,6 +31,7 @@
import net.hydromatic.morel.parse.ParseException;
import net.hydromatic.morel.type.Binding;
import net.hydromatic.morel.type.TypeSystem;
+import net.hydromatic.morel.util.MorelException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -150,6 +151,7 @@ void run(Session session, BufferingReader in2) {
new SubShell(main, outLines, bindingMap, env0);
for (;;) {
try {
+ parser.zero("stdIn");
final AstNode statement = parser.statementSemicolonOrEof();
String code = in2.flush();
if (statement == null && code.endsWith("\n")) {
@@ -182,15 +184,17 @@ void run(Session session, BufferingReader in2) {
}
}
- @Override public void use(String fileName) {
+ @Override public void use(String fileName, Pos pos) {
throw new UnsupportedOperationException();
}
@Override public void handle(RuntimeException e, StringBuilder buf) {
- if (e instanceof Codes.MorelRuntimeException) {
- ((Codes.MorelRuntimeException) e).describeTo(buf);
- } else if (e instanceof CompileException) {
- buf.append(e.getMessage());
+ if (e instanceof MorelException) {
+ final MorelException me = (MorelException) e;
+ me.describeTo(buf)
+ .append("\n")
+ .append(" raised at: ");
+ me.pos().describeTo(buf);
} else {
buf.append(e);
}
@@ -209,7 +213,7 @@ static class SubShell extends Shell {
super(main, env0, outLines, outBindings);
}
- @Override public void use(String fileName) {
+ @Override public void use(String fileName, Pos pos) {
outLines.accept("[opening " + fileName + "]");
File file = new File(fileName);
if (!file.isAbsolute()) {
@@ -219,7 +223,7 @@ static class SubShell extends Shell {
outLines.accept("[use failed: Io: openIn failed on "
+ fileName
+ ", No such file or directory]");
- throw new Codes.MorelRuntimeException(Codes.BuiltInExn.ERROR);
+ throw new Codes.MorelRuntimeException(Codes.BuiltInExn.ERROR, pos);
}
try (FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader)) {
@@ -234,16 +238,20 @@ void command(AstNode statement, Consumer outLines) {
final Environment env = env0.bindAll(bindingMap.values());
final CompiledStatement compiled =
Compiles.prepareStatement(main.typeSystem, main.session, env,
- statement, null);
+ statement, null, e -> appendToOutput(e, outLines));
final List bindings = new ArrayList<>();
compiled.eval(main.session, env, outLines, bindings::add);
bindings.forEach(b -> this.bindingMap.put(b.id.name, b));
} catch (Codes.MorelRuntimeException e) {
- final StringBuilder buf = new StringBuilder();
- main.session.handle(e, buf);
- outLines.accept(buf.toString());
+ appendToOutput(e, outLines);
}
}
+
+ private void appendToOutput(MorelException e, Consumer outLines) {
+ final StringBuilder buf = new StringBuilder();
+ main.session.handle(e, buf);
+ outLines.accept(buf.toString());
+ }
}
/** Reader that snoops which characters have been read and saves
diff --git a/src/main/java/net/hydromatic/morel/ProgrammaticShell.java b/src/main/java/net/hydromatic/morel/ProgrammaticShell.java
new file mode 100644
index 000000000..02ee31085
--- /dev/null
+++ b/src/main/java/net/hydromatic/morel/ProgrammaticShell.java
@@ -0,0 +1,156 @@
+package net.hydromatic.morel;
+
+import com.google.common.collect.ImmutableMap;
+import net.hydromatic.morel.ast.AstNode;
+import net.hydromatic.morel.ast.Pos;
+import net.hydromatic.morel.compile.CompiledStatement;
+import net.hydromatic.morel.compile.Compiles;
+import net.hydromatic.morel.compile.Environment;
+import net.hydromatic.morel.compile.Environments;
+import net.hydromatic.morel.eval.Codes;
+import net.hydromatic.morel.eval.Session;
+import net.hydromatic.morel.foreign.ForeignValue;
+import net.hydromatic.morel.parse.MorelParserImpl;
+import net.hydromatic.morel.parse.ParseException;
+import net.hydromatic.morel.type.Binding;
+import net.hydromatic.morel.type.TypeSystem;
+import net.hydromatic.morel.util.MorelException;
+
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+
+/**
+ * Programmatic, non-interactive shell for Morel.
+ *
+ * Meant to be used for programmatic evaluation of expressions.
+ * Useful for embedding Morel in other programs, and for testing.
+ *
+ * The shell contains a persistent environment/foreign value map,
+ * along with a cache of compiled statements and their results,
+ * which reduces the cost of repeated invocations.
+ */
+public class ProgrammaticShell implements Session.Shell {
+ private Environment env0;
+ private Map foreignValueMap;
+
+ private final Session session = new Session();
+ private TypeSystem typeSystem = new TypeSystem();
+
+ ProgrammaticShell(Map foreignValueMap) {
+ this.foreignValueMap = ImmutableMap.copyOf(foreignValueMap);
+ this.env0 = makeEnv(typeSystem, foreignValueMap);
+ }
+
+ public Map getForeignValueMap() {
+ return foreignValueMap;
+ }
+
+ public void setForeignValueMap(Map foreignValueMap) {
+ this.foreignValueMap = ImmutableMap.copyOf(foreignValueMap);
+ this.env0 = makeEnv(typeSystem, foreignValueMap);
+ }
+
+ public void setTypeSystem(TypeSystem typeSystem) {
+ this.typeSystem = typeSystem;
+ this.env0 = makeEnv(typeSystem, foreignValueMap);
+ }
+
+ private static Environment makeEnv(TypeSystem typeSystem, Map foreignValueMap) {
+ return Environments.env(typeSystem, foreignValueMap);
+ }
+
+ /**************************************************************/
+
+ public void run(String code, PrintWriter out, boolean echo) {
+ final MorelParserImpl parser = new MorelParserImpl(new StringReader(code));
+ final Consumer outLines = out::println;
+
+ while (true) {
+ try {
+ parser.zero("stdIn");
+ final AstNode statement = parser.statementSemicolonOrEof();
+
+ if (statement == null && code.endsWith("\n")) {
+ code = code.substring(0, code.length() - 1);
+ }
+
+ if (echo) {
+ outLines.accept(code);
+ }
+
+ if (statement == null) {
+ break;
+ }
+
+ session.withShell(this, outLines, session1 ->
+ command(statement, outLines));
+ } catch (ParseException e) {
+ final String message = e.getMessage();
+
+ if (message.startsWith("Encountered \"\" ")) {
+ break;
+ }
+
+ if (echo) {
+ outLines.accept(code);
+ }
+
+ outLines.accept(message);
+ if (code.length() == 0) {
+ // If we consumed no input, we're not making progress, so we'll
+ // never finish. Abort.
+ break;
+ }
+ }
+ }
+ }
+
+ private void command(AstNode statement, Consumer outLines) {
+ try {
+ final Map outBindings = new LinkedHashMap<>();
+ final Environment env = env0.bindAll(outBindings.values());
+
+ final CompiledStatement compiled =
+ Compiles.prepareStatement(typeSystem, session, env,
+ statement, null, e -> appendToOutput(e, outLines));
+
+ final List bindings = new ArrayList<>();
+ compiled.eval(session, env, outLines, bindings::add);
+ bindings.forEach(b -> outBindings.put(b.id.name, b));
+ } catch (Codes.MorelRuntimeException e) {
+ appendToOutput(e, outLines);
+ }
+ }
+
+ private void appendToOutput(MorelException e, Consumer outLines) {
+ final StringBuilder buf = new StringBuilder();
+ session.handle(e, buf);
+ outLines.accept(buf.toString());
+ }
+
+ /**************************************************************/
+
+ @Override
+ public void use(String fileName, Pos pos) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void handle(RuntimeException e, StringBuilder buf) {
+ if (e instanceof MorelException) {
+ final MorelException me = (MorelException) e;
+ me.describeTo(buf)
+ .append("\n")
+ .append(" raised at: ");
+ me.pos().describeTo(buf);
+ } else {
+ buf.append(e);
+ }
+ }
+}
diff --git a/src/main/java/net/hydromatic/morel/Shell.java b/src/main/java/net/hydromatic/morel/Shell.java
index 7ac30679e..6fa3b9784 100644
--- a/src/main/java/net/hydromatic/morel/Shell.java
+++ b/src/main/java/net/hydromatic/morel/Shell.java
@@ -19,6 +19,7 @@
package net.hydromatic.morel;
import net.hydromatic.morel.ast.AstNode;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.compile.CompileException;
import net.hydromatic.morel.compile.CompiledStatement;
import net.hydromatic.morel.compile.Compiles;
@@ -33,6 +34,7 @@
import net.hydromatic.morel.parse.ParseException;
import net.hydromatic.morel.type.Binding;
import net.hydromatic.morel.type.TypeSystem;
+import net.hydromatic.morel.util.MorelException;
import net.hydromatic.morel.util.Pair;
import com.google.common.collect.ImmutableList;
@@ -474,11 +476,13 @@ void extracted(@Nullable Map outBindings) {
new MorelParserImpl(new StringReader(code));
final AstNode statement;
try {
+ smlParser.zero("stdIn");
statement = smlParser.statementSemicolon();
final Environment env0 = env1;
+ final List warningList = new ArrayList<>();
final CompiledStatement compiled =
Compiles.prepareStatement(typeSystem, session, env0,
- statement, null);
+ statement, null, warningList::add);
final Use shell = new Use(env0, bindingMap);
session.withShell(shell, outLines, session1 ->
compiled.eval(session1, env0, outLines, bindings::add));
@@ -515,7 +519,7 @@ private class Use implements Session.Shell {
this.bindings = bindings;
}
- @Override public void use(String fileName) {
+ @Override public void use(String fileName, Pos pos) {
outLines.accept("[opening " + fileName + "]");
File file = new File(fileName);
if (!file.isAbsolute()) {
@@ -525,13 +529,13 @@ private class Use implements Session.Shell {
outLines.accept("[use failed: Io: openIn failed on "
+ fileName
+ ", No such file or directory]");
- throw new Codes.MorelRuntimeException(Codes.BuiltInExn.ERROR);
+ throw new Codes.MorelRuntimeException(Codes.BuiltInExn.ERROR, pos);
}
if (depth > maxDepth && maxDepth >= 0) {
outLines.accept("[use failed: Io: openIn failed on "
+ fileName
+ ", Too many open files]");
- throw new Codes.MorelRuntimeException(Codes.BuiltInExn.ERROR);
+ throw new Codes.MorelRuntimeException(Codes.BuiltInExn.ERROR, pos);
}
try (FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader)) {
@@ -549,10 +553,12 @@ private class Use implements Session.Shell {
if (depth != 1) {
throw e;
}
- if (e instanceof Codes.MorelRuntimeException) {
- ((Codes.MorelRuntimeException) e).describeTo(buf);
- } else if (e instanceof CompileException) {
- buf.append(e.getMessage());
+ if (e instanceof MorelException) {
+ final MorelException me = (MorelException) e;
+ me.describeTo(buf)
+ .append("\n")
+ .append(" raised at: ");
+ me.pos().describeTo(buf);
} else {
buf.append(e);
}
diff --git a/src/main/java/net/hydromatic/morel/ast/Ast.java b/src/main/java/net/hydromatic/morel/ast/Ast.java
index 808f0202d..da5490d59 100644
--- a/src/main/java/net/hydromatic/morel/ast/Ast.java
+++ b/src/main/java/net/hydromatic/morel/ast/Ast.java
@@ -496,7 +496,7 @@ public static class AnnotatedExp extends Exp {
public final Exp exp;
/** Creates a type annotation. */
- AnnotatedExp(Pos pos, Type type, Exp exp) {
+ AnnotatedExp(Pos pos, Exp exp, Type type) {
super(pos, Op.ANNOTATED_EXP);
this.type = requireNonNull(type);
this.exp = requireNonNull(exp);
@@ -1087,12 +1087,15 @@ AstWriter unparse(AstWriter w, int left, int right) {
public static class FunMatch extends AstNode {
public final String name;
public final List patList;
+ @Nullable public final Type returnType;
public final Exp exp;
- FunMatch(Pos pos, String name, ImmutableList patList, Exp exp) {
+ FunMatch(Pos pos, String name, ImmutableList patList,
+ @Nullable Type returnType, Exp exp) {
super(pos, Op.FUN_MATCH);
this.name = name;
this.patList = patList;
+ this.returnType = returnType;
this.exp = exp;
}
diff --git a/src/main/java/net/hydromatic/morel/ast/AstBuilder.java b/src/main/java/net/hydromatic/morel/ast/AstBuilder.java
index a9a13cf38..56a604d5d 100644
--- a/src/main/java/net/hydromatic/morel/ast/AstBuilder.java
+++ b/src/main/java/net/hydromatic/morel/ast/AstBuilder.java
@@ -362,8 +362,10 @@ public Ast.FunBind funBind(Pos pos,
}
public Ast.FunMatch funMatch(Pos pos, String name,
- Iterable extends Ast.Pat> patList, Ast.Exp exp) {
- return new Ast.FunMatch(pos, name, ImmutableList.copyOf(patList), exp);
+ Iterable extends Ast.Pat> patList, @Nullable Ast.Type returnType,
+ Ast.Exp exp) {
+ return new Ast.FunMatch(pos, name, ImmutableList.copyOf(patList),
+ returnType, exp);
}
public Ast.Apply apply(Ast.Exp fn, Ast.Exp arg) {
@@ -379,8 +381,8 @@ public Ast.InfixPat infixPat(Pos pos, Op op, Ast.Pat p0, Ast.Pat p1) {
return new Ast.InfixPat(pos, op, p0, p1);
}
- public Ast.Exp annotatedExp(Pos pos, Ast.Type type, Ast.Exp expression) {
- return new Ast.AnnotatedExp(pos, type, expression);
+ public Ast.Exp annotatedExp(Pos pos, Ast.Exp expression, Ast.Type type) {
+ return new Ast.AnnotatedExp(pos, expression, type);
}
public Ast.Exp infixCall(Pos pos, Op op, Ast.Exp a0, Ast.Exp a1) {
diff --git a/src/main/java/net/hydromatic/morel/ast/Core.java b/src/main/java/net/hydromatic/morel/ast/Core.java
index 067dd957a..b291811ec 100644
--- a/src/main/java/net/hydromatic/morel/ast/Core.java
+++ b/src/main/java/net/hydromatic/morel/ast/Core.java
@@ -41,12 +41,10 @@
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
-import java.util.function.BiConsumer;
import java.util.function.ObjIntConsumer;
import javax.annotation.Nullable;
import static net.hydromatic.morel.ast.CoreBuilder.core;
-import static net.hydromatic.morel.ast.Pos.ZERO;
import static com.google.common.base.Preconditions.checkArgument;
@@ -59,13 +57,14 @@
* class names short.
*/
// TODO: remove 'parse tree for...' from all the comments below
+@SuppressWarnings("StaticPseudoFunctionalStyleMethod")
public class Core {
private Core() {}
/** Abstract base class of Core nodes. */
abstract static class BaseNode extends AstNode {
- BaseNode(Op op) {
- super(ZERO, op);
+ BaseNode(Pos pos, Op op) {
+ super(pos, op);
}
@Override public AstNode accept(Shuttle shuttle) {
@@ -87,7 +86,7 @@ public abstract static class Pat extends BaseNode {
public final Type type;
Pat(Op op, Type type) {
- super(op);
+ super(Pos.ZERO, op);
this.type = requireNonNull(type);
}
@@ -459,8 +458,8 @@ public Pat copy(TypeSystem typeSystem, Set argNames,
public abstract static class Exp extends BaseNode {
public final Type type;
- Exp(Op op, Type type) {
- super(op);
+ Exp(Pos pos, Op op, Type type) {
+ super(pos, op);
this.type = requireNonNull(type);
}
@@ -488,7 +487,7 @@ public static class Id extends Exp {
/** Creates an Id. */
Id(NamedPat idPat) {
- super(Op.ID, idPat.type);
+ super(Pos.ZERO, Op.ID, idPat.type);
this.idPat = requireNonNull(idPat);
}
@@ -523,7 +522,7 @@ public static class RecordSelector extends Exp {
/** Creates a record selector. */
RecordSelector(FnType fnType, int slot) {
- super(Op.RECORD_SELECTOR, fnType);
+ super(Pos.ZERO, Op.RECORD_SELECTOR, fnType);
this.slot = slot;
}
@@ -567,7 +566,7 @@ public static class Literal extends Exp {
/** Creates a Literal. */
Literal(Op op, Type type, Comparable value) {
- super(op, type);
+ super(Pos.ZERO, op, type);
this.value = requireNonNull(value);
}
@@ -608,8 +607,8 @@ public Object unwrap() {
/** Base class for declarations. */
public abstract static class Decl extends BaseNode {
- Decl(Op op) {
- super(op);
+ Decl(Pos pos, Op op) {
+ super(pos, op);
}
@Override public abstract Decl accept(Shuttle shuttle);
@@ -620,7 +619,7 @@ public static class DatatypeDecl extends Decl {
public final List dataTypes;
DatatypeDecl(ImmutableList dataTypes) {
- super(Op.DATATYPE_DECL);
+ super(Pos.ZERO, Op.DATATYPE_DECL);
this.dataTypes = requireNonNull(dataTypes);
checkArgument(!this.dataTypes.isEmpty());
}
@@ -652,13 +651,19 @@ public static class DatatypeDecl extends Decl {
/** Abstract (recursive or non-recursive) value declaration. */
public abstract static class ValDecl extends Decl {
- ValDecl(Op op) {
- super(op);
+ ValDecl(Pos pos, Op op) {
+ super(pos, op);
}
@Override public abstract ValDecl accept(Shuttle shuttle);
- public abstract void forEachBinding(BiConsumer consumer);
+ public abstract void forEachBinding(BindingConsumer consumer);
+ }
+
+ /** Consumer of bindings. */
+ @FunctionalInterface
+ public interface BindingConsumer {
+ void accept(NamedPat namedPat, Exp exp, Pos pos);
}
/** Non-recursive value declaration.
@@ -668,8 +673,8 @@ public static class NonRecValDecl extends ValDecl {
public final NamedPat pat;
public final Exp exp;
- NonRecValDecl(NamedPat pat, Exp exp) {
- super(Op.VAL_DECL);
+ NonRecValDecl(NamedPat pat, Exp exp, Pos pos) {
+ super(pos, Op.VAL_DECL);
this.pat = pat;
this.exp = exp;
}
@@ -700,11 +705,11 @@ public static class NonRecValDecl extends ValDecl {
public NonRecValDecl copy(NamedPat pat, Exp exp) {
return pat == this.pat && exp == this.exp ? this
- : core.nonRecValDecl(pat, exp);
+ : core.nonRecValDecl(pat, exp, pos);
}
- @Override public void forEachBinding(BiConsumer consumer) {
- consumer.accept(pat, exp);
+ @Override public void forEachBinding(BindingConsumer consumer) {
+ consumer.accept(pat, exp, pos);
}
}
@@ -713,7 +718,7 @@ public static class RecValDecl extends ValDecl {
public final ImmutableList list;
RecValDecl(ImmutableList list) {
- super(Op.REC_VAL_DECL);
+ super(Pos.ZERO, Op.REC_VAL_DECL);
this.list = requireNonNull(list);
}
@@ -743,7 +748,7 @@ public static class RecValDecl extends ValDecl {
visitor.visit(this);
}
- @Override public void forEachBinding(BiConsumer consumer) {
+ @Override public void forEachBinding(BindingConsumer consumer) {
list.forEach(b -> b.forEachBinding(consumer));
}
@@ -759,7 +764,7 @@ public static class Tuple extends Exp {
public final List args;
Tuple(RecordLikeType type, ImmutableList args) {
- super(Op.TUPLE, type);
+ super(Pos.ZERO, Op.TUPLE, type);
this.args = ImmutableList.copyOf(args);
}
@@ -806,7 +811,7 @@ public static class Let extends Exp {
public final Exp exp;
Let(ValDecl decl, Exp exp) {
- super(Op.LET, exp.type);
+ super(Pos.ZERO, Op.LET, exp.type);
this.decl = requireNonNull(decl);
this.exp = requireNonNull(exp);
}
@@ -837,7 +842,7 @@ public static class Local extends Exp {
public final Exp exp;
Local(DataType dataType, Exp exp) {
- super(Op.LOCAL, exp.type);
+ super(Pos.ZERO, Op.LOCAL, exp.type);
this.dataType = requireNonNull(dataType);
this.exp = requireNonNull(exp);
}
@@ -873,8 +878,8 @@ public static class Match extends BaseNode {
public final Pat pat;
public final Exp exp;
- Match(Pat pat, Exp exp) {
- super(Op.MATCH);
+ Match(Pos pos, Pat pat, Exp exp) {
+ super(pos, Op.MATCH);
this.pat = pat;
this.exp = exp;
}
@@ -893,7 +898,7 @@ public static class Match extends BaseNode {
public Match copy(Pat pat, Exp exp) {
return pat == this.pat && exp == this.exp ? this
- : core.match(pat, exp);
+ : core.match(pat, exp, pos);
}
}
@@ -903,7 +908,7 @@ public static class Fn extends Exp {
public final Exp exp;
Fn(FnType type, IdPat idPat, Exp exp) {
- super(Op.FN, type);
+ super(Pos.ZERO, Op.FN, type);
this.idPat = requireNonNull(idPat);
this.exp = requireNonNull(exp);
}
@@ -938,8 +943,8 @@ public static class Case extends Exp {
public final Exp exp;
public final List matchList;
- Case(Type type, Exp exp, ImmutableList matchList) {
- super(Op.CASE, type);
+ Case(Pos pos, Type type, Exp exp, ImmutableList matchList) {
+ super(pos, Op.CASE, type);
this.exp = exp;
this.matchList = matchList;
}
@@ -959,7 +964,7 @@ public static class Case extends Exp {
public Case copy(Exp exp, List matchList) {
return exp == this.exp && matchList.equals(this.matchList) ? this
- : core.caseOf(type, exp, matchList);
+ : core.caseOf(type, exp, matchList, pos);
}
}
@@ -968,7 +973,7 @@ public static class From extends Exp {
public final ImmutableList steps;
From(ListType type, ImmutableList steps) {
- super(Op.FROM, type);
+ super(Pos.ZERO, Op.FROM, type);
this.steps = requireNonNull(steps);
}
@@ -1007,7 +1012,7 @@ public abstract static class FromStep extends BaseNode {
public final ImmutableList bindings;
FromStep(Op op, ImmutableList bindings) {
- super(op);
+ super(Pos.ZERO, op);
this.bindings = bindings;
}
@@ -1150,7 +1155,7 @@ public static class OrderItem extends BaseNode {
public final Ast.Direction direction;
OrderItem(Exp exp, Ast.Direction direction) {
- super(Op.ORDER_ITEM);
+ super(Pos.ZERO, Op.ORDER_ITEM);
this.exp = requireNonNull(exp);
this.direction = requireNonNull(direction);
}
@@ -1251,8 +1256,8 @@ public static class Apply extends Exp {
public final Exp fn;
public final Exp arg;
- Apply(Type type, Exp fn, Exp arg) {
- super(Op.APPLY, type);
+ Apply(Pos pos, Type type, Exp fn, Exp arg) {
+ super(pos, Op.APPLY, type);
this.fn = fn;
this.arg = arg;
}
@@ -1292,7 +1297,7 @@ public static class Apply extends Exp {
public Apply copy(Exp fn, Exp arg) {
return fn == this.fn && arg == this.arg ? this
- : core.apply(type, fn, arg);
+ : core.apply(pos, type, fn, arg);
}
}
@@ -1306,7 +1311,7 @@ public static class Aggregate extends BaseNode {
public final @Nullable Exp argument;
Aggregate(Type type, Exp aggregate, @Nullable Exp argument) {
- super(Op.AGGREGATE);
+ super(Pos.ZERO, Op.AGGREGATE);
this.type = type;
this.aggregate = requireNonNull(aggregate);
this.argument = argument;
diff --git a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
index bb6e2e2c4..efcbf7ea6 100644
--- a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
+++ b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
@@ -292,8 +292,9 @@ public Core.Local local(DataType dataType, Core.Exp exp) {
return new Core.Local(dataType, exp);
}
- public Core.NonRecValDecl nonRecValDecl(Core.NamedPat pat, Core.Exp exp) {
- return new Core.NonRecValDecl(pat, exp);
+ public Core.NonRecValDecl nonRecValDecl(Core.NamedPat pat, Core.Exp exp,
+ Pos pos) {
+ return new Core.NonRecValDecl(pat, exp, pos);
}
public Core.RecValDecl recValDecl(
@@ -301,13 +302,13 @@ public Core.RecValDecl recValDecl(
return new Core.RecValDecl(ImmutableList.copyOf(list));
}
- public Core.Match match(Core.Pat pat, Core.Exp exp) {
- return new Core.Match(pat, exp);
+ public Core.Match match(Core.Pat pat, Core.Exp exp, Pos pos) {
+ return new Core.Match(pos, pat, exp);
}
public Core.Case caseOf(Type type, Core.Exp exp,
- Iterable extends Core.Match> matchList) {
- return new Core.Case(type, exp, ImmutableList.copyOf(matchList));
+ Iterable extends Core.Match> matchList, Pos pos) {
+ return new Core.Case(pos, type, exp, ImmutableList.copyOf(matchList));
}
public Core.From from(ListType type, List steps) {
@@ -371,17 +372,19 @@ public Core.Fn fn(FnType type, Core.IdPat idPat, Core.Exp exp) {
return new Core.Fn(type, idPat, exp);
}
- public Core.Apply apply(Type type, Core.Exp fn, Core.Exp arg) {
- return new Core.Apply(type, fn, arg);
+ public Core.Apply apply(Pos pos, Type type, Core.Exp fn, Core.Exp arg) {
+ return new Core.Apply(pos, type, fn, arg);
}
public Core.Case ifThenElse(Core.Exp condition, Core.Exp ifTrue,
Core.Exp ifFalse) {
// Translate "if c then a else b"
- // as if user had written "case c of true => a | _ => b"
- return new Core.Case(ifTrue.type, condition,
- ImmutableList.of(match(truePat, ifTrue),
- match(boolWildcardPat, ifFalse)));
+ // as if user had written "case c of true => a | _ => b".
+ // Pos.ZERO is ok because match failure is impossible.
+ final Pos pos = Pos.ZERO;
+ return new Core.Case(pos, ifTrue.type, condition,
+ ImmutableList.of(match(truePat, ifTrue, pos),
+ match(boolWildcardPat, ifFalse, pos)));
}
public Core.DatatypeDecl datatypeDecl(Iterable dataTypes) {
diff --git a/src/main/java/net/hydromatic/morel/ast/Op.java b/src/main/java/net/hydromatic/morel/ast/Op.java
index 5605fd28b..0d01623ba 100644
--- a/src/main/java/net/hydromatic/morel/ast/Op.java
+++ b/src/main/java/net/hydromatic/morel/ast/Op.java
@@ -91,7 +91,7 @@ public enum Op {
FORALL_TYPE,
// annotated expression "e: t"
- ANNOTATED_EXP(" : "),
+ ANNOTATED_EXP(" : ", 0),
TIMES(" * ", 7),
DIVIDE(" / ", 7),
diff --git a/src/main/java/net/hydromatic/morel/ast/Pos.java b/src/main/java/net/hydromatic/morel/ast/Pos.java
index 932d8f9bf..b45686c10 100644
--- a/src/main/java/net/hydromatic/morel/ast/Pos.java
+++ b/src/main/java/net/hydromatic/morel/ast/Pos.java
@@ -20,6 +20,9 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.mapping.IntPair;
+import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.AbstractList;
import java.util.List;
@@ -29,21 +32,49 @@
/** Position of a parse-tree node. */
public class Pos {
- public static final Pos ZERO = new Pos(0, 0, 0, 0);
+ public static final Pos ZERO = new Pos("", 0, 0, 0, 0);
+ public final String file;
public final int startLine;
public final int startColumn;
public final int endLine;
public final int endColumn;
/** Creates a Pos. */
- public Pos(int startLine, int startColumn, int endLine, int endColumn) {
+ public Pos(String file, int startLine, int startColumn,
+ int endLine, int endColumn) {
+ this.file = file;
this.startLine = startLine;
this.startColumn = startColumn;
this.endLine = endLine;
this.endColumn = endColumn;
}
+ /** Creates a Pos from two offsets. */
+ public static Pos of(String ml, String file, int startOffset, int endOffset) {
+ IntPair start = lineCol(ml, startOffset);
+ IntPair end = lineCol(ml, endOffset);
+ return new Pos(file, start.source, start.target, end.source, end.target);
+ }
+
+ /** Creates a Pos from a filename and a string with a delimiter character.
+ * The delimiter must occur exactly twice in the string. */
+ public static Pair<@NonNull String, @NonNull Pos> split(String s,
+ char delimiter, String file) {
+ final int i = s.indexOf(delimiter);
+ final int j = s.indexOf(delimiter, i + 1);
+ final int k = s.indexOf(delimiter, j + 1);
+ if (i < 0 || j <= i || k >= 0) {
+ throw new IllegalArgumentException("expected exactly two occurrences "
+ + "of delimiter, '" + delimiter + "'");
+ }
+ final String s2 = s.substring(0, i)
+ + s.substring(i + 1, j)
+ + s.substring(j + 1);
+ final Pos pos = of(s2, file, i, j - 1);
+ return Pair.of(s2, pos);
+ }
+
@Override public int hashCode() {
return Objects.hash(startLine, startColumn, endLine, endColumn);
}
@@ -58,7 +89,22 @@ public Pos(int startLine, int startColumn, int endLine, int endColumn) {
}
@Override public String toString() {
- return "line " + startLine + ", column " + startColumn;
+ return describeTo(new StringBuilder()).toString();
+ }
+
+ public StringBuilder describeTo(StringBuilder buf) {
+ buf.append(file)
+ .append(file.isEmpty() ? "" : ":")
+ .append(startLine)
+ .append('.')
+ .append(startColumn);
+ if (endColumn != startColumn + 1 || endLine != startLine) {
+ buf.append('-')
+ .append(endLine)
+ .append('.')
+ .append(endColumn);
+ }
+ return buf;
}
/**
@@ -127,10 +173,12 @@ private static Pos sum(
int endColumn) {
int testLine;
int testColumn;
+ String file = Pos.ZERO.file;
for (Pos pos : poses) {
if (pos == null || pos.equals(Pos.ZERO)) {
continue;
}
+ file = pos.file;
testLine = pos.startLine;
testColumn = pos.startColumn;
if (testLine < line || testLine == line && testColumn < column) {
@@ -145,11 +193,27 @@ private static Pos sum(
endColumn = testColumn;
}
}
- return new Pos(line, column, endLine, endColumn);
+ return new Pos(file, line, column, endLine, endColumn);
}
public Pos plus(Pos pos) {
- return new Pos(startLine, startColumn, pos.endLine, pos.endColumn);
+ int startLine = this.startLine;
+ int startColumn = this.startColumn;
+ if (pos.startLine < startLine
+ || pos.startLine == startLine
+ && pos.startColumn < startColumn) {
+ startLine = pos.startLine;
+ startColumn = pos.startColumn;
+ }
+ int endLine = pos.endLine;
+ int endColumn = pos.endColumn;
+ if (this.endLine > endLine
+ || this.endLine == endLine
+ && this.endColumn > endColumn) {
+ endLine = this.endLine;
+ endColumn = this.endColumn;
+ }
+ return new Pos(file, startLine, startColumn, endLine, endColumn);
}
public Pos plusAll(Iterable poses) {
@@ -160,6 +224,25 @@ public Pos plusAll(@Nonnull List extends AstNode> nodes) {
//noinspection StaticPseudoFunctionalStyleMethod,ConstantConditions
return plusAll(Lists.transform(nodes, (AstNode node) -> node.pos));
}
+
+ /** Returns the 1-based line. */
+ private static IntPair lineCol(String s, int offset) {
+ int line = 1;
+ int lineStart = 0;
+ int i;
+ final int n = Math.min(s.length(), offset);
+ for (i = 0; i < n; i++) {
+ if (s.charAt(i) == '\n') {
+ ++line;
+ lineStart = i + 1;
+ }
+ }
+ if (i == offset) {
+ return IntPair.of(line, offset - lineStart + 1);
+ } else {
+ throw new IllegalArgumentException("not found");
+ }
+ }
}
// End Pos.java
diff --git a/src/main/java/net/hydromatic/morel/ast/Shuttle.java b/src/main/java/net/hydromatic/morel/ast/Shuttle.java
index dac5e06c6..62187246d 100644
--- a/src/main/java/net/hydromatic/morel/ast/Shuttle.java
+++ b/src/main/java/net/hydromatic/morel/ast/Shuttle.java
@@ -73,9 +73,8 @@ protected Ast.Id visit(Ast.Id id) {
}
protected Ast.Exp visit(Ast.AnnotatedExp annotatedExp) {
- return ast.annotatedExp(annotatedExp.pos,
- annotatedExp.type.accept(this),
- annotatedExp.exp.accept(this));
+ return ast.annotatedExp(annotatedExp.pos, annotatedExp.exp.accept(this),
+ annotatedExp.type.accept(this));
}
protected Ast.Exp visit(Ast.If ifThenElse) {
@@ -206,7 +205,9 @@ protected Ast.FunBind visit(Ast.FunBind funBind) {
protected Ast.FunMatch visit(Ast.FunMatch funMatch) {
return ast.funMatch(funMatch.pos, funMatch.name,
- visitList(funMatch.patList), funMatch.exp.accept(this));
+ visitList(funMatch.patList),
+ funMatch.returnType == null ? null : funMatch.returnType.accept(this),
+ funMatch.exp.accept(this));
}
protected Ast.ValDecl visit(Ast.ValDecl valDecl) {
diff --git a/src/main/java/net/hydromatic/morel/compile/BuiltIn.java b/src/main/java/net/hydromatic/morel/compile/BuiltIn.java
index 9c76d7479..83afab927 100644
--- a/src/main/java/net/hydromatic/morel/compile/BuiltIn.java
+++ b/src/main/java/net/hydromatic/morel/compile/BuiltIn.java
@@ -31,6 +31,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
+import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -42,7 +43,6 @@
import java.util.function.Function;
import java.util.function.UnaryOperator;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import static net.hydromatic.morel.type.PrimitiveType.BOOL;
import static net.hydromatic.morel.type.PrimitiveType.CHAR;
@@ -1059,7 +1059,8 @@ public enum BuiltIn {
*
*
Returns true if and only if {@code signBit r1} equals
* {@code signBit r2}. */
- REAL_SAME_SIGN("Real", "sameSign", ts -> ts.fnType(ts.tupleType(REAL, REAL), BOOL)),
+ REAL_SAME_SIGN("Real", "sameSign", ts ->
+ ts.fnType(ts.tupleType(REAL, REAL), BOOL)),
/** Function "Real.sign", of type "real → int".
*
@@ -1662,19 +1663,30 @@ public static void forEachStructure(TypeSystem typeSystem,
/** Defines built-in {@code datatype} and {@code eqtype} instances, e.g.
* {@code option}, {@code vector}. */
public static void dataTypes(TypeSystem typeSystem, List bindings) {
- defineDataType(typeSystem, bindings, "order", 0, h ->
+ defineDataType(typeSystem, bindings, "order", false, 0, h ->
h.tyCon("LESS").tyCon("EQUAL").tyCon("GREATER"));
- defineDataType(typeSystem, bindings, "option", 1, h ->
+ defineDataType(typeSystem, bindings, "option", false, 1, h ->
h.tyCon("NONE").tyCon("SOME", h.get(0)));
defineEqType(typeSystem, "vector", 1);
+
+ // Define two internal datatypes:
+ // datatype 'a list = NIL | CONS of ('a * 'a list);
+ // datatype bool = FALSE | TRUE;
+ // These are not available from within programs but are used by the
+ // match coverage checker.
+ defineDataType(typeSystem, bindings, "$list", true, 1, h ->
+ h.tyCon("NIL").tyCon("CONS", h.get(0)));
+ defineDataType(typeSystem, bindings, "$bool", true, 0, h ->
+ h.tyCon("FALSE").tyCon("TRUE"));
}
private static void defineEqType(TypeSystem ts, String name, int varCount) {
- defineDataType(ts, new ArrayList<>(), name, varCount, h -> h);
+ defineDataType(ts, new ArrayList<>(), name, false, varCount, h -> h);
}
private static void defineDataType(TypeSystem ts, List bindings,
- String name, int varCount, UnaryOperator transform) {
+ String name, boolean internal, int varCount,
+ UnaryOperator transform) {
final List tyVars = new ArrayList<>();
for (int i = 0; i < varCount; i++) {
tyVars.add(ts.typeVariable(i));
@@ -1697,8 +1709,12 @@ public TypeVar get(int i) {
final Type type = ts.dataTypeScheme(name, tyVars, tyCons);
final DataType dataType = (DataType) (type instanceof DataType ? type
: ((ForallType) type).type);
- tyCons.keySet().forEach(tyConName ->
- bindings.add(ts.bindTyCon(dataType, tyConName)));
+ if (internal) {
+ ts.setInternal(name);
+ } else {
+ tyCons.keySet().forEach(tyConName ->
+ bindings.add(ts.bindTyCon(dataType, tyConName)));
+ }
}
/** Callback used when defining a datatype. */
diff --git a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java
index 4e5397647..d58e72ee4 100644
--- a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java
+++ b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java
@@ -22,6 +22,7 @@
import net.hydromatic.morel.ast.AstNode;
import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.ast.Op;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.ast.Visitor;
import net.hydromatic.morel.eval.Applicable;
import net.hydromatic.morel.eval.Code;
@@ -635,7 +636,7 @@ private Core.Tuple toRecord(RelContext cx, Core.Id id) {
final List args = new ArrayList<>();
recordType.argNameTypes.forEach((field, fieldType) ->
args.add(
- core.apply(fieldType,
+ core.apply(Pos.ZERO, fieldType,
core.recordSelector(typeSystem, recordType, field),
id)));
return core.tuple(recordType, args);
diff --git a/src/main/java/net/hydromatic/morel/compile/CompileException.java b/src/main/java/net/hydromatic/morel/compile/CompileException.java
index ef697d0b5..ae992d2be 100644
--- a/src/main/java/net/hydromatic/morel/compile/CompileException.java
+++ b/src/main/java/net/hydromatic/morel/compile/CompileException.java
@@ -18,10 +18,33 @@
*/
package net.hydromatic.morel.compile;
+import net.hydromatic.morel.ast.Pos;
+import net.hydromatic.morel.util.MorelException;
+
/** An error occurred during compilation. */
-public class CompileException extends RuntimeException {
- public CompileException(String message) {
+public class CompileException extends RuntimeException
+ implements MorelException {
+ private final boolean warning;
+ private final Pos pos;
+
+ public CompileException(String message, boolean warning, Pos pos) {
super(message);
+ this.warning = warning;
+ this.pos = pos;
+ }
+
+ @Override public String toString() {
+ return super.toString() + " at " + pos;
+ }
+
+ @Override public Pos pos() {
+ return pos;
+ }
+
+ public StringBuilder describeTo(StringBuilder buf) {
+ return pos.describeTo(buf)
+ .append(warning ? " Warning: " : " Error: ")
+ .append(getMessage());
}
}
diff --git a/src/main/java/net/hydromatic/morel/compile/Compiler.java b/src/main/java/net/hydromatic/morel/compile/Compiler.java
index 518b2d64f..c734aa8fb 100644
--- a/src/main/java/net/hydromatic/morel/compile/Compiler.java
+++ b/src/main/java/net/hydromatic/morel/compile/Compiler.java
@@ -20,6 +20,7 @@
import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.ast.Op;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.eval.Applicable;
import net.hydromatic.morel.eval.Applicable2;
import net.hydromatic.morel.eval.Applicable3;
@@ -221,7 +222,7 @@ public Code compile(Context cx, Core.Exp expression) {
case FN:
final Core.Fn fn = (Core.Fn) expression;
return compileMatchList(cx,
- ImmutableList.of(core.match(fn.idPat, fn.exp)));
+ ImmutableList.of(core.match(fn.idPat, fn.exp, fn.pos)));
case CASE:
final Core.Case case_ = (Core.Case) expression;
@@ -266,11 +267,12 @@ protected Code compileApply(Context cx, Core.Apply apply) {
case FN_LITERAL:
final Core.Literal literal = (Core.Literal) apply.fn;
final BuiltIn builtIn = (BuiltIn) literal.value;
- return compileCall(cx, builtIn, apply.arg);
+ return compileCall(cx, builtIn, apply.arg, apply.pos);
}
final Code argCode = compileArg(cx, apply.arg);
final Type argType = apply.arg.type;
- final Applicable fnValue = compileApplicable(cx, apply.fn, argType);
+ final Applicable fnValue =
+ compileApplicable(cx, apply.fn, argType, apply.pos);
if (fnValue != null) {
return finishCompileApply(cx, fnValue, argCode, argType);
}
@@ -379,7 +381,7 @@ && getOnlyElement(bindings).id.type.equals(elementType)) {
}
final Applicable aggregateApplicable =
compileApplicable(cx, aggregate.aggregate,
- typeSystem.listType(argumentType));
+ typeSystem.listType(argumentType), aggregate.pos);
final Code aggregateCode;
if (aggregateApplicable == null) {
aggregateCode = compile(cx, aggregate.aggregate);
@@ -414,16 +416,17 @@ private ImmutableList bindingNames(List bindings) {
/** Compiles a function value to an {@link Applicable}, if possible, or
* returns null. */
- private Applicable compileApplicable(Context cx, Core.Exp fn, Type argType) {
+ private Applicable compileApplicable(Context cx, Core.Exp fn, Type argType,
+ Pos pos) {
switch (fn.op) {
case FN_LITERAL:
final BuiltIn builtIn = (BuiltIn) ((Core.Literal) fn).value;
final Object o = Codes.BUILT_IN_VALUES.get(builtIn);
- return toApplicable(cx, o, argType);
+ return toApplicable(cx, o, argType, pos);
case VALUE_LITERAL:
final Core.Literal literal = (Core.Literal) fn;
- return toApplicable(cx, literal.unwrap(), argType);
+ return toApplicable(cx, literal.unwrap(), argType, pos);
case ID:
final Binding binding = cx.env.getOpt(((Core.Id) fn).idPat.name);
@@ -432,7 +435,7 @@ private Applicable compileApplicable(Context cx, Core.Exp fn, Type argType) {
|| binding.value == Unit.INSTANCE) {
return null;
}
- return toApplicable(cx, binding.value, argType);
+ return toApplicable(cx, binding.value, argType, pos);
case RECORD_SELECTOR:
final Core.RecordSelector recordSelector = (Core.RecordSelector) fn;
@@ -444,9 +447,13 @@ private Applicable compileApplicable(Context cx, Core.Exp fn, Type argType) {
}
private @Nullable Applicable toApplicable(Context cx, Object o,
- Type argType) {
+ Type argType, Pos pos) {
if (o instanceof Applicable) {
- return (Applicable) o;
+ final Applicable applicable = (Applicable) o;
+ if (applicable instanceof Codes.Positioned) {
+ return ((Codes.Positioned) applicable).withPos(pos);
+ }
+ return applicable;
}
if (o instanceof Macro) {
final Macro value = (Macro) o;
@@ -532,7 +539,7 @@ private void compileDatatypeDecl(List dataTypes,
}
}
- private Code compileCall(Context cx, BuiltIn builtIn, Core.Exp arg) {
+ private Code compileCall(Context cx, BuiltIn builtIn, Core.Exp arg, Pos pos) {
final List argCodes;
switch (builtIn) {
case Z_ANDALSO:
@@ -548,7 +555,13 @@ private Code compileCall(Context cx, BuiltIn builtIn, Core.Exp arg) {
argCodes = compileArgs(cx, ((Core.Tuple) arg).args);
return Codes.list(argCodes);
default:
- final Object o = Codes.BUILT_IN_VALUES.get(builtIn);
+ final Object o0 = Codes.BUILT_IN_VALUES.get(builtIn);
+ final Object o;
+ if (o0 instanceof Codes.Positioned) {
+ o = ((Codes.Positioned) o0).withPos(pos);
+ } else {
+ o = o0;
+ }
if (o instanceof Applicable) {
final Code argCode = compile(cx, arg);
if (argCode instanceof Codes.TupleCode) {
@@ -587,7 +600,7 @@ private Code compileMatchList(Context cx,
matchList.stream()
.map(match -> compileMatch(cx, match))
.collect(toImmutableList());
- return new MatchCode(patCodes);
+ return new MatchCode(patCodes, Util.last(matchList).pos);
}
private Pair compileMatch(Context cx, Core.Match match) {
@@ -603,7 +616,7 @@ private void compileValDecl(Context cx, Core.ValDecl valDecl, boolean isDecl,
final List newBindings = new TailList<>(bindings);
final Map linkCodes = new HashMap<>();
if (valDecl.op == Op.REC_VAL_DECL) {
- valDecl.forEachBinding((pat, exp) -> {
+ valDecl.forEachBinding((pat, exp, pos) -> {
final LinkCode linkCode = new LinkCode();
linkCodes.put(pat, linkCode);
bindings.add(Binding.of(pat, linkCode));
@@ -611,8 +624,7 @@ private void compileValDecl(Context cx, Core.ValDecl valDecl, boolean isDecl,
}
final Context cx1 = cx.bindAll(newBindings);
- valDecl.forEachBinding((pat, exp) -> {
-
+ valDecl.forEachBinding((pat, exp, pos) -> {
// Using 'compileArg' rather than 'compile' encourages CalciteCompiler
// to use a pure Calcite implementation if possible, and has no effect
// in the basic Compiler.
@@ -620,7 +632,7 @@ private void compileValDecl(Context cx, Core.ValDecl valDecl, boolean isDecl,
if (!linkCodes.isEmpty()) {
link(linkCodes, pat, code);
}
- matchCodes.add(new MatchCode(ImmutableList.of(Pair.of(pat, code))));
+ matchCodes.add(new MatchCode(ImmutableList.of(Pair.of(pat, code)), pos));
if (actions != null) {
final Type type0 = exp.type;
@@ -633,7 +645,7 @@ private void compileValDecl(Context cx, Core.ValDecl valDecl, boolean isDecl,
final Object o = code.eval(evalEnv);
final Map pairs = new LinkedHashMap<>();
if (!Closure.bindRecurse(pat.withType(type), o, pairs::put)) {
- throw new Codes.MorelRuntimeException(Codes.BuiltInExn.BIND);
+ throw new Codes.MorelRuntimeException(Codes.BuiltInExn.BIND, pos);
}
pairs.forEach((pat2, o2) -> {
outBindings.accept(Binding.of(pat2, o2));
@@ -748,9 +760,11 @@ public Object eval(EvalEnv env) {
/** Code that implements {@link Compiler#compileMatchList(Context, List)}. */
private static class MatchCode implements Code {
private final ImmutableList> patCodes;
+ private final Pos pos;
- MatchCode(ImmutableList> patCodes) {
+ MatchCode(ImmutableList> patCodes, Pos pos) {
this.patCodes = patCodes;
+ this.pos = pos;
}
@Override public Describer describe(Describer describer) {
@@ -760,7 +774,7 @@ private static class MatchCode implements Code {
}
@Override public Object eval(EvalEnv evalEnv) {
- return new Closure(evalEnv, patCodes);
+ return new Closure(evalEnv, patCodes, pos);
}
}
}
diff --git a/src/main/java/net/hydromatic/morel/compile/Compiles.java b/src/main/java/net/hydromatic/morel/compile/Compiles.java
index 23e6e7844..82ed2d385 100644
--- a/src/main/java/net/hydromatic/morel/compile/Compiles.java
+++ b/src/main/java/net/hydromatic/morel/compile/Compiles.java
@@ -34,8 +34,10 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import javax.annotation.Nullable;
import static net.hydromatic.morel.ast.AstBuilder.ast;
@@ -59,7 +61,7 @@ public static TypeResolver.Resolved validateExpression(AstNode statement,
*/
public static CompiledStatement prepareStatement(TypeSystem typeSystem,
Session session, Environment env, AstNode statement,
- @Nullable Calcite calcite) {
+ @Nullable Calcite calcite, Consumer warningConsumer) {
Ast.Decl decl;
if (statement instanceof Ast.Exp) {
decl = toValDecl((Ast.Exp) statement);
@@ -67,7 +69,7 @@ public static CompiledStatement prepareStatement(TypeSystem typeSystem,
decl = (Ast.Decl) statement;
}
return prepareDecl(typeSystem, session, env, calcite, decl,
- decl == statement);
+ decl == statement, warningConsumer);
}
/**
@@ -76,7 +78,8 @@ public static CompiledStatement prepareStatement(TypeSystem typeSystem,
*/
private static CompiledStatement prepareDecl(TypeSystem typeSystem,
Session session, Environment env, @Nullable Calcite calcite,
- Ast.Decl decl, boolean isDecl) {
+ Ast.Decl decl, boolean isDecl,
+ Consumer warningConsumer) {
final TypeResolver.Resolved resolved =
TypeResolver.deduceType(env, decl, typeSystem);
final boolean hybrid = Prop.HYBRID.booleanValue(session.map);
@@ -85,6 +88,14 @@ private static CompiledStatement prepareDecl(TypeSystem typeSystem,
final Resolver resolver = Resolver.of(resolved.typeMap, env);
final Core.Decl coreDecl0 = resolver.toCore(resolved.node);
+ // Check for exhaustive and redundant patterns, and throw errors or
+ // warnings.
+ final boolean matchCoverageEnabled =
+ Prop.MATCH_COVERAGE_ENABLED.booleanValue(session.map);
+ if (matchCoverageEnabled) {
+ checkPatternCoverage(typeSystem, coreDecl0, warningConsumer);
+ }
+
Core.Decl coreDecl;
if (inlinePassCount == 0) {
// Inlining is disabled. Use the Inliner in a limited mode.
@@ -116,6 +127,50 @@ private static CompiledStatement prepareDecl(TypeSystem typeSystem,
return compiler.compileStatement(env, coreDecl, isDecl);
}
+ /** Checks for exhaustive and redundant patterns, and throws if there are
+ * errors/warnings. */
+ private static void checkPatternCoverage(TypeSystem typeSystem,
+ Core.Decl decl, final Consumer warningConsumer) {
+ final List errorList = new ArrayList<>();
+ decl.accept(new Visitor() {
+ @Override protected void visit(Core.Case kase) {
+ super.visit(kase);
+ checkPatternCoverage(typeSystem, kase, errorList::add,
+ warningConsumer);
+ }
+ });
+ if (!errorList.isEmpty()) {
+ throw errorList.get(0);
+ }
+ }
+
+ private static void checkPatternCoverage(TypeSystem typeSystem,
+ Core.Case kase, Consumer errorConsumer,
+ Consumer warningConsumer) {
+ final List prevPatList = new ArrayList<>();
+ final List redundantMatchList = new ArrayList<>();
+ for (Core.Match match : kase.matchList) {
+ if (PatternCoverageChecker.isCoveredBy(typeSystem, prevPatList,
+ match.pat)) {
+ redundantMatchList.add(match);
+ }
+ prevPatList.add(match.pat);
+ }
+ final boolean exhaustive =
+ PatternCoverageChecker.isExhaustive(typeSystem, prevPatList);
+ if (!redundantMatchList.isEmpty()) {
+ final String message = exhaustive
+ ? "match redundant"
+ : "match redundant and nonexhaustive";
+ errorConsumer.accept(
+ new CompileException(message, false,
+ redundantMatchList.get(0).pos));
+ } else if (!exhaustive) {
+ warningConsumer.accept(
+ new CompileException("match nonexhaustive", true, kase.pos));
+ }
+ }
+
/** Converts {@code e} to {@code val = e}. */
public static Ast.ValDecl toValDecl(Ast.Exp statement) {
final Pos pos = statement.pos;
@@ -150,9 +205,9 @@ static void bindPattern(TypeSystem typeSystem, List bindings,
* we have the expression. */
static void bindPattern(TypeSystem typeSystem, List bindings,
Core.ValDecl valDecl) {
- valDecl.forEachBinding((pat, exp) -> {
+ valDecl.forEachBinding((pat, exp, pos) -> {
if (pat instanceof Core.IdPat) {
- bindings.add(Binding.of((Core.IdPat) pat, exp));
+ bindings.add(Binding.of(pat, exp));
}
});
}
diff --git a/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java b/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java
index 919e40970..8b1baee20 100644
--- a/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java
+++ b/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java
@@ -56,7 +56,7 @@ protected EnvShuttle(TypeSystem typeSystem, Environment env) {
final List bindings = new ArrayList<>();
final Core.Pat pat2 = match.pat.accept(this);
Compiles.bindPattern(typeSystem, bindings, pat2);
- return core.match(pat2, match.exp.accept(bind(bindings)));
+ return core.match(pat2, match.exp.accept(bind(bindings)), match.pos);
}
@Override public Core.Exp visit(Core.Let let) {
diff --git a/src/main/java/net/hydromatic/morel/compile/Inliner.java b/src/main/java/net/hydromatic/morel/compile/Inliner.java
index 8aa72f175..d29e06a3f 100644
--- a/src/main/java/net/hydromatic/morel/compile/Inliner.java
+++ b/src/main/java/net/hydromatic/morel/compile/Inliner.java
@@ -144,7 +144,7 @@ public static Inliner of(TypeSystem typeSystem, Environment env,
// let x = A in E end
final Core.Fn fn = (Core.Fn) apply2.fn;
return core.let(
- core.nonRecValDecl(fn.idPat, apply2.arg), fn.exp);
+ core.nonRecValDecl(fn.idPat, apply2.arg, apply2.pos), fn.exp);
}
return apply2;
}
diff --git a/src/main/java/net/hydromatic/morel/compile/PatternCoverageChecker.java b/src/main/java/net/hydromatic/morel/compile/PatternCoverageChecker.java
new file mode 100644
index 000000000..ca008ddff
--- /dev/null
+++ b/src/main/java/net/hydromatic/morel/compile/PatternCoverageChecker.java
@@ -0,0 +1,352 @@
+/*
+ * 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.compile;
+
+import net.hydromatic.morel.ast.Core;
+import net.hydromatic.morel.ast.Op;
+import net.hydromatic.morel.type.DataType;
+import net.hydromatic.morel.type.ForallType;
+import net.hydromatic.morel.type.Type;
+import net.hydromatic.morel.type.TypeSystem;
+import net.hydromatic.morel.util.Ord;
+import net.hydromatic.morel.util.Pair;
+import net.hydromatic.morel.util.Sat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import org.apache.calcite.util.Util;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static net.hydromatic.morel.ast.CoreBuilder.core;
+
+import static java.util.Objects.requireNonNull;
+
+/** Checks whether patterns are exhaustive and/or redundant.
+ *
+ *
The algorithm converts a list of patterns into a boolean formula
+ * with several variables, then checks whether the formula is satisfiable
+ * (that is, whether there is a combination of assignments of boolean values
+ * to the variables such that the formula evaluates to true). */
+class PatternCoverageChecker {
+ final TypeSystem typeSystem;
+ final Sat sat = new Sat();
+ final Map pathSlots = new HashMap<>();
+
+ /** Creates a PatternCoverageChecker. */
+ private PatternCoverageChecker(TypeSystem typeSystem) {
+ this.typeSystem = requireNonNull(typeSystem, "typeSystem");
+ }
+
+ /** Returns whether every possible value that could be matched by
+ * pattern {@code pat} would already have been matched by one or more of
+ * {@code prevPatList}.
+ *
+ *
For example, the pattern "(1, b: bool)" is covered by "[(1, true),
+ * (_, false)]" but not by "[(1, true)]" or "[(_, false)]". */
+ static boolean isCoveredBy(TypeSystem typeSystem, List prevPatList,
+ Core.Pat pat) {
+ if (prevPatList.isEmpty()) {
+ return false; // shortcut
+ }
+ // p isCoveredBy [p0 ... pN ]
+ // iff
+ // (f ^ ~f0 ^ ... ^ ~fN) is not satisfiable
+ // where f is the formula for p
+ // and f0 is the formula for p0, etc.
+ return new PatternCoverageChecker(typeSystem).isCoveredBy(pat, prevPatList);
+ }
+
+ /** Returns whether a list of patterns covers every possible value.
+ * If so, any pattern added to this list would be redundant. */
+ @SuppressWarnings("StaticPseudoFunctionalStyleMethod")
+ static boolean isExhaustive(TypeSystem typeSystem, List patList) {
+ if (patList.isEmpty()) {
+ return false; // shortcut
+ }
+ if (Iterables.any(patList, p ->
+ p.op == Op.WILDCARD_PAT || p.op == Op.ID_PAT)) {
+ return true; // shortcut
+ }
+ final Core.WildcardPat wildcardPat =
+ core.wildcardPat(patList.get(0).type);
+ return isCoveredBy(typeSystem, patList, wildcardPat);
+ }
+
+ /** Converts a pattern to a logical term. */
+ private Sat.Term toTerm(Core.Pat pat) {
+ final List terms = new ArrayList<>();
+ toTerm(pat, Path.ROOT, terms);
+ return terms.size() == 1 ? terms.get(0) : sat.and(terms);
+ }
+
+ private void toTerm(Core.Pat pat, Path path, List terms) {
+ switch (pat.op) {
+ case WILDCARD_PAT:
+ case ID_PAT:
+ return; // no constraints to add
+
+ case AS_PAT:
+ toTerm(((Core.AsPat) pat).pat, path, terms);
+ return;
+
+ case BOOL_LITERAL_PAT:
+ // Transform false to FALSE and true to TRUE, constructor of the
+ // internal $bool datatype:
+ // datatype $bool = FALSE | TRUE
+ // Knowing there are only two values allows us to
+ final DataType boolDataType =
+ (DataType) typeSystem.lookupInternal("$bool");
+ final Core.LiteralPat literalPat0 = (Core.LiteralPat) pat;
+ final Boolean value = (Boolean) literalPat0.value;
+ toTerm(core.con0Pat(boolDataType, value ? "TRUE" : "FALSE"), path, terms);
+ return;
+
+ case CHAR_LITERAL_PAT:
+ case INT_LITERAL_PAT:
+ case REAL_LITERAL_PAT:
+ case STRING_LITERAL_PAT:
+ final Core.LiteralPat literalPat = (Core.LiteralPat) pat;
+ terms.add(sat.variable(path.toVar(literalPat.value.toString())));
+ return;
+
+ case CON0_PAT:
+ final Core.Con0Pat con0Pat = (Core.Con0Pat) pat;
+ terms.add(typeConstructorTerm(path, con0Pat.tyCon));
+ return;
+
+ case CON_PAT:
+ final Core.ConPat conPat = (Core.ConPat) pat;
+ terms.add(typeConstructorTerm(path, conPat.tyCon));
+ toTerm(conPat.pat, path, terms);
+ return;
+
+ case CONS_PAT:
+ final Core.ConPat consPat = (Core.ConPat) pat;
+ addConsTerms(path, terms, (Core.TuplePat) consPat.pat);
+ return;
+
+ case TUPLE_PAT:
+ final Core.TuplePat tuplePat = (Core.TuplePat) pat;
+ Ord.forEach(tuplePat.args, (pat2, i) ->
+ toTerm(pat2, path.sub(i), terms));
+ return;
+
+ case RECORD_PAT:
+ final Core.RecordPat recordPat = (Core.RecordPat) pat;
+ Ord.forEach(recordPat.args, (pat2, i) ->
+ toTerm(pat2, path.sub(i), terms));
+ return;
+
+ case LIST_PAT:
+ // For list
+ // [a, b, c]
+ // built terms as if they had written
+ // CONS (a, CONS (b, CONS (c, NIL))
+ // namely
+ // var(tag.0=CONS)
+ // ^ var(tag.0.1=CONS)
+ // ^ var(tag.0.1.1=CONS)
+ // ^ var(tag.0.1.1.1=NIL
+ toTerm(listToCons((Core.ListPat) pat), path, terms);
+ return;
+
+ default:
+ throw new AssertionError(pat.op);
+ }
+ }
+
+ /** Converts a list pattern into a pattern made up of the {@code CONS} and
+ * {@code NIL} constructors of the built-in {@code datatype list}.
+ *
+ *
For example, converts:
+ * "[]" to "NIL",
+ * "[x]" to "CONS (x, NIL)",
+ * "[x, y]" to "CONS (x, CONS (y, NIL))",
+ * etc. */
+ private Core.Pat listToCons(Core.ListPat listPat) {
+ final Type listType = typeSystem.lookupInternal("$list");
+ final DataType listDataType = (DataType) ((ForallType) listType).type;
+ return listToConsRecurse(listDataType, listPat.args);
+ }
+
+ private Core.Pat listToConsRecurse(DataType listDataType,
+ List args) {
+ if (args.isEmpty()) {
+ return core.con0Pat(listDataType, "NIL");
+ } else {
+ return core.consPat(listDataType, "CONS",
+ core.tuplePat(typeSystem,
+ ImmutableList.of(args.get(0),
+ listToConsRecurse(listDataType, Util.skip(args)))));
+ }
+ }
+
+ private void addConsTerms(Path path, List terms,
+ Core.TuplePat tuplePat) {
+ terms.add(typeConstructorTerm(path, "CONS"));
+ toTerm(tuplePat, path, terms);
+ }
+
+ private Sat.Variable typeConstructorTerm(Path path, String con) {
+ final Pair pair = typeSystem.lookupTyCon(con);
+ final DataType dataType = pair.left;
+ DataTypeSlot slot =
+ pathSlots.computeIfAbsent(path,
+ p -> new DataTypeSlot(dataType, p, sat));
+ return slot.constructorMap.get(con);
+ }
+
+ /** Returns whether a pattern is covered by a list of patterns.
+ *
+ *
A pattern {@code pat} is said to be covered by a list of
+ * patterns {@code patList} if any possible value would be caught by one of
+ * the patterns in {@code patList} before reaching {@code pat}. Thus
+ * {@code pat} is said to be redundant in that context, and could
+ * be removed without affecting behavior. */
+ public boolean isCoveredBy(Core.Pat pat, List patList) {
+ final List terms = new ArrayList<>();
+ patList.forEach(p -> terms.add(toTerm(p)));
+ final Sat.Term term = toTerm(pat);
+
+ final List terms1 = new ArrayList<>();
+ terms1.add(term);
+ terms.forEach(t -> terms1.add(sat.not(t)));
+
+ // Add constraints for tags, which are mutually exclusive.
+ // For example, for a type with constructors A, B, C
+ // (tag=A or tag=B or tag=C)
+ // because at least one tag must be present, and
+ // (not (tag=A or tag=B)
+ // or not (tag=B or tag=C)
+ // or not (tag=C or tag=A))
+ // because at most one tag must be present.
+ pathSlots.values().forEach(slot -> {
+ final List terms2 =
+ new ArrayList<>(slot.constructorMap.values());
+ terms1.add(sat.or(terms2));
+
+ final List terms3 = new ArrayList<>();
+ for (int i = 0; i < terms2.size(); i++) {
+ terms3.add(sat.not(sat.or(new ElideList<>(terms2, i))));
+ }
+ terms1.add(sat.or(terms3));
+ });
+ final Sat.Term formula = sat.and(terms1);
+ final Map solve = sat.solve(formula);
+ return solve == null;
+ }
+
+ /** List that removes one particular element from a backing list.
+ *
+ * @param element type */
+ private static class ElideList extends AbstractList {
+ private final List list;
+ private final int elide;
+
+ ElideList(List list, int elide) {
+ this.list = requireNonNull(list, "list");
+ this.elide = elide;
+ }
+
+ @Override public E get(int index) {
+ return list.get(index < elide ? index : index + 1);
+ }
+
+ @Override public int size() {
+ return list.size() - 1;
+ }
+ }
+
+ /** Identifies a point in a nested pattern.
+ *
+ *
Paths are basically immutable lists of integers, built by appending
+ * one element at a time. */
+ private abstract static class Path {
+ /** Root path. */
+ static final Path ROOT = new Path() {
+ @Override protected void path(StringBuilder b) {
+ }
+ };
+
+ @Override public String toString() {
+ return toVar("");
+ }
+
+ /** Creates a sub-path. */
+ Path sub(int i) {
+ return new SubPath(this, i);
+ }
+
+ /** Converts this to a variable.
+ *
+ *
{@code ROOT.sub(2).sub(1).toVar("x")}
+ * will return "2.1.x". */
+ String toVar(String name) {
+ final StringBuilder builder = new StringBuilder();
+ path(builder);
+ builder.append(name);
+ return builder.toString();
+ }
+
+ protected abstract void path(StringBuilder b);
+ }
+
+ /** Path that is a child of a given parent path.
+ * The {@code ordinal} makes it unique within its parent.
+ * For tuple and record patterns, {@code ordinal} is the field ordinal. */
+ private static class SubPath extends Path {
+ final Path parent;
+ final int ordinal;
+
+ SubPath(Path parent, int ordinal) {
+ this.parent = parent;
+ this.ordinal = ordinal;
+ }
+
+ @Override protected void path(StringBuilder b) {
+ parent.path(b);
+ b.append(ordinal).append('.');
+ }
+ }
+
+ /** Payload of a {@code Sat.Variable} that is an algebraic type.
+ * There are sub-variables representing whether the tag holds
+ * each of its allowed values (each of which is a constructor). */
+ private static class DataTypeSlot {
+ final DataType dataType;
+ final ImmutableMap constructorMap;
+
+ DataTypeSlot(DataType dataType, Path path, Sat sat) {
+ this.dataType = dataType;
+ final ImmutableMap.Builder b =
+ ImmutableMap.builder();
+ dataType.typeConstructors.forEach((name, type) ->
+ b.put(name, sat.variable(path.toVar(name))));
+ this.constructorMap = b.build();
+ }
+ }
+}
+
+// End PatternCoverageChecker.java
diff --git a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java
index 798b9744c..72006d1ac 100644
--- a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java
+++ b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java
@@ -87,7 +87,7 @@ public static Relationalizer of(TypeSystem typeSystem, Environment env) {
// "defaultYieldExp", and therefore we cannot add another yield
// step. We will have to inline the yield expression as a let.
final Core.Yield yieldStep = core.yield_(typeSystem,
- core.apply(fnType.resultType, f,
+ core.apply(apply.pos, fnType.resultType, f,
core.implicitYieldExp(typeSystem, from.steps)));
return core.from(typeSystem, append(from.steps, yieldStep));
}
@@ -100,7 +100,7 @@ public static Relationalizer of(TypeSystem typeSystem, Environment env) {
final Core.From from = toFrom(apply.arg);
final Core.Where whereStep =
core.where(core.lastBindings(from.steps),
- core.apply(fnType.resultType, f,
+ core.apply(apply.pos, fnType.resultType, f,
core.implicitYieldExp(typeSystem, from.steps)));
return core.from(typeSystem, append(from.steps, whereStep));
}
diff --git a/src/main/java/net/hydromatic/morel/compile/Resolver.java b/src/main/java/net/hydromatic/morel/compile/Resolver.java
index ab6160d49..e0c162303 100644
--- a/src/main/java/net/hydromatic/morel/compile/Resolver.java
+++ b/src/main/java/net/hydromatic/morel/compile/Resolver.java
@@ -21,6 +21,7 @@
import net.hydromatic.morel.ast.Ast;
import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.ast.Op;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.ast.Visitor;
import net.hydromatic.morel.type.Binding;
import net.hydromatic.morel.type.DataType;
@@ -44,6 +45,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -141,7 +143,8 @@ public Core.ValDecl toCore(Ast.ValDecl valDecl) {
final List bindings = new ArrayList<>(); // discard
final ResolvedValDecl resolvedValDecl = resolveValDecl(valDecl, bindings);
Core.NonRecValDecl nonRecValDecl =
- core.nonRecValDecl(resolvedValDecl.pat, resolvedValDecl.exp);
+ core.nonRecValDecl(resolvedValDecl.pat, resolvedValDecl.exp,
+ resolvedValDecl.patExps.get(0).pos);
return resolvedValDecl.rec
? core.recValDecl(ImmutableList.of(nonRecValDecl))
: nonRecValDecl;
@@ -181,19 +184,23 @@ private ResolvedValDecl resolveValDecl(Ast.ValDecl valDecl,
valDecl.valBinds.forEach(valBind ->
flatten(matches, composite, valBind.pat, valBind.exp));
- final List pats = new ArrayList<>();
- final List exps = new ArrayList<>();
+ final List patExps = new ArrayList<>();
if (valDecl.rec) {
+ final List pats = new ArrayList<>();
matches.forEach((pat, exp) -> pats.add(toCore(pat)));
pats.forEach(p -> Compiles.acceptBinding(typeMap.typeSystem, p, bindings));
final Resolver r = withEnv(bindings);
- matches.forEach((pat, exp) -> exps.add(r.toCore(exp)));
+ final Iterator patIter = pats.iterator();
+ matches.forEach((pat, exp) ->
+ patExps.add(
+ new PatExp(patIter.next(), r.toCore(exp),
+ pat.pos.plus(exp.pos))));
} else {
- matches.forEach((pat, exp) -> {
- pats.add(toCore(pat));
- exps.add(toCore(exp));
- });
- pats.forEach(p -> Compiles.acceptBinding(typeMap.typeSystem, p, bindings));
+ matches.forEach((pat, exp) ->
+ patExps.add(
+ new PatExp(toCore(pat), toCore(exp), pat.pos.plus(exp.pos))));
+ patExps.forEach(x ->
+ Compiles.acceptBinding(typeMap.typeSystem, x.pat, bindings));
}
// Convert recursive to non-recursive if the bound variable is not
@@ -203,17 +210,20 @@ private ResolvedValDecl resolveValDecl(Ast.ValDecl valDecl,
// val inc = fn i => i + 1
// because "i + 1" does not reference "inc".
boolean rec = valDecl.rec
- && references(exps, pats);
+ && references(patExps);
// Transform "let val v1 = E1 and v2 = E2 in E end"
// to "let val v = (v1, v2) in case v of (E1, E2) => E end"
final Core.Pat pat0;
final Core.Exp exp;
if (composite) {
+ final List pats = Util.transform(patExps, x -> x.pat);
+ final List exps = Util.transform(patExps, x -> x.exp);
pat0 = core.tuplePat(typeMap.typeSystem, pats);
exp = core.tuple((RecordLikeType) pat0.type, exps);
} else {
- pat0 = pats.get(0);
- exp = exps.get(0);
+ final PatExp patExp = patExps.get(0);
+ pat0 = patExp.pat;
+ exp = patExp.exp;
}
final Core.NamedPat pat;
if (pat0 instanceof Core.NamedPat) {
@@ -222,8 +232,7 @@ private ResolvedValDecl resolveValDecl(Ast.ValDecl valDecl,
pat = core.asPat(exp.type, "it", nameGenerator, pat0);
}
- return new ResolvedValDecl(rec, ImmutableList.copyOf(pats),
- ImmutableList.copyOf(exps), pat, exp);
+ return new ResolvedValDecl(rec, ImmutableList.copyOf(patExps), pat, exp);
}
/** Returns whether any of the expressions in {@code exps} references
@@ -231,11 +240,11 @@ private ResolvedValDecl resolveValDecl(Ast.ValDecl valDecl,
*
*
This method is used to decide whether it is safe to convert a recursive
* declaration into a non-recursive one. */
- private boolean references(List exps, List pats) {
+ private boolean references(List patExps) {
final Set refSet = new HashSet<>();
final ReferenceFinder finder =
new ReferenceFinder(typeMap.typeSystem, Environments.empty(), refSet);
- exps.forEach(e -> e.accept(finder));
+ patExps.forEach(x -> x.exp.accept(finder));
final Set defSet = new HashSet<>();
final Visitor v = new Visitor() {
@@ -243,7 +252,7 @@ private boolean references(List exps, List pats) {
defSet.add(idPat);
}
};
- pats.forEach(p -> p.accept(v));
+ patExps.forEach(x -> x.pat.accept(v));
return Util.intersects(refSet, defSet);
}
@@ -303,6 +312,8 @@ private Core.Exp toCore(Ast.Exp exp) {
return core.stringLiteral((String) ((Ast.Literal) exp).value);
case UNIT_LITERAL:
return core.unitLiteral();
+ case ANNOTATED_EXP:
+ return toCore(((Ast.AnnotatedExp) exp).exp);
case ID:
return toCore((Ast.Id) exp);
case ANDALSO:
@@ -374,7 +385,7 @@ private Core.Tuple toCore(Ast.Record record) {
private Core.Exp toCore(Ast.ListExp list) {
final ListType type = (ListType) typeMap.getType(list);
- return core.apply(type,
+ return core.apply(list.pos, type,
core.functionLiteral(typeMap.typeSystem, BuiltIn.Z_LIST),
core.tuple(typeMap.typeSystem, null,
transform(list.args, this::toCore)));
@@ -385,7 +396,7 @@ private Core.Exp toCore(Ast.ListExp list) {
private Core.Exp toCoreFromEq(Ast.Exp exp) {
final Type type = typeMap.getType(exp);
final ListType listType = typeMap.typeSystem.listType(type);
- return core.apply(listType,
+ return core.apply(exp.pos, listType,
core.functionLiteral(typeMap.typeSystem, BuiltIn.Z_LIST),
core.tuple(typeMap.typeSystem, null, ImmutableList.of(toCore(exp))));
}
@@ -401,7 +412,7 @@ private Core.Apply toCore(Ast.Apply apply) {
} else {
coreFn = toCore(apply.fn);
}
- return core.apply(type, coreFn, coreArg);
+ return core.apply(apply.pos, type, coreFn, coreArg);
}
private Core.RecordSelector toCore(Ast.RecordSelector recordSelector) {
@@ -414,7 +425,7 @@ private Core.Apply toCore(Ast.InfixCall call) {
Core.Exp core0 = toCore(call.a0);
Core.Exp core1 = toCore(call.a1);
final BuiltIn builtIn = toBuiltIn(call.op);
- return core.apply(typeMap.getType(call),
+ return core.apply(call.pos, typeMap.getType(call),
core.functionLiteral(typeMap.typeSystem, builtIn),
core.tuple(typeMap.typeSystem, null, ImmutableList.of(core0, core1)));
}
@@ -445,7 +456,8 @@ private Core.Fn toCore(Ast.Fn fn) {
// needs intermediate variable and case, "fn v => case v of (x, y) => exp"
final Core.IdPat idPat = core.idPat(type.paramType, nameGenerator);
final Core.Id id = core.id(idPat);
- return core.fn(type, idPat, core.caseOf(type.resultType, id, matchList));
+ return core.fn(type, idPat,
+ core.caseOf(type.resultType, id, matchList, fn.pos));
}
private Core.Case toCore(Ast.If if_) {
@@ -455,7 +467,7 @@ private Core.Case toCore(Ast.If if_) {
private Core.Case toCore(Ast.Case case_) {
return core.caseOf(typeMap.getType(case_), toCore(case_.exp),
- transform(case_.matchList, this::toCore));
+ transform(case_.matchList, this::toCore), case_.pos);
}
private Core.Exp toCore(Ast.Let let) {
@@ -525,6 +537,11 @@ private Core.Pat toCore(Ast.Pat pat, Type type, Type targetType) {
final Ast.AsPat asPat = (Ast.AsPat) pat;
return core.asPat(type, asPat.id.name, nameGenerator, toCore(asPat.pat));
+ case ANNOTATED_PAT:
+ // There is no annotated pat in core, because all patterns have types.
+ final Ast.AnnotatedPat annotatedPat = (Ast.AnnotatedPat) pat;
+ return toCore(annotatedPat.pat);
+
case CON_PAT:
final Ast.ConPat conPat = (Ast.ConPat) pat;
return core.conPat(type, conPat.tyCon.name, toCore(conPat.pat));
@@ -574,7 +591,7 @@ private Core.Match toCore(Ast.Match match) {
final List bindings = new ArrayList<>();
Compiles.acceptBinding(typeMap.typeSystem, pat, bindings);
final Core.Exp exp = withEnv(bindings).toCore(match.exp);
- return core.match(pat, exp);
+ return core.match(pat, exp, match.pos);
}
Core.Exp toCore(Ast.From from) {
@@ -585,7 +602,7 @@ Core.Exp toCore(Ast.From from) {
final Core.From coreFrom =
fromStepToCore(bindings, listType, from.steps,
ImmutableList.of());
- return core.apply(type,
+ return core.apply(from.pos, type,
core.functionLiteral(typeMap.typeSystem, BuiltIn.RELATIONAL_ONLY),
coreFrom);
} else {
@@ -731,20 +748,16 @@ public abstract static class ResolvedDecl {
class ResolvedValDecl extends ResolvedDecl {
final boolean rec;
final boolean composite;
- final ImmutableList pats;
- final ImmutableList exps;
+ final ImmutableList patExps;
final Core.NamedPat pat;
final Core.Exp exp;
ResolvedValDecl(boolean rec,
- ImmutableList pats,
- ImmutableList exps,
+ ImmutableList patExps,
Core.NamedPat pat, Core.Exp exp) {
this.rec = rec;
- this.composite = pats.size() > 1;
- this.pats = pats;
- this.exps = exps;
- assert pats.size() == exps.size();
+ this.composite = patExps.size() > 1;
+ this.patExps = patExps;
this.pat = pat;
this.exp = exp;
}
@@ -752,12 +765,14 @@ class ResolvedValDecl extends ResolvedDecl {
@Override Core.Let toExp(Core.Exp resultExp) {
if (rec) {
final List valDecls = new ArrayList<>();
- Pair.forEach(pats, exps, (pat, exp) ->
- valDecls.add(core.nonRecValDecl((Core.IdPat) pat, exp)));
+ patExps.forEach(x ->
+ valDecls.add(core.nonRecValDecl((Core.IdPat) x.pat, x.exp, x.pos)));
return core.let(core.recValDecl(valDecls), resultExp);
}
- if (!composite && pats.get(0) instanceof Core.IdPat) {
- Core.NonRecValDecl valDecl = core.nonRecValDecl((Core.IdPat) pats.get(0), exps.get(0));
+ if (!composite && patExps.get(0).pat instanceof Core.IdPat) {
+ final PatExp x = patExps.get(0);
+ Core.NonRecValDecl valDecl =
+ core.nonRecValDecl((Core.IdPat) x.pat, x.exp, x.pos);
return rec
? core.let(core.recValDecl(ImmutableList.of(valDecl)), resultExp)
: core.let(valDecl, resultExp);
@@ -769,13 +784,31 @@ class ResolvedValDecl extends ResolvedDecl {
final String name = nameGenerator.get();
final Core.IdPat idPat = core.idPat(pat.type, name, nameGenerator);
final Core.Id id = core.id(idPat);
- return core.let(core.nonRecValDecl(idPat, exp),
+ final Pos pos = patExps.get(0).pos;
+ return core.let(core.nonRecValDecl(idPat, exp, pos),
core.caseOf(resultExp.type, id,
- ImmutableList.of(core.match(pat, resultExp))));
+ ImmutableList.of(core.match(pat, resultExp, pos)), pos));
}
}
}
+ /** Pattern and expression. */
+ static class PatExp {
+ final Core.Pat pat;
+ final Core.Exp exp;
+ final Pos pos;
+
+ PatExp(Core.Pat pat, Core.Exp exp, Pos pos) {
+ this.pat = pat;
+ this.exp = exp;
+ this.pos = pos;
+ }
+
+ @Override public String toString() {
+ return "[pat: " + pat + ", exp: " + exp + ", pos: " + pos + "]";
+ }
+ }
+
/** Resolved datatype declaration. */
static class ResolvedDatatypeDecl extends ResolvedDecl {
private final ImmutableList dataTypes;
diff --git a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java
index 73730204c..14c2e62d7 100644
--- a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java
+++ b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java
@@ -48,6 +48,7 @@
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.calcite.util.Holder;
@@ -71,6 +72,7 @@
import java.util.stream.Collectors;
import static net.hydromatic.morel.ast.AstBuilder.ast;
+import static net.hydromatic.morel.type.RecordType.ORDERING;
import static net.hydromatic.morel.util.Static.skip;
import static net.hydromatic.morel.util.Static.toImmutableList;
@@ -194,6 +196,12 @@ private Ast.Exp deduceType(TypeEnv env, Ast.Exp node, Unifier.Variable v) {
case UNIT_LITERAL:
return reg(node, v, toTerm(PrimitiveType.UNIT));
+ case ANNOTATED_EXP:
+ final Ast.AnnotatedExp annotatedExp = (Ast.AnnotatedExp) node;
+ final Type type = toType(annotatedExp.type, typeSystem);
+ deduceType(env, annotatedExp.exp, v);
+ return reg(node, v, toTerm(type, Subst.EMPTY));
+
case ANDALSO:
case ORELSE:
return infix(env, (Ast.InfixCall) node, v, PrimitiveType.BOOL);
@@ -311,7 +319,9 @@ private Ast.Exp deduceType(TypeEnv env, Ast.Exp node, Unifier.Variable v) {
case ID:
final Ast.Id id = (Ast.Id) node;
- final Unifier.Term term = env.get(typeSystem, id.name);
+ final Unifier.Term term = env.get(typeSystem, id.name, name ->
+ new CompileException("unbound variable or constructor: " + name,
+ false, id.pos));
return reg(id, v, term);
case FN:
@@ -744,8 +754,9 @@ private Ast.Decl deduceValDeclType(TypeEnv env, Ast.ValDecl valDecl,
final Holder envHolder = Holder.of(env);
final Map> map0 =
new LinkedHashMap<>();
+ //noinspection FunctionalExpressionCanBeFolded
valDecl.valBinds.forEach(b ->
- map0.put(b, Suppliers.memoize(unifier::variable)));
+ map0.put(b, Suppliers.memoize(unifier::variable)::get));
map0.forEach((valBind, vPatSupplier) -> {
// If recursive, bind each value (presumably a function) to its type
// in the environment before we try to deduce the type of the expression.
@@ -792,14 +803,31 @@ private Type toType(Ast.Type type) {
final Ast.TupleType tupleType = (Ast.TupleType) type;
return typeSystem.tupleType(toTypes(tupleType.types));
+ case RECORD_TYPE:
+ final Ast.RecordType recordType = (Ast.RecordType) type;
+ final ImmutableSortedMap.Builder argNameTypes =
+ ImmutableSortedMap.orderedBy(ORDERING);
+ recordType.fieldTypes.forEach((name, t) ->
+ argNameTypes.put(name, toType(t)));
+ return typeSystem.recordType(argNameTypes.build());
+
+ case FUNCTION_TYPE:
+ final Ast.FunctionType functionType = (Ast.FunctionType) type;
+ final Type paramType = toType(functionType.paramType, typeSystem);
+ final Type resultType = toType(functionType.resultType, typeSystem);
+ return typeSystem.fnType(paramType, resultType);
+
case NAMED_TYPE:
final Ast.NamedType namedType = (Ast.NamedType) type;
+ final List typeList = toTypes(namedType.types);
+ if (namedType.name.equals(LIST_TY_CON) && typeList.size() == 1) {
+ // TODO: make 'list' a regular generic type
+ return typeSystem.listType(typeList.get(0));
+ }
final Type genericType = typeSystem.lookup(namedType.name);
if (namedType.types.isEmpty()) {
return genericType;
}
- final List typeList = namedType.types.stream().map(this::toType)
- .collect(toImmutableList());
return typeSystem.apply(genericType, typeList);
case TY_VAR:
@@ -808,7 +836,7 @@ private Type toType(Ast.Type type) {
name -> typeSystem.typeVariable(tyVarMap.size()));
default:
- throw new AssertionError("cannot convert type " + type);
+ throw new AssertionError("cannot convert type " + type + " " + type.op);
}
}
@@ -847,22 +875,39 @@ private Ast.ValDecl toValDecl(TypeEnv env, Ast.FunDecl funDecl) {
private Ast.ValBind toValBind(TypeEnv env, Ast.FunBind funBind) {
final List vars;
Ast.Exp exp;
+ Ast.Type returnType = null;
if (funBind.matchList.size() == 1) {
- exp = funBind.matchList.get(0).exp;
- vars = funBind.matchList.get(0).patList;
+ final Ast.FunMatch funMatch = funBind.matchList.get(0);
+ exp = funMatch.exp;
+ vars = funMatch.patList;
+ returnType = funMatch.returnType;
} else {
final List varNames =
MapList.of(funBind.matchList.get(0).patList.size(),
index -> "v" + index);
vars = Lists.transform(varNames, v -> ast.idPat(Pos.ZERO, v));
final List matchList = new ArrayList<>();
+ Pos prevReturnTypePos = null;
for (Ast.FunMatch funMatch : funBind.matchList) {
matchList.add(
ast.match(funMatch.pos, patTuple(env, funMatch.patList),
funMatch.exp));
+ if (funMatch.returnType != null) {
+ if (returnType != null
+ && !returnType.equals(funMatch.returnType)) {
+ throw new CompileException("parameter or result constraints of "
+ + "clauses don't agree [tycon mismatch]", false,
+ prevReturnTypePos.plus(funMatch.pos));
+ }
+ returnType = funMatch.returnType;
+ prevReturnTypePos = funMatch.pos;
+ }
}
exp = ast.caseOf(Pos.ZERO, idTuple(varNames), matchList);
}
+ if (returnType != null) {
+ exp = ast.annotatedExp(exp.pos, exp, returnType);
+ }
final Pos pos = funBind.pos;
for (Ast.Pat var : Lists.reverse(vars)) {
exp = ast.fn(pos, ast.match(pos, var, exp));
@@ -894,7 +939,8 @@ private Ast.Pat patTuple(TypeEnv env, List patList) {
case ID_PAT:
final Ast.IdPat idPat = (Ast.IdPat) pat;
if (env.has(idPat.name)) {
- final Unifier.Term term = env.get(typeSystem, idPat.name);
+ final Unifier.Term term = env.get(typeSystem, idPat.name, name ->
+ new RuntimeException("oops, should have " + idPat.name));
if (term instanceof Unifier.Sequence
&& ((Unifier.Sequence) term).operator.equals(FN_TY_CON)) {
list2.add(
@@ -959,6 +1005,12 @@ private Ast.Pat deducePatType(TypeEnv env, Ast.Pat pat,
deducePatType(env, asPat.pat, termMap, null, v);
return reg(pat, null, v);
+ case ANNOTATED_PAT:
+ final Ast.AnnotatedPat annotatedPat = (Ast.AnnotatedPat) pat;
+ final Type type = toType(annotatedPat.type, typeSystem);
+ deducePatType(env, annotatedPat.pat, termMap, null, v);
+ return reg(pat, v, toTerm(type, Subst.EMPTY));
+
case TUPLE_PAT:
final List typeTerms = new ArrayList<>();
final Ast.TuplePat tuple = (Ast.TuplePat) pat;
@@ -1186,8 +1238,9 @@ private Unifier.Term toTerm(Type type, Subst subst) {
enum EmptyTypeEnv implements TypeEnv {
INSTANCE;
- @Override public Unifier.Term get(TypeSystem typeSystem, String name) {
- throw new CompileException("unbound variable or constructor: " + name);
+ @Override public Unifier.Term get(TypeSystem typeSystem, String name,
+ Function exceptionFactory) {
+ throw exceptionFactory.apply(name);
}
@Override public boolean has(String name) {
@@ -1206,7 +1259,8 @@ enum EmptyTypeEnv implements TypeEnv {
/** Type environment. */
interface TypeEnv {
- Unifier.Term get(TypeSystem typeSystem, String name);
+ Unifier.Term get(TypeSystem typeSystem, String name,
+ Function exceptionFactory);
boolean has(String name);
TypeEnv bind(String name, Function termFactory);
@@ -1252,13 +1306,14 @@ private static class BindTypeEnv implements TypeEnv {
this.parent = Objects.requireNonNull(parent);
}
- @Override public Unifier.Term get(TypeSystem typeSystem, String name) {
+ @Override public Unifier.Term get(TypeSystem typeSystem, String name,
+ Function exceptionFactory) {
for (BindTypeEnv e = this;; e = (BindTypeEnv) e.parent) {
if (e.definedName.equals(name)) {
return e.termFactory.apply(typeSystem);
}
if (!(e.parent instanceof BindTypeEnv)) {
- return e.parent.get(typeSystem, name);
+ return e.parent.get(typeSystem, name, exceptionFactory);
}
}
}
diff --git a/src/main/java/net/hydromatic/morel/eval/Applicable2.java b/src/main/java/net/hydromatic/morel/eval/Applicable2.java
index 203f5655e..829817d1a 100644
--- a/src/main/java/net/hydromatic/morel/eval/Applicable2.java
+++ b/src/main/java/net/hydromatic/morel/eval/Applicable2.java
@@ -18,6 +18,7 @@
*/
package net.hydromatic.morel.eval;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.compile.BuiltIn;
import java.util.List;
@@ -63,8 +64,12 @@
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class Applicable2 extends ApplicableImpl {
+ protected Applicable2(BuiltIn builtIn, Pos pos) {
+ super(builtIn, pos);
+ }
+
protected Applicable2(BuiltIn builtIn) {
- super(builtIn);
+ this(builtIn, Pos.ZERO);
}
@Override public Object apply(EvalEnv env, Object argValue) {
diff --git a/src/main/java/net/hydromatic/morel/eval/Applicable3.java b/src/main/java/net/hydromatic/morel/eval/Applicable3.java
index 5e80f1dcd..9b5052375 100644
--- a/src/main/java/net/hydromatic/morel/eval/Applicable3.java
+++ b/src/main/java/net/hydromatic/morel/eval/Applicable3.java
@@ -18,6 +18,7 @@
*/
package net.hydromatic.morel.eval;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.compile.BuiltIn;
import java.util.List;
@@ -48,8 +49,12 @@
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class Applicable3
extends ApplicableImpl {
+ protected Applicable3(BuiltIn builtIn, Pos pos) {
+ super(builtIn, pos);
+ }
+
protected Applicable3(BuiltIn builtIn) {
- super(builtIn);
+ this(builtIn, Pos.ZERO);
}
@Override public Object apply(EvalEnv env, Object argValue) {
diff --git a/src/main/java/net/hydromatic/morel/eval/ApplicableImpl.java b/src/main/java/net/hydromatic/morel/eval/ApplicableImpl.java
index e6f68454f..e8208a513 100644
--- a/src/main/java/net/hydromatic/morel/eval/ApplicableImpl.java
+++ b/src/main/java/net/hydromatic/morel/eval/ApplicableImpl.java
@@ -18,23 +18,37 @@
*/
package net.hydromatic.morel.eval;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.compile.BuiltIn;
/** Abstract implementation of {@link Applicable} that describes itself
* with a constant name. */
abstract class ApplicableImpl implements Applicable {
private final String name;
+ final Pos pos;
- protected ApplicableImpl(String name) {
+ protected ApplicableImpl(String name, Pos pos) {
this.name = name;
+ this.pos = pos;
+ }
+
+ protected ApplicableImpl(String name) {
+ this(name, Pos.ZERO);
}
/** Creates an ApplicableImpl that directly implements a BuiltIn.
* The parameter is currently only for provenance purposes. */
- protected ApplicableImpl(BuiltIn builtIn) {
+ protected ApplicableImpl(BuiltIn builtIn, Pos pos) {
this(builtIn.mlName.startsWith("op ")
? builtIn.mlName.substring("op ".length())
- : builtIn.structure + "." + builtIn.mlName);
+ : builtIn.structure + "." + builtIn.mlName,
+ pos);
+ }
+
+ /** Creates an ApplicableImpl that directly implements a BuiltIn.
+ * The parameter is currently only for provenance purposes. */
+ protected ApplicableImpl(BuiltIn builtIn) {
+ this(builtIn, Pos.ZERO);
}
@Override public String toString() {
diff --git a/src/main/java/net/hydromatic/morel/eval/Closure.java b/src/main/java/net/hydromatic/morel/eval/Closure.java
index 0a8c1bfb3..0531a1b17 100644
--- a/src/main/java/net/hydromatic/morel/eval/Closure.java
+++ b/src/main/java/net/hydromatic/morel/eval/Closure.java
@@ -19,6 +19,7 @@
package net.hydromatic.morel.eval;
import net.hydromatic.morel.ast.Core;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.util.Pair;
import com.google.common.collect.ImmutableList;
@@ -48,12 +49,14 @@ public class Closure implements Comparable, Applicable {
* pattern ({@code _}) succeeds, and therefore we evaluate the second
* code {@code "no"}. */
private final ImmutableList> patCodes;
+ private final Pos pos;
/** Not a public API. */
public Closure(EvalEnv evalEnv,
- ImmutableList> patCodes) {
+ ImmutableList> patCodes, Pos pos) {
this.evalEnv = requireNonNull(evalEnv).fix();
this.patCodes = requireNonNull(patCodes);
+ this.pos = pos;
}
@Override public String toString() {
@@ -106,7 +109,7 @@ Object bindEval(Object argValue) {
return code.eval(envRef.env);
}
}
- throw new Codes.MorelRuntimeException(Codes.BuiltInExn.BIND);
+ throw new Codes.MorelRuntimeException(Codes.BuiltInExn.BIND, pos);
}
@Override public Object apply(EvalEnv env, Object argValue) {
diff --git a/src/main/java/net/hydromatic/morel/eval/Codes.java b/src/main/java/net/hydromatic/morel/eval/Codes.java
index cc12fc996..ff516ad33 100644
--- a/src/main/java/net/hydromatic/morel/eval/Codes.java
+++ b/src/main/java/net/hydromatic/morel/eval/Codes.java
@@ -20,6 +20,7 @@
import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.ast.Op;
+import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.compile.BuiltIn;
import net.hydromatic.morel.compile.Environment;
import net.hydromatic.morel.compile.Macro;
@@ -30,6 +31,7 @@
import net.hydromatic.morel.type.Type;
import net.hydromatic.morel.type.TypeSystem;
import net.hydromatic.morel.util.MapList;
+import net.hydromatic.morel.util.MorelException;
import net.hydromatic.morel.util.Ord;
import net.hydromatic.morel.util.Pair;
import net.hydromatic.morel.util.Static;
@@ -307,15 +309,26 @@ public static Code orElse(Code code0, Code code1) {
};
/** @see BuiltIn#INTERACT_USE */
- private static final Applicable INTERACT_USE =
- new ApplicableImpl(BuiltIn.INTERACT_USE) {
- @Override public Object apply(EvalEnv env, Object arg) {
- final String f = (String) arg;
- final Session session = (Session) env.getOpt(EvalEnv.SESSION);
- session.use(f);
- return Unit.INSTANCE;
- }
- };
+ private static final Applicable INTERACT_USE = new InteractUse(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#INTERACT_USE}. */
+ private static class InteractUse extends ApplicableImpl
+ implements Positioned {
+ InteractUse(Pos pos) {
+ super(BuiltIn.INTERACT_USE, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new InteractUse(pos);
+ }
+
+ @Override public Object apply(EvalEnv env, Object arg) {
+ final String f = (String) arg;
+ final Session session = (Session) env.getOpt(EvalEnv.SESSION);
+ session.use(f, pos);
+ return Unit.INSTANCE;
+ }
+ }
/** @see BuiltIn#OP_CARET */
private static final Applicable OP_CARET =
@@ -588,81 +601,140 @@ public static Applicable nth(int slot) {
};
/** @see BuiltIn#STRING_SUB */
- private static final Applicable STRING_SUB =
- new Applicable2(BuiltIn.STRING_SUB) {
- @Override public Character apply(String s, Integer i) {
- if (i < 0 || i >= s.length()) {
- throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT);
- }
- return s.charAt(i);
- }
- };
+ private static final Applicable STRING_SUB = new StringSub(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#STRING_SUB}. */
+ private static class StringSub extends Applicable2
+ implements Positioned {
+ StringSub(Pos pos) {
+ super(BuiltIn.STRING_SUB, pos);
+ }
+
+ @Override public Character apply(String s, Integer i) {
+ if (i < 0 || i >= s.length()) {
+ throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT, pos);
+ }
+ return s.charAt(i);
+ }
+
+ public StringSub withPos(Pos pos) {
+ return new StringSub(pos);
+ }
+ }
/** @see BuiltIn#STRING_EXTRACT */
private static final Applicable STRING_EXTRACT =
- new Applicable3(BuiltIn.STRING_EXTRACT) {
- @Override public String apply(String s, Integer i, List jOpt) {
- if (i < 0) {
- throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT);
- }
- if (jOpt.size() == 2) {
- final int j = (Integer) jOpt.get(1);
- if (j < 0 || i + j > s.length()) {
- throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT);
- }
- return s.substring(i, i + j);
- } else {
- if (i > s.length()) {
- throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT);
- }
- return s.substring(i);
- }
+ new StringExtract(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#STRING_SUB}. */
+ private static class StringExtract
+ extends Applicable3 implements Positioned {
+ StringExtract(Pos pos) {
+ super(BuiltIn.STRING_EXTRACT, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new StringExtract(pos);
+ }
+
+ @Override public String apply(String s, Integer i, List jOpt) {
+ if (i < 0) {
+ throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT, pos);
+ }
+ if (jOpt.size() == 2) {
+ final int j = (Integer) jOpt.get(1);
+ if (j < 0 || i + j > s.length()) {
+ throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT, pos);
}
- };
+ return s.substring(i, i + j);
+ } else {
+ if (i > s.length()) {
+ throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT, pos);
+ }
+ return s.substring(i);
+ }
+ }
+ }
/** @see BuiltIn#STRING_SUBSTRING */
private static final Applicable STRING_SUBSTRING =
- new Applicable3(
- BuiltIn.STRING_SUBSTRING) {
- @Override public String apply(String s, Integer i, Integer j) {
- if (i < 0 || j < 0 || i + j > s.length()) {
- throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT);
- }
- return s.substring(i, i + j);
- }
- };
+ new StringSubstring(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#STRING_SUBSTRING}. */
+ private static class StringSubstring
+ extends Applicable3
+ implements Positioned {
+ StringSubstring(Pos pos) {
+ super(BuiltIn.STRING_SUBSTRING, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new StringSubstring(pos);
+ }
+
+ @Override public String apply(String s, Integer i, Integer j) {
+ if (i < 0 || j < 0 || i + j > s.length()) {
+ throw new MorelRuntimeException(BuiltInExn.SUBSCRIPT, pos);
+ }
+ return s.substring(i, i + j);
+ }
+ }
/** @see BuiltIn#STRING_CONCAT */
- private static final Applicable STRING_CONCAT =
- new ApplicableImpl(BuiltIn.STRING_CONCAT) {
- @SuppressWarnings("unchecked")
- @Override public Object apply(EvalEnv env, Object arg) {
- return stringConcat("", (List) arg);
- }
- };
+ private static final Applicable STRING_CONCAT = new StringConcat(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#STRING_CONCAT}. */
+ private static class StringConcat extends ApplicableImpl
+ implements Positioned {
+ StringConcat(Pos pos) {
+ super(BuiltIn.STRING_CONCAT, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new StringConcat(pos);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public Object apply(EvalEnv env, Object arg) {
+ return stringConcat(pos, "", (List) arg);
+ }
+ }
/** @see BuiltIn#STRING_CONCAT_WITH */
private static final Applicable STRING_CONCAT_WITH =
- new ApplicableImpl(BuiltIn.STRING_CONCAT_WITH) {
- @Override public Object apply(EvalEnv env, Object argValue) {
- final String separator = (String) argValue;
- return new ApplicableImpl("String.concatWith$separator") {
- @SuppressWarnings("unchecked")
- @Override public Object apply(EvalEnv env, Object arg) {
- return stringConcat(separator, (List) arg);
- }
- };
+ new StringConcatWith(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#STRING_CONCAT_WITH}. */
+ private static class StringConcatWith extends ApplicableImpl
+ implements Positioned {
+ StringConcatWith(Pos pos) {
+ super(BuiltIn.STRING_CONCAT_WITH, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new StringConcatWith(pos);
+ }
+
+ @Override public Object apply(EvalEnv env, Object argValue) {
+ final String separator = (String) argValue;
+ return new ApplicableImpl("String.concatWith$separator") {
+ @SuppressWarnings("unchecked")
+ @Override public Object apply(EvalEnv env, Object arg) {
+ return stringConcat(pos, separator, (List) arg);
}
};
+ }
+ }
- private static String stringConcat(String separator, List list) {
+ private static String stringConcat(Pos pos, String separator,
+ List list) {
long n = 0;
for (String s : list) {
n += s.length();
n += separator.length();
}
if (n > STRING_MAX_SIZE) {
- throw new MorelRuntimeException(BuiltInExn.SIZE);
+ throw new MorelRuntimeException(BuiltInExn.SIZE, pos);
}
return String.join(separator, list);
}
@@ -825,41 +897,72 @@ private static ApplicableImpl union(final BuiltIn builtIn) {
/** @see BuiltIn#LIST_HD */
private static final Applicable LIST_HD =
- new ApplicableImpl(BuiltIn.LIST_HD) {
- @Override public Object apply(EvalEnv env, Object arg) {
- final List list = (List) arg;
- if (list.isEmpty()) {
- throw new MorelRuntimeException(BuiltInExn.EMPTY);
- }
- return list.get(0);
- }
- };
+ new ListHd(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#LIST_HD}. */
+ private static class ListHd extends ApplicableImpl implements Positioned {
+ ListHd(Pos pos) {
+ super(BuiltIn.LIST_HD, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new ListHd(pos);
+ }
+
+ @Override public Object apply(EvalEnv env, Object arg) {
+ final List list = (List) arg;
+ if (list.isEmpty()) {
+ throw new MorelRuntimeException(BuiltInExn.EMPTY, pos);
+ }
+ return list.get(0);
+ }
+ }
/** @see BuiltIn#LIST_TL */
- private static final Applicable LIST_TL =
- new ApplicableImpl(BuiltIn.LIST_TL) {
- @Override public Object apply(EvalEnv env, Object arg) {
- final List list = (List) arg;
- final int size = list.size();
- if (size == 0) {
- throw new MorelRuntimeException(BuiltInExn.EMPTY);
- }
- return list.subList(1, size);
- }
- };
+ private static final Applicable LIST_TL = new ListTl(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#LIST_TL}. */
+ private static class ListTl extends ApplicableImpl implements Positioned {
+ ListTl(Pos pos) {
+ super(BuiltIn.LIST_TL, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new ListTl(pos);
+ }
+
+ @Override public Object apply(EvalEnv env, Object arg) {
+ final List list = (List) arg;
+ final int size = list.size();
+ if (size == 0) {
+ throw new MorelRuntimeException(BuiltInExn.EMPTY, pos);
+ }
+ return list.subList(1, size);
+ }
+ }
/** @see BuiltIn#LIST_LAST */
- private static final Applicable LIST_LAST =
- new ApplicableImpl(BuiltIn.LIST_LAST) {
- @Override public Object apply(EvalEnv env, Object arg) {
- final List list = (List) arg;
- final int size = list.size();
- if (size == 0) {
- throw new MorelRuntimeException(BuiltInExn.EMPTY);
- }
- return list.get(size - 1);
- }
- };
+ private static final Applicable LIST_LAST = new ListLast(Pos.ZERO);
+
+ /** Implements {@link BuiltIn#LIST_LAST}. */
+ private static class ListLast extends ApplicableImpl implements Positioned {
+ ListLast(Pos pos) {
+ super(BuiltIn.LIST_LAST, pos);
+ }
+
+ @Override public Applicable withPos(Pos pos) {
+ return new ListLast(pos);
+ }
+
+ @Override public Object apply(EvalEnv env, Object arg) {
+ final List list = (List) arg;
+ final int size = list.size();
+ if (size == 0) {
+ throw new MorelRuntimeException(BuiltInExn.EMPTY, pos);
+ }
+ return list.get(size - 1);
+ }
+ }
/** @see BuiltIn#LIST_GET_ITEM */
private static final Applicable LIST_GET_ITEM =
@@ -876,29 +979,53 @@ private static ApplicableImpl union(final BuiltIn builtIn) {
};
/** @see BuiltIn#LIST_NTH */
- private static final Applicable LIST_NTH = nth(BuiltIn.LIST_NTH);
+ private static final Applicable LIST_NTH =
+ new ListNth(BuiltIn.LIST_NTH, Pos.ZERO);
- private static ApplicableImpl nth(BuiltIn builtIn) {
- return new Applicable2