aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2023-10-24 21:02:07 +0100
committerMichael Williamson <mike@zwobble.org>2023-10-24 21:02:07 +0100
commit0dccc0cdf7813c3c5571a79186a73bfd239a66d6 (patch)
treebc313d4336b83fe774ea1d8f14064bef2d09a97b /src/test/java
parent192cb27a8d8bc78211897c9f9325eb898390dbc4 (diff)
Parse function expressions
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java54
-rw-r--r--src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java4
-rw-r--r--src/test/java/org/shedlang/compiler/parser/ParserFunctionExpressionTests.java246
3 files changed, 304 insertions, 0 deletions
diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java
new file mode 100644
index 00000000..f5c8d161
--- /dev/null
+++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java
@@ -0,0 +1,54 @@
+package org.shedlang.compiler.ast.untyped;
+
+import org.pcollections.Empty;
+import org.pcollections.PVector;
+import org.shedlang.compiler.testing.MatcherBuilder;
+import org.zwobble.precisely.Matcher;
+
+import java.util.Optional;
+
+import static org.zwobble.precisely.Matchers.has;
+
+public class UntypedFunctionExpressionNodeMatcher extends MatcherBuilder<
+ UntypedExpressionNode,
+ UntypedFunctionExpressionNode,
+ UntypedFunctionExpressionNodeMatcher
+> {
+ public static UntypedFunctionExpressionNodeMatcher empty() {
+ return new UntypedFunctionExpressionNodeMatcher(Empty.vector());
+ }
+
+ public UntypedFunctionExpressionNodeMatcher(
+ PVector<Matcher<? super UntypedFunctionExpressionNode>> submatchers
+ ) {
+ super(submatchers);
+ }
+
+ @Override
+ protected Class<UntypedFunctionExpressionNode> expectedType() {
+ return UntypedFunctionExpressionNode.class;
+ }
+
+ @Override
+ protected UntypedFunctionExpressionNodeMatcher create(
+ PVector<Matcher<? super UntypedFunctionExpressionNode>> submatchers
+ ) {
+ return new UntypedFunctionExpressionNodeMatcher(submatchers);
+ }
+
+ public UntypedFunctionExpressionNodeMatcher withBody(Matcher<UntypedBlockNode> body) {
+ return with(has("body", x -> x.body(), body));
+ }
+
+ public UntypedFunctionExpressionNodeMatcher withEffect(Matcher<Optional<? extends UntypedTypeLevelExpressionNode>> effect) {
+ return with(has("effect", x -> x.effect(), effect));
+ }
+
+ public UntypedFunctionExpressionNodeMatcher withParams(Matcher<UntypedParamsNode> params) {
+ return with(has("params", x -> x.params(), params));
+ }
+
+ public UntypedFunctionExpressionNodeMatcher withReturnType(Matcher<UntypedTypeLevelExpressionNode> returnType) {
+ return with(has("returnType", x -> x.returnType(), returnType));
+ }
+}
diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java
index f45adba9..0d4940e8 100644
--- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java
+++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java
@@ -114,6 +114,10 @@ public class UntypedNodeMatchers {
return UntypedFunctionDefinitionNodeMatcher.empty();
}
+ public static UntypedFunctionExpressionNodeMatcher isUntypedFunctionExpressionNode() {
+ return UntypedFunctionExpressionNodeMatcher.empty();
+ }
+
public static Matcher<UntypedFunctionStatementNode> isUntypedFunctionReturnNode(
Matcher<UntypedExpressionNode> expression
) {
diff --git a/src/test/java/org/shedlang/compiler/parser/ParserFunctionExpressionTests.java b/src/test/java/org/shedlang/compiler/parser/ParserFunctionExpressionTests.java
new file mode 100644
index 00000000..d86776be
--- /dev/null
+++ b/src/test/java/org/shedlang/compiler/parser/ParserFunctionExpressionTests.java
@@ -0,0 +1,246 @@
+package org.shedlang.compiler.parser;
+
+import org.junit.jupiter.api.Test;
+import org.shedlang.compiler.parser.errors.PositionalParamAfterNamedParamError;
+import org.shedlang.compiler.tokeniser.UnexpectedTokenException;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.shedlang.compiler.ast.untyped.UntypedNodeMatchers.*;
+import static org.shedlang.compiler.parser.ParserTesting.parseString;
+import static org.zwobble.precisely.AssertThat.assertThat;
+import static org.zwobble.precisely.Matchers.*;
+
+public class ParserFunctionExpressionTests {
+ @Test
+ public void canParseMinimalFunction() {
+ var source = """
+ fn () -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withParams(isUntypedParamsNode(isSequence(), isSequence()))
+ .withEffect(isOptionalEmpty())
+ .withReturnType(isUntypedTypeLevelReferenceNode("Unit"))
+ .withBody(isUntypedBlockNode(isSequence()))
+ );
+ }
+
+ @Test
+ public void canParseFunctionWithSinglePositionalParam() {
+ var source = """
+ fn (a: Int) -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withParams(isUntypedParamsNode(
+ isSequence(
+ isUntypedParamNode("a", isUntypedTypeLevelReferenceNode("Int"))
+ ),
+ isSequence()
+ ))
+ );
+ }
+
+ @Test
+ public void canParseFunctionWithManyPositionalParams() {
+ var source = """
+ fn (a: Int, b: String, c: Bool) -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withParams(isUntypedParamsNode(
+ isSequence(
+ isUntypedParamNode("a", isUntypedTypeLevelReferenceNode("Int")),
+ isUntypedParamNode("b", isUntypedTypeLevelReferenceNode("String")),
+ isUntypedParamNode("c", isUntypedTypeLevelReferenceNode("Bool"))
+ ),
+ isSequence()
+ ))
+ );
+ }
+
+ @Test
+ public void canParseFunctionWithSingleNamedParam() {
+ var source = """
+ fn (.a: Int) -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withParams(isUntypedParamsNode(
+ isSequence(),
+ isSequence(
+ isUntypedParamNode("a", isUntypedTypeLevelReferenceNode("Int"))
+ )
+ ))
+ );
+ }
+
+ @Test
+ public void canParseFunctionWithManyNamedParams() {
+ var source = """
+ fn (.a: Int, .b: String, .c: Bool) -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withParams(isUntypedParamsNode(
+ isSequence(),
+ isSequence(
+ isUntypedParamNode("a", isUntypedTypeLevelReferenceNode("Int")),
+ isUntypedParamNode("b", isUntypedTypeLevelReferenceNode("String")),
+ isUntypedParamNode("c", isUntypedTypeLevelReferenceNode("Bool"))
+ )
+ ))
+ );
+ }
+
+ @Test
+ public void canParseNamedParamAfterPositionalParam() {
+ var source = """
+ fn (a: Int, .b: String) -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withParams(isUntypedParamsNode(
+ isSequence(
+ isUntypedParamNode("a", isUntypedTypeLevelReferenceNode("Int"))
+ ),
+ isSequence(
+ isUntypedParamNode("b", isUntypedTypeLevelReferenceNode("String"))
+ )
+ ))
+ );
+ }
+
+ @Test
+ public void cannotParsePositionalParamAfterNamedParam() {
+ var source = """
+ fn (.a: Int, b: String) -> Unit {
+ }""";
+
+ assertThrows(
+ PositionalParamAfterNamedParamError.class,
+ () -> parseString(source, Parser::parseTopLevelExpression)
+ );
+ }
+
+ @Test
+ public void paramsCanHaveTrailingComma() {
+ var source = """
+ fn (a: Int, ) -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withParams(isUntypedParamsNode(
+ isSequence(
+ isUntypedParamNode("a", isUntypedTypeLevelReferenceNode("Int"))
+ ),
+ isSequence()
+ ))
+ );
+ }
+
+ @Test
+ public void canParseFunctionWithEffect() {
+ var source = """
+ fn () ! Io -> Unit {
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withEffect(isOptionalOf(isUntypedTypeLevelReferenceNode("Io")))
+ );
+ }
+
+ @Test
+ public void canParseFunctionWithBodyWithoutReturn() {
+ var source = """
+ fn () -> Unit {
+ a;
+ b;
+ c;
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withBody(isUntypedBlockNode(isSequence(
+ isUntypedExpressionStatementNode(isUntypedReferenceNode("a")),
+ isUntypedExpressionStatementNode(isUntypedReferenceNode("b")),
+ isUntypedExpressionStatementNode(isUntypedReferenceNode("c"))
+ )))
+ );
+ }
+
+ @Test
+ public void canParseFunctionWithBodyWithBlockReturn() {
+ var source = """
+ fn () -> Unit {
+ a;
+ b;
+ c
+ }""";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(
+ node,
+ isUntypedFunctionExpressionNode()
+ .withBody(isUntypedBlockNode(isSequence(
+ isUntypedExpressionStatementNode(isUntypedReferenceNode("a")),
+ isUntypedExpressionStatementNode(isUntypedReferenceNode("b")),
+ isUntypedBlockReturnNode(isUntypedReferenceNode("c"))
+ )))
+ );
+ }
+
+ @Test
+ public void blockReturnEndsFunction() {
+ var source = """
+ fn () -> Unit {
+ a;
+ b
+ c
+ }""";
+
+ var error = assertThrows(
+ UnexpectedTokenException.class,
+ () -> parseString(source, Parser::parseTopLevelExpression)
+ );
+
+ assertThat(error.getActual().value(), equalTo("c"));
+ assertThat(error.getExpected(), equalTo(TokenType.SYMBOL_BRACE_CLOSE.toString()));
+ }
+}