From c0920f2fdb585a7f990692817d98de6c19ce2ae7 Mon Sep 17 00:00:00 2001 From: "Tristan B. Velloza Kildaire" Date: Tue, 11 Jul 2023 21:43:21 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9E=EF=B8=8F=20Functions:=20Expression?= =?UTF-8?q?less=20return=20and=20enforcing=20requirement=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Parser - Added a TODO in `parseReturn()` for issue #113 * Data - The `ReturnStmt` now has a default constructor which is for cases where one doesn't want to provide an expression (for expressionless returns) Parser - `parseReturn()` now supports expressionless returns Test cases - Added `simple_return_expressionless.t` to test expressionless return statement * Data - Added a method `hasReturnExpression()` to `ReturnStmt` which returns `true` if the return statement has an expression attached, `false` otherwise * Dependency - When processing a `ReturnStmt` only run do dependency generation for the return statement's expression IF it has one * Instruction - Made `ReturnInstruction` have a constructor which takes in no `Value` instruction (intended for return expression) - Added a `hasReturnExpInstr()` to `ReturnInstruction`such that during typechecking/codegen we can check for it * TypeChecker - Added a TODO regarding the missing typechecking for `ReturnStmt` typechecking. Added notes on how we'd go about this. - Fixed crash due to assuming there was always an expression on the stack that could be popped off for generating a `ReturnInstruction` (this is not the case when the return statement is expressionless) * Tests - Added a typecheck test for `simple_return_expressionless.t` * TypeChecker - Update `isSameType(Type t1, Type t2)` to check if the actual types of both `Type` objects are the same as a last resort - Added a `NOTE` comment on how `isSameType(Type t1, Type t2)` is implemented - Added typechecking code for `ReturnStmt` and updated the code generation with it. We now do the following: 1. We extract the container of the `ReturnStmt` and cast it to a `Function`; if it is not a `Function` we throw an error because you cannot have a `ReturnStmt` appear in a non-`Function` container 2. We extract the function's name relative to it container (the function's container) for use of it in error messages 3. Next, we get the return type of the function and do the following: a. If the return type is `void` i. If the return has an expression we throw an error ii. If the return has NO expression we pass typechecking and generate the `ReturnInstr` b. If the return type is non-`void` i. If the return has an expression we ensure that its type matches that of the function's return type and generate the `ReturnInstr` ii. If the return has NO expression we raise an exception as one is expected 4. If we pass and got here then we set the `ReturnInstr`'s context and `addInstrB(returnInstr)` * Test cases - Added test case `simple_return_type.t` which is here to test our return type checking * - Updated `.gitignore` * Parser - Use `lexer` for all `Token`-based operations --- .github/workflows/d.yml | 12 +++ .gitignore | 4 +- source/tlang/compiler/codegen/instruction.d | 10 +++ source/tlang/compiler/parsing/core.d | 29 ++++--- source/tlang/compiler/symbols/data.d | 10 +++ source/tlang/compiler/typecheck/core.d | 80 +++++++++++++++++-- .../compiler/typecheck/dependency/core.d | 14 ++-- .../return/simple_return_expressionless.t | 11 +++ .../tlang/testing/return/simple_return_type.t | 6 ++ 9 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 source/tlang/testing/return/simple_return_expressionless.t create mode 100644 source/tlang/testing/return/simple_return_type.t 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