/* Monster - an advanced game scripting language Copyright (C) 2007-2009 Nicolay Korslund Email: WWW: http://monster.snaptoad.com/ This file (variables.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.variables; import monster.compiler.types; import monster.compiler.tokenizer; import monster.compiler.expression; import monster.compiler.scopes; import monster.compiler.statement; import monster.compiler.states; import monster.compiler.block; import monster.compiler.operators; import monster.compiler.assembler; import std.string; import std.stdio; import monster.vm.mclass; import monster.vm.error; import monster.vm.vm; enum VarType { Class, Param, Local, } struct Variable { Type type; VarType vtype; Token name; VarScope sc; // Scope that owns this variable int number; // Index used in bytecode to reference this variable. It // corresponds to the stack offset for local variables / // parameters, and the data segment offset internally // for the given class for class variables. bool isRef; // Is this a reference variable? bool isConst; // Used for function parameters bool isVararg; // A vararg function parameter } // Variable declaration. Handles local and class variables, function // parameters and loop variables. class VarDeclaration : Block { Variable *var; // Initializer expression, if any. Eg: // int i = 3; => init = 3. Expression init; bool allowRef; // Allows reference variable. bool allowNoType; // Allow no type to be specified (used in foreach) bool allowConst; // Allows const. this() {} // Used when the type is already given, and we only need to read the // name and what follows. This is used for multiple declarations, // ie. int i=1, j=2; and will possibly also be used in other places // later. this(Type type) { assert(var is null); var = new Variable; var.type = type; } // Parse keywords allowed on variables private void parseKeywords(ref TokenArray toks) { Floc loc; while(1) { if(isNext(toks, TT.Ref, loc)) { if(var.isRef) fail("Multiple token 'ref' in variable declaration", loc); if(!allowRef) fail("You cannot use 'ref' variables here", loc); var.isRef = true; continue; } if(isNext(toks, TT.Const, loc)) { if(var.isConst) fail("Multiple token 'const' in variable declaration", loc); var.isConst = true; continue; } break; } } // Parse a series of array specifiers, ie. // [] // [][]... // [expr1][expr2].... // If takeExpr = false then the last form is not allowed static ExprArray getArray(ref TokenArray toks, bool takeExpr = false) { // Arrays? ExprArray arrayArgs; while(isNext(toks,TT.LeftSquare)) { Expression expr = null; // Is there an expression inside the brackets? if(!isNext(toks, TT.RightSquare)) { Floc loc = getLoc(toks); expr = Expression.identify(toks); if(!takeExpr) fail("Array expression [" ~ expr.toString ~ "] not allowed here", loc); if(!isNext(toks, TT.RightSquare)) fail("Expected matching ]", toks); } // Insert the expression (or a null if the brackets were // empty) arrayArgs ~= expr; } return arrayArgs; } // Get the total number of array dimensions. Eg. int[] j[][]; has a // total of three dimensions. This is now handled entirely by the // Type class so this function is here for backwards compatability. int arrays() { return var.type.arrays; } // This is slightly messy. But what I'm trying to do IS slightly // messy. static bool hasType(TokenArray toks) { // Remove the type, if any if(!Type.canParseRem(toks)) return false; // Skip any keywords while(1) { if(isNext(toks, TT.Ref)) continue; break; } // There must be a variable identifier at the end return isNext(toks, TT.Identifier); } override void parse(ref TokenArray toks) { if(var is null) { var = new Variable; // Keywords may come before or after the type parseKeywords(toks); // Parse the type, if any. if(!allowNoType || hasType(toks)) { var.type = Type.identify(toks); parseKeywords(toks); } } // The type was already set externally. else { assert(var.type !is null); // allowNoType is not used in these cases assert(allowNoType == false); } if(!isNext(toks, TT.Identifier, var.name)) fail("Variable name must be identifier", toks); loc = var.name.loc; // Look for arrays after the variable name. ExprArray arrayArgs = getArray(toks); /* We must append these arrays to the type, in the reverse order. Eg. int[1][2] i[4][3]; is the same as int[1][2][3][4] i; (also the same as int i[4][3][2][1];) Since we don't take expressions here yet the order is irrelevant, but let's set it up right. */ foreach_reverse(e; arrayArgs) { assert(e is null); var.type = new ArrayType(var.type); } // Does the variable have an initializer? if(isNext(toks, TT.Equals)) init = Expression.identify(toks); } char[] toString() { char[] res = var.type.toString() ~ " " ~ var.name.str; if(init !is null) res ~= " = " ~ init.toString; return res; } bool isParam = false; // Special version only called from FuncDeclaration.resolve() void resolveParam(Scope sc) { isParam = true; resolve(sc); } // Sets var.number. Only for function parameters. void setNumber(int num) { assert(num<0, "VarDec.setNumber was given a positive num: " ~ .toString(num)); assert(isParam); var.number = num; } // Calls resolve() for all sub-expressions override void resolve(Scope sc) { var.type.resolve(sc); if(var.type.isReplacer) var.type = var.type.getBase(); // Allow 'const' for function array parameters if(isParam && var.type.isArray()) allowConst = true; // Handle initial value, if present if(init !is null) { init.resolve(sc); // For now, var is disallowed for function parameters (will // be enabled again when dynamic types are implemented.) if(var.type.isVar && isParam) fail("var type not allowed in parameters", var.type.loc); // If 'var' is present, just copy the type of the init value if(var.type.isVar) var.type = init.type; else { // Convert type, if necessary. var.type.typeCast(init, var.name.str); } assert(init.type == var.type); } // If it's a struct, check that the variable is not part of // itself. if(var.type.isStruct) { auto st = cast(StructType)var.type; if(st.sc is sc) { // We are inside ourselves assert(sc.isStruct); fail("Struct variables cannot be declared inside the struct itself!", loc); } } // We can't have var at this point if(var.type.isVar) fail("Cannot implicitly determine type", loc); // Nor can we have void types if(var.type.isVoid) fail("Cannot declare variables with no type", loc); // Illegal types are illegal if(!var.type.isLegal) fail("Cannot create variables of type '" ~ var.type.toString ~ "'", loc); if(!allowConst && var.isConst) fail("'const' is not allowed here", loc); // Store the scope in the var struct for later referral. var.sc = cast(VarScope)sc; assert(var.sc !is null, "variables can only be declared in VarScopes"); if(!isParam) // If we are not a function parameter, we must get // var.number from the scope. var.number = sc.addNewVar(var.type.getSize()); else assert(sc.isFunc()); // Insert ourselves into the scope. var.sc.insertVar(var); } int[] getCTimeValue() out(res) { assert(res.length == var.type.getSize, "Size mismatch"); } body { // Does this variable have an initializer? if(init !is null) { // And can it be evaluated at compile time? if(!init.isCTime) fail("Expression " ~ init.toString ~ " is not computable at compile time", init.loc); return init.evalCTime(); } else // Use the default initializer. return var.type.defaultInit(); } // Executed for local variables upon declaration. Push the variable // on the stack. void compile() { // Validate the type var.type.validate(loc); setLine(); if(init !is null) // Push the initializer init.eval(); else // Default initializer var.type.pushInit(); } } // Declaration that overrides a default variable value in a parent // class. Only used at the class scope. Takes the form // varname=expression; Is also used to set the default state. class ClassVarSet : Block { Token name; Expression value; Variable *var; StateStatement stateSet; // The class owning the variable. NOT the same as the class we're // defined in. MonsterClass cls; static bool canParse(TokenArray toks) { if(isNext(toks, TT.Identifier) && isNext(toks, TT.Equals)) return true; if(isNext(toks, TT.State) && isNext(toks, TT.Equals)) return true; return false; } bool isState() { return stateSet !is null; } void parse(ref TokenArray toks) { if(isNext(toks, TT.Identifier, name)) { // Setting normal variable reqNext(toks, TT.Equals); value = Expression.identify(toks); loc = name.loc; } else { // Setting the state - use a StateStatement to handle // everything. assert(toks.length >= 1 && toks[0].type == TT.State); stateSet = new StateStatement; stateSet.parse(toks); loc = stateSet.loc; } reqSep(toks); } void resolve(Scope sc) { // Get the class we're defined in assert(sc.isClass); auto lc = sc.getClass(); assert(lc !is null); // Are we setting the state? if(stateSet !is null) { assert(value is null); // Make stateSet find the state and label pointers stateSet.resolve(sc); // Tell the class about us StateLabel *lb = null; if(stateSet.label !is null) lb = stateSet.label.lb; lc.setDefaultState(stateSet.stt, lb); return; } // We're setting a normal variable assert(stateSet is null); // Find the variable assert(lc.parents.length <= 1); var = null; // TODO: If we do mi later, create a parentLookup which handles // standard error cases for us. if(lc.parents.length == 1) { auto ln = lc.parents[0].sc.lookup(name); if(ln.isVar) { var = ln.var; cls = ln.sc.getClass(); } } if(var is null) { // No var found. It's possible that there's a match in our // own class though - that's still an error, but should give // a slightly different error message auto ln = lc.sc.lookup(name); if(ln.isVar) fail("Cannot override variable " ~ name.str ~ " because it was defined in the same class", name.loc); fail("No variable named " ~ name.str ~ " in parent classes of " ~ lc.name.str, name.loc); } assert(var.vtype == VarType.Class); assert(cls !is null); assert(cls.parentOf(lc)); value.resolve(sc); // Convert the type var.type.typeCast(value, var.name.str); if(!value.isCTime) fail("Expression " ~ value.toString ~ " cannot be computed at compile time", value.loc); } // Apply this data change to the given data segment void apply(int[] data) { assert(!isState); assert(var !is null); assert(cls.dataSize == data.length); assert(var.number >= 0); assert(var.number+var.type.getSize <= data.length); // Copy the data int start = var.number; int end = var.number + var.type.getSize; data[start..end] = value.evalCTime(); } char[] toString() { char[] res; if(cls !is null) res = cls.getName() ~ "."; if(isState) { res ~= "state=" ~ stateSet.stateName.str; if(stateSet.labelName.str != "") res ~= "." ~ stateSet.labelName.str; } else res ~= name.str ~ "=" ~ value.toString; return res; } } // Represents a reference to an identifier member, such as a variable, // function or property. Can also refer to type names. Simply stores // the token representing the identifier. Special names (currently // only __STACK__) are also handled. class MemberExpr : Expression { Token name; ScopeLookup look; // Used to simulate a member for imported variables DotOperator dotImport; bool recurse = true; enum VType { None, // Should never be set LocalVar, // Local variable ThisVar, // Variable in this object and this class ParentVar, // Variable in another class but this object FarOtherVar, // Another class, another object Property, // Property (like .length of arrays) Special, // Special name (like __STACK__) Function, // Function Type, // Typename Package, // Package } VType vtype; CIndex singCls = -1; // Singleton class index static bool canParse(TokenArray toks) { return isNext(toks, TT.Identifier) || isNext(toks, TT.Singleton) || isNext(toks, TT.State) || isNext(toks, TT.Clone) || isNext(toks, TT.Var) || isNext(toks, TT.Const); } // Does this variable name refer to a type name rather than an // actual variable? TODO: Could be swapped for "requireStatic" or // similar in expression instead. bool isType() { return type.isMeta(); } bool isProperty() out(res) { if(res) { assert(vtype == VType.Property); assert(!isSpecial); } else assert(vtype != VType.Property); } body { return look.isProperty(); } bool isSpecial() { return vtype == VType.Special; } bool isPackage() { return vtype == VType.Package; } // Is this a static member bool isStatic() { // Properties can be static if(isProperty) return look.isPropStatic; // Type names are always static. if(isType) return true; // Singletons are also static members (of packages), even if the // type is not a meta type if(singCls != -1) return true; // Ditto for packages if(isPackage) return true; // Currently no other static variables return false; } void resolveMember(Scope sc, Type ownerType) { assert(ownerType !is null); assert(sc !is null); Scope leftScope; leftScope = ownerType.getMemberScope(); // Look up the name in the scope belonging to the owner assert(leftScope !is null); look = leftScope.lookupClass(name); type = look.type; // Check for member properties if(look.isProperty) { // TODO: Need to account for ownerType here somehow - // rewrite the property system vtype = VType.Property; type = look.getPropType(ownerType); return; } // The rest is common resolveCommon(ownerType, leftScope); } // Common parts for members and non-members void resolveCommon(Type ownerType, Scope sc) { bool isMember = (ownerType !is null); // Package name? if(look.isPackage) { vtype = VType.Package; return; } // Variable? if(look.isVar) { assert(look.sc !is null); assert(look.sc is look.var.sc); if(isMember) { // We are a class member variable. vtype = VType.FarOtherVar; assert(look.sc.isClass); } // This/parent class variable? else if(look.sc.isClass) { // Check if it's in THIS class, which is a common // case. If so, we can use a simplified instruction that // doesn't have to look up the class. if(look.sc.getClass is sc.getClass) vtype = VType.ThisVar; else { // It's another class. For non-members this can only // mean a parent class. vtype = VType.ParentVar; } } else // Local stack variable vtype = VType.LocalVar; } // Function? else if(look.isFunc) { // The Function* is stored in the lookup variable type = new FunctionType(look.func, isMember); vtype = VType.Function; // TODO: Make the scope set the type for us. In fact, the // type should contain the param/return type information // rather than the Function itself, the function should just // refer to it. This would make checking type compatibility // easy. } // Class name? else if(look.isClass) { if(isMember && !ownerType.isPackage) fail(format("%s cannot have class member %s", ownerType, name), loc); assert(look.mc !is null); look.mc.requireScope(); type = look.mc.classType; vtype = VType.Type; // Singletons are treated differently - the class name can // be used to access the singleton object if(look.mc.isSingleton) { type = look.mc.objType; singCls = look.mc.getIndex(); } } // Type name? else if(look.isType) { assert(look.isType); assert(look.type !is null); type = look.type.getMeta(); vtype = VType.Type; } else { // Nothing useful was found. if(isMember) fail(name.str ~ " is not a member of " ~ ownerType.toString, loc); else fail("Unknown identifier "~name.str, name.loc); } } override: char[] toString() { return name.str; } // Ask the variable if we can write to it. bool isLValue() { // Specials are read only if(isSpecial) return false; // Properties may or may not be changable if(isProperty) return look.isPropLValue; // Normal variables are always lvalues. return true; } bool isCTime() { return isType; } void parse(ref TokenArray toks) { name = next(toks); loc = name.loc; } void resolve(Scope sc) out { // Some sanity checks on the result if(look.isVar) { assert(look.var.sc !is null); assert(!isProperty); } assert(type !is null); assert(vtype != VType.None); } body { // Look for reserved names first. if(name.str == "__STACK__") { vtype = VType.Special; type = BasicType.getInt; return; } if(name.type == TT.Const || name.type == TT.Clone) fail("Cannot use " ~ name.str ~ " as a variable", name.loc); if(name.type == TT.Var) { type = GenericType.getSingleton(); vtype = VType.Special; return; } // Look ourselves up in the local scope, and include imported // scopes. look = sc.lookupImport(name); if(look.isImport) { // We're imported from another scope. This means we're // essentially a member variable. Let DotOperator handle // this. dotImport = new DotOperator(look.imphold, this, loc); dotImport.resolve(sc); assert(dotImport.type is type); return; } // These are special cases that work both as properties // (object.state) and as non-member variables (state=...) inside // class functions / state code. Since we already handle them // nicely as properties, treat them as properties even if // they're not members. if(name.type == TT.Singleton || name.type == TT.State) { if(!sc.isInClass) fail(name.str ~ " can only be used in classes", name.loc); assert(look.isProperty, name.str ~ " expression not implemented yet"); vtype = VType.Property; assert(0); // FIXME: This can't be right!? Was ownerType. type = look.getPropType(null); return; } type = look.type; resolveCommon(null, sc); } int[] evalCTime() { assert(isCTime); assert(isType); assert(type.getSize == 0); return null; } void evalAsm() { if(type.isVar) fail("Cannot use 'var' as an expression", loc); // Hairy. But does the trick for now. if(dotImport !is null && recurse) { recurse = false; dotImport.evalAsm(); return; } if(isType) return; if(type.isFunc) return; setLine(); // Special name if(isSpecial) { if(name.str == "__STACK__") tasm.getStack(); else assert(0, "Unknown special name " ~ name.str); return; } // Property if(isProperty) { look.getPropValue(); return; } // Class singleton name if(singCls != -1) { assert(type.isObject); // Convert the class index into a object index at runtime tasm.pushSingleton(singCls); return; } // Normal variable int s = type.getSize; auto var = look.var; if(vtype == VType.LocalVar) // This is a variable local to this function. The number gives // the stack position. tasm.pushLocal(var.number, s); else if(vtype == VType.ThisVar) // The var.number gives the offset into the data segment in // this class tasm.pushClass(var.number, s); else if(vtype == VType.ParentVar) // Variable in a parent but this object tasm.pushParentVar(var.number, var.sc.getClass().getTreeIndex(), s); else if(vtype == VType.FarOtherVar) // Push the value from a "FAR pointer". The class index should // already have been pushed on the stack by DotOperator, we // only push the index. tasm.pushFarClass(var.number, var.sc.getClass().getTreeIndex(), s); else assert(0, "called evalAsm on " ~ toString()); } // Push the address of the variable rather than its value void evalDest() { if(dotImport !is null && recurse) { recurse = false; dotImport.evalDest(); return; } assert(!isType, "types can never be written to"); assert(isLValue()); assert(!isProperty); setLine(); // No size information is needed for addresses. auto var = look.var; if(vtype == VType.LocalVar) tasm.pushLocalAddr(var.number); else if(vtype == VType.ThisVar) tasm.pushClassAddr(var.number); else if(vtype == VType.ParentVar) tasm.pushParentVarAddr(var.number, var.sc.getClass().getTreeIndex()); else if(vtype == VType.FarOtherVar) tasm.pushFarClassAddr(var.number, var.sc.getClass().getTreeIndex()); else assert(0); } void store() { if(dotImport !is null && recurse) { recurse = false; dotImport.store(); return; } assert(isLValue); if(isProperty) look.setPropValue(); else { assert(look.isVar); auto var = look.var; // Get the destination and move the data evalDest(); tasm.mov(type.getSize()); assert(var.sc !is null); if(var.isRef) // TODO: This assumes all ref variables are foreach values, // which will probably not be true in the future. tasm.iterateUpdate(var.sc.getLoopStack()); } } void incDec(TT op, bool post) { if(dotImport !is null && recurse) { recurse = false; dotImport.incDec(op, post); return; } if(!isProperty) { assert(look.isVar); auto var = look.var; super.incDec(op, post); assert(var.sc !is null); if(var.isRef) tasm.iterateUpdate(var.sc.getLoopStack()); } else fail("Cannot use ++ and -- on properties yet", loc); } } // A variable declaration that works as a statement. Supports multiple // variable declarations, ie. int i, j; but they must be the same // type, so int i, j[]; is not allowed. class VarDeclStatement : Statement { VarDeclaration[] vars; bool reqSemi; Type preKnownType = null; // Pass 'true' to the constructor to require a semi-colon (used eg // in for loops) this(bool rs=false) { reqSemi = rs; } // Used from the console, when the type is already known this(Type type) { preKnownType = type; } static bool canParse(TokenArray toks) { if(Type.canParseRem(toks) && isNext(toks, TT.Identifier)) return true; return false; } void parse(ref TokenArray toks) { VarDeclaration varDec; if(preKnownType !is null) varDec = new VarDeclaration(preKnownType); else varDec = new VarDeclaration; varDec.parse(toks); vars ~= varDec; loc = varDec.var.name.loc; int arr = varDec.arrays(); // Are there more? while(isNext(toks, TT.Comma)) { // Read a variable, but with the same type as the last varDec = new VarDeclaration(varDec.var.type); varDec.parse(toks); if(varDec.arrays() != arr) fail("Multiple declarations must have same type", varDec.var.name.loc); vars ~= varDec; } if(reqSemi) reqNext(toks, TT.Semicolon); else reqSep(toks); /* if(!isNext(toks, TT.Semicolon)) fail("Declaration statement expected ;", toks); */ } char[] toString() { char[] res = "Variable declaration: "; foreach(vd; vars) res ~= vd.toString ~" "; return res; } void resolve(Scope sc) { if(sc.isStateCode()) fail("Variable declarations not allowed in state code", loc); // Add variables to the scope. foreach(vd; vars) vd.resolve(sc); } // Validate types void validate() { assert(vars.length >= 1); vars[0].var.type.validate(loc); } // Insert local variable(s) on the stack. void compile() { // Compile the variable declarations, they will push the right // values to the stack. foreach(vd; vars) vd.compile(); } }