You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/old_d_version/monster/compiler/statement.d

1608 lines
42 KiB
D

/*
Monster - an advanced game scripting language
Copyright (C) 2007-2009 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (statement.d) is part of the Monster script language
package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module monster.compiler.statement;
import std.string;
import std.stdio;
import monster.compiler.tokenizer;
import monster.compiler.expression;
import monster.compiler.scopes;
import monster.compiler.types;
import monster.compiler.block;
import monster.compiler.variables;
import monster.compiler.states;
import monster.compiler.operators;
import monster.compiler.functions;
import monster.compiler.assembler;
import monster.vm.error;
import monster.vm.mclass;
import monster.vm.vm;
import monster.options;
alias Statement[] StateArray;
abstract class Statement : Block
{
// Generate byte code from this statement
void compile();
}
/* Handles:
import type1, type2, ...; // module scope
import exp1, exp2, ...; // local scope (not implemented yet)
Also handles:
with(exp1, etc) statement;
which is equivalent to:
{
import exp1, etc;
statement;
}
*/
class ImportStatement : Statement
{
Type typeList[];
MonsterClass mc;
bool isLocal;
Statement stm;
this(bool local = false) { isLocal = local; }
// TODO: Things like this should be completely unnecessary - we'll
// fix a simpler and more concise parser-system later.
static bool canParse(TokenArray toks)
{ return
isNext(toks, TT.Import) ||
isNext(toks, TT.With);
}
void parse(ref TokenArray toks)
{
void getList()
{
typeList = [Type.identify(toks)];
while(isNext(toks, TT.Comma))
typeList ~= Type.identify(toks);
}
if(isNext(toks, TT.Import, loc))
{
// form: import ImportList;
getList();
reqSep(toks);
}
else if(isNext(toks, TT.With, loc))
{
// form: with(ImportList) statement
if(!isLocal)
fail("with(...) not allowed in class scopes", loc);
reqNext(toks, TT.LeftParen);
getList();
reqNext(toks, TT.RightParen);
stm = CodeBlock.identify(toks, false);
}
else assert(0);
}
void resolve(Scope sc)
{
// If a statement is present (ie. if this is a with-statement),
// wrap the imports in a code scope so they become temporary.
if(stm !is null)
sc = new CodeScope(sc, "with-scope");
// Add the imports to the scope
foreach(type; typeList)
{
type.resolve(sc);
if(type.isReplacer)
type = type.getBase();
if(type.isObject)
{
auto t = cast(ObjectType)type;
assert(t !is null);
mc = t.getClass(type.loc);
sc.registerImport(mc);
}
else if(type.isPackage)
{
auto t = cast(PackageType)type;
assert(t !is null);
auto psc = t.sc;
sc.registerImport(psc);
}
else
fail("Can only import from classes and packages", type.loc);
}
// Resolve the statement if present
if(stm !is null)
stm.resolve(sc);
}
void compile()
{
if(stm !is null)
stm.compile();
}
}
// Destination for a goto statement, on the form 'label:'. This
// statement is actually a bit complicated, since it must handle jumps
// occuring both above and below the label. Seeing as the assembler
// can only associate one jump with each label for jumps occuring
// before the label, we must insert multiple labels into the assembler
// if there are multiple such jumps / gotos. Therefore we must keep a
// list of jumps occuring before the label itself has been
// compiled.
// LabelStatements are also used for internal labels. They are
// inserted by loops to handle break and continue, since these have
// similar forward reference problems.
class LabelStatement : Statement
{
// Jump indices accumulated before compile() is called
int[] preIndex;
// This array is empty when the label is inserted with
// insertLabel. The label might be registered into the labels list
// before this happens, though, if a goto statement looks up a label
// that is not yet registered. In that case, the list below will
// contain all the goto statements that inserted this label.
GotoStatement gotos[];
// Label index used after compile() is called
int postIndex;
// Has compiled() been called yet?
bool isCompiled = false;
// Permanent data for state labels.
StateLabel *lb;
// Stack level where this label is defined. Should be zero for state
// labels, but might be nonzero when LabelStatements are used
// internally in loops.
int stacklevel;
// Normal creation, we are parsed.
this()
{
lb = new StateLabel;
lb.ls = this;
}
// Forward reference. A goto statement requested a label by this
// name, but none has been found yet.
this(GotoStatement gs)
{
this();
lb.name = gs.labelName;
gotos ~= gs;
}
// Used for internal levels (like continue and break labels). The
// parameter gives the stack level (within the function) at the
// label location, corresponds to sc.getTotLocals(). Internal labels
// are neither parsed nor resolved, just compiled.
this(int stack)
{
this();
stacklevel = stack;
lb.index = -1;
}
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Identifier) && isNext(toks, TT.Colon);
}
void parse(ref TokenArray toks)
{
// canParse has already checked the token types, all we need to
// do is to fetch the name.
reqNext(toks, TT.Identifier, lb.name);
reqNext(toks, TT.Colon);
loc = lb.name.loc;
}
void resolve(Scope sc)
{
if(!sc.isStateCode)
fail("Labels are only allowed in state code", lb.name.loc);
if(sc.isInLoop)
fail("Labels are not allowed in loops", lb.name.loc);
// Do the magic. Just tell the scope that we exist and let it
// handle the rest.
auto scp = sc.getState().sc;
assert(scp !is null);
scp.insertLabel(lb);
stacklevel = sc.getTotLocals();
assert(stacklevel == 0);
}
// Compile the label code. Note that when LabelStatement are used
// internally (such as in for loops to handle break and continue),
// then parse() and resolve() are never called, only compile().
void compile()
{
// Since labels are also used internally, there's a chance that
// we end up calling this function several times by mistake.
assert(!isCompiled, "LabelStatement.compile() was called twice.");
setLine();
// We must insert one label for each of the jumps that occured
// before us
foreach(int ind; preIndex)
tasm.labelNum(ind, lb.index);
preIndex = null;
// Now get a label index for all the jumps that occur after
// us. We only need one.
postIndex = tasm.labelNum(lb.index);
// Make sure jump() knows what it is doing.
isCompiled = true;
}
// This is called from GotoStatement.compile() and tells us to
// insert a jump that jumps to this label. The parameter gives the
// stack level at the jump point.
void jump(int stack)
{
// Before jumping, we have set the stack to the correct level
// for where the label is.
stack -= stacklevel;
assert(stack >= 0, "Negative stack correction on jump");
if(stack) tasm.pop(stack);
if(isCompiled)
// We have already compiled this label, so we have an index to
// jump to.
tasm.jump(postIndex);
else
// We must get a jump index that we can use later.
preIndex ~= tasm.jump();
}
}
// Used by GotoStatement and StateStatement, since both refer to a
// labels.
interface LabelUser
{
void setLabel(LabelStatement ls);
}
// goto label; Only allowed in state code (for now)
class GotoStatement : Statement, LabelUser
{
// Set in resolve(). This is the label we are jumping to.
LabelStatement label;
Token labelName;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Goto);
}
// Set the label
void setLabel(LabelStatement ls)
{
label = ls;
if(label is null)
fail("Cannot find label '" ~ labelName.str ~ "'", labelName.loc);
}
void parse(ref TokenArray toks)
{
reqNext(toks, TT.Goto, loc);
// Read the label name
if(!isNext(toks, TT.Identifier, labelName))
fail("goto expected label identifier", toks);
reqSep(toks);
}
void resolve(Scope sc)
{
if(!sc.isStateCode)
fail("Goto statements are only allowed in state code", labelName.loc);
// Tell the state scope that we are trying to goto. The system
// will will set our label variable for us, either straight away
// (if the label is known) or later (if the label is a forward
// reference.)
sc.getState().registerGoto(labelName.str, this);
}
void compile()
{
assert(label !is null);
// We let LabelStatement do all the dirty work.
label.jump(0);
}
}
// Handles continue and break, with or without a label.
class ContinueBreakStatement : Statement
{
Token labelName;
// Label we are jumping to. It is obtained from the scope. The label
// we are pointing to will be a break or continue label created
// internally in the loop coresponding to labelName. If labelName is
// empty then the scope will use the innermost loop.
LabelStatement label;
// Is this a break or a continue?
bool isBreak;
// Used for error messages. Contains either "break" or "continue".
char[] errName;
// Stack level (sc.getTotLocals) at the jump point.
int stacklevel;
static bool canParse(TokenArray toks)
{ return isNext(toks, TT.Continue) || isNext(toks, TT.Break); }
void parse(ref TokenArray toks)
{
if(isNext(toks, TT.Continue, loc)) isBreak = false;
else if(isNext(toks, TT.Break, loc)) isBreak = true;
else assert(0, "Internal error");
if(isBreak) errName = "break";
else errName = "continue";
if(!isSep(toks))
{
if(!isNext(toks, TT.Identifier, labelName))
fail(errName ~ " expected ; or label", toks);
reqSep(toks);
}
}
void resolve(Scope sc)
{
if(!sc.isInLoop())
fail("Cannot use " ~ errName ~ " outside a loop", loc);
// Get the correct label to jump to
if(isBreak) label = sc.getBreak(labelName.str);
else label = sc.getContinue(labelName.str);
// And the stack level at the jump point
stacklevel = sc.getTotLocals();
if(label is null)
{
assert(labelName.str != "", "Internal error");
fail("Loop label '" ~ labelName.str ~ "' not found", labelName.loc);
}
}
void compile()
{
// Our nice LabelStatement implementation does everything for
// us.
assert(label !is null);
label.jump(stacklevel);
}
}
// do-while loop (might also support do-until loops in the future)
// do statement while (expression)
// do : label statement while (expression)
class DoWhileStatement : Statement
{
Expression condition;
Statement block;
Token labelName;
LoopScope sc;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Do);
}
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.Do, loc))
assert(0, "Internal error");
// Is there a loop label?
if(isNext(toks, TT.Colon))
if(!isNext(toks, TT.Identifier, labelName))
fail("do statement expected label identifier", toks);
// Read the statement ('false' to disallow variable
// declarations.)
block = CodeBlock.identify(toks, false);
if(!isNext(toks, TT.While))
fail("do statement expected while(...)", toks);
if(!isNext(toks, TT.LeftParen))
fail("while statement expected '('", toks);
// Parse the conditional expression
condition = Expression.identify(toks);
if(!isNext(toks, TT.RightParen))
fail("while statement expected ')'", toks);
// Allow an optional semicolon after the while()
isNext(toks, TT.Semicolon);
}
void resolve(Scope last)
{
sc = new LoopScope(last, this);
// Resolve all parts
condition.resolve(sc);
if(!condition.type.isBool)
fail("while condition " ~ condition.toString ~ " must be a bool, not "
~condition.type.toString, condition.loc);
block.resolve(sc);
}
void compile()
{
LabelStatement
cont = sc.getContinue(),
brk = sc.getBreak();
setLine();
int label = tasm.label();
// Execute the block
block.compile();
// Continue label goes here, right after the code block
cont.compile();
// Push the conditionel expression
condition.eval();
setLine();
// Jump if the value is non-zero.
tasm.jumpnz(label);
// Break label go here, after the entire loop
brk.compile();
}
}
// while loop:
// while (expression) statement
// while (expression) : label statement
class WhileStatement : Statement
{
Expression condition;
Statement block;
Token labelName;
LoopScope sc;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.While);
}
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.While, loc))
assert(0);
if(!isNext(toks, TT.LeftParen))
fail("while statement expected '('", toks);
// Parse the conditional expression
condition = Expression.identify(toks);
if(!isNext(toks, TT.RightParen))
fail("while statement expected ')'", toks);
// Is there a loop label?
if(isNext(toks, TT.Colon))
if(!isNext(toks, TT.Identifier, labelName))
fail("while statement expected label identifier", toks);
// Read the statement ('false' to disallow variable
// declarations.)
block = CodeBlock.identify(toks, false);
}
void resolve(Scope sco)
{
sc = new LoopScope(sco, this);
// Resolve all parts
condition.resolve(sc);
if(!condition.type.isBool)
fail("while condition " ~ condition.toString ~ " must be a bool, not "
~condition.typeString, condition.loc);
block.resolve(sc);
}
void compile()
{
/* To avoid the extra jump back and forth at the end, the coding
here is similar to
if(condition)
{
do
block
while(condition);
}
*/
LabelStatement
cont = sc.getContinue(),
brk = sc.getBreak();
// Evaluate the condition for the first iteration
condition.eval();
setLine();
// Skip the entire loop if the condition is not met
int outer = tasm.jumpz();
// Jump here to repeat the loop
int inner = tasm.label();
// Execute the block
block.compile();
// Continue label
cont.compile();
// Push the conditionel expression
condition.eval();
setLine();
// Repeat the loop if the value is non-zero.
tasm.jumpnz(inner);
tasm.label(outer);
// Break label
brk.compile;
}
}
/* foreach loop:
foreach(indexDeclaration; arrayExpr) statement
foreach(indexDeclaration; arrayExpr) : label statement
arrayExpr - any expression that evaluates to an array.
indexDeclaration - either:
type var
or
int index, type var
where 'type' is the base type of arrayExpr. Either or both types
may be omitted since they can always be infered by the
compiler. For example:
foreach(i,v; "hello") // i is type int, v is type char
Using 'ref' in the declaration of the value variable means that
changes to it will affect the original array.
Use foreach_reverse instead of foreach to iterate the array in the
opposite direction.
*/
class ForeachStatement : Statement
{
Expression arrayExp;
VarDeclaration index, value;
Statement block;
LoopScope sc;
Token labelName;
bool isReverse; // Traverse in reverse order
bool isRef; // Set if the value variable is a reference (alters
// original content)
// Set if we are traversing the objects of a class
Token className;
bool isClass = false;
MonsterClass clsInfo;
char[] toString()
{
char[] res = "foreach";
if(isReverse) res ~= "_reverse";
res ~= "(";
if(index !is null) res ~= index.toString ~ ", ";
res ~= value.toString ~ "; " ~ arrayExp.toString ~ ")";
if(labelName.str != "") res ~= " : " ~ labelName.str;
res ~= "\n" ~ block.toString;
return res;
}
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Foreach) || isNext(toks, TT.ForeachRev);
}
void parse(ref TokenArray toks)
{
if(isNext(toks, TT.Foreach, loc)) isReverse = false;
else if(isNext(toks, TT.ForeachRev, loc)) isReverse = true;
else assert(0);
if(!isNext(toks, TT.LeftParen))
fail("foreach statement expected '('", toks);
// Read the first variable declaration (TODO: allow
// 'ref'). Assume it is the value.
value = new VarDeclaration();
value.allowRef = true;
value.allowNoType = true;
value.parse(toks);
if(value.init !is null)
fail("Variable initializer is not allowed in foreach", loc);
if(isNext(toks, TT.Comma)) // Is there another one?
{
// The previous variable was the index, not the value
index = value;
// Sanity check the index variable. TODO: At some point it
// might be possible to mark it as constant.
if(index.var.isRef)
fail("Index cannot be a reference variable", loc);
value = new VarDeclaration();
value.allowRef = true;
value.allowNoType = true;
value.parse(toks);
if(value.init !is null)
fail("Variable initializer is not allowed in foreach", loc);
}
isRef = value.var.isRef;
if(!isNext(toks, TT.Semicolon))
fail("foreach expected ;", toks);
// Is the expression a class?
if(isNext(toks, TT.Class))
{
// Get the class name
if(!isNext(toks, TT.Identifier, className))
fail("foreach expected class name after 'class'");
isClass = true;
}
else
{
// Get the array
arrayExp = Expression.identify(toks);
}
if(!isNext(toks, TT.RightParen))
fail("foreach statement expected ')'", toks);
// Is there a loop label?
if(isNext(toks, TT.Colon))
if(!isNext(toks, TT.Identifier, labelName))
fail("foreach statement expected label identifier", toks);
// Read the statement (the 'false' parameter is to disallow
// variable declarations.)
block = CodeBlock.identify(toks, false);
}
void resolve(Scope sco)
{
if(sco.isStateCode)
fail("Foreach loops are currently not allowed in state code.");
sc = new LoopScope(sco, this);
if(isClass)
{
// Class loops
clsInfo = vm.load(className.str);
assert(clsInfo !is null);
clsInfo.requireScope();
if(index !is null)
fail("Index not allowed in class iteration");
// Set the value variable type if it is missing
if(value.var.type is null)
value.var.type = clsInfo.objType;
// Resolve and allocate stack for the value variable
value.resolve(sc);
// Check that the type is correct
if(value.var.type.toString != className.str)
fail("Loop variable must be of type " ~ className.str ~ ", not "
~ value.var.type.toString, value.var.name.loc);
// Reference variables are not allowed
if(value.var.isRef)
fail("Reference variable not allowed in class iteration.");
// Reverse is not allowed
if(isReverse)
fail("Cannot traverse class instances in reverse order.");
}
else
{
// This is for array loops
// Resolve the index, if present
if(index !is null)
{
if(index.var.type is null)
index.var.type = BasicType.getInt;
index.resolve(sc);
if(!index.var.type.isInt)
fail("foreach index type must be an int, not " ~
index.var.type.toString, index.var.name.loc);
}
// If not, allocate a stack value for it anyway.
else sc.addNewVar(1);
// Check that the array is in fact an array
arrayExp.resolve(sc);
if(!arrayExp.type.isArray)
fail("foreach expected an array, not " ~ arrayExp.toString, arrayExp.loc);
if(value.var.type is null)
value.var.type = arrayExp.type.getBase();
// This also allocates a stack value
value.resolve(sc);
if(value.var.type != arrayExp.type.getBase())
fail("foreach iteration variable must be of type " ~
arrayExp.type.getBase().toString() ~ ", not " ~
value.var.type.toString(), value.var.name.loc);
}
// Tell the scope that the iterator index is on the stack, like
// a local variable. This is not the same as the index variable
// above - the iterator index is an internal variable used by
// the system for storing the iterator state.
sc.addNewVar(1);
// This defines the stack level of the loop internals. Break and
// continue labels are set up so that they return to this level
// when invoked.
sc.stackPoint();
// Our stack now looks like this (last pushed at the top):
// For arrays:
// Array Index / iterator index
// Value variable
// Index variable
// For classes:
// Iterator index
// Object index variable
// It is important that we have called sc.addNewLocalVar() for
// each of these, to let the scope know about our stack
// usage. Otherwise local variables declared inside the loop
// body will not work correctly. This is either done explicitly
// or through VarDecl.resolve().
block.resolve(sc);
}
void compile()
{
// Get loop labels. These are set up after the loop variables
// and the array index was declared, and their stack levels
// reflect that. They must only be compiled while the variables
// are still on the stack.
LabelStatement
cont = sc.getContinue(),
brk = sc.getBreak();
setLine;
// First push the index, in case of array iteration
if(!isClass)
{
// Push the variables on the stack
if(index !is null) index.compile();
else tasm.push(0); // Create an unused int on the stack
}
// Then push the value
value.compile();
// Create iteration reference.
if(isClass)
{
// Classes. Create a class iterator from the given class
// number.
tasm.createClassIterator(clsInfo.getIndex());
}
else
{
// Arrays. First, evaluate the array expression to get the
// array index on the stack.
arrayExp.eval();
// This will replace the array index with an "iterator"
// index. The VM will handle the iteration data from there,
// it is "aware" of the index and value on the stack as
// well.
tasm.createArrayIterator(isReverse, isRef);
}
setLine();
// The rest is the same for all iterators.
// Skip the loop the array is empty
int outer = tasm.jumpz();
// Jump here to repeat the loop
int inner = tasm.label();
// Execute the block
block.compile();
// Continue statements bring us here
cont.compile();
setLine;
// Go to next iteration. Leaves the iteration index and
// variables on the stack. Pushes true if we should continue, or
// false otherwise. The iterator reference is destroyed when the
// last iteration is done.
tasm.iterateNext();
// Repeat the loop if the value is non-zero.
tasm.jumpnz(inner);
int outer2 = tasm.jump();
// Break statements get us here. We have to finish the last
// iteration step (copy ref values and delete the
// iterator). This code is NOT guaranteed to be run. If we
// break/continue to an outer loop, or return from the function,
// this will not be run and the iterator never destroyed. This
// is not a big problem though, since the iterator list is
// cleared by the garbage collector.
brk.compile();
setLine;
tasm.iterateBreak();
tasm.label(outer);
tasm.label(outer2);
// Undo the local scope
tasm.pop(sc.getLocals);
}
}
/* for loop:
for(initExpr; condExpr; iterExpr) statement
for(initExpr; condExpr; iterExpr) : label statement
initExpr is either an expression (of any type), a variable
declaration (scoped only for the interior of the loop), or empty
condExpr is an expression of type bool, or empty
iterExpr is an expression of any type, or empty
An empty condExpr is treated as true. The label, if specified, may
be used with break and continue statements inside the loop.
*/
class ForStatement : Statement
{
ExprStatement init, iter;
Expression condition;
VarDeclStatement varDec;
Token labelName;
Statement block;
LoopScope sc; // Scope for this loop
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.For);
}
void parse(ref TokenArray toks)
{
reqNext(toks, TT.For, loc);
reqNext(toks, TT.LeftParen);
// Check if the init as a variable declaration, if so then parse
// it as a statement (since that takes care of multiple
// variables as well.)
if(VarDeclStatement.canParse(toks))
{
varDec = new VarDeclStatement(true);
varDec.parse(toks); // This also kills the trailing ;
}
// Is it an empty init statement?
else if(!isNext(toks, TT.Semicolon))
{
// If not, then assume it's an expression
// statement. (Expression statements also handle
// assignments.)
init = new ExprStatement;
init.term = false;
init.parse(toks);
if(!isNext(toks, TT.Semicolon))
fail("initialization expression " ~ init.toString() ~
" must be followed by a ;", toks);
}
// Phew! Now read the conditional expression, if there is one
if(!isNext(toks, TT.Semicolon))
{
condition = Expression.identify(toks);
if(!isNext(toks, TT.Semicolon))
fail("conditional expression " ~ condition.toString() ~
" must be followed by a ;", toks);
}
// Finally the last expression statement
if(!isNext(toks, TT.RightParen))
{
iter = new ExprStatement;
iter.term = false;
iter.parse(toks);
reqNext(toks, TT.RightParen);
}
// Is there a loop label?
if(isNext(toks, TT.Colon))
if(!isNext(toks, TT.Identifier, labelName))
fail("for statement expected label identifier", toks);
// Read the statement (the 'false' parameter is to disallow
// variable declarations.)
block = CodeBlock.identify(toks, false);
}
void resolve(Scope sco)
{
sc = new LoopScope(sco, this);
// Resolve all parts
if(init !is null)
{
init.resolve(sc);
assert(varDec is null);
}
if(varDec !is null)
{
varDec.resolve(sc);
assert(init is null);
}
// This affects break and continue, making sure they set the
// stack properly when jumping.
sc.stackPoint();
if(condition !is null)
{
condition.resolve(sc);
if(!condition.type.isBool)
fail("for condition " ~ condition.toString ~ " must be a bool, not "
~condition.type.toString, condition.loc);
}
if(iter !is null) iter.resolve(sc);
block.resolve(sc);
}
void compile()
{
// Compiled in a similar way as a while loop, with an outer
// check for the initial state of the condition that is separate
// from the check after each iteration.
// Get loop labels. Remember that these are set up after the
// loop variable was declared (if any), and their stack levels
// reflect that. They must only be compiled while the variable
// is still on the stack.
LabelStatement
cont = sc.getContinue(),
brk = sc.getBreak();
// Push any local variables on the stack, or do initialization.
if(varDec !is null) varDec.compile();
else if(init !is null)
init.compile();
int outer;
// Evaluate the condition for the first iteration
if(condition !is null)
{
condition.eval();
// Skip the entire loop if the condition is not met
outer = tasm.jumpz();
}
else
// A missing condition is always met, do nothing
outer = -1;
// Jump here to repeat the loop
int inner = tasm.label();
// Execute the block
block.compile();
// Continue statements bring us here
cont.compile();
// Do the iteration step, if any
if(iter !is null) iter.compile();
// Push the conditionel expression, or assume it's true if
// missing.
if(condition !is null)
{
condition.eval();
// Repeat the loop if the value is non-zero.
tasm.jumpnz(inner);
}
else tasm.jump(inner);
if(outer != -1) tasm.label(outer);
// Break statements get us here
brk.compile();
// Undo the local scope
tasm.pop(sc.getLocals);
}
}
// Handles state = statename; Might be removed if states become a real
// type. Supports state = null; as well, to set the null state. You
// may specify a label to jump to, as state = statename.label;
class StateStatement : Statement, LabelUser
{
Token stateName, labelName;
State* stt;
LabelStatement label;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.State);
}
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.State, loc))
assert(0, "Internal error in StateStatement");
if(!isNext(toks, TT.Equals))
fail("state statement expected =", toks);
if(!isNext(toks, TT.Identifier, stateName) &&
!isNext(toks, TT.Null) )
fail("state name identifier or null expected", toks);
// Check if a label is specified
if(stateName.type == TT.Identifier)
if(isNext(toks, TT.Dot))
if(!isNext(toks, TT.Identifier, labelName))
fail("state label expected after .", toks);
reqSep(toks);
}
// Set the label to jump to. This is called from the state
// declaration. The argument is null if the label was never
// resolved.
void setLabel(LabelStatement ls)
{
if(ls is null)
fail("Undefined label '" ~ labelName.str ~ "'", labelName.loc);
label = ls;
}
void resolve(Scope sc)
{
// Check the state name.
if(stateName.type == TT.Identifier)
{
auto sl = sc.lookup(stateName);
if(!sl.isState)
fail("Undefined state " ~ stateName.str, stateName.loc);
stt = sl.state;
// If a label is specified, tell the state that we are
// asking for a label. The function will set label for us,
// either now or when the label is resolved. The label will
// in any case always be set before compile() is called.
if(labelName.str != "")
stt.registerGoto(labelName.str, this);
}
else
{
assert(labelName.str == "");
stt = null; // Signifies the empty state
label = null; // And no label
}
}
void compile()
{
setLine();
// If there is a label, make sure it has been resolved.
assert(labelName.str == "" || label !is null);
if(stt is null)
{
tasm.setState(-1, -1, 0);
assert(label is null);
return;
}
int cindex = stt.owner.getTreeIndex();
if(label is null)
tasm.setState(stt.index, -1, cindex);
else
tasm.setState(stt.index, label.lb.index, cindex);
}
}
// Handles if(expr) {} and if(expr) {} else {}. Expr must be of type
// bool. (We will not support variable declarations in the expression,
// since the strict bool requirement would make it useless anyway.)
class IfStatement : Statement
{
Expression condition;
Statement block, elseBlock;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.If);
}
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.If, loc))
assert(0, "Internal error in IfStatement");
if(!isNext(toks, TT.LeftParen))
fail("if statement expected '('", toks);
// Parse conditional expression.
condition = Expression.identify(toks);
if(!isNext(toks, TT.RightParen))
fail("if statement expected ')'", toks);
// Parse the first statement, but disallow variable
// declarations. (if(b) int i; is not allowed, but if(b) {int
// i;} works ok)
block = CodeBlock.identify(toks, false);
// We are either done now, or there is an 'else' following the
// first statement.
if(isNext(toks, TT.Else))
elseBlock = CodeBlock.identify(toks, false);
}
void resolve(Scope sc)
{
// Resolve all parts
condition.resolve(sc);
if(!condition.type.isBool &&
!condition.type.isArray &&
!condition.type.isObject)
fail("if condition " ~ condition.toString ~ " must be a bool, not "
~condition.type.toString, condition.loc);
block.resolve(sc);
if(elseBlock !is null) elseBlock.resolve(sc);
}
void compile()
{
bool hasElse = elseBlock !is null;
// Push the conditionel expression
condition.eval();
// For arrays, get the length
if(condition.type.isArray)
tasm.getArrayLength();
// TODO: For objects, the null reference will automatically
// evaluate to false. However, we should make a "isValid"
// instruction, both to check if the object is dead (deleted)
// and to guard against any future changes to the object index
// type.
assert(condition.type.getSize == 1);
// Jump if the value is zero. Get a label reference.
int label = tasm.jumpz();
// Execute the first block
block.compile();
if(hasElse)
{
// If the condition was true, we must skip the else block
int l2 = tasm.jump();
// Otherwise, we jump here to execute the else block
tasm.label(label);
elseBlock.compile();
tasm.label(l2);
}
else tasm.label(label);
}
}
// Return statement - on the form return; or return expr;
class ReturnStatement : Statement
{
Expression exp;
Function *fn;
// Number of local variables to unwind from the stack, and the
// number of parameters. (Counts number of ints / stack values, not
// actual number of variables.)
int locals;
int params;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Return);
}
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.Return, loc))
assert(0, "Internal error in ReturnStatement");
if(!isSep(toks))
{
exp = Expression.identify(toks);
reqSep(toks);
}
}
char[] toString()
{
if(exp is null) return "ReturnStatement;";
return "ReturnStatement: " ~ exp.toString;
}
void resolve(Scope sc)
{
/*
// Not allowed in state code.
if(sc.isStateCode)
fail("return not allowed in state code", loc);
*/
// Return in state code is handled as a special case
if(sc.isStateCode)
{
assert(!sc.isInFunc);
if(exp !is null)
fail("Cannot return an expression in state code", loc);
return;
}
// Store the number of local variables we have to pop of the
// stack
locals = sc.getTotLocals;
fn = sc.getFunction();
assert(fn !is null, "return called outside a function scope");
// Get the size of all parameters
params = fn.paramSize;
// Next, we must check that the returned expression, if any, is
// of the right type.
if(exp is null)
{
if(!fn.type.isVoid)
fail(format("Function expected a return value of type '%s'",
fn.type.toString()), loc);
return;
}
if(fn.type.isVoid)
fail("Function does not have a return type", loc);
exp.resolve(sc);
fn.type.typeCast(exp, "return value");
}
void compile()
{
setLine();
if(exp !is null && fn !is null)
{
assert(!fn.type.isVoid);
exp.eval();
// Return an expression
tasm.exit(params, locals, exp.type.getSize);
}
else
// Return without an expression
tasm.exit(params, locals, 0);
}
}
// A block of executable statements between a pair of curly braces {}
class CodeBlock : Statement
{
StateArray contents;
Scope sc;
Floc endLine; // Last line, used in error messages
bool isState; // True if this block belongs to a state
bool isFile; // True if this is a stand-alone file
private:
char[] stateName; // Name of the state, used to set up the
// scope. Really a hack but it works.
// Parses the given tokens as a statement. The second parameter
// specifies if the statement is allowed to be a variable
// declaration (for example, "if (expr) int i;" is not allowed.)
static Statement identify(ref TokenArray toks, bool allowVar = true)
{
Statement b = null;
if(VarDeclStatement.canParse(toks))
{
if(!allowVar) fail("Variable declaration not allowed here", toks[0].loc);
b = new VarDeclStatement;
}
else if(CodeBlock.canParse(toks)) b = new CodeBlock;
else if(ReturnStatement.canParse(toks)) b = new ReturnStatement;
else if(IfStatement.canParse(toks)) b = new IfStatement;
else if(DoWhileStatement.canParse(toks)) b = new DoWhileStatement;
else if(WhileStatement.canParse(toks)) b = new WhileStatement;
else if(ForStatement.canParse(toks)) b = new ForStatement;
else if(StateStatement.canParse(toks)) b = new StateStatement;
else if(LabelStatement.canParse(toks)) b = new LabelStatement;
else if(GotoStatement.canParse(toks)) b = new GotoStatement;
else if(ContinueBreakStatement.canParse(toks)) b = new ContinueBreakStatement;
else if(ForeachStatement.canParse(toks)) b = new ForeachStatement;
else if(ImportStatement.canParse(toks)) b = new ImportStatement(true);
// switch / select
// case
// assert ?
// If this is not one of the above, default to an expression
// statement.
else b = new ExprStatement;
b.parse(toks);
return b;
}
public:
this(bool isState = false, bool isFile = false)
{
this.isState = isState;
this.isFile = isFile;
assert(!isFile || !isState);
}
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.LeftCurl);
}
void parse(ref TokenArray toks)
{
Token strt;
if(!isFile)
{
if(toks.length == 0)
fail("Code block expected, got end of file");
strt = toks[0];
if(strt.type != TT.LeftCurl)
fail("Code block expected a {", toks);
loc = strt.loc;
toks = toks[1..toks.length];
}
// Are we parsing stuff that comes before the code? Only
// applicable to state blocks.
bool beforeCode = isState;
bool theEnd()
{
if(isFile)
return isNext(toks, TT.EOF, endLine);
else
return isNext(toks, TT.RightCurl, endLine);
}
while(!theEnd())
{
if(toks.length == 0)
{
if(!isFile)
fail(format("Unterminated code block (starting at line %s)", strt.loc));
else break;
}
if(beforeCode)
{
// The first label marks the begining of the code block
if(LabelStatement.canParse(toks))
{
// Let identify insert the label
contents ~= identify(toks);
// We are now parsing code
beforeCode = false;
continue;
}
// TODO: Handle state functions here
else
fail("State code must begin with a label", toks);
}
contents ~= identify(toks);
}
}
char[] toString()
{
char[] res = "Codeblock: ";
for(int i=0; i<contents.length; i++)
res ~= "\n " ~ contents[i].toString();
return res;
}
void resolve(Scope prev)
{
// Set up the local scope
sc = new CodeScope(prev, this);
// Make sure that isStateCode is true at the main state level.
assert(!isState || sc.isStateCode());
foreach(int i, stats; contents)
{
static if(traceResolve)
writefln("Resolving %s at %s", stats, stats.loc);
stats.resolve(sc);
}
// TODO: Check that state code contains at least one idle
// function call. We could do that through the scope.
}
void compile()
{
foreach(st; contents)
st.compile();
setLine();
// If this is the main block at the state level, we must finish
// it with an exit instruction.
if(isState)
tasm.exit(0,0,0);
// Local variables are forbidden in any state block, no matter
// at what level they are
if(sc.isStateCode)
assert(sc.getLocals == 0);
else
// Remove local variables from the stack
tasm.pop(sc.getLocals);
}
}
// Used for declarations that create new types, like enum, struct,
// etc.
abstract class TypeDeclaration : Statement
{
override void compile() {}
// Insert the type into the scope. This is called before resolve().
void insertType(TFVScope sc);
}