diff options
| author | Michael Williamson <mike@zwobble.org> | 2023-10-24 21:02:07 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2023-10-24 21:02:07 +0100 |
| commit | 0dccc0cdf7813c3c5571a79186a73bfd239a66d6 (patch) | |
| tree | bc313d4336b83fe774ea1d8f14064bef2d09a97b /src/test/java | |
| parent | 192cb27a8d8bc78211897c9f9325eb898390dbc4 (diff) | |
Parse function expressions
Diffstat (limited to 'src/test/java')
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())); + } +} |
