package org.shedlang.compiler.patio; import org.junit.jupiter.api.Test; import org.shedlang.compiler.patio.instructions.Patio; import org.shedlang.compiler.patio.instructions.PatioInstruction; import org.shedlang.compiler.patio.operands.PatioOperand; import org.shedlang.compiler.patio.types.PatioDataLayouts; import org.shedlang.compiler.patio.types.PatioDataValues; import org.shedlang.compiler.patio.types.PatioTypes; import org.shedlang.compiler.sources.Sources; import org.shedlang.compiler.testing.ExecutionResult; import org.shedlang.compiler.util.Lists; import org.zwobble.precisely.Matcher; import java.io.IOException; import java.util.List; import java.util.Optional; import static org.zwobble.precisely.AssertThat.assertThat; import static org.zwobble.precisely.Matchers.*; public abstract class PatioExampleTests { protected static final String FUNCTION_NAME = "run"; @Test public void intConstCreatesConstantIntValue() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addBodyInstruction(emitInt(Patio.intConst(42))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void localsAreStoredByName() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(PatioLocal.val("a", PatioTypes.INT)) .addLocal(PatioLocal.val("b", PatioTypes.INT)) .addBodyInstruction(Patio.localStore(Patio.localRef("a"), Patio.intConst(42))) .addBodyInstruction(Patio.localStore(Patio.localRef("b"), Patio.intConst(47))) .addBodyInstruction(emitInt(Patio.localRef("a"))) .addBodyInstruction(emitInt(Patio.localRef("b"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(42), isInt(47) )); } @Test public void canLoadDataElementInt() throws IOException, InterruptedException { var patioDataElement = new PatioDataElementImmutableDefinition( "answer", PatioVisibility.PRIVATE, PatioDataValues.int_(42), Sources.NULL ); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.memoryLoad( Patio.localRef("x"), Patio.dataPtrConst("answer"), PatioDataLayouts.INT, List.of() )) .addBodyInstruction(emitInt( Patio.localRef("x") )) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioDataElement, patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void canLoadIntFromMiddleOfDataElement() throws IOException, InterruptedException { var patioDataElement = new PatioDataElementImmutableDefinition( "answer", PatioVisibility.PRIVATE, PatioDataValues.struct(List.of( PatioDataValues.int_(37), PatioDataValues.int_(42), PatioDataValues.int_(47) )), Sources.NULL ); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.memoryLoad( Patio.localRef("x"), Patio.dataPtrConst("answer"), PatioDataLayouts.struct(List.of( PatioDataLayouts.INT, PatioDataLayouts.INT, PatioDataLayouts.INT )), List.of(1) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioDataElement, patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void canStoreToThenLoadFromMutableDataElement() throws IOException, InterruptedException { var patioDataElement = new PatioDataElementMutableDefinition( "answer", PatioVisibility.PRIVATE, PatioDataValues.int_(42), Sources.NULL ); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.memoryStore( Patio.dataPtrConst("answer"), PatioDataLayouts.INT, List.of(), Patio.intConst(47) )) .addBodyInstruction(Patio.memoryLoad( Patio.localRef("x"), Patio.dataPtrConst("answer"), PatioDataLayouts.INT, List.of() )) .addBodyInstruction(emitInt( Patio.localRef("x") )) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioDataElement, patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(47) )); } @Test public void canLoadFromMutableDataElementDeclaration() throws IOException, InterruptedException { var patioDataElement = new PatioDataElementMutableDeclaration( "patio__test__101", PatioDataLayouts.INT32, Sources.NULL ); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.memoryLoad( Patio.localRef("x"), Patio.dataPtrConst("patio__test__101"), PatioDataLayouts.INT, List.of() )) .addBodyInstruction(emitInt( Patio.localRef("x") )) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioDataElement, patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(101) )); } @Test public void dataPtrCanBeNull() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(PatioLocal.val("x", PatioTypes.DATA_PTR)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.dataPtrConst(Patio.dataPtrValueNull()) )) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioFunction )); var result = run(patio); assertThat(result, isSequence()); } @Test public void canConvertBetweenDataPtrAndInt() throws IOException, InterruptedException { var patioDataElement = new PatioDataElementImmutableDefinition( "answer", PatioVisibility.PRIVATE, PatioDataValues.int_(42), Sources.NULL ); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("ptr_int", PatioTypes.INT)) .addLocal(Patio.val("ptr", PatioTypes.DATA_PTR)) .addBodyInstruction(Patio.dataPtrToInt(Patio.localRef("ptr_int"), Patio.dataPtrConst("answer"))) .addBodyInstruction(Patio.intToDataPtr(Patio.localRef("ptr"), Patio.localRef("ptr_int"))) .addBodyInstruction(Patio.memoryLoad( Patio.localRef("x"), Patio.localRef("ptr"), PatioDataLayouts.INT, List.of() )) .addBodyInstruction(emitInt( Patio.localRef("x") )) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioDataElement, patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void blockSingleResult() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.block( List.of( Patio.localStore( Patio.localRef("x"), Patio.intConst(42) ) ), List.of(Patio.localRef("x")) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void blockMultipleResults() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addBodyInstruction(Patio.block( List.of( Patio.localStore( Patio.localRef("x"), Patio.intConst(42) ), Patio.localStore( Patio.localRef("y"), Patio.intConst(47) ) ), List.of(Patio.localRef("x"), Patio.localRef("y")) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(emitInt(Patio.localRef("y"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42), isInt(47) )); } @Test public void ifCanHaveOneResult() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.if_( List.of(Patio.localRef("x")), Patio.boolConst(true), Patio.block(List.of(), List.of(Patio.intConst(42))), Patio.block(List.of(), List.of(Patio.intConst(47))) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void ifCanHaveMultipleResults() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addBodyInstruction(Patio.if_( List.of(Patio.localRef("x"), Patio.localRef("y")), Patio.boolConst(true), Patio.block(List.of(), List.of(Patio.intConst(42), Patio.intConst(47))), Patio.block(List.of(), List.of(Patio.intConst(52), Patio.intConst(57))) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(emitInt(Patio.localRef("y"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42), isInt(47) )); } @Test public void loopRunsUntilEndIsReachedWithoutContinue() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("c", PatioTypes.BOOL)) .addLocal(Patio.var("x_var", PatioTypes.INT)) .addLocal(Patio.val("x_current", PatioTypes.INT)) .addLocal(Patio.val("x_next", PatioTypes.INT)) .addBodyInstruction(Patio.varStore(Patio.localRef("x_var"), Patio.intConst(3))) .addBodyInstruction(Patio.loop(List.of( Patio.varLoad(Patio.localRef("x_current"), Patio.localRef("x_var")), emitInt(Patio.localRef("x_current")), Patio.intAdd(Patio.localRef("x_next"), Patio.localRef("x_current"), Patio.intConst(-1)), Patio.varStore(Patio.localRef("x_var"), Patio.localRef("x_next")), Patio.intEquals(Patio.localRef("c"), Patio.localRef("x_next"), Patio.intConst(1)), Patio.if_( List.of(), Patio.localRef("c"), Patio.block(List.of(), List.of()), Patio.block(List.of(Patio.loopContinue()), List.of()) ) ))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(3), isInt(2) )); } @Test public void loopCanExitEarlyWithBreak() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("c", PatioTypes.BOOL)) .addLocal(Patio.var("x_var", PatioTypes.INT)) .addLocal(Patio.val("x_current", PatioTypes.INT)) .addLocal(Patio.val("x_next", PatioTypes.INT)) .addBodyInstruction(Patio.varStore(Patio.localRef("x_var"), Patio.intConst(3))) .addBodyInstruction(Patio.loop(List.of( Patio.varLoad(Patio.localRef("x_current"), Patio.localRef("x_var")), Patio.intEquals(Patio.localRef("c"), Patio.localRef("x_current"), Patio.intConst(1)), Patio.if_( List.of(), Patio.localRef("c"), Patio.block(List.of(Patio.loopBreak()), List.of()), Patio.block(List.of(), List.of()) ), emitInt(Patio.localRef("x_current")), Patio.intAdd(Patio.localRef("x_next"), Patio.localRef("x_current"), Patio.intConst(-1)), Patio.varStore(Patio.localRef("x_var"), Patio.localRef("x_next")), Patio.loopContinue() ))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(3), isInt(2) )); } @Test public void boolToIntToBoolIsIdentityForFalse() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.BOOL)) .addLocal(Patio.val("y", PatioTypes.INT)) .addLocal(Patio.val("z", PatioTypes.BOOL)) .addLocal(Patio.val("r", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.boolConst(false) )) .addBodyInstruction(Patio.boolToInt( Patio.localRef("y"), Patio.localRef("x") )) .addBodyInstruction(Patio.intToBool( Patio.localRef("z"), Patio.localRef("y") )) .addBodyInstruction(Patio.if_( List.of(Patio.localRef("r")), Patio.localRef("z"), Patio.block(List.of(), List.of(Patio.intConst(42))), Patio.block(List.of(), List.of(Patio.intConst(47))) )) .addBodyInstruction(emitInt(Patio.localRef("r"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(47) )); } @Test public void boolToIntToBoolIsIdentityForTrue() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.BOOL)) .addLocal(Patio.val("y", PatioTypes.INT)) .addLocal(Patio.val("z", PatioTypes.BOOL)) .addLocal(Patio.val("r", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.boolConst(true) )) .addBodyInstruction(Patio.boolToInt( Patio.localRef("y"), Patio.localRef("x") )) .addBodyInstruction(Patio.intToBool( Patio.localRef("z"), Patio.localRef("y") )) .addBodyInstruction(Patio.if_( List.of(Patio.localRef("r")), Patio.localRef("z"), Patio.block(List.of(), List.of(Patio.intConst(42))), Patio.block(List.of(), List.of(Patio.intConst(47))) )) .addBodyInstruction(emitInt(Patio.localRef("r"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void intAdd() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addLocal(Patio.val("z", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.intConst(42) )) .addBodyInstruction(Patio.localStore( Patio.localRef("y"), Patio.intConst(5) )) .addBodyInstruction(Patio.intAdd( Patio.localRef("z"), Patio.localRef("x"), Patio.localRef("y") )) .addBodyInstruction(emitInt(Patio.localRef("z"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(47) )); } @Test public void intEquals() throws IOException, InterruptedException { assertIntEquals(42, 47, 0); assertIntEquals(42, 42, 1); } private void assertIntEquals( int left, int right, int expected ) throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addLocal(Patio.val("z_bool", PatioTypes.BOOL)) .addLocal(Patio.val("z_int", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.intConst(left) )) .addBodyInstruction(Patio.localStore( Patio.localRef("y"), Patio.intConst(right) )) .addBodyInstruction(Patio.intEquals( Patio.localRef("z_bool"), Patio.localRef("x"), Patio.localRef("y") )) .addBodyInstruction(Patio.boolToInt( Patio.localRef("z_int"), Patio.localRef("z_bool") )) .addBodyInstruction(emitInt(Patio.localRef("z_int"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(expected) )); } @Test public void intNotEqual() throws IOException, InterruptedException { assertIntNotEqual(42, 47, 1); assertIntNotEqual(42, 42, 0); } private void assertIntNotEqual( int left, int right, int expected ) throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addLocal(Patio.val("z_bool", PatioTypes.BOOL)) .addLocal(Patio.val("z_int", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.intConst(left) )) .addBodyInstruction(Patio.localStore( Patio.localRef("y"), Patio.intConst(right) )) .addBodyInstruction(Patio.intNotEqual( Patio.localRef("z_bool"), Patio.localRef("x"), Patio.localRef("y") )) .addBodyInstruction(Patio.boolToInt( Patio.localRef("z_int"), Patio.localRef("z_bool") )) .addBodyInstruction(emitInt(Patio.localRef("z_int"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(expected) )); } @Test public void intSub() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addLocal(Patio.val("z", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.intConst(47) )) .addBodyInstruction(Patio.localStore( Patio.localRef("y"), Patio.intConst(5) )) .addBodyInstruction(Patio.intSub( Patio.localRef("z"), Patio.localRef("x"), Patio.localRef("y") )) .addBodyInstruction(emitInt(Patio.localRef("z"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void switchWithUnreachableDefault() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("x1", PatioTypes.INT)) .addLocal(Patio.val("x2", PatioTypes.INT)) .addBodyInstruction(Patio.switch_( List.of(Patio.localRef("x")), Patio.int32Const(2), List.of( Patio.switchCase( 1, Patio.block( List.of( Patio.localStore(Patio.localRef("x1"), Patio.intConst(42)) ), List.of(Patio.localRef("x1")) ) ), Patio.switchCase( 2, Patio.block( List.of( Patio.localStore(Patio.localRef("x2"), Patio.intConst(47)) ), List.of(Patio.localRef("x2")) ) ) ), Patio.block( List.of(Patio.unreachable()), List.of() ) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(47) )); } @Test public void switchWithReachableDefault() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("x1", PatioTypes.INT)) .addLocal(Patio.val("x2", PatioTypes.INT)) .addBodyInstruction(Patio.switch_( List.of(Patio.localRef("x")), Patio.int32Const(2), List.of( Patio.switchCase( 1, Patio.block( List.of( Patio.localStore(Patio.localRef("x1"), Patio.intConst(42)) ), List.of(Patio.localRef("x1")) ) ) ), Patio.block( List.of( Patio.localStore(Patio.localRef("x2"), Patio.intConst(47)) ), List.of(Patio.localRef("x2")) ) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(47) )); } @Test public void canCallFunctionWithoutArgsDefinedInModule() throws IOException, InterruptedException { var calledFunction = PatioFunctionDefinition.builder() .withName("get") .addReturnType(PatioTypes.INT) .addBodyInstruction(Patio.return_(Patio.intConst(42))) .build(); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.call( Optional.of(Patio.localRef("x")), calledFunction.name(), List.of() )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(calledFunction, patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void canCallFunctionWithArgsDefinedInModule() throws IOException, InterruptedException { var calledFunction = PatioFunctionDefinition.builder() .withName("identity") .addLocal(PatioLocal.param("x", PatioTypes.INT)) .addReturnType(PatioTypes.INT) .addBodyInstruction(Patio.return_(Patio.localRef("x"))) .build(); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.call( Optional.of(Patio.localRef("x")), calledFunction.name(), List.of(Patio.intConst(42)) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(calledFunction, patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void canCallFunctionWithMultipleReturnValuesDefinedInModule() throws IOException, InterruptedException { var calledFunction = PatioFunctionDefinition.builder() .withName("f") .addLocal(PatioLocal.param("x", PatioTypes.INT)) .addLocal(PatioLocal.val("xPlus5", PatioTypes.INT)) .addReturnType(PatioTypes.INT) .addReturnType(PatioTypes.INT) .addBodyInstruction(Patio.intAdd(Patio.localRef("xPlus5"), Patio.localRef("x"), Patio.intConst(5))) .addBodyInstruction(Patio.return_(List.of( Patio.localRef("x"), Patio.localRef("xPlus5") ))) .build(); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addBodyInstruction(Patio.call( List.of(Patio.localRef("x"), Patio.localRef("y")), calledFunction.name(), List.of(Patio.intConst(42)) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(emitInt(Patio.localRef("y"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(calledFunction, patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42), isInt(47) )); } @Test public void canCallPointerToFunctionDefinedInModule() throws IOException, InterruptedException { var calledFunction = PatioFunctionDefinition.builder() .withName("get") .addReturnType(PatioTypes.INT) .addBodyInstruction(Patio.return_(Patio.intConst(42))) .build(); var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.callPointer( Patio.localRef("x"), Patio.functionPtrConst("get"), List.of() )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(calledFunction, patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(42) )); } @Test public void whenExceptionIsNotThrownThenTryReturnsFromBody() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r1", PatioTypes.INT)) .addLocal(Patio.val("r2", PatioTypes.INT)) .addLocal(Patio.val("e1", PatioTypes.INT)) .addLocal(Patio.val("e2", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r1")), List.of( Patio.catchClause( "Error", Patio.localRef("e1"), Patio.block( List.of(), List.of(Patio.intConst(1)) ) ) ), Patio.block( List.of(), List.of(Patio.intConst(0)) ) )) .addBodyInstruction(emitInt(Patio.localRef("r1"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(0) )); } @Test public void whenExceptionIsThrownThenFirstMatchingExceptionHandlerIsUsed() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r1", PatioTypes.INT)) .addLocal(Patio.val("r2", PatioTypes.INT)) .addLocal(Patio.val("e1", PatioTypes.INT)) .addLocal(Patio.val("e2", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r1")), List.of( Patio.catchClause( "Error", Patio.localRef("e1"), Patio.block( List.of(), List.of(Patio.intConst(1)) ) ) ), Patio.block( List.of( Patio.try_( List.of(Patio.localRef("r2")), List.of( Patio.catchClause( "Error", Patio.localRef("e2"), Patio.block( List.of(), List.of(Patio.intConst(2)) ) ) ), Patio.block( List.of( Patio.throw_("Error", Patio.intConst(0)) ), List.of(Patio.intConst(0)) ) ) ), List.of(Patio.localRef("r2")) ) )) .addBodyInstruction(emitInt(Patio.localRef("r1"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(2) )); } @Test public void multipleCatchClausesCanBeAttachedToSameTry() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r1", PatioTypes.INT)) .addLocal(Patio.val("r2", PatioTypes.INT)) .addLocal(Patio.val("e1", PatioTypes.INT)) .addLocal(Patio.val("e2", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r1")), List.of( Patio.catchClause( "Error1", Patio.localRef("e2"), Patio.block( List.of(), List.of(Patio.intConst(1)) ) ), Patio.catchClause( "Error2", Patio.localRef("e2"), Patio.block( List.of(), List.of(Patio.intConst(2)) ) ) ), Patio.block( List.of( Patio.throw_("Error2", Patio.intConst(0)) ), List.of(Patio.intConst(0)) ) )) .addBodyInstruction(emitInt(Patio.localRef("r1"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error1", PatioTypes.INT), Patio.exceptionTypeDefinition("Error2", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(2) )); } @Test public void whenExceptionIsThrownThenExceptionHandlerForOtherExceptionsIsIgnored() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r1", PatioTypes.INT)) .addLocal(Patio.val("r2", PatioTypes.INT)) .addLocal(Patio.val("e1", PatioTypes.INT)) .addLocal(Patio.val("e2", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r1")), List.of( Patio.catchClause( "Error", Patio.localRef("e1"), Patio.block( List.of(), List.of(Patio.intConst(1)) ) ) ), Patio.block( List.of( Patio.try_( List.of(Patio.localRef("r2")), List.of( Patio.catchClause( "OtherError", Patio.localRef("e2"), Patio.block( List.of(), List.of(Patio.intConst(2)) ) ) ), Patio.block( List.of( Patio.throw_("Error", Patio.intConst(0)) ), List.of(Patio.intConst(0)) ) ) ), List.of(Patio.localRef("r2")) ) )) .addBodyInstruction(emitInt(Patio.localRef("r1"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error", PatioTypes.INT), Patio.exceptionTypeDefinition("OtherError", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(1) )); } @Test public void catchClauseCanThrow() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r1", PatioTypes.INT)) .addLocal(Patio.val("r2", PatioTypes.INT)) .addLocal(Patio.val("e1", PatioTypes.INT)) .addLocal(Patio.val("e2", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r1")), List.of( Patio.catchClause( "Error", Patio.localRef("e1"), Patio.block( List.of(), List.of(Patio.intConst(1)) ) ) ), Patio.block( List.of( Patio.try_( List.of(Patio.localRef("r2")), List.of( Patio.catchClause( "Error", Patio.localRef("e2"), Patio.block( List.of( Patio.throw_("Error", Patio.intConst(0)) ), List.of(Patio.intConst(2)) ) ) ), Patio.block( List.of( Patio.throw_("Error", Patio.intConst(0)) ), List.of(Patio.intConst(0)) ) ) ), List.of(Patio.localRef("r2")) ) )) .addBodyInstruction(emitInt(Patio.localRef("r1"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(1) )); } @Test public void exceptionHandlerIsDiscardedAfterTry() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r1", PatioTypes.INT)) .addLocal(Patio.val("r2", PatioTypes.INT)) .addLocal(Patio.val("e1", PatioTypes.INT)) .addLocal(Patio.val("e2", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r1")), List.of( Patio.catchClause( "Error", Patio.localRef("e1"), Patio.block( List.of(), List.of(Patio.intConst(1)) ) ) ), Patio.block( List.of( Patio.try_( List.of(Patio.localRef("r2")), List.of( Patio.catchClause( "Error", Patio.localRef("e2"), Patio.block( List.of(), List.of(Patio.intConst(2)) ) ) ), Patio.block( List.of(), List.of(Patio.intConst(0)) ) ), Patio.throw_("Error", Patio.intConst(0)) ), List.of(Patio.localRef("r2")) ) )) .addBodyInstruction(emitInt(Patio.localRef("r1"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(1) )); } @Test public void thrownValueIsPassedToCatchClause() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r", PatioTypes.INT)) .addLocal(Patio.val("e", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r")), List.of( Patio.catchClause( "Error", Patio.localRef("e"), Patio.block( List.of(), List.of(Patio.localRef("e")) ) ) ), Patio.block( List.of( Patio.throw_("Error", Patio.intConst(1)) ), List.of(Patio.intConst(0)) ) )) .addBodyInstruction(emitInt(Patio.localRef("r"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(1) )); } @Test public void tryCanHaveMultipleResults() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("r1", PatioTypes.INT)) .addLocal(Patio.val("r2", PatioTypes.INT)) .addLocal(Patio.val("e1", PatioTypes.INT)) .addLocal(Patio.val("e2", PatioTypes.INT)) .addBodyInstruction(Patio.try_( List.of(Patio.localRef("r1"), Patio.localRef("r2")), List.of( Patio.catchClause( "Error", Patio.localRef("e1"), Patio.block( List.of(), List.of(Patio.intConst(11), Patio.intConst(12)) ) ) ), Patio.block( List.of(), List.of(Patio.intConst(1), Patio.intConst(2)) ) )) .addBodyInstruction(emitInt(Patio.localRef("r1"))) .addBodyInstruction(emitInt(Patio.localRef("r2"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( Patio.exceptionTypeDefinition("Error", PatioTypes.INT), patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(1), isInt(2) )); } @Test public void vars() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.var("x", PatioTypes.INT)) .addLocal(Patio.val("y", PatioTypes.INT)) .addLocal(Patio.val("z", PatioTypes.INT)) .addLocal(Patio.val("r", PatioTypes.INT)) .addBodyInstruction(Patio.varStore( Patio.localRef("x"), Patio.intConst(42) )) .addBodyInstruction(Patio.varLoad( Patio.localRef("y"), Patio.localRef("x") )) .addBodyInstruction(Patio.intAdd( Patio.localRef("z"), Patio.localRef("y"), Patio.intConst(5) )) .addBodyInstruction(Patio.varStore( Patio.localRef("x"), Patio.localRef("z") )) .addBodyInstruction(Patio.varLoad( Patio.localRef("r"), Patio.localRef("x") )) .addBodyInstruction(emitInt(Patio.localRef("r"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of(patioFunction)); var result = run(patio); assertThat(result, isSequence( isInt(47) )); } @Test public void canGetByteOffsetOf() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.intConst(Patio.intValueByteOffsetOf( PatioDataLayouts.struct(List.of( PatioDataLayouts.struct(List.of( PatioDataLayouts.BYTE, PatioDataLayouts.BYTE )), PatioDataLayouts.struct(List.of( PatioDataLayouts.BYTE, PatioDataLayouts.BYTE, PatioDataLayouts.BYTE, PatioDataLayouts.BYTE )), PatioDataLayouts.BYTE )), List.of(1, 2) )) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(4) )); } @Test public void canGetByteSizeOf() throws IOException, InterruptedException { var patioFunction = PatioFunctionDefinition.builder() .withName(FUNCTION_NAME) .addLocal(Patio.val("x", PatioTypes.INT)) .addBodyInstruction(Patio.localStore( Patio.localRef("x"), Patio.intConst(Patio.intValueByteSizeOf( PatioDataLayouts.struct(List.of( PatioDataLayouts.struct(List.of( PatioDataLayouts.BYTE, PatioDataLayouts.BYTE )), PatioDataLayouts.struct(List.of( PatioDataLayouts.BYTE, PatioDataLayouts.BYTE, PatioDataLayouts.BYTE, PatioDataLayouts.BYTE )), PatioDataLayouts.BYTE )) )) )) .addBodyInstruction(emitInt(Patio.localRef("x"))) .addBodyInstruction(Patio.returnVoid()) .build(); var patio = Patio.compilationUnit(List.of( patioFunction )); var result = run(patio); assertThat(result, isSequence( isInt(7) )); } private PatioInstruction emitInt(PatioOperand value) { return Patio.call(Optional.empty(), "patio__test__emit_int", List.of(value)); } private Matcher isInt(int value) { return instanceOf( PatioTestOutput.Int.class, has("value", PatioTestOutput.Int::value, equalTo(value)) ); } private List run(PatioCompilationUnit patio) throws IOException, InterruptedException { var emitIntDeclaration = PatioFunctionDeclaration.builder() .withName("patio__test__emit_int") .addParam(PatioLocal.param("value", PatioTypes.INT)) .build(); var result = executeCompilationUnit(new PatioCompilationUnit( Lists.concat(patio.elements(), List.of(emitIntDeclaration)), patio.source() )); assertThat(result.stderr(), equalTo("")); assertThat(result.exitCode(), equalTo(0)); return parseTestOutput(result.stdout()); } private List parseTestOutput(String stdout) { return stdout.lines() .map(line -> PatioTestOutput.int_(Integer.parseInt(line))) .toList(); } protected abstract ExecutionResult executeCompilationUnit( PatioCompilationUnit patio ) throws IOException, InterruptedException; }