Refactor pointer arithmetic
This commit is contained in:
parent
264c50509e
commit
a0d2575a83
|
@ -16,6 +16,7 @@
|
|||
|
||||
extern_ struct SymbolTable Symbols[SYMBOLS];
|
||||
|
||||
extern_ int TypeSizes[9];
|
||||
extern_ char* TypeNames[9];
|
||||
|
||||
extern_ char* TokenStrings[];
|
||||
|
|
|
@ -107,6 +107,7 @@ enum SyntaxOps {
|
|||
LV_IDENT, // Write an identifier in the form of an l-value.
|
||||
|
||||
OP_WIDEN, // Something contains a type that needs to be casted up
|
||||
OP_SCALE, // We have a pointer that needs to be scaled!
|
||||
|
||||
OP_CALL, // Call a function
|
||||
OP_RET, // Return from a function
|
||||
|
@ -128,6 +129,7 @@ struct ASTNode {
|
|||
struct ASTNode* Middle;
|
||||
struct ASTNode* Right;
|
||||
union {
|
||||
int Size; // OP_SCALE's linear representation
|
||||
int IntValue; // TERM_INTLIT's Value
|
||||
int ID; // LV_IDENT's Symbols[] index.
|
||||
} Value;
|
||||
|
@ -190,14 +192,21 @@ enum StructureType {
|
|||
|
||||
int Tokenise(struct Token* Token);
|
||||
|
||||
int TypesCompatible(int* Left, int* Right, int STRICT);
|
||||
|
||||
void VerifyToken(int Type, char* TokenExpected);
|
||||
void RejectToken(struct Token* Token);
|
||||
|
||||
static int ReadIdentifier(int Char, char* Buffer, int Limit);
|
||||
static int ReadKeyword(char* Str);
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* * * * * T Y P E S * * * * * *
|
||||
* * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
struct ASTNode* MutateType(struct ASTNode* Tree, int RightType, int Operation);
|
||||
|
||||
int TypeIsInt(int Type);
|
||||
int TypeIsPtr(int Type);
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* * * * * * S Y N T A X T R E E * * * * * *
|
||||
|
@ -318,6 +327,8 @@ int NewLabel(void);
|
|||
void AsJmp(int Label);
|
||||
void AsLabel(int Label);
|
||||
|
||||
int AsShl(int Register, int Val);
|
||||
|
||||
int AsReturn(int Register, int FuncID);
|
||||
int AsCall(int Register, int FuncID);
|
||||
|
||||
|
|
|
@ -83,6 +83,18 @@ int AssembleTree(struct ASTNode* Node, int Register, int ParentOp) {
|
|||
case OP_DIVIDE:
|
||||
return AsDiv(LeftVal, RightVal);
|
||||
|
||||
case OP_SCALE:
|
||||
// We can (ab)use the powers of 2 to do
|
||||
// efficient scaling with bitshifting.
|
||||
switch(Node->Value.Size) {
|
||||
case 2: return AsShl(LeftVal, 1);
|
||||
case 4: return AsShl(LeftVal, 2);
|
||||
case 8: return AsShl(LeftVal, 3);
|
||||
|
||||
default:
|
||||
RightVal = AsLoad(Node->Value.Size);
|
||||
return AsMul(LeftVal, RightVal);
|
||||
}
|
||||
case OP_ADDRESS:
|
||||
return AsAddr(Node->Value.ID);
|
||||
|
||||
|
@ -344,6 +356,12 @@ int AsDiv(int Left, int Right) {
|
|||
return Left;
|
||||
}
|
||||
|
||||
int AsShl(int Register, int Val) {
|
||||
printf("\tShifting %s to the left by %d bits.\n", Registers[Register], Val);
|
||||
fprintf(OutputFile, "\tsalq\t$%d, %s\n", Val, Registers[Register]);
|
||||
return Register;
|
||||
}
|
||||
|
||||
int AsLdVar(int ID) {
|
||||
int Reg = RetrieveRegister();
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#undef extern_
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
int TypeSizes[9] = { 0, 1, 4, 8, 0, 8, 8, 8, 8}; // in BYTES
|
||||
|
||||
char* TokenStrings[] = { "+", "-", "*", "/", "int" };
|
||||
char* TokenNames[] = {
|
||||
"End of file",
|
||||
|
|
95
src/Parser.c
95
src/Parser.c
|
@ -144,35 +144,11 @@ struct ASTNode* ParsePrimary(void) {
|
|||
}
|
||||
|
||||
|
||||
struct ASTNode* ParseNewASTNode(void) {
|
||||
//fprintf(stdout, "New node requested.");
|
||||
struct ASTNode* LeftNode, *RightNode;
|
||||
int NodeType;
|
||||
|
||||
|
||||
LeftNode = ParsePrimary(); // Fetches next token!
|
||||
|
||||
// If there's just a number, then this is the AST Node.
|
||||
// Return, as the root of the tree is the end of the tree.
|
||||
if(CurrentToken.type == LI_EOF)
|
||||
return(LeftNode);
|
||||
|
||||
|
||||
NodeType = ParseTokenToOperation(CurrentToken.type);
|
||||
|
||||
Tokenise(&CurrentToken);
|
||||
|
||||
RightNode = ParseNewASTNode();
|
||||
|
||||
return ConstructASTNode(NodeType, LeftNode->ExprType, LeftNode, NULL, RightNode, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) {
|
||||
struct ASTNode* LeftNode, *RightNode;
|
||||
struct ASTNode* LeftTemp, *RightTemp;
|
||||
int LeftType, RightType;
|
||||
int NodeType;
|
||||
int NodeType, OpType;
|
||||
|
||||
LeftNode = PrefixStatement();
|
||||
|
||||
|
@ -190,8 +166,45 @@ struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) {
|
|||
LeftType = LeftNode->ExprType;
|
||||
RightType = RightNode->ExprType;
|
||||
|
||||
if(!TypesCompatible(&LeftType, &RightType, 0))
|
||||
Die("Assignment between incompatible types");
|
||||
/**
|
||||
* While parsing this node, we may need to widen some types.
|
||||
* This requires a few functions and checks.
|
||||
*/
|
||||
|
||||
OpType = ParseTokenToOperation(NodeType);
|
||||
LeftTemp = MutateType(LeftNode, RightType, OpType);
|
||||
RightTemp = MutateType(RightNode, LeftType, OpType);
|
||||
|
||||
/**
|
||||
* If both are null, the types are incompatible.
|
||||
*/
|
||||
|
||||
if(LeftTemp == NULL && RightTemp == NULL)
|
||||
Die("Incompatible types in parsing nodes");
|
||||
|
||||
/**
|
||||
* If the left was valid, or valid for
|
||||
* expansion, then it will be non-null.
|
||||
*
|
||||
* If it was valid, then this will be
|
||||
* equivalent to LeftNode = LeftNode
|
||||
*/
|
||||
|
||||
if(LeftTemp)
|
||||
LeftNode = LeftTemp;
|
||||
|
||||
/**
|
||||
* Same here, but there is a higher chance
|
||||
* for the right node to be incompatible due
|
||||
* to the nature of widening types.
|
||||
*/
|
||||
|
||||
if(RightTemp)
|
||||
RightNode = RightTemp;
|
||||
|
||||
/**
|
||||
* Checks over, back to normal parsing.
|
||||
*/
|
||||
|
||||
if(LeftType)
|
||||
LeftNode = ConstructASTBranch(LeftType, RightNode->ExprType, LeftNode, 0);
|
||||
|
@ -439,29 +452,3 @@ void ParseGlobals() {
|
|||
}
|
||||
|
||||
|
||||
/* void ParseStatements() {
|
||||
while(1) {
|
||||
switch(CurrentToken.type) {
|
||||
|
||||
case KW_PRINT:
|
||||
PrintStatement();
|
||||
break;
|
||||
|
||||
case TY_INT:
|
||||
BeginVariableDeclaration();
|
||||
break;
|
||||
|
||||
case TY_IDENTIFIER:
|
||||
AssignVariable();
|
||||
break;
|
||||
|
||||
case LI_EOF:
|
||||
return;
|
||||
|
||||
default:
|
||||
DieDecimal("Syntax error; Token", CurrentToken.type);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
|
||||
|
|
128
src/Statements.c
128
src/Statements.c
|
@ -8,110 +8,6 @@
|
|||
#include <Data.h>
|
||||
|
||||
|
||||
/*
|
||||
* Turn a token type into its appropriate
|
||||
* primitive type.
|
||||
*
|
||||
* This is where we do redirections like:
|
||||
* short -> s16
|
||||
* long -> s64
|
||||
* int -> s32
|
||||
* char -> u8
|
||||
*
|
||||
*/
|
||||
|
||||
static int TypeSize[9] = { 0, 1, 4, 8, 0, 8, 8, 8, 8}; // in BYTES
|
||||
|
||||
int PrimitiveSize(int Type) {
|
||||
if(Type < RET_NONE || Type > PTR_VOID)
|
||||
DieDecimal("Checking size of bad data type", Type);
|
||||
|
||||
return TypeSize[Type];
|
||||
}
|
||||
|
||||
/*
|
||||
* Given two types, determine if they are compatible.
|
||||
*
|
||||
* Depending on the value of STRICT, it will try to
|
||||
* fit the right value into the left value.
|
||||
*
|
||||
* This is valid, for ie. a char into an int, as int is larger than char.
|
||||
* This is called widening the char.
|
||||
*
|
||||
* If STRICT is set, it will only allow widening the left to the right.
|
||||
* This means you cannot `char a; int b; b = 15000; a = b;`
|
||||
* As this would shrink the int and lose resolution.
|
||||
*
|
||||
* NOTE: THIS IS NOT THE DEFAULT BEHAVIOUR
|
||||
* By default, you CAN shrink an int into a char, a la shifting down.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
int TypesCompatible(int* Left, int* Right, int STRICT) {
|
||||
|
||||
int LeftSize, RightSize;
|
||||
|
||||
// Same types are compatible. No shrinking required
|
||||
if(*Left == *Right) {
|
||||
*Left = *Right = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
LeftSize = PrimitiveSize(*Left);
|
||||
RightSize = PrimitiveSize(*Right);
|
||||
|
||||
|
||||
// Types of size 0 are incompatible
|
||||
if((LeftSize == 0) || (RightSize == 0))
|
||||
return 0;
|
||||
|
||||
|
||||
/* char x;
|
||||
* int y;
|
||||
* y = 15;
|
||||
*
|
||||
* x = y;
|
||||
* x needs to be widened, y copied in, then x shrunk back down
|
||||
* AKA, the left must be widened.
|
||||
*/
|
||||
if(LeftSize < RightSize) {
|
||||
*Left = OP_WIDEN;
|
||||
*Right = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* char x;
|
||||
* int y;
|
||||
*
|
||||
* x = 15;
|
||||
*
|
||||
* y = x;
|
||||
* x must be widened to fit into y.
|
||||
* if STRICT mode, this is not allowed.
|
||||
* By default, this is valid.
|
||||
*
|
||||
*/
|
||||
|
||||
if(LeftSize > RightSize) {
|
||||
if(STRICT)
|
||||
return 0; // Not compatible if STRICT
|
||||
|
||||
*Left = 0;
|
||||
*Right = OP_WIDEN;
|
||||
return 1; // Compatible by default
|
||||
}
|
||||
|
||||
/*
|
||||
* Any other cases left, by default, are compatible.
|
||||
*
|
||||
*/
|
||||
|
||||
*Left = *Right = 0;
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the declaration of a type of a variable.
|
||||
|
@ -208,7 +104,8 @@ struct ASTNode* ReturnStatement() {
|
|||
ReturnType = Tree->ExprType;
|
||||
FunctionType = Symbols[CurrentFunction].Type;
|
||||
|
||||
if(!TypesCompatible(&ReturnType, &FunctionType, 0))
|
||||
Tree = MutateType(Tree, FunctionType, 0);
|
||||
if(!Tree)
|
||||
Die("Returning a value of incorrect type for function");
|
||||
|
||||
|
||||
|
@ -225,12 +122,17 @@ struct ASTNode* ReturnStatement() {
|
|||
}
|
||||
|
||||
/*
|
||||
* Handles the assignment of variables.
|
||||
* Handles Identifiers.
|
||||
*
|
||||
* You can assign variables with an assignment,
|
||||
* a statement, a function or a literal.
|
||||
* This is called for any of:
|
||||
* - Calling a function
|
||||
* - Assigning an lvalue variable
|
||||
* - Performing arithmetic on a variable
|
||||
* - Performing arithmetic with the return values of function calls
|
||||
*
|
||||
* This means we need to do some recursive parsing.
|
||||
* For the case where you're assigning an l-value;
|
||||
* You can assign with another assignment,
|
||||
* a statement, a function or a literal.
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -259,8 +161,9 @@ struct ASTNode* ParseIdentifier() {
|
|||
LeftType = Left->ExprType;
|
||||
RightType = Right->ExprType;
|
||||
|
||||
if(!TypesCompatible(&LeftType, &RightType, 1))
|
||||
Die("Incompatible variable types");
|
||||
Left = MutateType(Left, RightType, 0);
|
||||
if(!Left)
|
||||
Die("Incompatible types in assignment");
|
||||
|
||||
if(LeftType)
|
||||
Left = ConstructASTBranch(LeftType, Right->ExprType, Left, 0);
|
||||
|
@ -363,7 +266,8 @@ struct ASTNode* PrintStatement(void) {
|
|||
LeftType = RET_INT;
|
||||
RightType = Tree->ExprType;
|
||||
|
||||
if(!TypesCompatible(&LeftType, &RightType, 0))
|
||||
Tree = MutateType(Tree, RightType, 0);
|
||||
if(!Tree)
|
||||
DieDecimal("Attempting to print an invalid type:", RightType);
|
||||
|
||||
if(RightType)
|
||||
|
|
202
src/Types.c
Normal file
202
src/Types.c
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
/*************/
|
||||
/*GEMWIRE */
|
||||
/* ERYTHRO*/
|
||||
/*************/
|
||||
|
||||
#include <Defs.h>
|
||||
#include <Data.h>
|
||||
|
||||
/*
|
||||
* Turn a token type into its appropriate
|
||||
* primitive type.
|
||||
*
|
||||
* This is where we do redirections like:
|
||||
* short -> s16
|
||||
* long -> s64
|
||||
* int -> s32
|
||||
* char -> u8
|
||||
*
|
||||
*/
|
||||
|
||||
int PrimitiveSize(int Type) {
|
||||
if(Type < RET_NONE || Type > PTR_VOID)
|
||||
DieDecimal("Checking size of bad data type", Type);
|
||||
|
||||
return TypeSizes[Type];
|
||||
}
|
||||
|
||||
/*
|
||||
* Given two types, determine if they are compatible.
|
||||
*
|
||||
* Depending on the value of STRICT, it will try to
|
||||
* fit the right value into the left value.
|
||||
*
|
||||
* This is valid, for ie. a char into an int, as int is larger than char.
|
||||
* This is called widening the char.
|
||||
*
|
||||
* If STRICT is set, it will only allow widening the left to the right.
|
||||
* This means you cannot `char a; int b; b = 15000; a = b;`
|
||||
* As this would shrink the int and lose resolution.
|
||||
*
|
||||
* NOTE: THIS IS NOT THE DEFAULT BEHAVIOUR
|
||||
* By default, you CAN shrink an int into a char, a la shifting down.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
int TypesCompatible(int* Left, int* Right, int STRICT) {
|
||||
|
||||
int LeftSize, RightSize;
|
||||
|
||||
// Same types are compatible. No shrinking required
|
||||
if(*Left == *Right) {
|
||||
*Left = *Right = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
LeftSize = PrimitiveSize(*Left);
|
||||
RightSize = PrimitiveSize(*Right);
|
||||
|
||||
|
||||
// Types of size 0 are incompatible
|
||||
if((LeftSize == 0) || (RightSize == 0))
|
||||
return 0;
|
||||
|
||||
|
||||
/* char x;
|
||||
* int y;
|
||||
* y = 15;
|
||||
*
|
||||
* x = y;
|
||||
* x needs to be widened, y copied in, then x shrunk back down
|
||||
* AKA, the left must be widened.
|
||||
*/
|
||||
if(LeftSize < RightSize) {
|
||||
*Left = OP_WIDEN;
|
||||
*Right = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* char x;
|
||||
* int y;
|
||||
*
|
||||
* x = 15;
|
||||
*
|
||||
* y = x;
|
||||
* x must be widened to fit into y.
|
||||
* if STRICT mode, this is not allowed.
|
||||
* By default, this is valid.
|
||||
*
|
||||
*/
|
||||
|
||||
if(LeftSize > RightSize) {
|
||||
if(STRICT)
|
||||
return 0; // Not compatible if STRICT
|
||||
|
||||
*Left = 0;
|
||||
*Right = OP_WIDEN;
|
||||
return 1; // Compatible by default
|
||||
}
|
||||
|
||||
/*
|
||||
* Any other cases left, by default, are compatible.
|
||||
*
|
||||
*/
|
||||
|
||||
*Left = *Right = 0;
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given an operation on two types, we need to be able to
|
||||
* determine if the operation is valid for both types,
|
||||
* as well as modify the types if the operation is
|
||||
* theoretically valid but requires some changes.
|
||||
*
|
||||
* An example of the latter is assigning an int literal into
|
||||
* a char, or squeezing down the int into the char type.
|
||||
*
|
||||
* If the operation is not valid, this will return NULL.
|
||||
* If the operaton is valid without changes, this will return Tree.
|
||||
* If the operation is valid with changes, this will perform
|
||||
* the changes and return the new tree.
|
||||
*
|
||||
* This also serves to consolidate some of the gross widening
|
||||
* code that TypesCompatible led us to.
|
||||
*/
|
||||
|
||||
struct ASTNode* MutateType(struct ASTNode* Tree, int RightType, int Operation) {
|
||||
int LeftType;
|
||||
int LeftSize, RightSize;
|
||||
|
||||
LeftType = Tree->ExprType;
|
||||
|
||||
|
||||
if(TypeIsInt(LeftType) && TypeIsInt(RightType)) {
|
||||
|
||||
// Short-circuit for valid types
|
||||
if(LeftType == RightType) {
|
||||
return Tree;
|
||||
}
|
||||
|
||||
LeftSize = PrimitiveSize(LeftType);
|
||||
RightSize = PrimitiveSize(RightType);
|
||||
|
||||
/**
|
||||
* LeftSize > RightSize:
|
||||
* char x = 15000;
|
||||
*
|
||||
* (The left branch of the tree contains the current AST)
|
||||
*
|
||||
*/
|
||||
if(LeftSize > RightSize)
|
||||
return NULL;
|
||||
|
||||
/**
|
||||
* RightSize > LeftSize:
|
||||
* char x = 5;
|
||||
* int y = x;
|
||||
*
|
||||
* We have to widen x into an int in order for this to be compatible
|
||||
* BUT it is possible!
|
||||
*/
|
||||
|
||||
if(RightSize > LeftSize)
|
||||
return ConstructASTBranch(OP_WIDEN, RightType, Tree, 0);
|
||||
}
|
||||
|
||||
// Left branch pointers are compatible if we're not doing operations
|
||||
if(TypeIsPtr(LeftType)) {
|
||||
if(Operation == 0 && LeftType == RightType)
|
||||
return Tree;
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, we can perform some scaling for pointer addition & subtraction
|
||||
if(Operation == OP_ADD || Operation == OP_SUBTRACT) {
|
||||
|
||||
/**
|
||||
* Left int, right pointer:
|
||||
* int x = 5;
|
||||
* int* y;
|
||||
*
|
||||
* x = *(y + 1);
|
||||
*/
|
||||
|
||||
if(TypeIsInt(LeftType) && TypeIsPtr(RightType)) {
|
||||
RightSize = PrimitiveSize(ValueAt(RightType));
|
||||
|
||||
if(RightSize > 1)
|
||||
return ConstructASTBranch(OP_SCALE, RightType, Tree, RightSize);
|
||||
}
|
||||
}
|
||||
|
||||
// If all else fails, we've constructed a combination of types that are not compatible.
|
||||
// ie. left or right is a void.
|
||||
// You cannot do pointer arithmetic on void type.
|
||||
return NULL;
|
||||
}
|
Loading…
Reference in New Issue
Block a user