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/Symbols.c
src/Types.c src/Types.c
src/Importer.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 Tokenise();
void VerifyToken(int Type, char* TokenExpected); void VerifyToken(int Type, char* TokenExpected);
bool OptionallyConsume(int Type);
void RejectToken(struct Token* Token);
static int ReadIdentifier(int Char, char* Buffer, int Limit); static int ReadIdentifier(int Char, char* Buffer, int Limit);
@ -585,7 +584,7 @@ struct AssemblerVtable {
int (*AsIf)(struct ASTNode*, int, int); int (*AsIf)(struct ASTNode*, int, int);
int (*AsWhile)(struct ASTNode*); int (*AsWhile)(struct ASTNode*);
int (*AsSwitch)(struct ASTNode*); int (*AsSwitch)(struct ASTNode*);
int (*AsSwitchTable)(int, int, int, int*, int*, int); void (*AsSwitchTable)(int, int, int, int*, int*, int);
int (*NewLabel)(); int (*NewLabel)();
void (*AsJmp)(int); void (*AsJmp)(int);
void (*AsLabel)(int); void (*AsLabel)(int);

View File

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

View File

@ -72,12 +72,16 @@ void ErrorReport(char* message, ...) {
printErrorLine(file, line - 1); printErrorLine(file, line - 1);
printHelpLine(line, strbuf); printHelpLine(line, strbuf);
printLine(file, line); printLine(file, line);
printLine(file, line + 1); if (!feof(file))
printLine(file, line + 1);
} else { } else {
printErrorLine(file, line); printErrorLine(file, line);
printHelpLine(line, strbuf); printHelpLine(line, strbuf);
printLine(file, line + 1);
printLine(file, line + 2); 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; bool OptionallyConsume(int Type) {
if (CurrentFile->CurrentSymbol.type == Type) {
/* Tokenise();
* Rejected Tokens and the Overread Stream are identical concepts. return true;
* This was implemented first, but it is no longer used. }
* TODO: Refactor this function out. return false;
*/
void RejectToken(struct Token* Token) {
if (RejectedToken != NULL)
Die("Cannot reject two tokens in a row!");
RejectedToken = Token;
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * L I T E R A L S A N D I D E N T I F I E R S * * * * * * * * 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 * Currently only supports the decimal numbers, despite the
* FindDigitFromPos function allowing conversion. * 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. * 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 * At that point, it returns the non-numeric character to the Overread Stream
* and returns the calculated number. * and returns the calculated number.
@ -409,12 +401,6 @@ void Tokenise() {
int Char, TokenType; int Char, TokenType;
struct Token* Token = &CurrentFile->CurrentSymbol; struct Token* Token = &CurrentFile->CurrentSymbol;
if (RejectedToken != NULL) {
Token = RejectedToken;
RejectedToken = NULL;
return;
}
Char = FindChar(); Char = FindChar();
switch (Char) { switch (Char) {
@ -575,7 +561,7 @@ void Tokenise() {
if (Char == ':') { if (Char == ':') {
Token->type = KW_FUNC; Token->type = KW_FUNC;
} else { } else {
ReturnCharToStream(Char); Token->type = LI_COLON;
} }
break; break;

View File

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

View File

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

View File

@ -262,7 +262,7 @@ struct ASTNode* ParseFunction(int Type) {
if (OldFunction->Storage != ST_FUNC) if (OldFunction->Storage != ST_FUNC)
OldFunction = NULL; OldFunction = NULL;
if (OldFunction == NULL) { if (OldFunction == NULL) {
BreakLabel = NewLabel(); BreakLabel = Assembler->vtable->NewLabel();
NewFunction = AddSymbol(CurrentIdentifier, Type, ST_FUNC, SC_GLOBAL, BreakLabel, 0, NULL); NewFunction = AddSymbol(CurrentIdentifier, Type, ST_FUNC, SC_GLOBAL, BreakLabel, 0, NULL);
} }
@ -324,7 +324,7 @@ struct ASTNode* ReturnStatement() {
VerifyToken(KW_RETURN, "return"); VerifyToken(KW_RETURN, "return");
VerifyToken(LI_LPARE, "("); // TODO: Make optional! Reject? bool bracketed = OptionallyConsume(LI_LPARE);
Tree = ParsePrecedenceASTNode(0); Tree = ParsePrecedenceASTNode(0);
@ -337,7 +337,7 @@ struct ASTNode* ReturnStatement() {
printf("\t\tReturning from function %s\n", CurrentFile->FunctionEntry->Name); printf("\t\tReturning from function %s\n", CurrentFile->FunctionEntry->Name);
VerifyToken(LI_RPARE, ")"); // TODO: OPTIONALISE! if (bracketed) VerifyToken(LI_RPARE, ")");
return Tree; 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 * Handles the surrounding logic for break statements
* *
@ -571,7 +672,6 @@ struct ASTNode* BreakStatement() {
Die("Unable to break without a loop"); Die("Unable to break without a loop");
Tokenise(); Tokenise();
Tokenise();
return ConstructASTLeaf(OP_BREAK, 0, NULL, 0); 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. // Load a value into a register.
static int AsLoad(int Value) { static int AsLoad(int Value) {
int Register = RetrieveRegister(); int Register = RetrieveRegister();
@ -885,9 +947,27 @@ static int AsBooleanConvert(int Register, int Operation, int Label) {
static void AssemblerPreamble() { static void AssemblerPreamble() {
DeallocateAllRegisters(); DeallocateAllRegisters();
fputs( fputs(
"\t.text\n", /* "\t.text\n"
".LC0:\n" "switch:\n"
"\t.string\t\"%d\\n\"\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); OutputFile);
} }
@ -991,13 +1071,16 @@ static int AssembleTree(struct ASTNode* Node, int Register, int LoopBeginLabel,
return -1; return -1;
case OP_CALL: case OP_CALL:
return (AsCallWrapper(Node)); return AsCallWrapper(Node);
case OP_FUNC: case OP_FUNC:
AsFunctionPreamble(Node->Symbol); AsFunctionPreamble(Node->Symbol);
AssembleTree(Node->Left, -1, LoopBeginLabel, LoopEndLabel, Node->Operation); AssembleTree(Node->Left, -1, LoopBeginLabel, LoopEndLabel, Node->Operation);
AsFunctionEpilogue(Node->Symbol); AsFunctionEpilogue(Node->Symbol);
return -1; return -1;
case OP_SWITCH:
return AsSwitch(Node);
} }
@ -1219,6 +1302,8 @@ static const struct AssemblerVtable Win32ASMVtable = {
.AsStrGlobalVar = AsStrGlobalVar, .AsStrGlobalVar = AsStrGlobalVar,
.AsStrLocalVar = AsStrLocalVar, .AsStrLocalVar = AsStrLocalVar,
.AsSub = AsSub, .AsSub = AsSub,
.AsSwitch = AsSwitch,
.AsSwitchTable = AsSwitchTable,
.AsWhile = AsWhile, .AsWhile = AsWhile,
.DeallocateAllRegisters = DeallocateAllRegisters, .DeallocateAllRegisters = DeallocateAllRegisters,
.RetrieveRegister = RetrieveRegister, .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;
}