🧠 Feature: Direct function calls (#11)

* Test cases

- Added `simple_direct_func_call.t` to test direct function calls

* Test cases

- Removed tabs which broke lexing

* AST nodes

- `FunctionCall` now has the ability to be marked as statement-level by calling `makeStatementLevel()`, this can then be queried later via `isStatementLevelFuncCall()`

* Parser

- Statement-level function calls were never actually returned, resulting in `null` being returned by `parseName()` - this has now been fixed.
- Along with this we now "mark" this `FunctionCall` AST node as statement-level when it occurs in `parseName()`

* Instruction

- Allow `FuncCallInstr` to be makred as statement-level and queired much in the same manner as its corresponding AST-node/parser-node `FunctionCall`

* Dependency

- Added support for `DNode` generation in `generalPass()` for `FunctionCall` AST nodes

* TypeChecker

- Handle `FunctionCall`s differently in terms of code generation dependent on whether or not rhe call is within an expression of statement-level

* DGen

- Handle statement-level function calls (`FuncCallInstr`s) differently by tacking on an additional `";"` to the emit string

* - Added `simple_direct_func_call.t` to GitHub pipeline

* DGen

- Added instrumentation for semantic code generation for `simple_function_recursion_factorial.t`
- Added TODO for future `simple_func_call_direct.t`

Test cases

- Added `simple_function_recursion_factorial.t` to test recursion

Pipelines

- Added `simple_function_recursion_factorial.t` to `emit` stage

* DGen

- Made `if` an `else if` - this wouldn't of affected anything but just to be correct

* DGen

- Added semantic code generation instrumentation for test case `simple_direct_func_call.t`

Test cases

- Updated test case `simple_direct_func_call.t`
This commit is contained in:
Tristan B. Velloza Kildaire 2023-04-28 11:03:46 +02:00 committed by GitHub
parent a4c010f27f
commit c65c41eed2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 178 additions and 8 deletions

View File

@ -330,6 +330,17 @@ jobs:
run: |
./tlang compile source/tlang/testing/simple_functions.t
./tlang.out
- name: Simple functions (statement-level)
run: |
./tlang compile source/tlang/testing/simple_direct_func_call.t
./tlang.out
- name: Simple functions (recursive)
run: |
./tlang compile source/tlang/testing/simple_function_recursion_factorial.t
./tlang.out
# TODO: Re-enable when we support the `discard` keyword again
#- name: Simple variables
# run: |

View File

@ -293,6 +293,12 @@ public final class DCodeEmitter : CodeEmitter
emit ~= ")";
// If this is a statement-level function call then tack on a `;`
if(funcCallInstr.isStatementLevel())
{
emit ~= ";";
}
return emit;
}
/* ReturnInstruction */
@ -963,6 +969,41 @@ int main()
assert(t_7b6d477c5859059f16bc9da72fc8cc3b == 72);
printf("k: %u\n", t_7b6d477c5859059f16bc9da72fc8cc3b);
return 0;
}`);
}
// Test for `simple_function_recursion_factorial.t` (recursive function call testing)
else if(cmp(typeChecker.getModule().getName(), "simple_function_recursion_factorial") == 0)
{
file.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
int result = factorial(3);
assert(result == 6);
printf("factorial: %u\n", result);
return 0;
}`);
}
// Test for `simple_direct_func_call.t` (statement-level function call)
else if(cmp(typeChecker.getModule().getName(), "simple_direct_func_call") == 0)
{
file.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
// Before it should be 0
assert(t_de44aff5a74865c97c4f8701d329f28d == 0);
// Call the function
function();
// After it it should be 69
assert(t_de44aff5a74865c97c4f8701d329f28d == 69);
return 0;
}`);
}

View File

@ -297,6 +297,15 @@ public class CallInstr : Value
public class FuncCallInstr : CallInstr
{
/**
* This is described in the corresponding AST node
* `FunctionCall`. See that. For short, function calls
* from within expressions and those as appearing as statements
* require a tiny different code gen but for Instructions
* their emit also needs a tiny difference
*/
private bool statementLevel = false;
/* Per-argument instrructions */
private Value[] evaluationInstructions;
@ -328,6 +337,26 @@ public class FuncCallInstr : CallInstr
{
return evaluationInstructions;
}
/**
* Determines whether this function call instruction
* is within an expression or a statement itself
*
* Returns: true if statement-level, false otherwise
*/
public bool isStatementLevel()
{
return statementLevel;
}
/**
* Marks this function call instruction as statement
* level
*/
public void markStatementLevel()
{
statementLevel = true;
}
}

View File

@ -442,9 +442,12 @@ public final class Parser
/* If we have `(` then function call */
if(type == SymbolType.LBRACE)
{
/* TODO: Collect and return */
previousToken();
parseFuncCall();
FunctionCall funcCall = parseFuncCall();
ret = funcCall;
/* Set the flag to say this is a statement-level function call */
funcCall.makeStatementLevel();
/* Expect a semi-colon */
expect(SymbolType.SEMICOLON, getCurrentToken());

View File

@ -598,7 +598,15 @@ public class Call : IdentExpression
public final class FunctionCall : Call
{
/* Whether this is statement-level function call or not */
/**
* Function calls either appear as part of an expression
* (i.e. from `parseExpression()`) or directly as a statement
* in the body of a `Container`. This affects how code generation
* works and hence one needs to disambiguate between the two.
*/
private bool isStatementLevel = false;
/* Argument list */
private Expression[] arguments;
@ -618,6 +626,24 @@ public final class FunctionCall : Call
{
return arguments;
}
/**
* Mark this function call as statement-level
*/
public void makeStatementLevel()
{
this.isStatementLevel = true;
}
/**
* Determines if this function call is statement-level
*
* Returns: true if so, false otherwise
*/
public bool isStatementLevelFuncCall()
{
return isStatementLevel;
}
}
/**

View File

@ -1265,17 +1265,34 @@ public final class TypeChecker
/**
* TODO:
* Codegen
*
* 1. Create FuncCallInstr
* 2. Evaluate args and process them?! wait done elsewhere yeah!!!
* 3. Pop arts into here
* 4. AddInstr(combining those args)
* 5. DOne
* 3. Pop args into here
* 4. addInstr(combining those args)
* 4.1. If this is a statement-level function then `addInstrB()` is used
* 5. Done
*/
funcCallInstr.setContext(funcCall.getContext());
addInstr(funcCallInstr);
// If not a statement-level function call then it is an expression
// ... and ought to be placed at the top of the stack for later consumption
if(!funcCall.isStatementLevelFuncCall())
{
addInstr(funcCallInstr);
}
// If this IS a statement-level function call then it is not meant
// ... to be placed on the top of the stack as it won't be consumed later,
// ... rather it is finalised and should be added to the back of the code queue
else
{
addInstrB(funcCallInstr);
// We also, for emitter, must transfer this flag over by
// ... marking this function call instruction as statement-level
funcCallInstr.markStatementLevel();
}
/* Set the Value instruction's type */
Type funcCallInstrType = getType(func.parentOf(), func.getType());

View File

@ -1680,6 +1680,23 @@ public class DNodeGenerator
/* NOTE: If anything we ought to remove these ExternSTmt nodes during such a process */
return null;
}
/**
* Function call (statement-level)
*/
else if(cast(FunctionCall)entity)
{
FunctionCall funcCall = cast(FunctionCall)entity;
funcCall.setContext(context);
// It MUST be if we are processing it in `generalPass()`
assert(funcCall.isStatementLevelFuncCall());
gprintln("Function calls (at statement level)", DebugType.INFO);
// The FunctionCall is an expression, so to get a DNode from it `expressionPass()` it
DNode funcCallDNode = expressionPass(funcCall, context);
return funcCallDNode;
}
return null;
}

View File

@ -0,0 +1,13 @@
module simple_direct_func_call;
int myVar = 0;
void otherFunction(int i)
{
myVar = i;
}
void function()
{
otherFunction(69);
}

View File

@ -0,0 +1,13 @@
module simple_function_recursion_factorial;
ubyte factorial(ubyte i)
{
if(i == cast(ubyte)0)
{
return cast(ubyte)1;
}
else
{
return i*factorial(i-cast(ubyte)1);
}
}