DeyjaScript 4 : Functions

posted in Jemgine
Published September 18, 2011
Advertisement
In this installment I continue to reinvent the wheel by implementing function calls. First I write the grammar, and quickly implement calling system functions, since they already exist.


parameterList.Rule = MakeStarRule(parameterList, ToTerm(","), expression);
functionCall.Rule = identifier + "(" + parameterList + ")";
parameterDeclaration.Rule = identifier + identifier;
parameterListDeclaration.Rule = MakeStarRule(parameterListDeclaration, ToTerm(","), parameterDeclaration);
functionDeclaration.Rule = identifier + identifier + "(" + parameterListDeclaration + ")" + block;
member.Rule = functionDeclaration;
memberList.Rule = MakeStarRule(memberList, member);
returnStatement.Rule = ToTerm("return") + (Empty | expression);


In order to call functions defined in scripts, I need to be able to define functions in script. The root node of the grammar becomes a list of function definitions instead of a list of statements. I also added parsing of return statements.
Calling a function is fairly straightforward. I create a 'callFunction' instruction that does the important dirty work. It looks up the function in the function table (which I stash at the beginning of the bytecode), pushes the return address, and jumps to the first instruction of the function. A companion return instruction pops the return address from the stack and jumps to it.

case Instruction.callFunction:
{
var stackTopPointer = getTopPointer();
stack.store(stackTopPointer, instructionPointer + 3);
setTopPointer(stackTopPointer + 1);
var functionIndex = BitConverter.ToInt16(bytecode.GetRange(instructionPointer + 1, 2).ToArray(), 0);
var functionFirstInstruction = BitConverter.ToInt16(bytecode.GetRange(functionIndex * 2, 2).ToArray(), 0);
instructionPointer = functionFirstInstruction;
}
break;

case Instruction.returnFromFunction:
{
var stackTopPointer = getTopPointer();
var returnAddress = (stack.fetch(stackTopPointer - 1) as int?).Value;
setTopPointer(stackTopPointer - 1);
instructionPointer = returnAddress;
}
break;


To implement parameters, I do much the same as I did for variables inside the functions and assign ids to them all in advance. In this case, the ids are negative (which also means I need to offset them when writing the bytecode since the bytecode is all unsigned bytes) because they will be on the stack below the frame pointer.
Every function needs a chunk of entrance and exit code to maintain the frame pointer and make space for variables.


context.currentFunctionScope = this;
this.firstInstruction = bytecode.Count;
bytecode.AddBytecode(Instruction.pushRegisterToStack, (byte)Register.framePointer); //Remember the previous frame pointer
bytecode.AddBytecode(Instruction.pushRegisterToStack, (byte)Register.stackTopPointer); //Set our frame pointer
bytecode.AddBytecode(Instruction.loadRegisterFromStack, (byte)Register.framePointer);
bytecode.AddBytecode(Instruction.pop); //pop the new framepointer value - At this point, the old framebuffer is the stack top
if (localVariableCount > 0) bytecode.AddBytecode(Instruction.pushN, (byte)localVariableCount); //push space for variables
(ChildNodes[1] as CompilableNode).emitByteCode(bytecode, context, false); //function implementation

int returnDestination = bytecode.Count;

bytecode.AddBytecode(Instruction.pushRegisterToStack, (byte)Register.framePointer);
bytecode.AddBytecode(Instruction.loadRegisterFromStack, (byte)Register.stackTopPointer); //pops everything above the framepointer
bytecode.AddBytecode(Instruction.loadRegisterFromStack, (byte)Register.framePointer);
bytecode.AddBytecode(Instruction.pop); //pop framepointer from stack
bytecode.AddBytecode(Instruction.returnFromFunction);

foreach (var ret in returnStatements)
{
var relativeJump = returnDestination - ret;
var bytes = BitConverter.GetBytes((Int16)relativeJump);
bytecode[ret + 1] = bytes[0];
bytecode[ret + 2] = bytes[1];
}


To implement return statements, I keep track of every return statement I encounter. The return is implemented as a simple constantRelativeJump to the end of the function. I just have to go back and fill in the jump distance.

I mentioned a function table earlier. What I did was assign all the functions an ID, and smack their first instruction into a table. But I think I'm going to change that, it's not going to work so well when I start doing objects and inheritance and virtual functions.
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement