I also go ahead and define a function calling convention. The only functions being called right now are operators, but having a clearly defined calling convention will be important shortly. I don't know if this convention is named. Here it is - Parameters are pushed onto the stack in order. They are not popped by the calling function, and the return value is placed in the returnValue register. A bit of parameterization of the ExpressionNode's emit function lets me tell any arbitrary node whether or not to push it's return value onto the stack, but that push is now a separate instruction.
Now on with the grammar. I separate the idea of a list of statements and a block. I allow a code block to be used as a statement, but not an expression. For control structures, I'll need a jump instruction. Just two, actually. constantRelativeJump and constantRelativeJumpIfNotTrue. Why jump on false? So I can emit the then block first. Those are straightforward. I also add an operator== for ints so that I can actually try it out. Finally, the grammar, and the first real hurdle.
ifStatement.Rule = ToTerm("if") + "(" + expression + ")" + statement;
ifElseStatement.Rule = ToTerm("if") + "(" + expression + ")" + statement + this.PreferShiftHere() + "else" + statement;
I'd stumbled directly into something called the 'if-then-else ambiguity'. A bit of research told me why Irony was complaining, and led to that call to PreferShiftHere which took care of the problem. Here's some more information about it. http://docs.freebsd.org/info/bison/bison.info.Shift_Reduce.html This really hi-lites something I should probably mention, and that is that I have no idea what I am doing. That's part of the reason for blogging this whole experience. If I'm doing something obviously wrong, I'd hope someone would let me know.
So all that's left is to actually implement IfStatementNode.
public override void emitByteCode(List bytecode, ExecutionContext context, bool placeResultOnStack)
{
var hasElseClause = ChildNodes.Count == 3;
(ChildNodes[0] as ExpressionNode).emitByteCode(bytecode, context, false); //Leaves result in returnValue register
int jumpInstruction = bytecode.Count;
int elseSkipJumpInstruction = 0;
bytecode.AddBytecode(Instruction.constantRelativeJumpIfNotTrue, (byte)Register.returnValue, (byte)0, (byte)0);
(ChildNodes[1] as ExpressionNode).emitByteCode(bytecode, context, false);
if (hasElseClause)
{
elseSkipJumpInstruction = bytecode.Count;
bytecode.AddBytecode(Instruction.constantRelativeJump, (byte)0, (byte)0);
}
var jumpDistance = bytecode.Count - jumpInstruction;
var bytes = BitConverter.GetBytes((Int16)jumpDistance);
bytecode[jumpInstruction + 2] = bytes[0];
bytecode[jumpInstruction + 3] = bytes[1];
if (hasElseClause)
{
(ChildNodes[2] as ExpressionNode).emitByteCode(bytecode, context, false);
jumpDistance = bytecode.Count - elseSkipJumpInstruction;
bytes = BitConverter.GetBytes((Int16)jumpDistance);
bytecode[elseSkipJumpInstruction + 1] = bytes[0];
bytecode[elseSkipJumpInstruction + 2] = bytes[1];
}
}
It has to go back and fill in the jump instructions once it knows just how far to jump.