Instruction
- Added a new instruction, `ForLoop`, which contains a pre-run Instruction and a `Branch` instruction, coupled with some flags DGen - Added a TODO for WhileLoops (we need to implement do-while loops) - Implemented C code emitting in `emit()` for `ForLoop` instruction Check - Added missing back-mapping for `SymbolType.SMALLER_THAN` Data - Added new parser node type `ForLoop` Parser - Fixed typo in `parseWhile()` - Implemented `parseDoWhile()` for do-while loops - Implemented `parseFor()` for for-loops - Implemented `parseStatement()` for singular statement parsing - `parseStatement()` can now have the terminating symbol specified, defaults to `SymbolType.SEMICOLON` - `parseName()` and `parseAssignment()` now also accept a terminating symbol parameter as per `parseStatement()`'s behavior - `parseBody()` now makes multiple calls to `parseStatement()` for singular Statement parsing (dead code below still to be removed) - Removed commented-out unittests - Unittests that read from files now have the file source code embedded - Added unit test for while loops, for-loops (unfinished) and some other smaller language constructs (roughly 70% coverage) TypeChecker (CodeGen) - Do-while loops will fail if used (for now) - Added for-loop code generation Dependency - Implemented `generalStatement()` for statement processing - `generalPass()` now makes calls to `generalStatement()` Tests - Added `simple_for_loops.t` to test for-loops - Added `simple_do_while.t` to test do-while loops
This commit is contained in:
parent
22c4e8d5a1
commit
ec7d8cf424
|
@ -273,6 +273,8 @@ public final class DCodeEmitter : CodeEmitter
|
|||
}
|
||||
/**
|
||||
* While loops (WhileLoopInstruction)
|
||||
*
|
||||
* TODO: Add do-while check
|
||||
*/
|
||||
else if(cast(WhileLoopInstruction)instruction)
|
||||
{
|
||||
|
@ -299,6 +301,44 @@ public final class DCodeEmitter : CodeEmitter
|
|||
|
||||
return emit;
|
||||
}
|
||||
/**
|
||||
* For loops (ForLoopInstruction)
|
||||
*/
|
||||
else if(cast(ForLoopInstruction)instruction)
|
||||
{
|
||||
ForLoopInstruction forLoopInstr = cast(ForLoopInstruction)instruction;
|
||||
|
||||
BranchInstruction branchInstruction = forLoopInstr.getBranchInstruction();
|
||||
Value conditionInstr = branchInstruction.getConditionInstr();
|
||||
Instruction[] bodyInstructions = branchInstruction.getBodyInstructions();
|
||||
|
||||
string emit = "for(";
|
||||
|
||||
// Emit potential pre-run instruction
|
||||
emit ~= forLoopInstr.hasPreRunInstruction() ? transform(forLoopInstr.getPreRunInstruction()) : ";";
|
||||
|
||||
// Condition
|
||||
emit ~= transform(conditionInstr)~";";
|
||||
|
||||
// NOTE: We are leaving the post-iteration blank due to us including it in the body
|
||||
// TODO: We can hoist bodyInstructions[$] maybe if we want to generate it as C-for-loops
|
||||
// if(forLoopInstr.hasPostIterationInstruction())
|
||||
emit ~= ")\n";
|
||||
|
||||
// Open curly (begin body)
|
||||
emit~=genTabs(transformDepth)~"{\n";
|
||||
|
||||
/* Transform each body statement */
|
||||
foreach(Instruction curBodyInstr; bodyInstructions)
|
||||
{
|
||||
emit~=genTabs(transformDepth)~"\t"~transform(curBodyInstr)~"\n";
|
||||
}
|
||||
|
||||
// Close curly (body end)
|
||||
emit~=genTabs(transformDepth)~"}";
|
||||
|
||||
return emit;
|
||||
}
|
||||
|
||||
return "<TODO: Base emit: "~to!(string)(instruction)~">";
|
||||
}
|
||||
|
@ -508,6 +548,20 @@ int main()
|
|||
printf("result: %d\n", result);
|
||||
assert(result == 3);
|
||||
|
||||
return 0;
|
||||
}`);
|
||||
}
|
||||
else if(cmp(typeChecker.getModule().getName(), "simple_for_loops") == 0)
|
||||
{
|
||||
file.writeln(`
|
||||
#include<stdio.h>
|
||||
#include<assert.h>
|
||||
int main()
|
||||
{
|
||||
int result = function(3);
|
||||
printf("result: %d\n", result);
|
||||
assert(result == 3);
|
||||
|
||||
return 0;
|
||||
}`);
|
||||
}
|
||||
|
|
|
@ -335,6 +335,43 @@ public final class WhileLoopInstruction : Instruction
|
|||
}
|
||||
}
|
||||
|
||||
public final class ForLoopInstruction : Instruction
|
||||
{
|
||||
private Instruction preRunInstruction;
|
||||
private BranchInstruction branchInstruction;
|
||||
private bool hasPostIterate;
|
||||
|
||||
this(BranchInstruction branchInstruction, Instruction preRunInstruction = null, bool hasPostIterate = false)
|
||||
{
|
||||
this.branchInstruction = branchInstruction;
|
||||
this.preRunInstruction = preRunInstruction;
|
||||
|
||||
addInfo = (hasPreRunInstruction() ? "PreRun: "~to!(string)(preRunInstruction)~", " : "")~"Branch: "~to!(string)(branchInstruction);
|
||||
|
||||
this.hasPostIterate = hasPostIterate;
|
||||
}
|
||||
|
||||
public bool hasPostIterationInstruction()
|
||||
{
|
||||
return hasPostIterate;
|
||||
}
|
||||
|
||||
public Instruction getPreRunInstruction()
|
||||
{
|
||||
return preRunInstruction;
|
||||
}
|
||||
|
||||
public bool hasPreRunInstruction()
|
||||
{
|
||||
return !(preRunInstruction is null);
|
||||
}
|
||||
|
||||
public BranchInstruction getBranchInstruction()
|
||||
{
|
||||
return branchInstruction;
|
||||
}
|
||||
}
|
||||
|
||||
public final class BranchInstruction : Instruction
|
||||
{
|
||||
private Value branchConditionInstr;
|
||||
|
|
|
@ -241,7 +241,7 @@ public final class Parser
|
|||
branchCondition = parseExpression();
|
||||
expect(SymbolType.RBRACE, getCurrentToken());
|
||||
|
||||
/* Openening { */
|
||||
/* Opening { */
|
||||
nextToken();
|
||||
expect(SymbolType.OCURLY, getCurrentToken());
|
||||
|
||||
|
@ -268,7 +268,124 @@ public final class Parser
|
|||
return whileLoop;
|
||||
}
|
||||
|
||||
public VariableAssignmentStdAlone parseAssignment()
|
||||
private WhileLoop parseDoWhile()
|
||||
{
|
||||
gprintln("parseDoWhile(): Enter", DebugType.WARNING);
|
||||
|
||||
Expression branchCondition;
|
||||
Statement[] branchBody;
|
||||
|
||||
/* Pop off the `do` */
|
||||
nextToken();
|
||||
|
||||
/* Expect an opening curly `{` */
|
||||
expect(SymbolType.OCURLY, getCurrentToken());
|
||||
|
||||
/* Parse the do-while statement's body AND expect a closing curly */
|
||||
branchBody = parseBody();
|
||||
expect(SymbolType.CCURLY, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
/* Expect a `while` */
|
||||
expect(SymbolType.WHILE, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
/* Expect an opening brace `(` */
|
||||
expect(SymbolType.LBRACE, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
/* Parse the condition */
|
||||
branchCondition = parseExpression();
|
||||
expect(SymbolType.RBRACE, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
/* Expect a semicolon */
|
||||
expect(SymbolType.SEMICOLON, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
/* Create a Branch node coupling the condition and body statements */
|
||||
Branch branch = new Branch(branchCondition, branchBody);
|
||||
|
||||
/* Parent the branchBody to the branch */
|
||||
parentToContainer(branch, branchBody);
|
||||
|
||||
/* Create the while loop with the single branch and marked as a do-while loop */
|
||||
WhileLoop whileLoop = new WhileLoop(branch, true);
|
||||
|
||||
/* Parent the branch to the WhileLoop */
|
||||
parentToContainer(whileLoop, [branch]);
|
||||
|
||||
gprintln("parseDoWhile(): Leave", DebugType.WARNING);
|
||||
|
||||
return whileLoop;
|
||||
}
|
||||
|
||||
// TODO: Finish implementing this
|
||||
// TODO: We need to properly parent and build stuff
|
||||
// TODO: We ASSUME there is always pre-run, condition and post-iteration
|
||||
public ForLoop parseFor()
|
||||
{
|
||||
gprintln("parseFor(): Enter", DebugType.WARNING);
|
||||
|
||||
Expression branchCondition;
|
||||
Statement[] branchBody;
|
||||
|
||||
/* Pop of the token `for` */
|
||||
nextToken();
|
||||
|
||||
/* Expect an opening smooth brace `(` */
|
||||
expect(SymbolType.LBRACE, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
/* Expect a single Statement */
|
||||
// TODO: Make optional, add parser lookahead check
|
||||
Statement preRunStatement = parseStatement();
|
||||
|
||||
/* Expect an expression */
|
||||
// TODO: Make optional, add parser lookahead check
|
||||
branchCondition = parseExpression();
|
||||
|
||||
/* Expect a semi-colon, then move on */
|
||||
expect(SymbolType.SEMICOLON, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
/* Expect a post-iteration statement with `)` as terminator */
|
||||
// TODO: Make optional, add parser lookahead check
|
||||
Statement postIterationStatement = parseStatement(SymbolType.RBRACE);
|
||||
|
||||
/* Expect an opening curly `{` and parse the body */
|
||||
expect(SymbolType.OCURLY, getCurrentToken());
|
||||
branchBody = parseBody();
|
||||
|
||||
/* Expect a closing curly and move on */
|
||||
expect(SymbolType.CCURLY, getCurrentToken());
|
||||
nextToken();
|
||||
|
||||
gprintln("Yo: "~getCurrentToken().toString());
|
||||
|
||||
/* Create the Branch coupling the body statements (+post iteration statement) and condition */
|
||||
Branch forBranch = new Branch(branchCondition, branchBody~postIterationStatement);
|
||||
|
||||
/* Create the for loop */
|
||||
ForLoop forLoop = new ForLoop(forBranch, preRunStatement);
|
||||
|
||||
// TODO: Set `forLoop.hasPostIterate`
|
||||
|
||||
/* Parent the pre-run statement to its for loop */
|
||||
parentToContainer(forLoop, [preRunStatement]);
|
||||
|
||||
/* Parent the body of the branch (body statements + post iteration statement) */
|
||||
parentToContainer(forBranch, branchBody~postIterationStatement);
|
||||
|
||||
/* Parent the Branch to its for loop */
|
||||
parentToContainer(forLoop, [forBranch]);
|
||||
|
||||
gprintln("parseFor(): Leave", DebugType.WARNING);
|
||||
|
||||
return forLoop;
|
||||
}
|
||||
|
||||
public VariableAssignmentStdAlone parseAssignment(SymbolType terminatingSymbol = SymbolType.SEMICOLON)
|
||||
{
|
||||
/* Generated Assignment statement */
|
||||
VariableAssignmentStdAlone assignment;
|
||||
|
@ -286,15 +403,18 @@ public final class Parser
|
|||
assignment = new VariableAssignmentStdAlone(identifier, assignmentExpression);
|
||||
|
||||
/* TODO: Support for (a=1)? */
|
||||
/* Expect a semicolon */
|
||||
expect(SymbolType.SEMICOLON, getCurrentToken());
|
||||
/* Expect a the terminating symbol */
|
||||
// expect(SymbolType.SEMICOLON, getCurrentToken());
|
||||
expect(terminatingSymbol, getCurrentToken());
|
||||
|
||||
/* Move off terminating symbol */
|
||||
nextToken();
|
||||
|
||||
|
||||
return assignment;
|
||||
}
|
||||
|
||||
public Statement parseName()
|
||||
public Statement parseName(SymbolType terminatingSymbol = SymbolType.SEMICOLON)
|
||||
{
|
||||
Statement ret;
|
||||
|
||||
|
@ -328,7 +448,7 @@ public final class Parser
|
|||
else if(type == SymbolType.ASSIGN)
|
||||
{
|
||||
previousToken();
|
||||
ret = parseAssignment();
|
||||
ret = parseAssignment(terminatingSymbol);
|
||||
}
|
||||
/* Any other case */
|
||||
else
|
||||
|
@ -511,6 +631,12 @@ public final class Parser
|
|||
*/
|
||||
bool closedBeforeExit;
|
||||
|
||||
// TODO: Once issue #75 is closed, remove this
|
||||
bool useParseStatement = true;
|
||||
|
||||
// NOTE: See issue #75 - could we make a general `parseStatement()`
|
||||
// and then call that in a loop here rather? This would make certain things
|
||||
// a little easier like where we need to parse only a single statement
|
||||
while (hasTokens())
|
||||
{
|
||||
/* Get the token */
|
||||
|
@ -519,14 +645,47 @@ public final class Parser
|
|||
|
||||
gprintln("parseBody(): SymbolType=" ~ to!(string)(symbol));
|
||||
|
||||
// TODO: Once issue #75 is closed, remove this
|
||||
if(useParseStatement)
|
||||
{
|
||||
/* If it is a class definition */
|
||||
if(symbol == SymbolType.CLASS)
|
||||
{
|
||||
/* Parse the class and add its statements */
|
||||
statements ~= parseClass();
|
||||
}
|
||||
/* If it is a struct definition */
|
||||
else if(symbol == SymbolType.STRUCT)
|
||||
{
|
||||
/* Parse the struct and add it to the statements */
|
||||
statements ~= parseStruct();
|
||||
}
|
||||
/* If it is closing the body `}` */
|
||||
else if(symbol == SymbolType.CCURLY)
|
||||
{
|
||||
gprintln("parseBody(): Exiting body by }", DebugType.WARNING);
|
||||
|
||||
closedBeforeExit = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
statements ~= parseStatement();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Once issue #75 is closed, remove the below checks
|
||||
// NOTE: Below coce may become out-dated as we try implement the above
|
||||
|
||||
/* If it is a type */
|
||||
if (symbol == SymbolType.IDENT_TYPE)
|
||||
if(symbol == SymbolType.IDENT_TYPE)
|
||||
{
|
||||
/* Might be a function, might be a variable, or assignment */
|
||||
statements ~= parseName();
|
||||
}
|
||||
/* If it is an accessor */
|
||||
else if (isAccessor(tok))
|
||||
else if(isAccessor(tok))
|
||||
{
|
||||
statements ~= parseAccessor();
|
||||
}
|
||||
|
@ -536,33 +695,36 @@ public final class Parser
|
|||
statements ~= parseInitScope();
|
||||
}
|
||||
/* If it is a branch */
|
||||
else if (symbol == SymbolType.IF)
|
||||
else if(symbol == SymbolType.IF)
|
||||
{
|
||||
statements ~= parseIf();
|
||||
}
|
||||
/* If it is a while loop */
|
||||
else if (symbol == SymbolType.WHILE)
|
||||
else if(symbol == SymbolType.WHILE)
|
||||
{
|
||||
statements ~= parseWhile();
|
||||
}
|
||||
/* If it is a do-while loop */
|
||||
else if(symbol == SymbolType.DO)
|
||||
{
|
||||
statements ~= parseDoWhile();
|
||||
}
|
||||
/* If it is a function call (further inspection needed) */
|
||||
else if (symbol == SymbolType.IDENT_TYPE)
|
||||
else if(symbol == SymbolType.IDENT_TYPE)
|
||||
{
|
||||
/* Function calls can have dotted identifiers */
|
||||
parseFuncCall();
|
||||
}
|
||||
/* If it is closing the body `}` */
|
||||
else if (symbol == SymbolType.CCURLY)
|
||||
else if(symbol == SymbolType.CCURLY)
|
||||
{
|
||||
// gprintln("Error");
|
||||
// nextToken();
|
||||
gprintln("parseBody(): Exiting body by }", DebugType.WARNING);
|
||||
|
||||
closedBeforeExit = true;
|
||||
break;
|
||||
}
|
||||
/* If it is a class definition */
|
||||
else if (symbol == SymbolType.CLASS)
|
||||
else if(symbol == SymbolType.CLASS)
|
||||
{
|
||||
/* Parse the class and add its statements */
|
||||
statements ~= parseClass();
|
||||
|
@ -1442,21 +1604,77 @@ public final class Parser
|
|||
}
|
||||
}
|
||||
|
||||
private void parseStatement()
|
||||
// TODO: This ic currently dead code and ought to be used/implemented
|
||||
private Statement parseStatement(SymbolType terminatingSymbol = SymbolType.SEMICOLON)
|
||||
{
|
||||
gprintln("parseStatement(): Enter", DebugType.WARNING);
|
||||
|
||||
/* TODO: Implement parse statement */
|
||||
/* Get the token */
|
||||
Token tok = getCurrentToken();
|
||||
SymbolType symbol = getSymbolType(tok);
|
||||
|
||||
/**
|
||||
* TODO: We should remove the `;` from parseFuncCall
|
||||
* And rather do the following here:
|
||||
*
|
||||
* 1. parseFuncCall()
|
||||
* 2. expect(;)
|
||||
*/
|
||||
gprintln("parseStatement(): SymbolType=" ~ to!(string)(symbol));
|
||||
|
||||
Statement statement;
|
||||
|
||||
/* If it is a type */
|
||||
if(symbol == SymbolType.IDENT_TYPE)
|
||||
{
|
||||
/* Might be a function, might be a variable, or assignment */
|
||||
statement = parseName(terminatingSymbol);
|
||||
}
|
||||
/* If it is an accessor */
|
||||
else if(isAccessor(tok))
|
||||
{
|
||||
statement = parseAccessor();
|
||||
}
|
||||
/* If it is a modifier */
|
||||
else if(isModifier(tok))
|
||||
{
|
||||
statement = parseInitScope();
|
||||
}
|
||||
/* If it is a branch */
|
||||
else if(symbol == SymbolType.IF)
|
||||
{
|
||||
statement = parseIf();
|
||||
}
|
||||
/* If it is a while loop */
|
||||
else if(symbol == SymbolType.WHILE)
|
||||
{
|
||||
statement = parseWhile();
|
||||
}
|
||||
/* If it is a do-while loop */
|
||||
else if(symbol == SymbolType.DO)
|
||||
{
|
||||
statement = parseDoWhile();
|
||||
}
|
||||
/* If it is a for loop */
|
||||
else if(symbol == SymbolType.FOR)
|
||||
{
|
||||
statement = parseFor();
|
||||
}
|
||||
/* If it is a function call (further inspection needed) */
|
||||
else if(symbol == SymbolType.IDENT_TYPE)
|
||||
{
|
||||
/* Function calls can have dotted identifiers */
|
||||
parseFuncCall();
|
||||
}
|
||||
/* If it is the return keyword */
|
||||
//TODO: We should add a flag to prevent return being used in generla bodies? or wait we have a non parseBiody already
|
||||
else if(symbol == SymbolType.RETURN)
|
||||
{
|
||||
/* Parse the return statement */
|
||||
statement = parseReturn();
|
||||
}
|
||||
/* Error out */
|
||||
else
|
||||
{
|
||||
expect("parseStatement(): Unknown symbol: " ~ getCurrentToken().getToken());
|
||||
}
|
||||
|
||||
gprintln("parseStatement(): Leave", DebugType.WARNING);
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
private FunctionCall parseFuncCall()
|
||||
|
@ -1633,75 +1851,9 @@ public final class Parser
|
|||
}
|
||||
}
|
||||
|
||||
// unittest
|
||||
// {
|
||||
// /* TODO: Add some unit tests */
|
||||
// import std.file;
|
||||
// import std.stdio;
|
||||
// import compiler.lexer;
|
||||
|
||||
// isUnitTest = true;
|
||||
|
||||
// string sourceFile = "source/tlang/testing/basic1.t";
|
||||
|
||||
// File sourceFileFile;
|
||||
// sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
|
||||
// ulong fileSize = sourceFileFile.size();
|
||||
// byte[] fileBytes;
|
||||
// fileBytes.length = fileSize;
|
||||
// fileBytes = sourceFileFile.rawRead(fileBytes);
|
||||
// sourceFileFile.close();
|
||||
|
||||
|
||||
|
||||
// /* TODO: Open source file */
|
||||
// string sourceCode = cast(string)fileBytes;
|
||||
// // string sourceCode = "hello \"world\"|| ";
|
||||
// //string sourceCode = "hello \"world\"||"; /* TODO: Implement this one */
|
||||
// // string sourceCode = "hello;";
|
||||
// Lexer currentLexer = new Lexer(sourceCode);
|
||||
// assert(currentLexer.performLex());
|
||||
|
||||
|
||||
// Parser parser = new Parser(currentLexer.getTokens());
|
||||
// parser.parse();
|
||||
// }
|
||||
|
||||
|
||||
unittest
|
||||
{
|
||||
/* TODO: Add some unit tests */
|
||||
import std.file;
|
||||
import std.stdio;
|
||||
import compiler.lexer;
|
||||
|
||||
isUnitTest = true;
|
||||
|
||||
// string sourceFile = "source/tlang/testing/basic1.t";
|
||||
|
||||
// File sourceFileFile;
|
||||
// sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
|
||||
// ulong fileSize = sourceFileFile.size();
|
||||
// byte[] fileBytes;
|
||||
// fileBytes.length = fileSize;
|
||||
// fileBytes = sourceFileFile.rawRead(fileBytes);
|
||||
// sourceFileFile.close();
|
||||
|
||||
|
||||
|
||||
// /* TODO: Open source file */
|
||||
// string sourceCode = cast(string)fileBytes;
|
||||
// // string sourceCode = "hello \"world\"|| ";
|
||||
// //string sourceCode = "hello \"world\"||"; /* TODO: Implement this one */
|
||||
// // string sourceCode = "hello;";
|
||||
// Lexer currentLexer = new Lexer(sourceCode);
|
||||
// assert(currentLexer.performLex());
|
||||
|
||||
|
||||
// Parser parser = new Parser(currentLexer.getTokens());
|
||||
// parser.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic Module test case
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
|
||||
|
@ -1709,23 +1861,10 @@ unittest
|
|||
import std.stdio;
|
||||
import compiler.lexer;
|
||||
|
||||
string sourceFile = "source/tlang/testing/simple1_module_positive.t";
|
||||
|
||||
File sourceFileFile;
|
||||
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
|
||||
ulong fileSize = sourceFileFile.size();
|
||||
byte[] fileBytes;
|
||||
fileBytes.length = fileSize;
|
||||
fileBytes = sourceFileFile.rawRead(fileBytes);
|
||||
sourceFileFile.close();
|
||||
string sourceCode = `
|
||||
module myModule;
|
||||
`;
|
||||
|
||||
|
||||
|
||||
/* TODO: Open source file */
|
||||
string sourceCode = cast(string)fileBytes;
|
||||
// string sourceCode = "hello \"world\"|| ";
|
||||
//string sourceCode = "hello \"world\"||"; /* TODO: Implement this one */
|
||||
// string sourceCode = "hello;";
|
||||
Lexer currentLexer = new Lexer(sourceCode);
|
||||
assert(currentLexer.performLex());
|
||||
|
||||
|
@ -1754,20 +1893,28 @@ unittest
|
|||
import compiler.lexer;
|
||||
import compiler.typecheck.core;
|
||||
|
||||
string sourceFile = "source/tlang/testing/simple2_name_recognition.t";
|
||||
|
||||
File sourceFileFile;
|
||||
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
|
||||
ulong fileSize = sourceFileFile.size();
|
||||
byte[] fileBytes;
|
||||
fileBytes.length = fileSize;
|
||||
fileBytes = sourceFileFile.rawRead(fileBytes);
|
||||
sourceFileFile.close();
|
||||
string sourceCode = `
|
||||
module myModule;
|
||||
|
||||
class myClass1
|
||||
{
|
||||
class myClass1_1
|
||||
{
|
||||
int entity;
|
||||
}
|
||||
|
||||
class myClass2
|
||||
{
|
||||
int inner;
|
||||
}
|
||||
}
|
||||
|
||||
class myClass2
|
||||
{
|
||||
int outer;
|
||||
}
|
||||
`;
|
||||
|
||||
/* TODO: Open source file */
|
||||
string sourceCode = cast(string)fileBytes;
|
||||
Lexer currentLexer = new Lexer(sourceCode);
|
||||
assert(currentLexer.performLex());
|
||||
|
||||
|
@ -1894,4 +2041,417 @@ unittest
|
|||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function definition test case
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
import std.stdio;
|
||||
import compiler.lexer;
|
||||
import compiler.typecheck.core;
|
||||
|
||||
|
||||
string sourceCode = `
|
||||
module parser_function_def;
|
||||
|
||||
int myFunction(int i, int j)
|
||||
{
|
||||
int k = i + j;
|
||||
|
||||
return k+1;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
Lexer currentLexer = new Lexer(sourceCode);
|
||||
assert(currentLexer.performLex());
|
||||
|
||||
|
||||
Parser parser = new Parser(currentLexer.getTokens());
|
||||
|
||||
try
|
||||
{
|
||||
Module modulle = parser.parse();
|
||||
|
||||
/* Module name must be parser_while */
|
||||
assert(cmp(modulle.getName(), "parser_function_def")==0);
|
||||
TypeChecker tc = new TypeChecker(modulle);
|
||||
|
||||
|
||||
/* Find the function named `myFunction` */
|
||||
Entity func = tc.getResolver().resolveBest(modulle, "myFunction");
|
||||
assert(func);
|
||||
assert(cast(Function)func); // Ensure it is a Funciton
|
||||
|
||||
/* Get the function's body */
|
||||
Container funcContainer = cast(Container)func;
|
||||
assert(funcContainer);
|
||||
Statement[] functionStatements = funcContainer.getStatements();
|
||||
|
||||
// Two parameters, 1 local and a return
|
||||
assert(functionStatements.length == 4);
|
||||
|
||||
/* First statement should be a variable (param) */
|
||||
VariableParameter varPar1 = cast(VariableParameter)functionStatements[0];
|
||||
assert(varPar1);
|
||||
assert(cmp(varPar1.getName(), "i") == 0);
|
||||
|
||||
/* Second statement should be a variable (param) */
|
||||
VariableParameter varPar2 = cast(VariableParameter)functionStatements[1];
|
||||
assert(varPar2);
|
||||
assert(cmp(varPar2.getName(), "j") == 0);
|
||||
|
||||
/* ThirdFirst statement should be a variable (local) */
|
||||
Variable varLocal = cast(Variable)functionStatements[2];
|
||||
assert(varLocal);
|
||||
assert(cmp(varLocal.getName(), "k") == 0);
|
||||
|
||||
/* Last statement should be a return */
|
||||
assert(cast(ReturnStmt)functionStatements[3]);
|
||||
}
|
||||
catch(TError e)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While loop test case (nested)
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
import std.stdio;
|
||||
import compiler.lexer;
|
||||
import compiler.typecheck.core;
|
||||
|
||||
|
||||
string sourceCode = `
|
||||
module parser_while;
|
||||
|
||||
void function()
|
||||
{
|
||||
int i = 0;
|
||||
while(i)
|
||||
{
|
||||
int p = i;
|
||||
|
||||
while(i)
|
||||
{
|
||||
int f;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
Lexer currentLexer = new Lexer(sourceCode);
|
||||
assert(currentLexer.performLex());
|
||||
|
||||
|
||||
Parser parser = new Parser(currentLexer.getTokens());
|
||||
|
||||
try
|
||||
{
|
||||
Module modulle = parser.parse();
|
||||
|
||||
/* Module name must be parser_while */
|
||||
assert(cmp(modulle.getName(), "parser_while")==0);
|
||||
TypeChecker tc = new TypeChecker(modulle);
|
||||
|
||||
|
||||
/* Find the function named `function` */
|
||||
Entity func = tc.getResolver().resolveBest(modulle, "function");
|
||||
assert(func);
|
||||
assert(cast(Function)func); // Ensure it is a Funciton
|
||||
|
||||
/* Get the function's body */
|
||||
Container funcContainer = cast(Container)func;
|
||||
assert(funcContainer);
|
||||
Statement[] functionStatements = funcContainer.getStatements();
|
||||
assert(functionStatements.length == 2);
|
||||
|
||||
/* Find the while loop within the function's body */
|
||||
WhileLoop potentialWhileLoop;
|
||||
foreach(Statement curStatement; functionStatements)
|
||||
{
|
||||
potentialWhileLoop = cast(WhileLoop)curStatement;
|
||||
|
||||
if(potentialWhileLoop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* This must pass if we found the while loop */
|
||||
assert(potentialWhileLoop);
|
||||
|
||||
/* Grab the branch of the while loop */
|
||||
Branch whileBranch = potentialWhileLoop.getBranch();
|
||||
assert(whileBranch);
|
||||
|
||||
/* Ensure that we have one statement in this branch's body and that it is a Variable and next is a while loop */
|
||||
Statement[] whileBranchStatements = whileBranch.getStatements();
|
||||
assert(whileBranchStatements.length == 2);
|
||||
assert(cast(Variable)whileBranchStatements[0]);
|
||||
assert(cast(WhileLoop)whileBranchStatements[1]);
|
||||
|
||||
/* The inner while loop also has a similiar structure, only one variable */
|
||||
WhileLoop innerLoop = cast(WhileLoop)whileBranchStatements[1];
|
||||
Branch innerWhileBranch = innerLoop.getBranch();
|
||||
assert(innerWhileBranch);
|
||||
Statement[] innerWhileBranchStatements = innerWhileBranch.getStatements();
|
||||
assert(innerWhileBranchStatements.length == 1);
|
||||
assert(cast(Variable)innerWhileBranchStatements[0]);
|
||||
}
|
||||
catch(TError e)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do-while loop tests (TODO: Add this)
|
||||
*/
|
||||
|
||||
/**
|
||||
* For loop tests (TODO: Add this)
|
||||
*/
|
||||
/**
|
||||
* While loop test case (nested)
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
import std.stdio;
|
||||
import compiler.lexer;
|
||||
import compiler.typecheck.core;
|
||||
|
||||
|
||||
string sourceCode = `
|
||||
module parser_for;
|
||||
|
||||
void function()
|
||||
{
|
||||
int i = 0;
|
||||
for(int idx = i; idx < i; idx=idx+1)
|
||||
{
|
||||
i = i + 1;
|
||||
|
||||
for(int idxInner = idx; idxInner < idx; idxInner = idxInner +1)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
Lexer currentLexer = new Lexer(sourceCode);
|
||||
assert(currentLexer.performLex());
|
||||
|
||||
|
||||
Parser parser = new Parser(currentLexer.getTokens());
|
||||
|
||||
try
|
||||
{
|
||||
Module modulle = parser.parse();
|
||||
|
||||
/* Module name must be parser_while */
|
||||
assert(cmp(modulle.getName(), "parser_for")==0);
|
||||
TypeChecker tc = new TypeChecker(modulle);
|
||||
|
||||
|
||||
/* Find the function named `function` */
|
||||
Entity func = tc.getResolver().resolveBest(modulle, "function");
|
||||
assert(func);
|
||||
assert(cast(Function)func); // Ensure it is a Funciton
|
||||
|
||||
/* Get the function's body */
|
||||
Container funcContainer = cast(Container)func;
|
||||
assert(funcContainer);
|
||||
Statement[] functionStatements = funcContainer.getStatements();
|
||||
assert(functionStatements.length == 2);
|
||||
|
||||
/* First statement should be a variable declaration */
|
||||
assert(cast(Variable)functionStatements[0]);
|
||||
|
||||
/* Next statement should be a for loop */
|
||||
ForLoop outerLoop = cast(ForLoop)functionStatements[1];
|
||||
assert(outerLoop);
|
||||
|
||||
/* Get the body of the for-loop which should be [preRun, Branch] */
|
||||
Statement[] outerLoopBody = outerLoop.getStatements();
|
||||
assert(outerLoopBody.length == 2);
|
||||
|
||||
/* We should have a preRun Statement */
|
||||
assert(outerLoop.hasPreRunStatement());
|
||||
|
||||
/* The first should be the [preRun, ] which should be a Variable (declaration) */
|
||||
Variable preRunVarDec = cast(Variable)(outerLoopBody[0]);
|
||||
assert(preRunVarDec);
|
||||
|
||||
/* Next up is the branch */
|
||||
Branch outerLoopBranch = cast(Branch)outerLoopBody[1];
|
||||
assert(outerLoopBranch);
|
||||
|
||||
/* The branch should have a condition */
|
||||
Expression outerLoopBranchCondition = outerLoopBranch.getCondition();
|
||||
assert(outerLoopBranchCondition);
|
||||
|
||||
/* The branch should have a body made up of [varAssStdAlone, forLoop, postIteration] */
|
||||
Statement[] outerLoopBranchBody = outerLoopBranch.getStatements();
|
||||
assert(outerLoopBranchBody.length == 3);
|
||||
|
||||
/* Check for [varAssStdAlone, ] */
|
||||
VariableAssignmentStdAlone outerLoopBranchBodyStmt1 = cast(VariableAssignmentStdAlone)outerLoopBranchBody[0];
|
||||
assert(outerLoopBranchBodyStmt1);
|
||||
|
||||
/* Check for [, forLoop, ] */
|
||||
ForLoop innerLoop = cast(ForLoop)outerLoopBranchBody[1];
|
||||
assert(innerLoop);
|
||||
|
||||
/* Check for [, postIteration] */
|
||||
VariableAssignmentStdAlone outerLoopBranchBodyStmt3 = cast(VariableAssignmentStdAlone)outerLoopBranchBody[2];
|
||||
assert(outerLoopBranchBodyStmt3);
|
||||
|
||||
|
||||
// /* The outer loop should have a Variable as pre-run Statement */
|
||||
// Statement preRunStatement = outerLoop.getPreLoopStatement();
|
||||
// assert(preRunStatement);
|
||||
// assert(cast(Variable)preRunStatement);
|
||||
|
||||
// /* The outer loop should have a variable assignment as the post-iteration statement */
|
||||
// Statement postIrerationStatement = outerLoop.getPreIterationStatement();
|
||||
// assert(postIrerationStatement);
|
||||
// assert(cast(VariableAssignmentStdAlone)postIrerationStatement);
|
||||
|
||||
// /* Extract the statements of the body of the outer loop */
|
||||
// Branch outerLoopBranch = outerLoop.getBranch();
|
||||
// assert(outerLoopBranch);
|
||||
// Statement[] outerLoopBody = outerLoopBranch.getStatements();
|
||||
// assert(outerLoopBody.length == 2);
|
||||
|
||||
// /* First statement is a VarAssStdAlone */
|
||||
// assert(cast(VariableAssignmentStdAlone)outerLoopBody[0]);
|
||||
|
||||
// /* Second statement is a for loop */
|
||||
// ForLoop innerLoop = cast(ForLoop)outerLoopBody[1];
|
||||
// assert(innerLoop);
|
||||
|
||||
// /* The inner loop should have a Variable as pre-run Statement */
|
||||
// Statement innerPreRunStatement = innerLoop.getPreLoopStatement();
|
||||
// assert(innerPreRunStatement);
|
||||
// assert(cast(Variable)innerPreRunStatement);
|
||||
|
||||
// /* The inner loop should have a variable assignment as the post-iteration statement */
|
||||
// Statement innerPostIrerationStatement = outerLoop.getPreIterationStatement();
|
||||
// assert(innerPostIrerationStatement);
|
||||
// assert(cast(VariableAssignmentStdAlone)innerPostIrerationStatement);
|
||||
|
||||
// /* Extract the statements of the body of the inner loop */
|
||||
// Branch innerLoopBranch = innerLoop.getBranch();
|
||||
// assert(innerLoopBranch);
|
||||
// Statement[] innerLoopBody = innerLoopBranch.getStatements();
|
||||
// assert(innerLoopBody.length == 0);
|
||||
}
|
||||
catch(TError e)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If statement tests
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
import std.stdio;
|
||||
import compiler.lexer;
|
||||
import compiler.typecheck.core;
|
||||
|
||||
|
||||
string sourceCode = `
|
||||
module parser_if;
|
||||
|
||||
void function()
|
||||
{
|
||||
int i = 0;
|
||||
if(i)
|
||||
{
|
||||
int p = -i;
|
||||
}
|
||||
else if(i)
|
||||
{
|
||||
int p = 3+(i*9);
|
||||
}
|
||||
else if(i)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
Lexer currentLexer = new Lexer(sourceCode);
|
||||
assert(currentLexer.performLex());
|
||||
|
||||
|
||||
Parser parser = new Parser(currentLexer.getTokens());
|
||||
|
||||
try
|
||||
{
|
||||
Module modulle = parser.parse();
|
||||
|
||||
/* Module name must be parser_while */
|
||||
assert(cmp(modulle.getName(), "parser_if")==0);
|
||||
TypeChecker tc = new TypeChecker(modulle);
|
||||
|
||||
/* Find the function named `function` */
|
||||
Entity func = tc.getResolver().resolveBest(modulle, "function");
|
||||
assert(func);
|
||||
assert(cast(Function)func); // Ensure it is a Funciton
|
||||
|
||||
/* Get the function's body */
|
||||
Container funcContainer = cast(Container)func;
|
||||
assert(funcContainer);
|
||||
Statement[] functionStatements = funcContainer.getStatements();
|
||||
assert(functionStatements.length == 2);
|
||||
|
||||
/* Second statement is an if statemnet */
|
||||
IfStatement ifStatement = cast(IfStatement)functionStatements[1];
|
||||
assert(ifStatement);
|
||||
|
||||
/* Extract the branches (should be 4) */
|
||||
Branch[] ifStatementBranches = ifStatement.getBranches();
|
||||
assert(ifStatementBranches.length == 4);
|
||||
|
||||
/* First branch should have one statement which is a variable declaration */
|
||||
Statement[] firstBranchBody = ifStatementBranches[0].getStatements();
|
||||
assert(firstBranchBody.length == 1);
|
||||
assert(cast(Variable)firstBranchBody[0]);
|
||||
|
||||
/* Second branch should have one statement which is a variable declaration */
|
||||
Statement[] secondBranchBody = ifStatementBranches[1].getStatements();
|
||||
assert(secondBranchBody.length == 1);
|
||||
assert(cast(Variable)secondBranchBody[0]);
|
||||
|
||||
/* Third branch should have no statements */
|
||||
Statement[] thirdBranchBody = ifStatementBranches[2].getStatements();
|
||||
assert(thirdBranchBody.length == 0);
|
||||
|
||||
/* Forth branch should have no statements */
|
||||
Statement[] fourthBranchBody = ifStatementBranches[3].getStatements();
|
||||
assert(fourthBranchBody.length == 0);
|
||||
|
||||
// TODO: @Tristan Add this
|
||||
}
|
||||
catch(TError e)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
|
@ -501,6 +501,10 @@ public string getCharacter(SymbolType symbolIn)
|
|||
{
|
||||
return "==";
|
||||
}
|
||||
else if(symbolIn == SymbolType.SMALLER_THAN)
|
||||
{
|
||||
return "<";
|
||||
}
|
||||
else
|
||||
{
|
||||
gprintln("getCharacter: No back-mapping for "~to!(string)(symbolIn), DebugType.ERROR);
|
||||
|
|
|
@ -668,6 +668,93 @@ public final class WhileLoop : Entity, Container
|
|||
}
|
||||
}
|
||||
|
||||
public final class ForLoop : Entity, Container
|
||||
{
|
||||
private Statement preLoopStatement;
|
||||
private Branch branch;
|
||||
private bool hasPostIterate;
|
||||
private static ulong forStmtContainerRollingNameCounter = 0;
|
||||
|
||||
/**
|
||||
* Creates a new For Loop parser node
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* preLoopStatement = The <code>Statement</code> to run before
|
||||
* beginning the first iteration
|
||||
* branch = The <code>Branch</code> that makes up this for
|
||||
* loop
|
||||
*/
|
||||
this(Branch branch, Statement preLoopStatement = null, bool hasPostIterate = false)
|
||||
{
|
||||
forStmtContainerRollingNameCounter++;
|
||||
super("forStmt_"~to!(string)(forStmtContainerRollingNameCounter));
|
||||
|
||||
this.preLoopStatement = preLoopStatement;
|
||||
this.branch = branch;
|
||||
this.hasPostIterate = hasPostIterate;
|
||||
|
||||
weight = 2;
|
||||
}
|
||||
|
||||
public bool hasPostIterateStatement()
|
||||
{
|
||||
return hasPostIterate;
|
||||
}
|
||||
|
||||
public bool hasPreRunStatement()
|
||||
{
|
||||
return !(preLoopStatement is null);
|
||||
}
|
||||
|
||||
public Branch getBranch()
|
||||
{
|
||||
return branch;
|
||||
}
|
||||
|
||||
public Statement getPreRunStatement()
|
||||
{
|
||||
return preLoopStatement;
|
||||
}
|
||||
|
||||
public override void addStatement(Statement statement)
|
||||
{
|
||||
// You should only be adding one branch to a for loop
|
||||
assert(branch is null);
|
||||
branch = cast(Branch)statement;
|
||||
}
|
||||
|
||||
public override void addStatements(Statement[] statements)
|
||||
{
|
||||
// Only one Branch in the given input list
|
||||
assert(statements.length == 1);
|
||||
|
||||
// You should only be adding one branch to a for loop
|
||||
assert(branch is null);
|
||||
|
||||
branch = (cast(Branch[])statements)[0];
|
||||
}
|
||||
|
||||
public override Statement[] getStatements()
|
||||
{
|
||||
// If there is a pre-run statement then prepend it
|
||||
if(hasPreRunStatement())
|
||||
{
|
||||
return cast(Statement[])[preLoopStatement, branch];
|
||||
}
|
||||
// If not, then just the Branch container
|
||||
else
|
||||
{
|
||||
return cast(Statement[])[branch];
|
||||
}
|
||||
}
|
||||
|
||||
public override string toString()
|
||||
{
|
||||
return "ForLoop";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch
|
||||
*
|
||||
|
|
|
@ -978,6 +978,13 @@ public final class TypeChecker
|
|||
{
|
||||
WhileLoop whileLoop = cast(WhileLoop)statement;
|
||||
|
||||
// FIXME: Do-while loops are still being considered in terms of dependency construction
|
||||
if(whileLoop.isDoWhile)
|
||||
{
|
||||
gprintln("Still looking at dependency construction in this thing (do while loops )");
|
||||
assert(false);
|
||||
}
|
||||
|
||||
Branch branch = whileLoop.getBranch();
|
||||
|
||||
/* The condition `Value` instruction should be on the stack */
|
||||
|
@ -1015,6 +1022,52 @@ public final class TypeChecker
|
|||
whileLoopInstruction.context = whileLoop.getContext();
|
||||
addInstrB(whileLoopInstruction);
|
||||
}
|
||||
/**
|
||||
* For loop (ForLoop)
|
||||
*/
|
||||
else if(cast(ForLoop)statement)
|
||||
{
|
||||
ForLoop forLoop = cast(ForLoop)statement;
|
||||
|
||||
/* Pop-off the Value-instruction for the condition */
|
||||
Value valueInstrCondition = cast(Value)popInstr();
|
||||
assert(valueInstrCondition);
|
||||
|
||||
/* Calculate the number of instructions representing the body to tailPopInstr() */
|
||||
ulong bodyTailPopNumber = forLoop.getBranch().getStatements().length;
|
||||
gprintln("bodyTailPopNumber: "~to!(string)(bodyTailPopNumber));
|
||||
|
||||
/* Pop off the body instructions, then reverse final list */
|
||||
Instruction[] bodyInstructions;
|
||||
for(ulong idx = 0; idx < bodyTailPopNumber; idx++)
|
||||
{
|
||||
bodyInstructions ~= tailPopInstr();
|
||||
}
|
||||
bodyInstructions = reverse(bodyInstructions);
|
||||
|
||||
// Create a branch instruction coupling the condition instruction + body instructions (in corrected order)
|
||||
BranchInstruction branchInstr = new BranchInstruction(valueInstrCondition, bodyInstructions);
|
||||
|
||||
|
||||
/* If there is a pre-run instruction */
|
||||
Instruction preRunInstruction;
|
||||
if(forLoop.hasPreRunStatement())
|
||||
{
|
||||
preRunInstruction = tailPopInstr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Code gen
|
||||
*
|
||||
* 1. Create the ForLoopInstruction containing the BranchInstruction and
|
||||
* preRunInstruction
|
||||
* 2. Set the context
|
||||
* 3. Add the instruction
|
||||
*/
|
||||
ForLoopInstruction forLoopInstruction = new ForLoopInstruction(branchInstr, preRunInstruction);
|
||||
forLoopInstruction.context = forLoop.context;
|
||||
addInstrB(forLoopInstruction);
|
||||
}
|
||||
/* Branch */
|
||||
else if(cast(Branch)statement)
|
||||
{
|
||||
|
|
|
@ -1134,6 +1134,362 @@ public class DNodeGenerator
|
|||
return newDNode;
|
||||
}
|
||||
|
||||
// TODO: Work in progress
|
||||
private DNode generalStatement(Container c, Context context, Statement entity)
|
||||
{
|
||||
// /* Pool the container as `node` */
|
||||
// Entity namedContainer = cast(Entity)c;
|
||||
// assert(namedContainer);
|
||||
// DNode node = pool(namedContainer);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Variable paremeters (for functions)
|
||||
*/
|
||||
if(cast(VariableParameter)entity)
|
||||
{
|
||||
VariableParameter varParamDec = cast(VariableParameter)entity;
|
||||
|
||||
// Set context
|
||||
entity.setContext(context);
|
||||
|
||||
// Pool and mark as visited
|
||||
// NOTE: I guess for now use VariableDNode as that is what is used in expressionPass
|
||||
// with the poolT! constrcutor, doing otherwise causes a cast failure and hence
|
||||
// null: /git/tlang/tlang/issues/52#issuecomment-325
|
||||
DNode dnode = poolT!(VariableNode, Variable)(varParamDec);
|
||||
dnode.markVisited();
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Variable declarations
|
||||
*/
|
||||
else if(cast(Variable)entity)
|
||||
{
|
||||
/* Get the Variable and information */
|
||||
Variable variable = cast(Variable)entity;
|
||||
|
||||
/* TODO: 25Oct new */
|
||||
// Context d = new Context( cast(Container)modulle, InitScope.STATIC);
|
||||
entity.setContext(context);
|
||||
/* TODO: Above 25oct new */
|
||||
|
||||
Type variableType = tc.getType(c, variable.getType());
|
||||
assert(variableType); /* TODO: Handle invalid variable type */
|
||||
DNode variableDNode = poolT!(StaticVariableDeclaration, Variable)(variable);
|
||||
writeln("Hello");
|
||||
writeln("VarType: "~to!(string)(variableType));
|
||||
|
||||
/* Basic type */
|
||||
if(cast(Primitive)variableType)
|
||||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
/* Class-type */
|
||||
else if(cast(Clazz)variableType)
|
||||
{
|
||||
writeln("Literally hello");
|
||||
|
||||
/* Get the static class dependency */
|
||||
ClassStaticNode classDependency = classPassStatic(cast(Clazz)variableType);
|
||||
|
||||
/* Make this variable declaration depend on static initalization of the class */
|
||||
variableDNode.needs(classDependency);
|
||||
}
|
||||
/* Struct-type */
|
||||
else if(cast(Struct)variableType)
|
||||
{
|
||||
|
||||
}
|
||||
/* Anything else */
|
||||
else
|
||||
{
|
||||
/* This should never happen */
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/* Set as visited */
|
||||
variableDNode.markVisited();
|
||||
|
||||
/* If there is an assignment attached to this */
|
||||
if(variable.getAssignment())
|
||||
{
|
||||
/* Extract the assignment */
|
||||
VariableAssignment varAssign = variable.getAssignment();
|
||||
|
||||
/* Set the Context of the assignment to the current context */
|
||||
varAssign.setContext(context);
|
||||
|
||||
/* Pool the assignment to get a DNode */
|
||||
DNode expressionNode = expressionPass(varAssign.getExpression(), context);
|
||||
|
||||
/* This assignment depends on an expression being evaluated */
|
||||
VariableAssignmentNode varAssignNode = new VariableAssignmentNode(this, varAssign);
|
||||
varAssignNode.needs(expressionNode);
|
||||
|
||||
/* The variable declaration is dependant on the assignment */
|
||||
variableDNode.needs(varAssignNode);
|
||||
}
|
||||
|
||||
/* The current container is dependent on this variable declaration */
|
||||
// node.needs(variableDNode);
|
||||
return variableDNode;
|
||||
}
|
||||
/**
|
||||
* Variable asignments
|
||||
*/
|
||||
else if(cast(VariableAssignmentStdAlone)entity)
|
||||
{
|
||||
VariableAssignmentStdAlone vAsStdAl = cast(VariableAssignmentStdAlone)entity;
|
||||
vAsStdAl.setContext(context);
|
||||
|
||||
/* TODO: CHeck avriable name even */
|
||||
gprintln("YEAST ENJOYER");
|
||||
|
||||
|
||||
// FIXME: The below assert fails for function definitions trying to refer to global values
|
||||
// as a reoslveBest (up) is needed. We should firstly check if within fails, if so,
|
||||
// resolveBest, if that fails, then it is an error (see #46)
|
||||
assert(tc.getResolver().resolveBest(c, vAsStdAl.getVariableName()));
|
||||
gprintln("YEAST ENJOYER");
|
||||
Variable variable = cast(Variable)tc.getResolver().resolveBest(c, vAsStdAl.getVariableName());
|
||||
assert(variable);
|
||||
|
||||
|
||||
/* Pool the variable */
|
||||
DNode varDecDNode = pool(variable);
|
||||
|
||||
/* TODO: Make sure a DNode exists (implying it's been declared already) */
|
||||
if(varDecDNode.isVisisted())
|
||||
{
|
||||
/* Pool varass stdalone */
|
||||
DNode vStdAlDNode = pool(vAsStdAl);
|
||||
// node.needs(vStdAlDNode);
|
||||
|
||||
DNode expression = expressionPass(vAsStdAl.getExpression(), context);
|
||||
vStdAlDNode.needs(expression);
|
||||
|
||||
return vStdAlDNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
Parser.expect("Cannot reference variable "~vAsStdAl.getVariableName()~" which exists but has not been declared yet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Function definitions
|
||||
*/
|
||||
else if(cast(Function)entity)
|
||||
{
|
||||
// /* Grab the function */
|
||||
Function func = cast(Function)entity;
|
||||
|
||||
/* Add funtion definition */
|
||||
gprintln("Hello");
|
||||
addFunctionDef(tc, func);
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Return statement
|
||||
*/
|
||||
else if(cast(ReturnStmt)entity)
|
||||
{
|
||||
ReturnStmt returnStatement = cast(ReturnStmt)entity;
|
||||
returnStatement.setContext(context);
|
||||
|
||||
DNode returnStatementDNode = pool(returnStatement);
|
||||
|
||||
/* Process the return expression */
|
||||
Expression returnExpression = returnStatement.getReturnExpression();
|
||||
DNode returnExpressionDNode = expressionPass(returnExpression, context);
|
||||
|
||||
/* Make return depend on the return expression */
|
||||
returnStatementDNode.needs(returnExpressionDNode);
|
||||
|
||||
/* Make this container depend on this return statement */
|
||||
// node.needs(returnStatementDNode);
|
||||
return returnStatementDNode;
|
||||
}
|
||||
/**
|
||||
* If statements
|
||||
*/
|
||||
else if(cast(IfStatement)entity)
|
||||
{
|
||||
IfStatement ifStatement = cast(IfStatement)entity;
|
||||
ifStatement.setContext(context);
|
||||
DNode ifStatementDNode = pool(ifStatement);
|
||||
|
||||
/* Add each branch as a dependency */
|
||||
foreach(Branch branch; ifStatement.getBranches())
|
||||
{
|
||||
DNode branchDNode = pool(branch);
|
||||
// Set context of branch (it is parented by the IfStmt)
|
||||
// NOTE: This is dead code as the above is done by Parser and
|
||||
// we need not set context here, only matters at the generalPass
|
||||
// call later (context being passed in) as a starting point
|
||||
branch.setContext(new Context(ifStatement, context.initScope));
|
||||
|
||||
// Extract the potential branch condition
|
||||
Expression branchCondition = branch.getCondition();
|
||||
|
||||
// Check if this branch has a condition
|
||||
if(!(branchCondition is null))
|
||||
{
|
||||
// We use container of IfStmt and nt IfStmt otself as nothing can really be
|
||||
// contained in it that the condition expression would be able to lookup
|
||||
DNode branchConditionDNode = expressionPass(branchCondition, context);
|
||||
branchDNode.needs(branchConditionDNode);
|
||||
}
|
||||
|
||||
gprintln("branch parentOf(): "~to!(string)(branch.parentOf()));
|
||||
assert(branch.parentOf());
|
||||
gprintln("branch generalPass(context="~to!(string)(context.getContainer())~")");
|
||||
|
||||
// When generalPass()'ing a branch's body we don't want to pass in `context`
|
||||
// as that is containing the branch container and hence we skip anything IN the
|
||||
// branch container
|
||||
// NOTE: Check initScope
|
||||
Context branchContext = new Context(branch, context.initScope);
|
||||
DNode branchStatementsDNode = generalPass(branch, branchContext);
|
||||
branchDNode.needs(branchStatementsDNode);
|
||||
|
||||
/* Make the if statement depend on this branch */
|
||||
ifStatementDNode.needs(branchDNode);
|
||||
}
|
||||
|
||||
/* Make this container depend on this if statement */
|
||||
// node.needs(ifStatementDNode);
|
||||
return ifStatementDNode;
|
||||
}
|
||||
/**
|
||||
* While loops
|
||||
*/
|
||||
else if(cast(WhileLoop)entity)
|
||||
{
|
||||
WhileLoop whileLoopStmt = cast(WhileLoop)entity;
|
||||
whileLoopStmt.setContext(context);
|
||||
DNode whileLoopDNode = pool(whileLoopStmt);
|
||||
|
||||
// Extract the branch (body Statement[] + condition)
|
||||
Branch whileBranch = whileLoopStmt.getBranch();
|
||||
DNode branchDNode = pool(whileBranch);
|
||||
gprintln("Branch: "~to!(string)(whileBranch));
|
||||
|
||||
// If this is a while-loop
|
||||
if(!whileLoopStmt.isDoWhile)
|
||||
{
|
||||
// Extract the condition
|
||||
Expression branchCondition = whileBranch.getCondition();
|
||||
|
||||
// Pass the expression
|
||||
DNode branchConditionDNode = expressionPass(branchCondition, context);
|
||||
|
||||
// Make the branch dependent on this expression's evaluation
|
||||
branchDNode.needs(branchConditionDNode);
|
||||
|
||||
|
||||
// Now pass over the statements in the branch's body
|
||||
Context branchContext = new Context(whileBranch, InitScope.STATIC);
|
||||
DNode branchBodyDNode = generalPass(whileBranch, branchContext);
|
||||
|
||||
// Finally make the branchDNode depend on the body dnode (above)
|
||||
branchDNode.needs(branchBodyDNode);
|
||||
}
|
||||
// If this is a do-while loop
|
||||
// TODO: I don't think we really need to reverse this?
|
||||
// Logically we should, but the typechecker will add this things in the correct order anyways?
|
||||
// We need to look into this!
|
||||
// Our nodes at the back will always be placed at the back, and the expression will end ip upfront
|
||||
// i think it is a problem oif maybe other expressions are left on the stack but is that ever a problem
|
||||
//now with the statement <-> instruction mapping (like will that ever even occur?)
|
||||
else
|
||||
{
|
||||
// Pass over the statements in the branch's body
|
||||
Context branchContext = new Context(whileBranch, InitScope.STATIC);
|
||||
DNode branchBodyDNode = generalPass(whileBranch, branchContext);
|
||||
|
||||
// Make the branchDNode depend on the body dnode (above)
|
||||
branchDNode.needs(branchBodyDNode);
|
||||
|
||||
|
||||
// Extract the condition
|
||||
Expression branchCondition = whileBranch.getCondition();
|
||||
|
||||
// Pass the expression
|
||||
DNode branchConditionDNode = expressionPass(branchCondition, context);
|
||||
|
||||
// Make the branch dependent on this expression's evaluation
|
||||
branchDNode.needs(branchConditionDNode);
|
||||
}
|
||||
|
||||
/* Make the while-loop/do-while loop depend on the branchDNode */
|
||||
whileLoopDNode.needs(branchDNode);
|
||||
|
||||
/* Make the node of this generalPass we are in depend on the whileLoop's DNode */
|
||||
// node.needs(whileLoopDNode);
|
||||
return whileLoopDNode;
|
||||
}
|
||||
/**
|
||||
* For loops
|
||||
*/
|
||||
else if(cast(ForLoop)entity)
|
||||
{
|
||||
ForLoop forLoop = cast(ForLoop)entity;
|
||||
forLoop.setContext(context);
|
||||
DNode forLoopDNode = pool(forLoop);
|
||||
|
||||
|
||||
// Check for a pre-run statement
|
||||
if(forLoop.hasPreRunStatement())
|
||||
{
|
||||
Statement preRunStatement = forLoop.getPreRunStatement();
|
||||
DNode preRunStatementDNode = generalStatement(c, context, preRunStatement);
|
||||
forLoopDNode.needs(preRunStatementDNode);
|
||||
}
|
||||
|
||||
// Get the branch
|
||||
Branch forLoopBranch = forLoop.getBranch();
|
||||
Expression forLoopCondition = forLoopBranch.getCondition();
|
||||
|
||||
// TODO: The below context won't work until we make the `preLoopStatement` (and maybe `postIterationStatement`??)
|
||||
// a part of the body of the for-loop (see issue #78)
|
||||
// Pass over the condition expression
|
||||
DNode forLoopConditionDNode = expressionPass(forLoopCondition, new Context(forLoop, InitScope.STATIC));
|
||||
forLoopDNode.needs(forLoopConditionDNode);
|
||||
|
||||
|
||||
// TODO: What we need here now is effectively the equivalent of the Parser's `parseStatement()`
|
||||
// (i.e. for a single statement), so this body of code should be `generalStatement(Container, Context, Statement)`
|
||||
// and should be called within this loop
|
||||
|
||||
// We want to generalPass the Branch Container and the context if within the Branch container
|
||||
DNode branchDNode = generalPass(forLoopBranch, new Context(forLoopBranch, InitScope.STATIC));
|
||||
forLoopDNode.needs(branchDNode);
|
||||
|
||||
return forLoopDNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a general pass over the Statement(s) in the given container
|
||||
* and with the given Context
|
||||
*
|
||||
* Params:
|
||||
* c = the Container on which to pass through all of its elements
|
||||
* context = the Context to use for the pass
|
||||
*
|
||||
* Returns: a DNode for the Container c
|
||||
*/
|
||||
private DNode generalPass(Container c, Context context)
|
||||
{
|
||||
Entity namedContainer = cast(Entity)c;
|
||||
|
@ -1189,267 +1545,16 @@ public class DNodeGenerator
|
|||
// continue;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Variable paremeters (for functions)
|
||||
*/
|
||||
if(cast(VariableParameter)entity)
|
||||
DNode statementDNode = generalStatement(c, context, entity);
|
||||
if(statementDNode is null)
|
||||
{
|
||||
VariableParameter varParamDec = cast(VariableParameter)entity;
|
||||
|
||||
// Set context
|
||||
entity.setContext(context);
|
||||
|
||||
// Pool and mark as visited
|
||||
// NOTE: I guess for now use VariableDNode as that is what is used in expressionPass
|
||||
// with the poolT! constrcutor, doing otherwise causes a cast failure and hence
|
||||
// null: /git/tlang/tlang/issues/52#issuecomment-325
|
||||
DNode dnode = poolT!(VariableNode, Variable)(varParamDec);
|
||||
dnode.markVisited();
|
||||
gprintln("Not adding dependency '"~to!(string)(statementDNode)~"' as it is null");
|
||||
}
|
||||
/**
|
||||
* Variable declarations
|
||||
*/
|
||||
else if(cast(Variable)entity)
|
||||
else
|
||||
{
|
||||
/* Get the Variable and information */
|
||||
Variable variable = cast(Variable)entity;
|
||||
|
||||
/* TODO: 25Oct new */
|
||||
// Context d = new Context( cast(Container)modulle, InitScope.STATIC);
|
||||
entity.setContext(context);
|
||||
/* TODO: Above 25oct new */
|
||||
|
||||
Type variableType = tc.getType(c, variable.getType());
|
||||
assert(variableType); /* TODO: Handle invalid variable type */
|
||||
DNode variableDNode = poolT!(StaticVariableDeclaration, Variable)(variable);
|
||||
writeln("Hello");
|
||||
writeln("VarType: "~to!(string)(variableType));
|
||||
|
||||
/* Basic type */
|
||||
if(cast(Primitive)variableType)
|
||||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
/* Class-type */
|
||||
else if(cast(Clazz)variableType)
|
||||
{
|
||||
writeln("Literally hello");
|
||||
|
||||
/* Get the static class dependency */
|
||||
ClassStaticNode classDependency = classPassStatic(cast(Clazz)variableType);
|
||||
|
||||
/* Make this variable declaration depend on static initalization of the class */
|
||||
variableDNode.needs(classDependency);
|
||||
}
|
||||
/* Struct-type */
|
||||
else if(cast(Struct)variableType)
|
||||
{
|
||||
|
||||
}
|
||||
/* Anything else */
|
||||
else
|
||||
{
|
||||
/* This should never happen */
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/* Set as visited */
|
||||
variableDNode.markVisited();
|
||||
|
||||
/* If there is an assignment attached to this */
|
||||
if(variable.getAssignment())
|
||||
{
|
||||
/* Extract the assignment */
|
||||
VariableAssignment varAssign = variable.getAssignment();
|
||||
|
||||
/* Set the Context of the assignment to the current context */
|
||||
varAssign.setContext(context);
|
||||
|
||||
/* Pool the assignment to get a DNode */
|
||||
DNode expressionNode = expressionPass(varAssign.getExpression(), context);
|
||||
|
||||
/* This assignment depends on an expression being evaluated */
|
||||
VariableAssignmentNode varAssignNode = new VariableAssignmentNode(this, varAssign);
|
||||
varAssignNode.needs(expressionNode);
|
||||
|
||||
/* The variable declaration is dependant on the assignment */
|
||||
variableDNode.needs(varAssignNode);
|
||||
}
|
||||
|
||||
/* The current container is dependent on this variable declaration */
|
||||
node.needs(variableDNode);
|
||||
}
|
||||
/**
|
||||
* Variable asignments
|
||||
*/
|
||||
else if(cast(VariableAssignmentStdAlone)entity)
|
||||
{
|
||||
VariableAssignmentStdAlone vAsStdAl = cast(VariableAssignmentStdAlone)entity;
|
||||
vAsStdAl.setContext(context);
|
||||
|
||||
/* TODO: CHeck avriable name even */
|
||||
gprintln("YEAST ENJOYER");
|
||||
|
||||
|
||||
// FIXME: The below assert fails for function definitions trying to refer to global values
|
||||
// as a reoslveBest (up) is needed. We should firstly check if within fails, if so,
|
||||
// resolveBest, if that fails, then it is an error (see #46)
|
||||
assert(tc.getResolver().resolveBest(c, vAsStdAl.getVariableName()));
|
||||
gprintln("YEAST ENJOYER");
|
||||
Variable variable = cast(Variable)tc.getResolver().resolveBest(c, vAsStdAl.getVariableName());
|
||||
assert(variable);
|
||||
|
||||
|
||||
/* Pool the variable */
|
||||
DNode varDecDNode = pool(variable);
|
||||
|
||||
/* TODO: Make sure a DNode exists (implying it's been declared already) */
|
||||
if(varDecDNode.isVisisted())
|
||||
{
|
||||
/* Pool varass stdalone */
|
||||
DNode vStdAlDNode = pool(vAsStdAl);
|
||||
node.needs(vStdAlDNode);
|
||||
|
||||
DNode expression = expressionPass(vAsStdAl.getExpression(), context);
|
||||
vStdAlDNode.needs(expression);
|
||||
}
|
||||
else
|
||||
{
|
||||
Parser.expect("Cannot reference variable "~vAsStdAl.getVariableName()~" which exists but has not been declared yet");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Function definitions
|
||||
*/
|
||||
else if(cast(Function)entity)
|
||||
{
|
||||
// /* Grab the function */
|
||||
Function func = cast(Function)entity;
|
||||
|
||||
/* Add funtion definition */
|
||||
gprintln("Hello");
|
||||
addFunctionDef(tc, func);
|
||||
}
|
||||
/**
|
||||
* Return statement
|
||||
*/
|
||||
else if(cast(ReturnStmt)entity)
|
||||
{
|
||||
ReturnStmt returnStatement = cast(ReturnStmt)entity;
|
||||
returnStatement.setContext(context);
|
||||
|
||||
DNode returnStatementDNode = pool(returnStatement);
|
||||
|
||||
/* Process the return expression */
|
||||
Expression returnExpression = returnStatement.getReturnExpression();
|
||||
DNode returnExpressionDNode = expressionPass(returnExpression, context);
|
||||
|
||||
/* Make return depend on the return expression */
|
||||
returnStatementDNode.needs(returnExpressionDNode);
|
||||
|
||||
/* Make this container depend on this return statement */
|
||||
node.needs(returnStatementDNode);
|
||||
}
|
||||
/**
|
||||
* If statements
|
||||
*/
|
||||
else if(cast(IfStatement)entity)
|
||||
{
|
||||
IfStatement ifStatement = cast(IfStatement)entity;
|
||||
ifStatement.setContext(context);
|
||||
DNode ifStatementDNode = pool(ifStatement);
|
||||
|
||||
/* Add each branch as a dependency */
|
||||
foreach(Branch branch; ifStatement.getBranches())
|
||||
{
|
||||
DNode branchDNode = pool(branch);
|
||||
// Set context of branch (it is parented by the IfStmt)
|
||||
// NOTE: This is dead code as the above is done by Parser and
|
||||
// we need not set context here, only matters at the generalPass
|
||||
// call later (context being passed in) as a starting point
|
||||
branch.setContext(new Context(ifStatement, context.initScope));
|
||||
|
||||
// Extract the potential branch condition
|
||||
Expression branchCondition = branch.getCondition();
|
||||
|
||||
// Check if this branch has a condition
|
||||
if(!(branchCondition is null))
|
||||
{
|
||||
// We use container of IfStmt and nt IfStmt otself as nothing can really be
|
||||
// contained in it that the condition expression would be able to lookup
|
||||
DNode branchConditionDNode = expressionPass(branchCondition, context);
|
||||
branchDNode.needs(branchConditionDNode);
|
||||
}
|
||||
|
||||
gprintln("branch parentOf(): "~to!(string)(branch.parentOf()));
|
||||
assert(branch.parentOf());
|
||||
gprintln("branch generalPass(context="~to!(string)(context.getContainer())~")");
|
||||
|
||||
// When generalPass()'ing a branch's body we don't want to pass in `context`
|
||||
// as that is containing the branch container and hence we skip anything IN the
|
||||
// branch container
|
||||
// NOTE: Check initScope
|
||||
Context branchContext = new Context(branch, context.initScope);
|
||||
DNode branchStatementsDNode = generalPass(branch, branchContext);
|
||||
branchDNode.needs(branchStatementsDNode);
|
||||
|
||||
/* Make the if statement depend on this branch */
|
||||
ifStatementDNode.needs(branchDNode);
|
||||
}
|
||||
|
||||
/* Make this container depend on this if statement */
|
||||
node.needs(ifStatementDNode);
|
||||
}
|
||||
/**
|
||||
* While loops
|
||||
*/
|
||||
else if(cast(WhileLoop)entity)
|
||||
{
|
||||
WhileLoop whileLoopStmt = cast(WhileLoop)entity;
|
||||
whileLoopStmt.setContext(context);
|
||||
DNode whileLoopDNode = pool(whileLoopStmt);
|
||||
|
||||
// Extract the branch (body Statement[] + condition)
|
||||
Branch whileBranch = whileLoopStmt.getBranch();
|
||||
DNode branchDNode = pool(whileBranch);
|
||||
gprintln("Branch: "~to!(string)(whileBranch));
|
||||
|
||||
// If this is a while-loop
|
||||
if(!whileLoopStmt.isDoWhile)
|
||||
{
|
||||
gprintln("Logan", DebugType.ERROR);
|
||||
|
||||
// Extract the condition
|
||||
Expression branchCondition = whileBranch.getCondition();
|
||||
|
||||
// Pass the expression
|
||||
DNode branchConditionDNode = expressionPass(branchCondition, context);
|
||||
|
||||
// Make the branch dependent on this expression's evaluation
|
||||
branchDNode.needs(branchConditionDNode);
|
||||
|
||||
|
||||
// Now pass over the statements in the branch's body
|
||||
Context branchContext = new Context(whileBranch, InitScope.STATIC);
|
||||
DNode branchBodyDNode = generalPass(whileBranch, branchContext);
|
||||
|
||||
// Finally make the branchDNode depend on the body dnode (above)
|
||||
branchDNode.needs(branchBodyDNode);
|
||||
}
|
||||
// If this is a do-while loop
|
||||
else
|
||||
{
|
||||
gprintln("Implement do-while loops please", DebugType.ERROR);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/* Make the while-loop/do-while loop depend on the branchDNode */
|
||||
whileLoopDNode.needs(branchDNode);
|
||||
|
||||
/* Make the node of this generalPass we are in depend on the whileLoop's DNode */
|
||||
node.needs(whileLoopDNode);
|
||||
node.needs(statementDNode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return node;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
module simple_do_while;
|
||||
|
||||
int function(int i)
|
||||
{
|
||||
int test = 2;
|
||||
do
|
||||
{
|
||||
i = i - 1;
|
||||
test = test + i;
|
||||
}
|
||||
while(i);
|
||||
|
||||
return test;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
module simple_for_loops;
|
||||
|
||||
int function(int i)
|
||||
{
|
||||
int test = 0;
|
||||
|
||||
for(int idx = 0; idx < i; idx=idx+1)
|
||||
{
|
||||
test = test + 1;
|
||||
}
|
||||
|
||||
return test;
|
||||
}
|
Loading…
Reference in New Issue