Add switch statement, case and default handling, wire in the error handler for a sample program

This commit is contained in:
Curle 2023-04-24 03:03:31 +01:00
parent 70ae06af44
commit 4e47cdcaf6
10 changed files with 270 additions and 44 deletions

View File

@ -21,4 +21,4 @@ add_executable(Erythro
src/Symbols.c
src/Types.c
src/Importer.c
src/assemble/JVMAssembler.c)
src/assemble/JVMAssembler.c src/Errors.c)

View File

@ -373,8 +373,7 @@ void DisplayUsage(char* ProgName);
void Tokenise();
void VerifyToken(int Type, char* TokenExpected);
void RejectToken(struct Token* Token);
bool OptionallyConsume(int Type);
static int ReadIdentifier(int Char, char* Buffer, int Limit);
@ -585,7 +584,7 @@ struct AssemblerVtable {
int (*AsIf)(struct ASTNode*, int, int);
int (*AsWhile)(struct ASTNode*);
int (*AsSwitch)(struct ASTNode*);
int (*AsSwitchTable)(int, int, int, int*, int*, int);
void (*AsSwitchTable)(int, int, int, int*, int*, int);
int (*NewLabel)();
void (*AsJmp)(int);
void (*AsLabel)(int);

View File

@ -16,12 +16,12 @@ static int GenerateSrg() {
* Walk the Node tree, and dump the AST tree to stdout.
*/
void DumpTree(struct ASTNode* Node, int level) {
int Lfalse, Lstart, Lend;
int Lstart, Lend;
// Handle weirdo loops and conditions first.
switch (Node->Operation) {
case OP_IF:
Lfalse = GenerateSrg();
GenerateSrg();
for (int i = 0; i < level; i++)
fprintf(stdout, " ");
fprintf(stdout, "IF");
@ -43,7 +43,15 @@ void DumpTree(struct ASTNode* Node, int level) {
for (int i = 0; i < level; i++)
fprintf(stdout, " ");
fprintf(stdout, "LOOP starts at %d\n", Lstart);
Lend = GenerateSrg();
GenerateSrg();
DumpTree(Node->Left, level + 2);
DumpTree(Node->Right, level + 2);
return;
case OP_SWITCH:
for (int i = 0; i < level; i++)
fprintf(stdout, " ");
fprintf(stdout, "SWITCH\n");
DumpTree(Node->Left, level + 2);
DumpTree(Node->Right, level + 2);
return;
@ -187,6 +195,17 @@ void DumpTree(struct ASTNode* Node, int level) {
fprintf(stdout, "OP_BOOLCONV\n");
return;
case OP_DEFAULT:
fprintf(stdout, "OP_DEFAULT\n");
DumpTree(Node->Left, level + 2);
return;
case OP_CASE:
fprintf(stdout, "OP_CASE %d\n", Node->IntValue);
DumpTree(Node->Left, level + 2);
DumpTree(Node->Right, level);
return;
default:
DieDecimal("Unknown Dump Operator", Node->Operation);
}

View File

@ -72,11 +72,15 @@ void ErrorReport(char* message, ...) {
printErrorLine(file, line - 1);
printHelpLine(line, strbuf);
printLine(file, line);
if (!feof(file))
printLine(file, line + 1);
} else {
printErrorLine(file, line);
printHelpLine(line, strbuf);
if (!feof(file))
printLine(file, line + 1);
if (!feof(file))
printLine(file, line + 2);
}

View File

@ -103,21 +103,13 @@ void VerifyToken(int Type, char* TokenExpected) {
}
}
static struct Token* RejectedToken = NULL;
/*
* Rejected Tokens and the Overread Stream are identical concepts.
* This was implemented first, but it is no longer used.
* TODO: Refactor this function out.
*/
void RejectToken(struct Token* Token) {
if (RejectedToken != NULL)
Die("Cannot reject two tokens in a row!");
RejectedToken = Token;
bool OptionallyConsume(int Type) {
if (CurrentFile->CurrentSymbol.type == Type) {
Tokenise();
return true;
}
return false;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * L I T E R A L S A N D I D E N T I F I E R S * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@ -127,7 +119,7 @@ void RejectToken(struct Token* Token) {
* Currently only supports the decimal numbers, despite the
* FindDigitFromPos function allowing conversion.
*
* The functon loops over the characters, multiplying by 10 and adding
* The function loops over the characters, multiplying by 10 and adding
* the new value on top, until a non-numeric character is found.
* At that point, it returns the non-numeric character to the Overread Stream
* and returns the calculated number.
@ -409,12 +401,6 @@ void Tokenise() {
int Char, TokenType;
struct Token* Token = &CurrentFile->CurrentSymbol;
if (RejectedToken != NULL) {
Token = RejectedToken;
RejectedToken = NULL;
return;
}
Char = FindChar();
switch (Char) {
@ -575,7 +561,7 @@ void Tokenise() {
if (Char == ':') {
Token->type = KW_FUNC;
} else {
ReturnCharToStream(Char);
Token->type = LI_COLON;
}
break;

View File

@ -47,6 +47,7 @@ char* TokenNames[] = {
"Integer literal",
"String literal",
"Statement End",
"Colon",
"Compound Block Start",
"Compound Block End",
@ -72,6 +73,10 @@ char* TokenNames[] = {
"Break keyword",
"Continue keyword",
"Switch Keyword",
"Default Keyword",
"Case Keyword",
"Print Keyword",
"If keyword",
"Else keyword",

View File

@ -286,18 +286,16 @@ struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) {
// int LeftType, RightType;
int NodeType, OpType;
printf("Left node branch\r\n");
fflush(stdout);
LeftNode = PrefixStatement();
NodeType = CurrentFile->CurrentSymbol.type;
if (NodeType == LI_SEMIC || NodeType == LI_RPARE || NodeType == LI_RBRAS || NodeType == LI_COM) {
if (NodeType == LI_SEMIC || NodeType == LI_COLON || NodeType == LI_RPARE || NodeType == LI_RBRAS || NodeType == LI_COM || NodeType == LI_INT) {
LeftNode->RVal = 1;
return LeftNode;
}
printf("Operator expected\r\n");
while ((OperatorPrecedence(NodeType) > PreviousTokenPrecedence) ||
(IsRightExpr(OpType) && OperatorPrecedence(OpType) == PreviousTokenPrecedence)) {
Tokenise();
@ -306,7 +304,6 @@ struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) {
RightNode = ParsePrecedenceASTNode(Precedence[NodeType]);
/**
* While parsing this node, we may need to widen some types.
* This requires a few functions and checks.
@ -472,6 +469,7 @@ struct ASTNode* GetExpressionList() {
* * If Statement
* * While Statement
* * For Statement
* * Switch Statement
* * Return Statement
* * Numeric literals and variables
* * Binary Expressions
@ -497,6 +495,9 @@ struct ASTNode* ParseStatement(void) {
VerifyToken(LI_SEMIC, ";"); // TODO: single line assignment?
return NULL;
case KW_SWITCH:
return SwitchStatement();
case KW_IF:
return IfStatement();

View File

@ -262,7 +262,7 @@ struct ASTNode* ParseFunction(int Type) {
if (OldFunction->Storage != ST_FUNC)
OldFunction = NULL;
if (OldFunction == NULL) {
BreakLabel = NewLabel();
BreakLabel = Assembler->vtable->NewLabel();
NewFunction = AddSymbol(CurrentIdentifier, Type, ST_FUNC, SC_GLOBAL, BreakLabel, 0, NULL);
}
@ -324,7 +324,7 @@ struct ASTNode* ReturnStatement() {
VerifyToken(KW_RETURN, "return");
VerifyToken(LI_LPARE, "("); // TODO: Make optional! Reject?
bool bracketed = OptionallyConsume(LI_LPARE);
Tree = ParsePrecedenceASTNode(0);
@ -337,7 +337,7 @@ struct ASTNode* ReturnStatement() {
printf("\t\tReturning from function %s\n", CurrentFile->FunctionEntry->Name);
VerifyToken(LI_RPARE, ")"); // TODO: OPTIONALISE!
if (bracketed) VerifyToken(LI_RPARE, ")");
return Tree;
}
@ -543,6 +543,107 @@ struct ASTNode* PrintStatement(void) {
}
struct ASTNode* SwitchStatement() {
struct ASTNode* left, *root, *c, *casetree=NULL, *casetail;
int looping=1, cases=0;
int defaultpresent=0;
int ASTOp, casevalue;
printf("\tParsing switch statement\n");
// Skip switch(
Tokenise();
VerifyToken(LI_LPARE, "(");
printf("\tSwitch: Reading switch expression\n");
// Fetch switch expression
left = ParsePrecedenceASTNode(0);
// Consume ) {
VerifyToken(LI_RPARE, ")");
VerifyToken(LI_LBRAC, "{");
// Verify the switch expression (must be integer-compatible)
if (!TypeIsInt(!left->ExprType))
Die("Switch expression is not of integer type");
Safe();
// Create the root Switch node
root = ConstructASTBranch(OP_SWITCH, 0, left, NULL, 0);
// Iterate down the switch node, generating cases
while (looping) {
switch (CurrentFile->CurrentSymbol.type) {
case LI_RBRAC:
if (cases == 0)
Die("No cases in switch statement");
looping = 0;
break;
case KW_CASE:
if (defaultpresent)
Die("Case present after default in switch.");
ASTOp = OP_CASE;
Safe();
Tokenise();
// Parse case value
left = ParsePrecedenceASTNode(0);
if (left->Operation != TERM_INTLITERAL)
Die("Expecting integer literal for case value");
casevalue = left->IntValue;
printf("\t\tSwitch case %d found\n", casevalue);
// Make sure nothing resolves to the same case value
for (c = casetree; c != NULL; c = c->Right)
if (casevalue == c->IntValue)
Die("Duplicate case ID in switch statement");
// Fallthrough so that we get the case tree logic deduplicated
case KW_DEFAULT:
if (defaultpresent)
Die("Duplicate default entries in switch");
// Duplicate check because CASE falls through into this block
if (CurrentFile->CurrentSymbol.type == KW_DEFAULT) {
ASTOp = OP_DEFAULT;
defaultpresent = true;
Tokenise();
printf("\t\tSwitch default case found\n");
}
VerifyToken(LI_COLON, ":");
Safe();
left = ParseStatement();
OptionallyConsume(LI_SEMIC);
cases++;
Safe();
// Append this new case to the tree
if (casetree == NULL) {
casetree = casetail = ConstructASTBranch(ASTOp, 0, left, NULL, casevalue);
} else {
casetail->Right = ConstructASTBranch(ASTOp, 0, left, NULL, casevalue);
casetail = casetail->Right;
}
break;
default:
ErrorReport("Unexpected token in switch statement: %s\n", TokenNames[CurrentFile->CurrentSymbol.type]);
exit(1);
}
}
root->IntValue = cases;
root->Right = casetree;
// Consume the right brace immediately
VerifyToken(LI_RBRAC, "}");
return root;
}
/**
* Handles the surrounding logic for break statements
*
@ -571,7 +672,6 @@ struct ASTNode* BreakStatement() {
Die("Unable to break without a loop");
Tokenise();
Tokenise();
return ConstructASTLeaf(OP_BREAK, 0, NULL, 0);
}

View File

@ -296,6 +296,68 @@ static int AsWhile(struct ASTNode* Node) {
}
static void AsSwitchTable(int reg, int cases, int toplabel, int* caselabel, int* caseval, int defaultlabel) {
int i, label;
label = NewLabel();
AsLabel(label);
// Add a default case even if not present.
if (cases == 0) {
caseval[0] = 0;
caselabel[0] = defaultlabel;
cases = 1;
}
fprintf(OutputFile, "\t.quad\t%d\n", cases);
for (i = 0; i < cases; i++)
fprintf(OutputFile, "\t.quad\t%d, L%d\n", caseval[i], caselabel[i]);
fprintf(OutputFile, "\t.quad\tL%d\n", defaultlabel);
AsLabel(toplabel);
fprintf(OutputFile, "\tmovq\t%s, %%rax\n", Registers[reg]);
fprintf(OutputFile, "\tleaq\tL%d(%%rip), %%rdx\n", label);
fprintf(OutputFile, "\tjmp\tswitch\n");
}
static int AsSwitch(struct ASTNode* root) {
int* caseval, *caselabel;
int Ljump, Lend;
int i, reg, defaultlabel=0, cases=0;
struct ASTNode* c;
caseval = (int*) malloc((root->IntValue + 1) * sizeof(int));
caselabel = (int*) malloc((root->IntValue + 1) * sizeof(int));
Ljump = NewLabel();
Lend = NewLabel();
defaultlabel = Lend;
reg = AssembleTree(root->Left, -1, -1, -1, 0);
AsJmp(Ljump);
DeallocateAllRegisters();
for (i = 0, c = root->Right; c != NULL; i++, c = c->Right) {
caselabel[i] = NewLabel();
caseval[i] = c->IntValue;
AsLabel(caselabel[i]);
if (c->Operation == OP_DEFAULT)
defaultlabel = caselabel[i];
else
cases++;
AssembleTree(c->Left, -1, -1, Lend, 0);
DeallocateAllRegisters();
}
AsJmp(Lend);
AsSwitchTable(reg, cases, Ljump, caselabel, caseval, defaultlabel);
AsLabel(Lend);
return -1;
}
// Load a value into a register.
static int AsLoad(int Value) {
int Register = RetrieveRegister();
@ -885,9 +947,27 @@ static int AsBooleanConvert(int Register, int Operation, int Label) {
static void AssemblerPreamble() {
DeallocateAllRegisters();
fputs(
"\t.text\n", /*
".LC0:\n"
"\t.string\t\"%d\\n\"\n", */
"\t.text\n"
"switch:\n"
"\t\tpushq %rsi\n"
"\t\tmovq %rdx, %rsi\n"
"\t\tmovq %rax, %rbx\n"
"\t\tcld\n"
"\t\tlodsq\n"
"\t\tmovq %rax, %rcx\n"
"next:\n"
"\t\tlodsq\n"
"\t\tmovq %rax, %rdx\n"
"\t\tlodsq\n"
"\t\tcmpq %rdx, %rbx\n"
"\t\tjnz no\n"
"\t\tpopq %rsi\n"
"\t\tjmp *%rax\n"
"no:\n"
"\t\tloop next\n"
"\t\tlodsq\n"
"\t\tpopq %rsi\n"
"\t\tjmp *%rax\n",
OutputFile);
}
@ -991,13 +1071,16 @@ static int AssembleTree(struct ASTNode* Node, int Register, int LoopBeginLabel,
return -1;
case OP_CALL:
return (AsCallWrapper(Node));
return AsCallWrapper(Node);
case OP_FUNC:
AsFunctionPreamble(Node->Symbol);
AssembleTree(Node->Left, -1, LoopBeginLabel, LoopEndLabel, Node->Operation);
AsFunctionEpilogue(Node->Symbol);
return -1;
case OP_SWITCH:
return AsSwitch(Node);
}
@ -1219,6 +1302,8 @@ static const struct AssemblerVtable Win32ASMVtable = {
.AsStrGlobalVar = AsStrGlobalVar,
.AsStrLocalVar = AsStrLocalVar,
.AsSub = AsSub,
.AsSwitch = AsSwitch,
.AsSwitchTable = AsSwitchTable,
.AsWhile = AsWhile,
.DeallocateAllRegisters = DeallocateAllRegisters,
.RetrieveRegister = RetrieveRegister,

27
tests/switch.er Normal file
View File

@ -0,0 +1,27 @@
import "tests/print.em"
int :: main() {
int x;
int y;
y = 0;
for (x = 0; x < 5; x++) {
switch(x) {
case 1: {
y = 5;
break; }
case 2: {
y = 7;
break; }
case 3:
y = 9;
default:
y = 100;
}
printf("%d\n", y);
}
return 0;
}