diff --git a/.github/workflows/d.yml b/.github/workflows/d.yml index 3a35e332..28601dd9 100644 --- a/.github/workflows/d.yml +++ b/.github/workflows/d.yml @@ -115,6 +115,10 @@ jobs: exit 1 fi + + - name: Simple return (expressionless) + run: ./tlang syntaxcheck source/tlang/testing/return/simple_return_expressionless.t + typecheck: needs: [build, unittests] name: Typechecking tests @@ -129,6 +133,14 @@ jobs: - name: Chmod compiler run: chmod +x tlang + + - name: Simple return (expressionless) + run: ./tlang typecheck source/tlang/testing/return/simple_return_expressionless.t + + - name: Simple return (with expression) + run: ./tlang typecheck source/tlang/testing/return/simple_return_type.t + + - name: Simple function call run: ./tlang typecheck source/tlang/testing/typecheck/simple_function_call.t diff --git a/.gitignore b/.gitignore index 675ca60b..0e50f4a6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,7 @@ dub.selections.json # Documentation )generated) docs/ docs.json -desktop_dev.code-workspace + +# Codium workspace files testing.code-workspace +desktop_dev.code-workspace \ No newline at end of file diff --git a/source/tlang/compiler/codegen/instruction.d b/source/tlang/compiler/codegen/instruction.d index fbdfde84..e35da095 100644 --- a/source/tlang/compiler/codegen/instruction.d +++ b/source/tlang/compiler/codegen/instruction.d @@ -369,10 +369,20 @@ public final class ReturnInstruction : Instruction this.returnExprInstr = returnExprInstr; } + this() + { + + } + public Value getReturnExpInstr() { return returnExprInstr; } + + public bool hasReturnExpInstr() + { + return returnExprInstr !is null; + } } public final class IfStatementInstruction : Instruction diff --git a/source/tlang/compiler/parsing/core.d b/source/tlang/compiler/parsing/core.d index e317d0c4..dca400b7 100644 --- a/source/tlang/compiler/parsing/core.d +++ b/source/tlang/compiler/parsing/core.d @@ -606,20 +606,31 @@ public final class Parser /* Move from `return` onto start of expression */ lexer.nextToken(); - /* Parse the expression till termination */ - Expression returnExpression = parseExpression(); + // TODO: Check if semicolon here (no expression) else expect expression - /* Expect a semi-colon as the terminator */ - gprintln(lexer.getCurrentToken()); - expect(SymbolType.SEMICOLON, lexer.getCurrentToken()); - + /* If the next token after `return` is a `;` then it is an expressionless return */ + if(getSymbolType(lexer.getCurrentToken()) == SymbolType.SEMICOLON) + { + /* Create the ReturnStmt (without an expression) */ + returnStatement = new ReturnStmt(); + } + /* Else, then look for an expression */ + else + { + /* Parse the expression till termination */ + Expression returnExpression = parseExpression(); + + /* Expect a semi-colon as the terminator */ + gprintln(lexer.getCurrentToken()); + expect(SymbolType.SEMICOLON, lexer.getCurrentToken()); + + /* Create the ReturnStmt */ + returnStatement = new ReturnStmt(returnExpression); + } /* Move off of the terminator */ lexer.nextToken(); - /* Create the ReturnStmt */ - returnStatement = new ReturnStmt(returnExpression); - return returnStatement; } diff --git a/source/tlang/compiler/symbols/data.d b/source/tlang/compiler/symbols/data.d index 9e909408..5e4effb6 100644 --- a/source/tlang/compiler/symbols/data.d +++ b/source/tlang/compiler/symbols/data.d @@ -966,6 +966,11 @@ public final class ReturnStmt : Statement { this.returnExpression = returnExpression; + this(); + } + + this() + { /* Statement level weighting is 2 */ weight = 2; } @@ -974,6 +979,11 @@ public final class ReturnStmt : Statement { return returnExpression; } + + public bool hasReturnExpression() + { + return returnExpression !is null; + } } /** diff --git a/source/tlang/compiler/typecheck/core.d b/source/tlang/compiler/typecheck/core.d index c7f5b50d..891a98c3 100644 --- a/source/tlang/compiler/typecheck/core.d +++ b/source/tlang/compiler/typecheck/core.d @@ -1674,19 +1674,89 @@ public final class TypeChecker else if(cast(ReturnStmt)statement) { ReturnStmt returnStatement = cast(ReturnStmt)statement; + Function funcContainer = cast(Function)statement.parentOf(); + string functionName = resolver.generateName(funcContainer.parentOf(), funcContainer); + + /* Generated return instruction */ + ReturnInstruction returnInstr; + + /** + * Typecheck + * + * TODO: Add typechecking for the return expression + * we'd need context of where we are, probably our + * parent could be used to check which function + * we belong to and hence do the typecheck + */ + if(!funcContainer) + { + throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "A return statement can only appear in the body of a function"); + } + + + Type functionReturnType = getType(funcContainer, funcContainer.getType()); + /** * Codegen * + * (1 and 2 only apply for return statements with an expression) + * * 1. Pop the expression on the stack * 2. Create a new ReturnInstruction with the expression instruction * embedded in it - * 3. Set the Context of the instruction - * 4. Add this instruction back */ - Value returnExpressionInstr = cast(Value)popInstr(); - assert(returnExpressionInstr); - ReturnInstruction returnInstr = new ReturnInstruction(returnExpressionInstr); + + /* If the function's return type is void */ + if(isSameType(functionReturnType, getBuiltInType(this, "void"))) + { + /* It is an error to have a return expression if function is return void */ + if(returnStatement.hasReturnExpression()) + { + throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "Function '"~functionName~"' of type void cannot have a return expression"); + } + /* If we don't have an expression (expected) */ + else + { + /* Generate the instruction */ + returnInstr = new ReturnInstruction(); + } + } + /* If there is a non-void return type */ + else + { + /* We should have an expression in the non-void case */ + if(returnStatement.hasReturnExpression()) + { + Value returnExpressionInstr = cast(Value)popInstr(); + assert(returnExpressionInstr); + Type returnExpressionInstrType = returnExpressionInstr.getInstrType(); + + /* Ensure the expression's type matches the function's return type */ + if(isSameType(functionReturnType, returnExpressionInstrType)) + { + /* Generate the instruction */ + returnInstr = new ReturnInstruction(returnExpressionInstr); + } + /* If not, then raise an error */ + else + { + throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "Returning an expression of type '"~returnExpressionInstrType.toString()~"' does not match function's return type '"~functionReturnType.toString()~"'"); + } + } + /* If not then this is an error */ + else + { + throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "Function '"~functionName~"' of has a type therefore it requires an expression in the return statement"); + } + } + + /** + * Codegen (continued) + * + * 3. Set the Context of the instruction + * 4. Add this instruction back + */ returnInstr.setContext(returnStatement.getContext()); addInstrB(returnInstr); } diff --git a/source/tlang/compiler/typecheck/dependency/core.d b/source/tlang/compiler/typecheck/dependency/core.d index 9ecd59be..9c32529a 100644 --- a/source/tlang/compiler/typecheck/dependency/core.d +++ b/source/tlang/compiler/typecheck/dependency/core.d @@ -1462,12 +1462,16 @@ public class DNodeGenerator DNode returnStatementDNode = pool(returnStatement); - /* Process the return expression */ - Expression returnExpression = returnStatement.getReturnExpression(); - DNode returnExpressionDNode = expressionPass(returnExpression, context); + /* Check if this return statement has an expression attached */ + if(returnStatement.hasReturnExpression()) + { + /* Process the return expression */ + Expression returnExpression = returnStatement.getReturnExpression(); + DNode returnExpressionDNode = expressionPass(returnExpression, context); - /* Make return depend on the return expression */ - returnStatementDNode.needs(returnExpressionDNode); + /* Make return depend on the return expression */ + returnStatementDNode.needs(returnExpressionDNode); + } /* Make this container depend on this return statement */ // node.needs(returnStatementDNode); diff --git a/source/tlang/testing/return/simple_return_expressionless.t b/source/tlang/testing/return/simple_return_expressionless.t new file mode 100644 index 00000000..a4e40d60 --- /dev/null +++ b/source/tlang/testing/return/simple_return_expressionless.t @@ -0,0 +1,11 @@ +module simple_return_expressionless; + +void expressionless() +{ + return; +} + +int expressionful() +{ + return 2; +} \ No newline at end of file diff --git a/source/tlang/testing/return/simple_return_type.t b/source/tlang/testing/return/simple_return_type.t new file mode 100644 index 00000000..f58893b1 --- /dev/null +++ b/source/tlang/testing/return/simple_return_type.t @@ -0,0 +1,6 @@ +module simple_return_type; + +byte expressionful() +{ + return 1; +} \ No newline at end of file