DeyjaScript 2 : Variables

posted in Jemgine
Published September 15, 2011
Advertisement
Next I'm going to add variables. First I'll add identifiers and variable declarations to the grammar. I won't be supporting any sort of initialization yet, so I don't need to add that to the grammar. I also go ahead and create a statement rule, and a statement block. Later I added assignment statements.


var identifier = TerminalFactory.CreateCSharpIdentifier("identifier");
identifier.AstNodeType = typeof(IdentifierNode);

var variableDeclaration = new NonTerminal("Variable Declaration", typeof(VariableDeclarationNode));
var statement = new NonTerminal("Statement");
var statementBlock = new NonTerminal("Statement Block", typeof(StatementBlockNode));
var assignment = new NonTerminal("Assignment", typeof(AssignmentNode));

expression.Rule = binaryOperation | numberLiteral | parenExpression | identifier;
assignment.Rule = identifier + "=" + expression;
variableDeclaration.Rule = identifier + identifier + ";";
statement.Rule = (assignment + ";") | variableDeclaration;
statementBlock.Rule = MakeStarRule(statementBlock, statement);


Hey, I got the featured article. On the very first one. The day after I posted it. I also noticed the website just takes the beginning of the article and uses it on the front page, so I tried to put something on topic right at the beginning instead of this.

Variables will be stored on the stack. In order to know how much stack space to reserve, I need to count how many variables are declared. I can do this in one pass or two passes. I choose two because it seems more natural to me, because it was the first way to occur to me, and because I didn't think of the other way until just now. So I split the compilation into two phases.


public class ExpressionNode : AstNode
{
public virtual void gatherInformation(ExecutionContext context) { throw new NotImplementedException(); }
public virtual void emitByteCode(List bytecode, ExecutionContext context) { throw new NotImplementedException(); }
internal virtual String getResultType(ExecutionContext context) { throw new NotImplementedException(); }
}


To count the variables, and to look up variables by name, I need to keep track of them. I created a 'Scope' class with information about the variables, and gave context a value 'currentScope'. In the new StatementBlockNode, I set the currentScope in the first compilation path. VariableDeclarationNodes add variables to it, and IdentifierNodes lookup variables by name. These are the two compilation phases for the IdentifierNode, which will just push the correct variable onto the top of the stack.


public override void gatherInformation(ExecutionContext context)
{
var variable = context.currentScope.findVariable(AsString);
if (variable == null) throw new CompileError("Undefined variable");
variableIndex = variable.variableIndex;
}

public override void emitByteCode(List bytecode, ExecutionContext context)
{
bytecode.Add((byte)Instruction.pushVar);
bytecode.Add((byte)variableIndex);
}


I can now declare variables and use them in expressions, but if I actually try to I will trigger an exception. As of right now, trying to use an unitialized variable will make the virtual machine throw a null reference exception, and there isn't actually any way to initialize a variable. I need an assignment operator. Assignment is technically a binary operation, but it's handled very differently, so I won't implement it as one. First, the left-side of an assignment operator must be an 'LValue'. So I create an LValue interface. Any ExpressionNode that implements this interface is an LValue, and can be assigned to.


internal interface LValue
{
void emitAssignmentByteCode(List bytecode, ExecutionContext context);
}


Because only ExpressionNodes will ever be LValues, I will assume the node does error checking for both usages in gatherInformation. This is true of IdentifierNode, and if it proves an impossible assumption I will change it. I implement this interface in IdentifierNode.


void LValue.emitAssignmentByteCode(List bytecode, ExecutionContext context)
{
bytecode.Add((byte)Instruction.popVar);
bytecode.Add((byte)variableIndex);
}


And then I move on to AssignmentNode. It looks very similar to BinaryOperationNode, except that I never call emitByteCode on the first child. I emit the bytecode for the second child instead, and then I call emitAssignmentByteCode on the first child. I assume that the second child leaves it's result on the stack.


public override void gatherInformation(ExecutionContext context)
{
var LValue = ChildNodes[0] as LValue;
if (LValue == null) throw new CompileError("Only assignment to LValues is allowed.");
if ((ChildNodes[0] as ExpressionNode).getResultType(context) != (ChildNodes[1] as ExpressionNode).getResultType(context))
throw new CompileError("Assignment type mismatch");
(ChildNodes[0] as ExpressionNode).gatherInformation(context);
(ChildNodes[1] as ExpressionNode).gatherInformation(context);
}

public override void emitByteCode(List bytecode, ExecutionContext context)
{
(ChildNodes[1] as ExpressionNode).emitByteCode(bytecode, context);
(ChildNodes[0] as LValue).emitAssignmentByteCode(bytecode, context);
}


Implementation of the new instructions is straight forward. The biggest change to the virtual machine is that I change how the stack is addressed, and I add a frame pointer which, for now, is always 0. I write a new test script - "int foo; foo = 6 * 6;" and compile and run it. Afterwards, the stack is empty, but since the values are never actually cleared I can still pull the value of foo out of the stack and confirm that it is, in fact, 36.

There's a few gaps in assignment, however. I can't chain assignments. Foo = bar = 5; will not compile. Also, I can't use assignments as an expression. It might be a good idea not to support the latter, but I want to support it. There's another problem; expressions can leave garbage on the stack. It's not possible now, but only because I only allow declaration and assignment in statements. It will be a problem when I implement functions. I'll clean those up but I probably won't mention them in the next installment, because next time, I'm going to work on embedded code blocks, the first step in implementing control structures.

DeyjaScript02.zip
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