package org.shedlang.compiler.parser; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.shedlang.compiler.ast.untyped.Untyped; import org.shedlang.compiler.ast.untyped.UntypedBlockNode; import org.shedlang.compiler.ast.untyped.UntypedFunctionStatementBehaviour; import org.shedlang.compiler.parser.errors.InconsistentBlockBehaviourError; import java.util.List; 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.equalTo; import static org.zwobble.precisely.Matchers.isSequence; public class ParserExpressionStatementTests { @Test public void canParseExpressionStatement() { var source = "f;"; var node = parseString(source, Parser::parseFunctionStatement); assertThat(node, isUntypedExpressionStatementNode(isUntypedReferenceNode("f"))); } @Test public void canParseBlockReturn() { var source = "f"; var node = parseString(source, Parser::parseFunctionStatement); assertThat(node, isUntypedBlockReturnNode(isUntypedReferenceNode("f"))); } @Test public void canParseTuple() { var source = "#(a)"; var node = parseString(source, Parser::parseFunctionStatement); assertThat(node, isUntypedBlockReturnNode(isUntypedTupleNode( isSequence(isUntypedReferenceNode("a")) ))); } @Test public void ifWithAllBranchesAsExpressionStatementsIsExpressionStatement() { var source = "if (a) { b; } else { c; }"; var node = parseString(source, Parser::parseFunctionStatement); assertThat(node, isUntypedExpressionStatementNode( isUntypedIfNode( isSequence( isUntypedConditionalBranchNode( isUntypedReferenceNode("a"), isUntypedBlockNode(isSequence( isUntypedExpressionStatementNode(isUntypedReferenceNode("b")) )) ) ), isUntypedBlockNode(isSequence( isUntypedExpressionStatementNode(isUntypedReferenceNode("c")) )) ) )); } @Test public void ifWithAllBranchesAsBlockReturnsIsBlockReturn() { var source = "if (a) { b } else { c }"; var node = parseString(source, Parser::parseFunctionStatement); assertThat(node, isUntypedBlockReturnNode( isUntypedIfNode( isSequence( isUntypedConditionalBranchNode( isUntypedReferenceNode("a"), isUntypedBlockNode(isSequence( isUntypedBlockReturnNode(isUntypedReferenceNode("b")) )) ) ), isUntypedBlockNode(isSequence( isUntypedBlockReturnNode(isUntypedReferenceNode("c")) )) ) )); } @Nested public class BranchBehaviourTests { @Test public void whenAllBranchesContinueThenExpressionStatementContinues() { var blocks = List.of( blockWithBehaviour(UntypedFunctionStatementBehaviour.CONTINUE), blockWithBehaviour(UntypedFunctionStatementBehaviour.CONTINUE) ); var result = Parser.expressionStatementBehaviour(blocks); assertThat(result, equalTo(UntypedFunctionStatementBehaviour.CONTINUE)); } @Test public void whenAllBranchesBlockThenExpressionStatementBlockReturns() { var blocks = List.of( blockWithBehaviour(UntypedFunctionStatementBehaviour.BLOCK_RETURN), blockWithBehaviour(UntypedFunctionStatementBehaviour.BLOCK_RETURN) ); var result = Parser.expressionStatementBehaviour(blocks); assertThat(result, equalTo(UntypedFunctionStatementBehaviour.BLOCK_RETURN)); } @Test public void whenAllBranchesTerminateThenExpressionStatementTerminates() { var blocks = List.of( blockWithBehaviour(UntypedFunctionStatementBehaviour.TERMINATE), blockWithBehaviour(UntypedFunctionStatementBehaviour.TERMINATE) ); var result = Parser.expressionStatementBehaviour(blocks); assertThat(result, equalTo(UntypedFunctionStatementBehaviour.TERMINATE)); } @Test public void whenBranchesTerminateOrContinueThenExpressionStatementContinues() { var blocks = List.of( blockWithBehaviour(UntypedFunctionStatementBehaviour.TERMINATE), blockWithBehaviour(UntypedFunctionStatementBehaviour.CONTINUE), blockWithBehaviour(UntypedFunctionStatementBehaviour.TERMINATE) ); var result = Parser.expressionStatementBehaviour(blocks); assertThat(result, equalTo(UntypedFunctionStatementBehaviour.CONTINUE)); } @Test public void whenBranchesTerminateOrBlockReturnThenExpressionStatementBlockReturns() { var blocks = List.of( blockWithBehaviour(UntypedFunctionStatementBehaviour.TERMINATE), blockWithBehaviour(UntypedFunctionStatementBehaviour.BLOCK_RETURN), blockWithBehaviour(UntypedFunctionStatementBehaviour.TERMINATE) ); var result = Parser.expressionStatementBehaviour(blocks); assertThat(result, equalTo(UntypedFunctionStatementBehaviour.BLOCK_RETURN)); } @Test public void whenBranchesContinueOrBlockReturnThenErrorIsThrown() { var blocks = List.of( blockWithBehaviour(UntypedFunctionStatementBehaviour.CONTINUE), blockWithBehaviour(UntypedFunctionStatementBehaviour.BLOCK_RETURN) ); assertThrows( InconsistentBlockBehaviourError.class, () -> Parser.expressionStatementBehaviour(blocks) ); } @Test public void whenThereAreNoBranchesThenExpressionStatementTerminates() { var blocks = List.of(); var result = Parser.expressionStatementBehaviour(blocks); assertThat(result, equalTo(UntypedFunctionStatementBehaviour.TERMINATE)); } private UntypedBlockNode blockWithBehaviour(UntypedFunctionStatementBehaviour behaviour) { var statement = switch (behaviour) { case TERMINATE -> Untyped.functionReturn(Untyped.intLiteral(0)); case BLOCK_RETURN -> Untyped.blockReturn(Untyped.intLiteral(0)); case CONTINUE -> Untyped.expressionStatement(Untyped.intLiteral(0)); }; var block = Untyped.block(List.of(statement)); assertThat(block.behaviour(), equalTo(behaviour)); return block; } } }