From 0d7b08cbae5090a14a27597d247083cfd1b3abaf Mon Sep 17 00:00:00 2001 From: nkorslund Date: Tue, 30 Dec 2008 01:59:24 +0000 Subject: [PATCH] - Updated to latest Monster trunk. - Changed config and music manager to singletons git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@77 ea6a568a-9f4f-0410-981a-c910a81bb256 --- monster/compiler/assembler.d | 19 +- monster/compiler/block.d | 19 + monster/compiler/bytecode.d | 10 +- monster/compiler/enums.d | 67 ++ monster/compiler/expression.d | 602 +------------- monster/compiler/functions.d | 520 ++++++++++-- monster/compiler/operators.d | 5 +- monster/compiler/properties.d | 1 + monster/compiler/scopes.d | 256 +++++- monster/compiler/statement.d | 167 ++-- monster/compiler/structs.d | 167 ++++ monster/compiler/tokenizer.d | 31 +- monster/compiler/types.d | 394 +++++++-- monster/compiler/variables.d | 558 ++++++++++++- monster/vm/fstack.d | 13 +- monster/vm/mclass.d | 328 +++++--- monster/vm/mobject.d | 20 +- monster/vm/thread.d | 1269 +++++++++++++++++++++++++++++ monster/vm/vm.d | 1235 +--------------------------- mscripts/config.mn | 7 +- mscripts/gameobjects/apparatus.mn | 7 + mscripts/gameobjects/clothing.mn | 14 + mscripts/gamesettings.mn | 2 +- mscripts/object.d | 15 +- mscripts/object.mn | 5 - mscripts/sound/jukebox.mn | 6 +- mscripts/sound/music.mn | 4 +- sound/music.d | 2 +- 28 files changed, 3521 insertions(+), 2222 deletions(-) create mode 100644 monster/compiler/enums.d create mode 100644 monster/compiler/structs.d create mode 100644 monster/vm/thread.d diff --git a/monster/compiler/assembler.d b/monster/compiler/assembler.d index e3fc00dc1a..dfac9c2f3f 100644 --- a/monster/compiler/assembler.d +++ b/monster/compiler/assembler.d @@ -625,6 +625,14 @@ struct Assembler // Push 'this', the current object reference void pushThis() { cmd(BC.PushThis); } + // Push the singleton object of the given class index + void pushSingleton(int classIndex) + { + assert(classIndex >= 0); + cmd(BC.PushSingleton); + addi(classIndex); + } + private void pushPtr(PT pt, int index) { cmd(BC.PushData); @@ -728,7 +736,11 @@ struct Assembler } } - void mov(int s) { cmd2(BC.StoreRet, BC.StoreRet8, s); } + void mov(int s) + { + if(s < 3) cmd2(BC.StoreRet, BC.StoreRet8, s); + else cmdmult(BC.StoreRet, BC.StoreRetMult, s); + } // Set object state to the given index void setState(int st, int label, int cls) @@ -748,9 +760,8 @@ struct Assembler void catArrayLeft(int s) { cmd(BC.CatLeft); addi(s); } void catArrayRight(int s) { cmd(BC.CatRight); addi(s); } - // Get the length of an array. The parameter gives element size. For - // example, an array of six ints with an element size of 2 (eg. a - // double) has length 3. + // Get the length of an array. Converts the array index to an int + // (holding the length) on the stack. void getArrayLength() { cmd(BC.GetArrLen); } // Reverse an array in place diff --git a/monster/compiler/block.d b/monster/compiler/block.d index 56a0e7c2da..1b44573b29 100644 --- a/monster/compiler/block.d +++ b/monster/compiler/block.d @@ -76,6 +76,25 @@ abstract class Block return true; } + static void reqNext(ref TokenArray toks, TT type, out Token tok) + { + if(!isNext(toks, type, tok)) + fail("Expected " ~ tokenList[type], toks); + } + + static void reqNext(ref TokenArray toks, TT type, out Floc loc) + { + Token t; + reqNext(toks, type, t); + loc = t.loc; + } + + static void reqNext(ref TokenArray toks, TT type) + { + Token t; + reqNext(toks, type, t); + } + // Sets the assembler debug line to the line belonging to this // block. final void setLine() { tasm.setLine(loc.line); } diff --git a/monster/compiler/bytecode.d b/monster/compiler/bytecode.d index 09c6f64a23..a0f42a4cee 100644 --- a/monster/compiler/bytecode.d +++ b/monster/compiler/bytecode.d @@ -108,6 +108,9 @@ enum BC PushThis, // Push the 'this' object reference + PushSingleton, // Push the singleton object. Takes the global + // class index (int) as parameter. + // The Push*8 instructions are not implemented yet as they are // just optimizations of existing features. The names are reserved // for future use. @@ -136,11 +139,14 @@ enum BC // the Ptr, and then pushes the value back. Store, // Same as StoreRet but does not push the - // value back. Not implemented. + // value back. Not implemented, but will later + // replace storeret completely. StoreRet8, // Same as StoreRet except two ints are popped // from the stack and moved into the data. + StoreRetMult, // Takes the size as an int parameter + IAdd, // Standard addition, operates on the two next // ints in the stack, and stores the result in // the stack. @@ -535,6 +541,7 @@ char[][] bcToString = BC.PushFarClassVar: "PushFarClassVar", BC.PushFarClassMulti: "PushFarClassMulti", BC.PushThis: "PushThis", + BC.PushSingleton: "PushSingleton", BC.Push8: "Push8", BC.PushLocal8: "PushLocal8", BC.PushClassVar8: "PushClassVar8", @@ -545,6 +552,7 @@ char[][] bcToString = BC.StoreRet: "StoreRet", BC.Store: "Store", BC.StoreRet8: "StoreRet8", + BC.StoreRetMult: "StoreRetMult", BC.FetchElem: "FetchElem", BC.GetArrLen: "GetArrLen", BC.IMul: "IMul", diff --git a/monster/compiler/enums.d b/monster/compiler/enums.d new file mode 100644 index 0000000000..321ebf84a6 --- /dev/null +++ b/monster/compiler/enums.d @@ -0,0 +1,67 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2007, 2008 Nicolay Korslund + Email: + WWW: http://monster.snaptoad.com/ + + This file (enums.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.enums; + +import monster.compiler.scopes; +import monster.compiler.types; +import monster.compiler.statement; +import monster.compiler.tokenizer; + +class EnumDeclaration : TypeDeclaration +{ + static bool canParse(TokenArray toks) + { return toks.isNext(TT.Enum); } + + Token name; + EnumType type; + + override: + void parse(ref TokenArray toks) + { + reqNext(toks, TT.Enum); + reqNext(toks, TT.Identifier, name); + reqNext(toks, TT.LeftCurl); + + // Just skip everything until the matching }. This lets us + // define some enums and play around in the scripts, even if it + // doesn't actually work. + while(!isNext(toks, TT.RightCurl)) next(toks); + + isNext(toks, TT.Semicolon); + } + + void insertType(TFVScope last) + { + type = new EnumType(this); + + // Insert ourselves into the parent scope + assert(last !is null); + last.insertEnum(this); + } + + // Very little to resolve, really. It's purely a declarative + // statement. + void resolve(Scope last) + {} +} diff --git a/monster/compiler/expression.d b/monster/compiler/expression.d index 5791dd6f56..83d3336557 100644 --- a/monster/compiler/expression.d +++ b/monster/compiler/expression.d @@ -72,6 +72,7 @@ abstract class Expression : Block // The rest are not allowed for members (eg. con.(a+b) is not // allowed) else if(NewExpression.canParse(toks)) b = new NewExpression; + else if(TypeofExpression.canParse(toks)) b = new TypeofExpression; else if(LiteralExpr.canParse(toks)) b = new LiteralExpr; else if(ArrayLiteralExpr.canParse(toks)) b = new ArrayLiteralExpr; // Sub-expression (expr) @@ -450,6 +451,32 @@ abstract class Expression : Block int[] evalCTime() { assert(0); return null; } } +// Handles typeof(exp), returns an empty expression with the meta-type +// of exp. +class TypeofExpression : Expression +{ + TypeofType tt; + + static bool canParse(TokenArray toks) + { return TypeofType.canParse(toks); } + + void parse(ref TokenArray toks) + { + // Let TypeofType handle the details + tt = new TypeofType; + tt.parse(toks); + } + + void resolve(Scope sc) + { + tt.resolve(sc); + type = tt.getBase().getMeta(); + } + + // Don't actually produce anything + void evalAsm() {} +} + // new-expressions, ie. (new Sometype[]). class NewExpression : Expression { @@ -471,9 +498,7 @@ class NewExpression : Expression void parse(ref TokenArray toks) { - if(!isNext(toks, TT.New, loc)) - assert(0, "Internal error in NewExpression"); - + reqNext(toks, TT.New, loc); type = Type.identify(toks, true, exArr); } @@ -486,6 +511,9 @@ class NewExpression : Expression { type.resolve(sc); + if(type.isReplacer) + type = type.getBase(); + if(type.isObject) { // We need to find the index associated with this class, and @@ -600,12 +628,11 @@ class ArrayLiteralExpr : Expression void parse(ref TokenArray toks) { - if(!isNext(toks, TT.LeftSquare, loc)) - assert(0, "Internal error in ArrayLiteralExpr"); + reqNext(toks, TT.LeftSquare, loc); Floc loc2; if(isNext(toks, TT.RightSquare, loc2)) - fail("Array literal cannot be empty", loc2); + fail("Array literal cannot be empty. Use 'null' instead.", loc2); // Read the first expression params ~= Expression.identify(toks); @@ -614,8 +641,7 @@ class ArrayLiteralExpr : Expression while(isNext(toks, TT.Comma)) params ~= Expression.identify(toks); - if(!isNext(toks, TT.RightSquare)) - fail("Array literal expected closing ]", toks); + reqNext(toks, TT.RightSquare); } char[] toString() @@ -642,7 +668,7 @@ class ArrayLiteralExpr : Expression // Set the type Type base = params[0].type; - type = new ArrayType(base); + type = ArrayType.get(base); foreach(ref par; params) { @@ -656,8 +682,10 @@ class ArrayLiteralExpr : Expression cls = sc.getClass(); + /* if(isCTime) cls.reserveStatic(params.length * base.getSize); + */ } int[] evalCTime() @@ -682,9 +710,8 @@ class ArrayLiteralExpr : Expression foreach(i, par; params) data[i*elem..(i+1)*elem] = par.evalCTime(); - // Insert the array into the static data, and get the array - // reference - arrind = cls.insertStatic(data, elem); + // Create the array and get the index + arrind = arrays.createConst(data, elem).getIndex; // Delete the array, if necessary if(data.length > LEN) @@ -877,7 +904,7 @@ class LiteralExpr : Expression // Strings if(value.type == TT.StringLiteral) { - type = new ArrayType(BasicType.getChar); + type = ArrayType.getString; // Check that we do indeed have '"'s at the ends of the // string. Special cases which we allow later (like wysiwig @@ -887,7 +914,7 @@ class LiteralExpr : Expression "Encountered invalid string literal token: " ~ value.str); strVal = toUTF32(value.str[1..$-1]); - cls.reserveStatic(strVal.length); + //cls.reserveStatic(strVal.length); return; } @@ -914,9 +941,8 @@ class LiteralExpr : Expression if(type.isString) { - // Insert the array into the static data, and get the array - // reference - arrind = cls.insertStatic(cast(int[])strVal, 1); + // Create array index + arrind = arrays.createConst(cast(int[])strVal, 1).getIndex; // Create an array from it return (cast(int*)&arrind)[0..1]; } @@ -971,548 +997,6 @@ abstract class MemberExpression : Expression } } -// Represents a reference to a variable. Simply stores the token -// representing the identifier. Evaluation is handled by the variable -// declaration itself. This allows us to use this class for local and -// global variables as well as for properties, without handling each -// case separately. The special names (currently __STACK__) are -// handled internally. -class VariableExpr : MemberExpression -{ - Token name; - Variable *var; - Property prop; - - 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__) - Type, // Typename - } - - VType vtype; - int classIndex = -1; // Index of the class that owns this variable. - - 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.Const); - } - - void parse(ref TokenArray toks) - { - name = next(toks); - loc = name.loc; - } - - // Does this variable name refer to a type name rather than an - // actual variable? - bool isType() - { - return type.isMeta(); - } - - bool isProperty() - out(res) - { - if(res) - { - assert(prop.name != ""); - assert(var is null); - assert(!isSpecial); - } - else - { - assert(prop.name == ""); - } - } - body - { - return vtype == VType.Property; - } - - bool isSpecial() { return vtype == VType.Special; } - - 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 prop.isLValue; - - // Normal variables are always lvalues. - return true; - } - - bool isStatic() - { - // Properties can be static - if(isProperty) - return prop.isStatic; - - // Type names are always static (won't be true for type - // variables later, though.) - if(isType) - return true; - - return false; - } - - // TODO: isCTime - should be usable for static properties and members - - void writeProperty() - { - assert(isProperty); - prop.setValue(); - } - - void resolve(Scope sc) - out - { - // Some sanity checks on the result - if(isProperty) assert(var is null); - if(var !is null) - { - assert(var.sc !is null); - assert(!isProperty); - } - assert(type !is null); - assert(vtype != VType.None); - } - body - { - if(isMember) // Are we called as a member? - { - // Look up the name in the scope belonging to the owner - assert(leftScope !is null); - - // Check first if this is a variable - var = leftScope.findVar(name.str); - if(var !is null) - { - // We are a member variable - type = var.type; - - // The object pointer is pushed on the stack. We must - // also provide the class index, so the variable is - // changed in the correct class (it could be a parent - // class of the given object.) - vtype = VType.FarOtherVar; - assert(var.sc.isClass); - classIndex = var.sc.getClass().getIndex(); - - return; - } - - // Check for properties last - if(leftScope.findProperty(name, ownerType, prop)) - { - // We are a property - vtype = VType.Property; - type = prop.getType; - return; - } - - // No match - fail(name.str ~ " is not a member of " ~ ownerType.toString, - loc); - } - - // Not a member - - // Look for reserved names first. - if(name.str == "__STACK__") - { - vtype = VType.Special; - type = BasicType.getInt; - return; - } - - if(name.type == TT.Const) - fail("Cannot use const as a variable", name.loc); - - if(name.type == TT.Clone) - fail("Cannot use clone as a variable", name.loc); - - // 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. - if(name.type == TT.Singleton || name.type == TT.State) - { - if(!sc.isInClass) - fail(name.str ~ " can only be used in classes", name.loc); - - if(!sc.findProperty(name, sc.getClass().objType, prop)) - assert(0, "should have found property " ~ name.str ~ - " in scope " ~ sc.toString); - - vtype = VType.Property; - type = prop.getType; - return; - } - - // Not a member, property or a special name. Look ourselves up - // in the local variable scope. - var = sc.findVar(name.str); - - if(var !is null) - { - type = var.type; - - assert(var.sc !is null); - - // Class variable? - if(var.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(var.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; - classIndex = var.sc.getClass().getIndex(); - } - } - else - vtype = VType.LocalVar; - - return; - } - - // We are not a variable. Maybe we're a basic type? - if(BasicType.isBasic(name.str)) - { - // Yes! - type = MetaType.get(name.str); - vtype = VType.Type; - return; - } - - // No match at all - fail("Undefined identifier "~name.str, name.loc); - } - - void evalAsm() - { - // If we are a type, just do nothing. If the type is used for - // anything, it will be typecast and all the interesting stuff - // will be handled by the type system. - if(isType) return; - - setLine(); - - // Special name - if(isSpecial) - { - if(name.str == "__STACK__") - tasm.getStack(); - else assert(0, "Unknown special name " ~ name.str); - return; - } - - // Property - if(isProperty) - { - prop.getValue(); - return; - } - - // Normal variable - - int s = type.getSize; - - 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, classIndex, 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, classIndex, s); - - else assert(0); - } - - // Push the address of the variable rather than its value - void evalDest() - { - assert(!isType, "types can never be written to"); - assert(isLValue()); - assert(!isProperty); - - setLine(); - - // No size information is needed for addresses. - - 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, classIndex); - else if(vtype == VType.FarOtherVar) - tasm.pushFarClassAddr(var.number, classIndex); - - else assert(0); - } - - void postWrite() - { - assert(!isProperty); - assert(isLValue()); - 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()); - } -} - -// Expression representing a function call -class FunctionCallExpr : MemberExpression -{ - Token name; - ExprArray params; - Function* fd; - - bool isVararg; - - static bool canParse(TokenArray toks) - { - return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen); - } - - // Read a parameter list (a,b,...) - static ExprArray getParams(ref TokenArray toks) - { - ExprArray res; - if(!isNext(toks, TT.LeftParen)) return res; - - Expression exp; - - // No parameters? - if(isNext(toks, TT.RightParen)) return res; - - // Read the first parameter - res ~= Expression.identify(toks); - - // Are there more? - while(isNext(toks, TT.Comma)) - res ~= Expression.identify(toks); - - if(!isNext(toks, TT.RightParen)) - fail("Parameter list expected ')'", toks); - - return res; - } - - void parse(ref TokenArray toks) - { - name = next(toks); - loc = name.loc; - - params = getParams(toks); - } - - char[] toString() - { - char[] result = name.str ~ "("; - foreach(b; params) - result ~= b.toString ~" "; - return result ~ ")"; - } - - void resolve(Scope sc) - { - if(isMember) // Are we called as a member? - { - assert(leftScope !is null); - fd = leftScope.findFunc(name.str); - if(fd is null) fail(name.str ~ " is not a member function of " - ~ leftScope.toString, loc); - } - else - { - assert(leftScope is null); - fd = sc.findFunc(name.str); - if(fd is null) fail("Undefined function "~name.str, name.loc); - } - - // Is this an idle function? - if(fd.isIdle) - { - if(!sc.isStateCode) - fail("Idle functions can only be called from state code", - name.loc); - - if(isMember) - fail("Idle functions cannot be called as members", name.loc); - } - - type = fd.type; - assert(type !is null); - - isVararg = fd.isVararg; - - if(isVararg) - { - // The vararg parameter can match a variable number of - // arguments, including zero. - if(params.length < fd.params.length-1) - fail(format("%s() expected at least %s parameters, got %s", - name.str, fd.params.length-1, params.length), - name.loc); - } - else - // Non-vararg functions must match function parameter number - // exactly - if(params.length != fd.params.length) - fail(format("%s() expected %s parameters, got %s", - name.str, fd.params.length, params.length), - name.loc); - - // Check parameter types - foreach(int i, par; fd.params) - { - // Handle varargs below - if(isVararg && i == fd.params.length-1) - break; - - params[i].resolve(sc); - try par.type.typeCast(params[i]); - catch(TypeException) - fail(format("%s() expected parameter %s to be type %s, not type %s", - name.str, i+1, par.type.toString, params[i].typeString), - name.loc); - } - - // Loop through remaining arguments - if(isVararg) - { - int start = fd.params.length-1; - - assert(fd.params[start].type.isArray); - Type base = fd.params[start].type.getBase(); - - foreach(int i, ref par; params[start..$]) - { - par.resolve(sc); - - // If the first and last vararg parameter is of the - // array type itself, then we are sending an actual - // array. Treat it like a normal parameter. - if(i == 0 && start == params.length-1 && - par.type == fd.params[start].type) - { - isVararg = false; - break; - } - - // Otherwise, cast the type to the array base type. - try base.typeCast(par); - catch(TypeException) - fail(format("Cannot convert %s of type %s to %s", par.toString, - par.typeString, base.toString), par.loc); - } - } - } - - // Used in cases where the parameters need to be evaluated - // separately from the function call. This is done by DotOperator, - // in cases like obj.func(expr); Here expr is evaluated first, then - // obj, and then finally the far function call. This is because the - // far function call needs to read 'obj' off the top of the stack. - void evalParams() - { - assert(pdone == false); - - foreach(i, ex; params) - { - ex.eval(); - - // Convert 'const' parameters to actual constant references - if(i < fd.params.length-1) // Skip the last parameter (in - // case of vararg functions) - if(fd.params[i].isConst) - { - assert(fd.params[i].type.isArray); - tasm.makeArrayConst(); - } - } - - if(isVararg) - { - // Compute the length of the vararg array. - int len = params.length - fd.params.length + 1; - - // If it contains no elements, push a null array reference - // (0 is always null). - if(len == 0) tasm.push(0); - else - // Converte the pushed values to an array index - tasm.popToArray(len, params[$-1].type.getSize()); - } - - // Handle the last parameter after everything has been - // pushed. This will work for both normal and vararg parameters. - if(fd.params.length != 0 && fd.params[$-1].isConst) - { - assert(fd.params[$-1].type.isArray); - tasm.makeArrayConst(); - } - - pdone = true; - } - - bool pdone; - - void evalAsm() - { - if(!pdone) evalParams(); - setLine(); - assert(fd.owner !is null); - - if(isMember) - tasm.callFarFunc(fd.index, fd.owner.getIndex()); - else if(fd.isIdle) - tasm.callIdle(fd.index, fd.owner.getIndex()); - else - tasm.callFunc(fd.index, fd.owner.getIndex()); - } -} - // Expression that handles conversion from one type to another. This // is used for implisit casts (eg. when using ints and floats // together) and in the future it will also parse explisit casts. It diff --git a/monster/compiler/functions.d b/monster/compiler/functions.d index 05bc6da601..c7f642e28b 100644 --- a/monster/compiler/functions.d +++ b/monster/compiler/functions.d @@ -39,6 +39,7 @@ import monster.compiler.types; import monster.compiler.assembler; import monster.compiler.bytecode; import monster.compiler.scopes; +import monster.compiler.expression; import monster.compiler.variables; import monster.compiler.tokenizer; import monster.compiler.linespec; @@ -49,8 +50,11 @@ import monster.vm.idlefunction; import monster.vm.mclass; import monster.vm.error; import monster.vm.fstack; +import monster.vm.vm; import std.stdio; +import std.stream; +import std.string; // One problem with these split compiler / vm classes is that we // likely end up with data (or at least pointers) we don't need, and a @@ -81,7 +85,13 @@ struct Function int paramSize; int imprint; // Stack imprint of this function. Equals - // (type.getSize() - paramSize) + // (type.getSize() - paramSize) (NOT USED YET) + + // Is this function final? (can not be overridden in child classes) + bool isFinal; + + // What function we override (if any) + Function *overrides; union { @@ -117,32 +127,16 @@ struct Function // you. A c++ version could be much more difficult to handle, and // might rely more on manual coding. - // Used to find the current virtual replacement for this - // function. The result will depend on the objects real class, and - // possibly on the object state. Functions might also be overridden - // explicitly. - Function *findVirtual(MonsterObject *obj) - { - assert(0, "not implemented"); - //return obj.upcast(owner).getVirtual(index); - } - - // Call the function virtually for the given object - void vcall(MonsterObject *obj) - { - assert(0, "not implemented"); - //obj = obj.upcast(owner); - //obj.getVirtual(index).call(obj); - } - // This is used to call the given function from native code. Note // that this is used internally for native functions, but not for // any other type. Idle functions can NOT be called directly from // native code. void call(MonsterObject *obj) { + assert(obj !is null); + // Cast the object to the correct type for this function. - obj = obj.upcast(owner); + obj = obj.Cast(owner); // Push the function on the stack fstack.push(this, obj); @@ -175,9 +169,84 @@ struct Function fstack.pop(); } + // Call without an object. TODO: Only allowed for functions compiled + // without a class using compile() below, but in the future this + // will be replaced with a static function. + void call() + { + assert(owner is int_mc); + assert(owner !is null); + assert(int_mo !is null); + call(int_mo); + } + + static Function opCall(char[] file, MonsterClass mc = null) + { + Function fn; + fn.compile(file, mc); + return fn; + } + + // Compile the function script 'file' in the context of the class + // 'mc'. If no class is given, use an empty internal class. + void compile(char[] file, MonsterClass mc = null) + { + assert(name.str == "", + "Function " ~ name.str ~ " has already been set up"); + + // Check if the file exists + if(!vm.findFile(file)) + fail("File not found: " ~ file); + + // Set mc to an empty class if no class is given + if(mc is null) + { + if(int_mc is null) + { + assert(int_mo is null); + + int_mc = new MonsterClass(MC.String, int_class); + int_mo = int_mc.createObject; + } + assert(int_mo !is null); + + mc = int_mc; + } + + // TODO: Maybe we should centralize this stuff somewhere. + // Especially since we have to reinvent loading from string or + // other custom streams, and so on. It's easier to put the tools + // for this here than to have them in MonsterClass. Fix this later + // when you see how things turn out. + auto bf = new BufferedFile(file); + auto ef = new EndianStream(bf); + int bom = ef.readBOM(); + TokenArray tokens = tokenizeStream(file, ef, bom); + delete bf; + + // Check if this is a class or a module file first + if(MonsterClass.canParse(tokens)) + fail("Cannot run " ~ file ~ " - it is a class or module."); + + auto fd = new FuncDeclaration; + // Parse and comile the function + fd.parseFile(tokens, this); + fd.resolve(mc.sc); + fd.resolveBody(); + fd.compile(); + assert(fd.fn == this); + delete fd; + } + // Returns the function name, on the form Class.func() char[] toString() { return owner.name.str ~ "." ~ name.str ~ "()"; } + + private: + + static const char[] int_class = "class _Func_Internal_;"; + static MonsterClass int_mc; + static MonsterObject *int_mo; } // Responsible for parsing, analysing and compiling functions. @@ -191,7 +260,15 @@ class FuncDeclaration : Statement // the VM when the compiler is done working. Function *fn; - // Parse keywords allowed to be used on functions + // Is the 'override' keyword present + bool isOverride; + + // Is this a stand-alone script file (not really needed) + bool isFile; + + // Parse keywords allowed to be used on functions. This (and its + // borthers elsewhere) is definitely ripe for some pruning / + // refactoring. private void parseKeywords(ref TokenArray toks) { Floc loc; @@ -213,7 +290,7 @@ class FuncDeclaration : Statement } if(isNext(toks, TT.Abstract, loc)) { - if(isNative) + if(isAbstract) fail("Multiple token 'abstract' in function declaration", loc); isAbstract = true; @@ -227,10 +304,26 @@ class FuncDeclaration : Statement isIdle = true; continue; } + if(isNext(toks, TT.Override, loc)) + { + if(isOverride) + fail("Multiple token 'override' in function declaration", + loc); + isOverride = true; + continue; + } + if(isNext(toks, TT.Final, loc)) + { + if(fn.isFinal) + fail("Multiple token 'final' in function declaration", + loc); + fn.isFinal = true; + continue; + } break; } - // Check that only one of the keywords are used + // Check that only one of the type keywords are used if( (isAbstract && isNative) || (isAbstract && isIdle) || (isNative && isIdle) ) @@ -243,6 +336,43 @@ class FuncDeclaration : Statement else assert(fn.isNormal); } + void parseFile(ref TokenArray toks, Function *fnc) + { + isFile = true; + fn = fnc; + fn.ftype = FuncType.Normal; + + // Is there an explicit function declaration? + if(isNext(toks, TT.Function)) + { + TokenArray temp = toks; + + reqNext(temp, TT.Identifier); + + // Is this a function without type? + if(isFuncDec(toks)) + // If so, set the type to void + fn.type = BasicType.getVoid; + else + // Otherwise, parse it + fn.type = Type.identify(toks); + + // In any case, parse the rest of the declaration + parseParams(toks); + + isNext(toks, TT.Semicolon); + } + else + { + // No type, no parameters + fn.type = BasicType.getVoid; + fn.name.str = "script-file"; + } + + code = new CodeBlock(false, true); + code.parse(toks); + } + void parse(ref TokenArray toks) { // Create a Function struct. Will change later. @@ -265,6 +395,33 @@ class FuncDeclaration : Statement // Parse any other keywords parseKeywords(toks); + parseParams(toks); + + if(fn.isAbstract || fn.isNative || fn.isIdle) + { + // Check that the function declaration ends with a ; rather + // than a code block. + if(!isNext(toks, TT.Semicolon)) + { + if(fn.isAbstract) + fail("Abstract function declaration expected ;", toks); + else if(fn.isNative) + fail("Native function declaration expected ;", toks); + else if(fn.isIdle) + fail("Idle function declaration expected ;", toks); + else assert(0); + } + } + else + { + code = new CodeBlock; + code.parse(toks); + } + } + + // Parse function name and parameters + void parseParams(ref TokenArray toks) + { fn.name = next(toks); loc = fn.name.loc; if(fn.name.type != TT.Identifier) @@ -297,27 +454,6 @@ class FuncDeclaration : Statement if(!isNext(toks, TT.RightParen)) fail("Expected end of parameter list", toks); } - - if(fn.isAbstract || fn.isNative || fn.isIdle) - { - // Check that the function declaration ends with a ; rather - // than a code block. - if(!isNext(toks, TT.Semicolon)) - { - if(fn.isAbstract) - fail("Abstract function declaration expected ;", toks); - else if(fn.isNative) - fail("Native function declaration expected ;", toks); - else if(fn.isIdle) - fail("Idle function declaration expected ;", toks); - else assert(0); - } - } - else - { - code = new CodeBlock; - code.parse(toks); - } } // Can the given tokens be parsed as the main function declaration? @@ -334,6 +470,8 @@ class FuncDeclaration : Statement return isNext(toks, TT.Native) || isNext(toks, TT.Abstract) || + isNext(toks, TT.Override) || + isNext(toks, TT.Final) || isNext(toks, TT.Idle); } @@ -380,8 +518,16 @@ class FuncDeclaration : Statement // types). The rest is handed by resolveBody() void resolve(Scope last) { + assert(fn.type !is null); + fn.type.resolve(last); + if(fn.type.isVar) + fail("var not allowed here", fn.type.loc); + + if(fn.type.isReplacer) + fn.type = fn.type.getBase(); + // Create a local scope for this function sc = new FuncScope(last, fn); @@ -389,9 +535,16 @@ class FuncDeclaration : Statement // in compile() and by external classes, so we store it. fn.paramSize = 0; foreach(vd; paramList) - fn.paramSize += vd.var.type.getSize(); + { + // Resolve the variable first, to make sure we get the rigth + // size + vd.resolveParam(sc); + assert(!vd.var.type.isReplacer); + + fn.paramSize += vd.var.type.getSize(); + } - // Set the owner class. + // Set the owner class fn.owner = sc.getClass(); // Parameters are given negative numbers according to their @@ -403,18 +556,16 @@ class FuncDeclaration : Statement // TODO: Do fancy memory management fn.params.length = paramList.length; - // Add function parameters to scope. + // Set up function parameter numbers and insert them into the + // list. foreach(i, dec; paramList) { - if(dec.var.type.isArray()) - dec.allowConst = true; - - dec.resolve(sc, pos); - + assert(pos < 0); + dec.setNumber(pos); pos += dec.var.type.getSize(); - fn.params[i] = dec.var; } + assert(pos == 0); // Vararg functions must have the last parameter as an array. if(fn.isVararg) @@ -427,6 +578,56 @@ class FuncDeclaration : Statement } assert(pos == 0, "Variable positions didn't add up"); + + // Do we override a function? + if(fn.overrides) + { + Function *o = fn.overrides; + assert(fn.owner !is null); + assert(o.owner !is null, + "overrided function must be resolved before us"); + + if(fn.owner is o.owner) + fail(format("Function %s is already declared on line %s", + fn.name.str, o.name.loc.line), fn.name.loc); + + // Check that the function we're overriding isn't final + if(o.isFinal) + fail("Cannot override final function " ~ o.toString, fn.name.loc); + + // Check that signatures match + if(o.type != fn.type) + fail(format("Cannot override %s with different return type (%s -> %s)", + fn.name.str, o.type, fn.type), fn.name.loc); + + bool parFail = false; + if(o.params.length != fn.params.length) parFail = true; + else + foreach(i,p; fn.params) + if(p.type != o.params[i].type || + p.isVararg != o.params[i].isVararg) + { + parFail = true; + break; + } + + if(parFail) + fail(format("Cannot override %s, parameter types do not match", + o.toString), fn.name.loc); + + // There's no big technical reason why this shouldn't be + // possible, but we haven't tested it or thought about it + // yet. + if(o.isIdle || fn.isIdle) + fail("Cannot override idle functions", fn.name.loc); + } + else + { + // No overriding. Make sure the 'override' flag isn't set. + if(isOverride) + fail("function " ~ fn.name.str ~ + " doesn't override anything", fn.name.loc); + } } // Resolve the interior of the function @@ -468,3 +669,216 @@ class FuncDeclaration : Statement fn.bcode = tasm.assemble(fn.lines); } } + +// Expression representing a function call +class FunctionCallExpr : MemberExpression +{ + Token name; + ExprArray params; + Function* fd; + + bool isVararg; + + static bool canParse(TokenArray toks) + { + return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen); + } + + // Read a parameter list (a,b,...) + static ExprArray getParams(ref TokenArray toks) + { + ExprArray res; + if(!isNext(toks, TT.LeftParen)) return res; + + Expression exp; + + // No parameters? + if(isNext(toks, TT.RightParen)) return res; + + // Read the first parameter + res ~= Expression.identify(toks); + + // Are there more? + while(isNext(toks, TT.Comma)) + res ~= Expression.identify(toks); + + if(!isNext(toks, TT.RightParen)) + fail("Parameter list expected ')'", toks); + + return res; + } + + void parse(ref TokenArray toks) + { + name = next(toks); + loc = name.loc; + + params = getParams(toks); + } + + char[] toString() + { + char[] result = name.str ~ "("; + foreach(b; params) + result ~= b.toString ~" "; + return result ~ ")"; + } + + void resolve(Scope sc) + { + if(isMember) // Are we called as a member? + { + assert(leftScope !is null); + fd = leftScope.findFunc(name.str); + if(fd is null) fail(name.str ~ " is not a member function of " + ~ leftScope.toString, loc); + } + else + { + assert(leftScope is null); + fd = sc.findFunc(name.str); + if(fd is null) fail("Undefined function "~name.str, name.loc); + } + + // Is this an idle function? + if(fd.isIdle) + { + if(!sc.isStateCode) + fail("Idle functions can only be called from state code", + name.loc); + + if(isMember) + fail("Idle functions cannot be called as members", name.loc); + } + + type = fd.type; + assert(type !is null); + + isVararg = fd.isVararg; + + if(isVararg) + { + // The vararg parameter can match a variable number of + // arguments, including zero. + if(params.length < fd.params.length-1) + fail(format("%s() expected at least %s parameters, got %s", + name.str, fd.params.length-1, params.length), + name.loc); + } + else + // Non-vararg functions must match function parameter number + // exactly + if(params.length != fd.params.length) + fail(format("%s() expected %s parameters, got %s", + name.str, fd.params.length, params.length), + name.loc); + + // Check parameter types + foreach(int i, par; fd.params) + { + // Handle varargs below + if(isVararg && i == fd.params.length-1) + break; + + params[i].resolve(sc); + try par.type.typeCast(params[i]); + catch(TypeException) + fail(format("%s() expected parameter %s to be type %s, not type %s", + name.str, i+1, par.type.toString, params[i].typeString), + name.loc); + } + + // Loop through remaining arguments + if(isVararg) + { + int start = fd.params.length-1; + + assert(fd.params[start].type.isArray); + Type base = fd.params[start].type.getBase(); + + foreach(int i, ref par; params[start..$]) + { + par.resolve(sc); + + // If the first and last vararg parameter is of the + // array type itself, then we are sending an actual + // array. Treat it like a normal parameter. + if(i == 0 && start == params.length-1 && + par.type == fd.params[start].type) + { + isVararg = false; + break; + } + + // Otherwise, cast the type to the array base type. + try base.typeCast(par); + catch(TypeException) + fail(format("Cannot convert %s of type %s to %s", par.toString, + par.typeString, base.toString), par.loc); + } + } + } + + // Used in cases where the parameters need to be evaluated + // separately from the function call. This is done by DotOperator, + // in cases like obj.func(expr); Here expr is evaluated first, then + // obj, and then finally the far function call. This is because the + // far function call needs to read 'obj' off the top of the stack. + void evalParams() + { + assert(pdone == false); + + foreach(i, ex; params) + { + ex.eval(); + + // Convert 'const' parameters to actual constant references + if(i < fd.params.length-1) // Skip the last parameter (in + // case of vararg functions) + if(fd.params[i].isConst) + { + assert(fd.params[i].type.isArray); + tasm.makeArrayConst(); + } + } + + if(isVararg) + { + // Compute the length of the vararg array. + int len = params.length - fd.params.length + 1; + + // If it contains no elements, push a null array reference + // (0 is always null). + if(len == 0) tasm.push(0); + else + // Converte the pushed values to an array index + tasm.popToArray(len, params[$-1].type.getSize()); + } + + // Handle the last parameter after everything has been + // pushed. This will work for both normal and vararg parameters. + if(fd.params.length != 0 && fd.params[$-1].isConst) + { + assert(fd.params[$-1].type.isArray); + tasm.makeArrayConst(); + } + + pdone = true; + } + + bool pdone; + + void evalAsm() + { + if(!pdone) evalParams(); + setLine(); + assert(fd.owner !is null); + + if(isMember) + tasm.callFarFunc(fd.index, fd.owner.getTreeIndex()); + else if(fd.isIdle) + tasm.callIdle(fd.index, fd.owner.getIndex()); + else + tasm.callFunc(fd.index, fd.owner.getTreeIndex()); + } +} diff --git a/monster/compiler/operators.d b/monster/compiler/operators.d index 7d7e2e3945..31e80648a4 100644 --- a/monster/compiler/operators.d +++ b/monster/compiler/operators.d @@ -29,6 +29,7 @@ import monster.compiler.assembler; import monster.compiler.tokenizer; import monster.compiler.scopes; import monster.compiler.types; +import monster.compiler.functions; import monster.vm.error; import monster.vm.arrays; @@ -584,8 +585,8 @@ class AssignOperator : BinaryOperator // Cast the right side to the left type, if possible. try type.typeCast(right); catch(TypeException) - fail("Assignment " ~tokenList[opType] ~ " not allowed for types " ~ - left.typeString() ~ " and " ~ right.typeString(), loc); + fail("Assignment " ~tokenList[opType] ~ ": cannot implicitly cast " ~ + right.typeString() ~ " to " ~ left.typeString(), loc); assert(left.type == right.type); } diff --git a/monster/compiler/properties.d b/monster/compiler/properties.d index 2db8fa86fa..bf32630da3 100644 --- a/monster/compiler/properties.d +++ b/monster/compiler/properties.d @@ -151,6 +151,7 @@ class ArrayProperties: SimplePropertyScope } } +// TODO: Rename to ObjectProperties class ClassProperties : SimplePropertyScope { static ClassProperties singleton; diff --git a/monster/compiler/scopes.d b/monster/compiler/scopes.d index 4d500778fa..c46fdf84df 100644 --- a/monster/compiler/scopes.d +++ b/monster/compiler/scopes.d @@ -32,13 +32,17 @@ import monster.compiler.statement; import monster.compiler.expression; import monster.compiler.tokenizer; import monster.compiler.types; +import monster.compiler.assembler; import monster.compiler.properties; import monster.compiler.functions; import monster.compiler.states; +import monster.compiler.structs; +import monster.compiler.enums; import monster.compiler.variables; import monster.vm.mclass; import monster.vm.error; +import monster.vm.vm; // The global scope PackageScope global; @@ -109,6 +113,7 @@ abstract class Scope bool isLoop() { return false; } bool isClass() { return false; } bool isArray() { return false; } + bool isStruct() { return false; } bool isPackage() { return false; } bool isProperty() { return false; } @@ -202,6 +207,18 @@ abstract class Scope return parent.findFunc(name); } + StructType findStruct(char[] name) + { + if(isRoot()) return null; + return parent.findStruct(name); + } + + EnumType findEnum(char[] name) + { + if(isRoot()) return null; + return parent.findEnum(name); + } + void insertLabel(StateLabel *lb) { assert(!isRoot); @@ -448,7 +465,7 @@ final class PackageScope : Scope // Find a parsed class of the given name. Looks in the list of // loaded classes and in the file system. Returns null if the class // cannot be found. - private MonsterClass findParsed(char[] name) + MonsterClass findParsed(char[] name) { MonsterClass result = null; @@ -459,7 +476,7 @@ final class PackageScope : Scope { // Class not loaded. Check if the file exists. char[] fname = classToFile(name); - if(MonsterClass.findFile(fname)) + if(vm.findFile(fname)) { // File exists. Load it right away. If the class is // already forward referenced, this will be taken care @@ -570,16 +587,207 @@ abstract class VarScope : Scope } } -// A class scope. In addition to variables, they can contain states -// and functions, and they keep track of the data segment size. -final class ClassScope : VarScope +// A scope that can contain functions and variables +class FVScope : VarScope +{ + protected: + HashTable!(char[], Function*) functions; + + public: + this(Scope last, char[] name) + { super(last, name); } + + // Insert a function. + void insertFunc(Function* fd) + { + if(isClass) + { + // Are we overriding a function? + auto old = findFunc(fd.name.str); + + // If there is no existing function, call clearId + if(old is null) + clearId(fd.name); + else + // We're overriding. Let fd know, and let it handle the + // details when it resolves. + fd.overrides = old; + } + else + // Non-class functions can never override anything + clearId(fd.name); + + fd.index = functions.length; + + // Store the function definition + functions[fd.name.str] = fd; + } + + override: + void clearId(Token name) + { + assert(name.str != ""); + + Function* fd; + + if(functions.inList(name.str, fd)) + { + fail(format("Identifier '%s' already declared on line %s (as a function)", + name.str, fd.name.loc), + name.loc); + } + + // Let VarScope handle variables + super.clearId(name); + } + + Function* findFunc(char[] name) + { + Function* fd; + + if(functions.inList(name, fd)) + return fd; + + assert(!isRoot()); + return parent.findFunc(name); + } +} + +// Can contain types, functions and variables. 'Types' means structs, +// enums and other user-generated sub-types (may also include classes +// later.) + +// The TFV, FV and Var scopes might (probably will) be merged at some +// point. I'm just keeping them separate for clearity while the scope +// structure is being developed. +class TFVScope : FVScope +{ + private: + HashTable!(char[], StructType) structs; + HashTable!(char[], EnumType) enums; + + public: + this(Scope last, char[] name) + { super(last, name); } + + void insertStruct(StructDeclaration sd) + { + clearId(sd.name); + structs[sd.name.str] = sd.type; + } + + void insertEnum(EnumDeclaration sd) + { + clearId(sd.name); + enums[sd.name.str] = sd.type; + } + + override: + void clearId(Token name) + { + assert(name.str != ""); + + StructType fd; + EnumType ed; + + if(structs.inList(name.str, fd)) + { + fail(format("Identifier '%s' already declared on line %s (as a struct)", + name.str, fd.loc), + name.loc); + } + + if(enums.inList(name.str, ed)) + { + fail(format("Identifier '%s' already declared on line %s (as an enum)", + name.str, ed.loc), + name.loc); + } + + // Let VarScope handle variables + super.clearId(name); + } + + StructType findStruct(char[] name) + { + StructType fd; + + if(structs.inList(name, fd)) + return fd; + + assert(!isRoot()); + return parent.findStruct(name); + } + + EnumType findEnum(char[] name) + { + EnumType ed; + + if(enums.inList(name, ed)) + return ed; + + assert(!isRoot()); + return parent.findEnum(name); + } +} + +// Lookup scope for enums. For simplicity we use the property system +// to handle enum members. +final class EnumScope : SimplePropertyScope +{ + this() { super("EnumScope"); } + + int index; // Index in a global enum index list. Do whatever you do + // with global class indices here. + + void setup() + { + /* + insert("name", ArrayType.getString(), { tasm.getEnumName(index); }); + + // Replace these with the actual fields of the enum + insert("value", type1, { tasm.getEnumValue(index, field); }); + + // Some of them are static + inserts("length", "int", { tasm.push(length); }); + inserts("last", "owner", { tasm.push(last); }); + inserts("first", "owner", { tasm.push(first); }); + */ + } +} + +// Scope for the interior of structs +final class StructScope : VarScope +{ + int offset; + + StructType str; + + this(Scope last, StructType t) + { + str = t; + assert(str !is null); + super(last, t.name); + } + + bool isStruct() { return true; } + + int addNewLocalVar(int varSize) + { return (offset+=varSize) - varSize; } + + // Define it only here since we don't need it anywhere else. + Scope getParent() { return parent; } +} + +// A class scope. In addition to variables, and functions, classes can +// contain states and they keep track of the data segment size. +final class ClassScope : TFVScope { private: // The class information for this class. MonsterClass cls; HashTable!(char[], State*) states; - HashTable!(char[], Function*) functions; int dataSize; // Data segment size for this class @@ -611,22 +819,14 @@ final class ClassScope : VarScope { assert(name.str != ""); - Function* fd; State* sd; - if(functions.inList(name.str, fd)) - { - fail(format("Identifier '%s' already declared on line %s (as a function)", - name.str, fd.name.loc), - name.loc); - } - if(states.inList(name.str, sd)) fail(format("Identifier '%s' already declared on line %s (as a state)", name.str, sd.name.loc), name.loc); - // Let VarScope handle variables and parent scopes + // Let the parent handle everything else super.clearId(name); } @@ -642,20 +842,6 @@ final class ClassScope : VarScope states[st.name.str] = st; } - // Insert a function. - void insertFunc(Function* fd) - { - // TODO: First check if we are legally overriding an existing - // function. If not, we are creating a new identifier and must - // call clearId. - clearId(fd.name); - - fd.index = functions.length; - - // Store the function definition - functions[fd.name.str] = fd; - } - State* findState(char[] name) { State* st; @@ -666,17 +852,6 @@ final class ClassScope : VarScope assert(!isRoot()); return parent.findState(name); } - - Function* findFunc(char[] name) - { - Function* fd; - - if(functions.inList(name, fd)) - return fd; - - assert(!isRoot()); - return parent.findFunc(name); - } } // A scope that keeps track of the stack @@ -745,6 +920,7 @@ abstract class StackScope : VarScope int getPos() { return getTotLocals() + getExpStack(); } } +// Scope used for the inside of functions class FuncScope : StackScope { // Function definition, for function scopes diff --git a/monster/compiler/statement.d b/monster/compiler/statement.d index 287ff381ec..eb0fc177c3 100644 --- a/monster/compiler/statement.d +++ b/monster/compiler/statement.d @@ -56,8 +56,7 @@ class ExprStatement : Statement void parse(ref TokenArray toks) { exp = Expression.identify(toks); - if(!isNext(toks, TT.Semicolon, loc)) - fail("Statement expected ;", toks); + reqNext(toks, TT.Semicolon, loc); } char[] toString() { return "ExprStatement: " ~ exp.toString(); } @@ -67,89 +66,6 @@ class ExprStatement : Statement void compile() { exp.evalPop(); } } -// 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; - - static bool canParse(TokenArray toks) - { - if(Type.canParseRem(toks) && - isNext(toks, TT.Identifier)) - return true; - return false; - } - - void parse(ref TokenArray toks) - { - VarDeclaration varDec; - 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(!isNext(toks, TT.Semicolon)) - fail("Declaration statement expected ;", toks); - } - - char[] toString() - { - char[] res = "Variable declaration: "; - foreach(vd; vars) res ~= vd.toString ~" "; - return res; - } - - // Special version used for function parameters, to set the number - // explicitly. - void resolve(Scope sc, int num) - { - assert(vars.length == 1); - vars[0].resolve(sc, num); - } - - 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(); - } - - // 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(); - } -} - // 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 @@ -226,10 +142,8 @@ class LabelStatement : Statement { // canParse has already checked the token types, all we need to // do is to fetch the name. - if(!isNext(toks, TT.Identifier, lb.name)) - assert(0, "Internal error"); - if(!isNext(toks, TT.Colon)) - assert(0, "Internal error"); + reqNext(toks, TT.Identifier, lb.name); + reqNext(toks, TT.Colon); loc = lb.name.loc; } @@ -325,15 +239,13 @@ class GotoStatement : Statement, LabelUser void parse(ref TokenArray toks) { - if(!isNext(toks, TT.Goto, loc)) - assert(0, "Internal error"); + reqNext(toks, TT.Goto, loc); // Read the label name if(!isNext(toks, TT.Identifier, labelName)) fail("goto expected label identifier", toks); - if(!isNext(toks, TT.Semicolon)) - fail("goto statement expected ;", toks); + reqNext(toks, TT.Semicolon); } void resolve(Scope sc) @@ -1299,7 +1211,9 @@ class IfStatement : Statement // Resolve all parts condition.resolve(sc); - if(!condition.type.isBool) + 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); @@ -1314,6 +1228,17 @@ class IfStatement : Statement // 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(); @@ -1431,6 +1356,7 @@ class CodeBlock : Statement 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: @@ -1475,8 +1401,13 @@ class CodeBlock : Statement public: - this(bool isState = false) - { this.isState = isState; } + this(bool isState = false, bool isFile = false) + { + this.isState = isState; + this.isFile = isFile; + + assert(!isFile || !isState); + } static bool canParse(TokenArray toks) { @@ -1485,25 +1416,41 @@ class CodeBlock : Statement void parse(ref TokenArray toks) { - if(toks.length == 0) - fail("Code block expected, got end of file"); + Token strt; + if(!isFile) + { + if(toks.length == 0) + fail("Code block expected, got end of file"); - Token strt = toks[0]; - if(strt.type != TT.LeftCurl) - fail("Code block expected a {", toks); + strt = toks[0]; + if(strt.type != TT.LeftCurl) + fail("Code block expected a {", toks); - loc = strt.loc; + loc = strt.loc; - toks = toks[1..toks.length]; + toks = toks[1..toks.length]; + } // Are we parsing stuff that comes before the code? Only // applicable to state blocks. bool beforeCode = isState; - while(!isNext(toks, TT.RightCurl, endLine)) + bool theEnd() + { + if(isFile) + return isNext(toks, TT.EOF, endLine); + else + return isNext(toks, TT.RightCurl, endLine); + } + + while(!theEnd()) { if(toks.length == 0) - fail(format("Unterminated code block (starting at line %s)", strt.loc)); + { + if(!isFile) + fail(format("Unterminated code block (starting at line %s)", strt.loc)); + else break; + } if(beforeCode) { @@ -1571,3 +1518,13 @@ class CodeBlock : Statement 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); +} diff --git a/monster/compiler/structs.d b/monster/compiler/structs.d new file mode 100644 index 0000000000..7772b938a9 --- /dev/null +++ b/monster/compiler/structs.d @@ -0,0 +1,167 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2007, 2008 Nicolay Korslund + Email: + WWW: http://monster.snaptoad.com/ + + This file (structs.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.structs; + +import monster.compiler.types; +import monster.compiler.scopes; +import monster.compiler.variables; +import monster.compiler.functions; +import monster.compiler.tokenizer; +import monster.compiler.statement; +import monster.vm.error; +import std.stdio; + +class StructDeclaration : TypeDeclaration +{ + StructType type; + Token name; + + private: + FuncDeclaration[] funcdecs; + VarDeclStatement[] vardecs; + + // Identify what kind of block the given set of tokens represent, + // parse them, and store it in the appropriate list; + void store(ref TokenArray toks) + { + // canParse() is not ment as a complete syntax test, only to be + // enough to identify which Block parser to apply. + if(FuncDeclaration.canParse(toks)) + { + auto fd = new FuncDeclaration; + funcdecs ~= fd; + fd.parse(toks); + } + else if(VarDeclStatement.canParse(toks)) + { + auto vd = new VarDeclStatement; + vd.parse(toks); + vardecs ~= vd; + } + else + fail("Illegal type or declaration", toks); + } + + public: + + static bool canParse(TokenArray toks) + { return toks.isNext(TT.Struct); } + + override: + void parse(ref TokenArray toks) + { + if(!isNext(toks, TT.Struct, loc)) + fail("Internal error in StructDeclaration"); + + if(!isNext(toks, TT.Identifier, name)) + fail("Expected struct name", toks); + + if(!isNext(toks, TT.LeftCurl)) + fail("Struct expected {", toks); + + // Parse the rest of the file + while(!isNext(toks, TT.RightCurl)) + store(toks); + + // Allow an optional semicolon + isNext(toks, TT.Semicolon); + } + + void insertType(TFVScope last) + { + // Set up the struct type. + type = new StructType(this); + + // Create a new scope + type.sc = new StructScope(last, type); + + // Insert ourselves into the parent scope + assert(last !is null); + last.insertStruct(this); + } + + void resolve(Scope last) + { + if(type.set) + return; + + // Get the total number of variables. + uint tot = 0; + foreach(dec; vardecs) + tot += dec.vars.length; + + // Must have at least one variable per declaration statement + assert(tot >= vardecs.length); + + // Get one array containing all the variables + Variable*[] vars = new Variable*[tot]; + int ind = 0; + foreach(st; vardecs) + foreach(dec; st.vars) + vars[ind++] = dec.var; + assert(ind == tot); + + // Store the variables in the StructType + type.vars = vars; + + // Mark the size as "set" now. + type.set = true; + + // Resolve + foreach(dec; vardecs) + dec.resolve(type.sc); + + // Calculate the struct size + type.size = 0; + foreach(t; vars) + type.size += t.type.getSize(); + + // Set up the init value + ind = 0; + int[] init = new int[type.getSize()]; + foreach(st; vardecs) + foreach(dec; st.vars) + { + int si = dec.var.type.getSize(); + init[ind..ind+si] = dec.getCTimeValue(); + ind += si; + } + assert(ind == init.length); + type.defInit = init; + + // Functions: + + // Disallow anything but normal functions (we can fix static and + // native struct functions later.) + + // Struct resolve only resolves header information, it doesn't + // resolve function bodies. + assert(funcdecs.length == 0, "struct functions not supported yet"); + /* + foreach(dec; funcdecs) + type.sc.insertFunc(dec); + */ + } +} diff --git a/monster/compiler/tokenizer.d b/monster/compiler/tokenizer.d index a529bed454..2e4bababcd 100644 --- a/monster/compiler/tokenizer.d +++ b/monster/compiler/tokenizer.d @@ -91,7 +91,7 @@ enum TT // Keywords. Note that we use Class as a separator below, so it // must be first in this list. All operator tokens must occur // before Class, and all keywords must come after Class. - Class, + Class, Module, For, If, Else, @@ -107,10 +107,11 @@ enum TT Switch, Select, State, - Singleton, Clone, + Struct, Enum, Thread, + Singleton, Clone, Override, Final, Function, This, New, Static, Const, Out, Ref, Abstract, Idle, Public, Private, Protected, True, False, Native, Null, - Goto, Halt, Auto, Var, In, + Goto, Halt, Var, In, Last, // Tokens after this do not have a specific string // associated with them. @@ -184,7 +185,7 @@ const char[][] tokenList = TT.MinusEq : "-=", TT.MultEq : "*=", TT.DivEq : "/=", - TT.RemEq : "%%=", + TT.RemEq : "%=", TT.IDivEq : "\\=", TT.CatEq : "~=", @@ -196,10 +197,11 @@ const char[][] tokenList = TT.Minus : "-", TT.Mult : "*", TT.Div : "/", - TT.Rem : "%%", + TT.Rem : "%", TT.IDiv : "\\", TT.Class : "class", + TT.Module : "module", TT.Return : "return", TT.For : "for", TT.This : "this", @@ -216,12 +218,18 @@ const char[][] tokenList = TT.Switch : "switch", TT.Select : "select", TT.State : "state", + TT.Struct : "struct", + TT.Enum : "enum", + TT.Thread : "thread", TT.Typeof : "typeof", TT.Singleton : "singleton", TT.Clone : "clone", TT.Static : "static", TT.Const : "const", TT.Abstract : "abstract", + TT.Override : "override", + TT.Final : "final", + TT.Function : "function", TT.Idle : "idle", TT.Out : "out", TT.Ref : "ref", @@ -234,7 +242,6 @@ const char[][] tokenList = TT.Null : "null", TT.Goto : "goto", TT.Halt : "halt", - TT.Auto : "auto", TT.Var : "var", TT.In : "in", ]; @@ -519,11 +526,13 @@ class StreamTokenizer // Treat the rest as we would an identifier - the actual // interpretation will be done later. We allow non-numerical // tokens in the literal, such as 0x0a or 1_000_000. We must - // also explicitly allow '.' dots. A number literal can end - // with a percentage sign '%'. + // also explicitly allow '.' dots. int len = 1; bool lastDot = false; // Was the last char a '.'? - bool lastPer = false; // Was it a '%'? + // I've tried with percentage literals (10% = 0.10), but it + // conflicts with the remainder division operator (which + // shouldn't change), so I've disabled it for now. + //bool lastPer = false; // Was it a '%'? foreach(char ch; line[1..$]) { if(ch == '.') @@ -537,6 +546,7 @@ class StreamTokenizer } lastDot = true; } + /* else if(ch == '%') { // Ditto for percentage signs. We allow '%' but not @@ -548,11 +558,12 @@ class StreamTokenizer } lastPer = true; } + */ else { if(!validIdentChar(ch)) break; lastDot = false; - lastPer = false; + //lastPer = false; } // This was a valid character, count it diff --git a/monster/compiler/types.d b/monster/compiler/types.d index 5a3b43d5f7..ab4e2bcc47 100644 --- a/monster/compiler/types.d +++ b/monster/compiler/types.d @@ -24,6 +24,7 @@ module monster.compiler.types; import monster.compiler.tokenizer; +import monster.compiler.enums; import monster.compiler.scopes; import monster.compiler.expression; import monster.compiler.assembler; @@ -32,6 +33,8 @@ import monster.compiler.block; import monster.compiler.functions; import monster.compiler.variables; import monster.compiler.states; +import monster.compiler.structs; +import monster.vm.arrays; import monster.vm.mclass; import monster.vm.error; @@ -43,11 +46,18 @@ import std.string; Type (abstract) InternalType (abstract) + ReplacerType (abstract) NullType (null expression) BasicType (covers int, char, bool, float, void) ObjectType ArrayType - MetaType + StructType + EnumType + UserType (replacer for identifier-named types like structs and + classes) + TypeofType (replacer for typeof(x) when used as a type) + GenericType (var) + MetaType (type of type expressions, like writeln(int);) */ class TypeException : Exception @@ -77,10 +87,15 @@ abstract class Type : Block // tokens.) static bool canParseRem(ref TokenArray toks) { - // TODO: Parse typeof(exp) here. We have to do a hack here, as - // we have no hope of parsing every expression in here. Instead - // we require the first ( and then remove every token until the - // matching ), allowing matching () pairs on the inside. + if(isNext(toks, TT.Typeof)) + { + reqNext(toks, TT.LeftParen); + Expression.identify(toks); // Possibly a bit wasteful... + reqNext(toks, TT.RightParen); + return true; + } + + if(isNext(toks, TT.Var)) return true; if(!isNext(toks, TT.Identifier)) return false; @@ -112,8 +127,9 @@ abstract class Type : Block // Find what kind of type this is and create an instance of the // corresponding class. if(BasicType.canParse(toks)) t = new BasicType(); - else if(ObjectType.canParse(toks)) t = new ObjectType(); - //else if(TypeofType.canParse(toks)) t = new TypeofType(); + else if(UserType.canParse(toks)) t = new UserType(); + else if(GenericType.canParse(toks)) t = new GenericType(); + else if(TypeofType.canParse(toks)) t = new TypeofType(); else fail("Cannot parse " ~ toks[0].str ~ " as a type", toks[0].loc); // Parse the actual tokens with our new and shiny object. @@ -123,6 +139,9 @@ abstract class Type : Block // an ArrayType. exps = VarDeclaration.getArray(toks, takeExpr); + if(t.isVar && exps.length) + fail("Cannot have arrays of var (yet)", t.loc); + // Despite looking strange, this code is correct. foreach(e; exps) t = new ArrayType(t); @@ -140,6 +159,14 @@ abstract class Type : Block // The complete type name including specifiers, eg. "int[]". char[] name; + MetaType meta; + final MetaType getMeta() + { + if(meta is null) + meta = new MetaType(this); + return meta; + } + // Used for easy checking bool isInt() { return false; } bool isUint() { return false; } @@ -151,10 +178,15 @@ abstract class Type : Block bool isDouble() { return false; } bool isVoid() { return false; } + bool isVar() { return false; } + bool isString() { return false; } - bool isObject() { return false; } bool isArray() { return arrays() != 0; } + bool isObject() { return false; } + bool isStruct() { return false; } + + bool isReplacer() { return false; } bool isIntegral() { return isInt || isUint || isLong || isUlong; } bool isFloating() { return isFloat || isDouble; } @@ -339,6 +371,9 @@ abstract class Type : Block assert(0, "doCastCTime not implemented for type " ~ toString); } + void parse(ref TokenArray toks) {assert(0, name);} + void resolve(Scope sc) {assert(0, name);} + /* Cast two expressions to their common type, if any. Throw a TypeException exception if not possible. This exception should be caught elsewhere to give a more useful error message. Examples of @@ -409,10 +444,8 @@ abstract class InternalType : Type { final: bool isLegal() { return false; } - int[] defaultInit() {assert(0);} - int getSize() { return 0; } - void parse(ref TokenArray toks) {assert(0);} - void resolve(Scope sc) {assert(0);} + int[] defaultInit() {assert(0, name);} + int getSize() {assert(0, name);} } // Handles the 'null' literal. This type is only used for @@ -500,7 +533,6 @@ class BasicType : Type { Token t; if(!isNext(toks, TT.Identifier, t)) return false; - return isBasic(t.str); } @@ -509,8 +541,8 @@ class BasicType : Type { Token t; - if(!isNext(toks, TT.Identifier, t) || !isBasic(t.str)) - assert(0, "Internal error in BasicType.parse()"); + reqNext(toks, TT.Identifier, t); + assert(isBasic(t.str)); // Get the name and the line from the token name = t.str; @@ -730,7 +762,13 @@ class ObjectType : Type public: - this() {} + this(Token t) + { + // Get the name and the line from the token + name = t.str; + loc = t.loc; + } + this(MonsterClass mc) { assert(mc !is null); @@ -739,12 +777,6 @@ class ObjectType : Type clsIndex = mc.gIndex; } - static bool canParse(TokenArray toks) - { - if(!isNext(toks, TT.Identifier)) return false; - return true; - } - MonsterClass getClass() { assert(clsIndex != 0); @@ -799,9 +831,6 @@ class ObjectType : Type assert(clsIndex !is tt.clsIndex); int cnum = tt.clsIndex; - // We have not decided what index to pass here yet. Think - // more about it when we implement full polymorphism. - assert(0, "not implemented"); tasm.upcast(cnum); return; } @@ -816,18 +845,6 @@ class ObjectType : Type return getClass().sc; } - void parse(ref TokenArray toks) - { - Token t; - - if(!isNext(toks, TT.Identifier, t)) - assert(0, "Internal error in ObjectType.parse()"); - - // Get the name and the line from the token - name = t.str; - loc = t.loc; - } - // This is called when the type is defined, and it can forward // reference classes that do not exist yet. The classes must exist // by the time getClass() is called though, usually when class body @@ -844,6 +861,21 @@ class ArrayType : Type // What type is this an array of? Type base; + static ArrayType str; + + static ArrayType getString() + { + if(str is null) + str = new ArrayType(BasicType.getChar); + return str; + } + + static ArrayType get(Type base) + { + // TODO: We can cache stuff here later + return new ArrayType(base); + } + this(Type btype) { base = btype; @@ -869,81 +901,291 @@ class ArrayType : Type return base.isChar(); } - void parse(ref TokenArray toks) - { assert(0, "array types aren't parsed"); } - void resolve(Scope sc) { base.resolve(sc); + + if(base.isReplacer()) + base = base.getBase(); } } -// This type is given to type-names that are used as variables -// (ie. they are gramatically parsed by VariableExpr.) It's only used -// for expressions like int.max and p == int. Only basic types are -// supported. Classes are handled in a separate type. -class MetaType : InternalType +class EnumType : Type +{ + // The scope contains the actual enum values + EnumScope sc; + + this(EnumDeclaration ed) + { + name = ed.name.str; + loc = ed.name.loc; + } + + int[] defaultInit() { return [0]; } + int getSize() { return 1; } + + void resolve(Scope sc) {} + + // can cast to int and to string, but not back + + // Scope getMemberScope() { return sc; } + Scope getMemberScope() + { return GenericProperties.singleton; } +} + +class StructType : Type { private: - static MetaType[char[]] store; + StructDeclaration sd; - BasicType base; + void setup() + { + if(!set) + sd.resolve(sc.getParent); + else + { + // We might get here if this function is being called + // recursively from sd.resolve. This only happens when the + // struct contains itself somehow. In that case, size will + // not have been set yet. + if(size == -1) + fail("Struct " ~ name ~ " indirectly contains itself", loc); + } + + assert(set); + assert(size != -1); + } public: - // Get a basic type of the given name. This will not allocate a new - // instance if another instance already exists. - static MetaType get(char[] tn) + int size = -1; + StructScope sc; // Scope of the struct interior + int[] defInit; + bool set; // Have vars and defInit been set? + + // Member variables + Variable*[] vars; + + this(StructDeclaration sdp) { - if(tn in store) return store[tn]; + sd = sdp; + name = sd.name.str; + loc = sd.name.loc; + } - return new MetaType(tn); + override: + // Calls validate for all member types + void validate() + { + foreach(v; vars) + v.type.validate(); } - this(char[] baseType) + // Resolves member + void resolve(Scope sc) {} + + bool isStruct() { return true; } + + int getSize() { - base = BasicType.get(baseType); - name = base.toString; - loc = base.loc; + setup(); + assert(size != -1); + return size; + } - store[name] = this; + int[] defaultInit() + { + setup(); + assert(defInit.length == getSize); + return defInit; } - // Return the scope belonging to the base type. This makes int.max - // work just like i.max. - Scope getMemberScope() { return base.getMemberScope(); } + Scope getMemberScope() + { return GenericProperties.singleton; } + // Scope getMemberScope() { return sc; } +} + +/* +// A type that mimics another after it is resolved. Used in cases +// where we can't know the real type until the resolve phase. +abstract class Doppelganger : Type +{ + private: + bool resolved; + + Type realType; + + public: + override: + int getSize() + { + assert(resolved); + return realType.getSize(); + } +} +*/ + +// A 'delayed lookup' type - can replace itself after resolve is +// called. +// OK - SCREW THIS. Make a doppelganger instead. +abstract class ReplacerType : InternalType +{ + protected: + Type realType; + + public: + override: + Type getBase() { assert(realType !is null); return realType; } + bool isReplacer() { return true; } +} + +// Type names that consist of an identifier - classes, structs, enums, +// etc. We can't know what type it is until we resolve it. +class UserType : ReplacerType +{ + private: + Token id; + + public: + + static bool canParse(TokenArray toks) + { + if(!isNext(toks, TT.Identifier)) return false; + return true; + } + + void parse(ref TokenArray toks) + { + reqNext(toks, TT.Identifier, id); + + // Get the name and the line from the token + name = id.str~"(replacer)"; + loc = id.loc; + } + + void resolve(Scope sc) + { + realType = sc.findStruct(id.str); + + if(realType is null) + // Not a struct. Maybe an enum? + realType = sc.findEnum(id.str); + + if(realType is null) + // Default to class name if nothing else is found. We can't + // really check this here since it might be a forward + // reference. These are handled later on. + realType = new ObjectType(id); + + realType.resolve(sc); + + assert(realType !is this); + assert(!realType.isReplacer); + } +} + +class TypeofType : ReplacerType +{ + static bool canParse(TokenArray toks) + { return isNext(toks, TT.Typeof); } + + Expression exp; + + void parse(ref TokenArray toks) + { + reqNext(toks, TT.Typeof, loc); + reqNext(toks, TT.LeftParen); + exp = Expression.identify(toks); + reqNext(toks, TT.RightParen); + } + + void resolve(Scope sc) + { + // Resolve the expression in the context of the scope + exp.resolve(sc); + if(exp.type.isMeta) + fail("Cannot use typeof on a meta type", exp.loc); + + realType = exp.type; + } +} + +// The 'var' type - generic type. Currently just works like 'auto' in +// D. +class GenericType : InternalType +{ + this() { name = "var"; } + + static bool canParse(TokenArray toks) + { return isNext(toks, TT.Var); } + + override: + bool isVar() { return true; } + + void parse(ref TokenArray toks) + { + reqNext(toks, TT.Var, loc); + } + + void resolve(Scope sc) {} +} + +// I never meta type I didn't like! +class MetaType : InternalType +{ + protected: + Type base; + AIndex ai; + + public: + + static Type getBasic(char[] basic) + { + return BasicType.get(basic).getMeta(); + } + + this(Type b) + { + base = b; + name = "(meta)"~base.toString; + ai = 0; + } - Type getBase() { return base; } bool isMeta() { return true; } bool canCastTo(Type type) { - return false;// type.isString; + return type.isString; } void evalCastTo(Type to) { assert(to.isString); - // TODO: Fix static strings soon - assert(0, "not supported yet"); + + // Create an array index and store it for reuse later. + if(ai == 0) + { + auto arf = monster.vm.arrays.arrays.create(base.toString); + arf.flags.set(AFlags.Const); + ai = arf.getIndex(); + } + assert(ai != 0); + + tasm.push(cast(uint)ai); } + + // Return the scope belonging to the base type. This makes int.max + // work just like i.max. Differentiation between static and + // non-static members is handled in the expression resolves. + Scope getMemberScope() { return base.getMemberScope(); } + Type getBase() { return base; } } /* Types we might add later: - ClassType - on the form 'class MyClass', variable may refer to - MyClass or to any subclass of MyClass. - - ListType - lists on the form { a; b; c } or similar - - AAType - associative array (hash map) - - TableType - something similar to Lua tables - - StructType - structs - - EnumType - enums and flags + TableType - a combination of lists, structures, hashmaps and + arrays. Loosely based on Lua tables, but not the same. FunctionType - pointer to a function with a given header. Since all Monster functions are object members (methods), all diff --git a/monster/compiler/variables.d b/monster/compiler/variables.d index cc9f2c97e8..23c4a463d0 100644 --- a/monster/compiler/variables.d +++ b/monster/compiler/variables.d @@ -28,10 +28,14 @@ import monster.compiler.types; import monster.compiler.tokenizer; import monster.compiler.expression; import monster.compiler.scopes; +import monster.compiler.statement; import monster.compiler.block; +import monster.compiler.assembler; import std.string; import std.stdio; + +import monster.vm.mclass; import monster.vm.error; enum VarType @@ -240,20 +244,85 @@ class VarDeclaration : Block return res; } - // Special version used for explicitly numbering function - // parameters. Only called from FuncDeclaration.resolve() - void resolve(Scope sc, int num) + bool isParam = false; + + // Special version only called from FuncDeclaration.resolve() + void resolveParam(Scope sc) { - assert(num<0, "VarDec.resolve was given a positive num: " ~ .toString(num)); - var.number = num; + 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); + //writefln("Type is: %s", var.type); + + if(var.type.isReplacer) + { + //writefln(" (we're here!)"); + var.type = var.type.getBase(); + } + + //writefln(" now it is: %s", var.type); + + // Allow 'const' for function array parameters + if(isParam && var.type.isArray()) + allowConst = true; + + // Handle initial value normally + if(init !is null) + { + init.resolve(sc); + + // 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. + try var.type.typeCast(init); + catch(TypeException) + fail(format("Cannot initialize %s of type %s with %s of type %s", + var.name.str, var.type, + init, init.type), loc); + } + 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 used inside the struct itself!", + loc); + } + } + + // We can't have var at this point + if(var.type.isVar) + fail("cannot implicitly determine 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); @@ -261,10 +330,10 @@ class VarDeclaration : Block var.sc = cast(VarScope)sc; assert(var.sc !is null, "variables can only be declared in VarScopes"); - if(var.number == 0) + if(!isParam) { - // If 'number' has not been set at this point (ie. we are - // not a function parameter), we must get it from the scope. + // If we are not a function parameter, we must get + // var.number from the scope. if(sc.isClass()) // Class variable. Get a position in the data segment. var.number = sc.addNewDataVar(var.type.getSize()); @@ -275,23 +344,32 @@ class VarDeclaration : Block } else assert(sc.isFunc()); - if(init !is null) - { - init.resolve(sc); - - // Convert type, if necessary. - try var.type.typeCast(init); - catch(TypeException) - fail(format("Cannot initialize %s of type %s with %s of type %s", - var.name.str, var.type, - init, init.type), loc); - assert(init.type == var.type); - } - // Insert ourselves into the scope. 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() @@ -309,3 +387,441 @@ class VarDeclaration : Block var.type.pushInit(); } } + +// Represents a reference to a variable. Simply stores the token +// representing the identifier. Evaluation is handled by the variable +// declaration itself. This allows us to use this class for local and +// global variables as well as for properties, without handling each +// case separately. The special names (currently __STACK__) are +// handled internally. +class VariableExpr : MemberExpression +{ + Token name; + Variable *var; + Property prop; + + 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__) + Type, // Typename + } + + VType vtype; + int classIndex = -1; // Index of the class that owns this variable. + + 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.Const); + } + + // Does this variable name refer to a type name rather than an + // actual variable? + bool isType() + { + return type.isMeta(); + } + + bool isProperty() + out(res) + { + if(res) + { + assert(prop.name != ""); + assert(var is null); + assert(!isSpecial); + } + else + { + assert(prop.name == ""); + } + } + body + { + return vtype == VType.Property; + } + + bool isSpecial() { return vtype == VType.Special; } + + 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 prop.isLValue; + + // Normal variables are always lvalues. + return true; + } + + bool isStatic() + { + // Properties can be static + if(isProperty) + return prop.isStatic; + + // Type names are always static. However, isType will return + // false for type names of eg. singletons, since these will not + // resolve to a meta type. + if(isType) + return true; + + return false; + } + + // TODO: isCTime - should be usable for static properties and members + + void parse(ref TokenArray toks) + { + name = next(toks); + loc = name.loc; + } + + void writeProperty() + { + assert(isProperty); + prop.setValue(); + } + + void resolve(Scope sc) + out + { + // Some sanity checks on the result + if(isProperty) assert(var is null); + if(var !is null) + { + assert(var.sc !is null); + assert(!isProperty); + } + assert(type !is null); + assert(vtype != VType.None); + } + body + { + if(isMember) // Are we called as a member? + { + // Look up the name in the scope belonging to the owner + assert(leftScope !is null); + + // Check first if this is a variable + var = leftScope.findVar(name.str); + if(var !is null) + { + // We are a member variable + type = var.type; + + // The object pointer is pushed on the stack. We must + // also provide the class index, so the variable is + // changed in the correct class (it could be a parent + // class of the given object.) + vtype = VType.FarOtherVar; + assert(var.sc.isClass); + classIndex = var.sc.getClass().getIndex(); + + return; + } + + // Check for properties last + if(leftScope.findProperty(name, ownerType, prop)) + { + // We are a property + vtype = VType.Property; + type = prop.getType; + return; + } + + // No match + fail(name.str ~ " is not a member of " ~ ownerType.toString, + loc); + } + + // Not a member + + // Look for reserved names first. + if(name.str == "__STACK__") + { + vtype = VType.Special; + type = BasicType.getInt; + return; + } + + if(name.type == TT.Const) + fail("Cannot use const as a variable", name.loc); + + if(name.type == TT.Clone) + fail("Cannot use clone as a variable", name.loc); + + // 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. + if(name.type == TT.Singleton || name.type == TT.State) + { + if(!sc.isInClass) + fail(name.str ~ " can only be used in classes", name.loc); + + if(!sc.findProperty(name, sc.getClass().objType, prop)) + assert(0, "should have found property " ~ name.str ~ + " in scope " ~ sc.toString); + + vtype = VType.Property; + type = prop.getType; + return; + } + + // Not a member, property or a special name. Look ourselves up + // in the local variable scope. + var = sc.findVar(name.str); + + if(var !is null) + { + type = var.type; + + assert(var.sc !is null); + + // Class variable? + if(var.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(var.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; + classIndex = var.sc.getClass().getIndex(); + } + } + else + vtype = VType.LocalVar; + + return; + } + + // We are not a variable. Our last chance is a type name. + vtype = VType.Type; + if(BasicType.isBasic(name.str)) + { + // Yes! Basic type. + type = MetaType.getBasic(name.str); + } + // Class name? + else if(auto mc = global.findParsed(name.str)) + { + // This doesn't allow forward references. + mc.requireScope(); + type = mc.classType; + + // Singletons are treated differently - the class name can + // be used to access the singleton object + if(mc.isSingleton) + { + type = mc.objType; + singCls = mc.getIndex(); + } + } + // Struct? + else if(auto tp = sc.findStruct(name.str)) + { + type = tp.getMeta(); + } + // Err, enum? + else if(auto tp = sc.findEnum(name.str)) + { + type = tp.getMeta(); + } + else + // No match at all + fail("Undefined identifier "~name.str, name.loc); + } + + void evalAsm() + { + assert(!isType); + + setLine(); + + // Special name + if(isSpecial) + { + if(name.str == "__STACK__") + tasm.getStack(); + else assert(0, "Unknown special name " ~ name.str); + return; + } + + // Property + if(isProperty) + { + prop.getValue(); + 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; + + 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, classIndex, 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, classIndex, s); + + else assert(0); + } + + // Push the address of the variable rather than its value + void evalDest() + { + assert(!isType, "types can never be written to"); + assert(isLValue()); + assert(!isProperty); + + setLine(); + + // No size information is needed for addresses. + + 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, classIndex); + else if(vtype == VType.FarOtherVar) + tasm.pushFarClassAddr(var.number, classIndex); + + else assert(0); + } + + void postWrite() + { + assert(!isProperty); + assert(isLValue()); + 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()); + } +} + +// 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; + + static bool canParse(TokenArray toks) + { + if(Type.canParseRem(toks) && + isNext(toks, TT.Identifier)) + return true; + return false; + } + + void parse(ref TokenArray toks) + { + VarDeclaration varDec; + 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(!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(); + } + + // 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(); + } +} diff --git a/monster/vm/fstack.d b/monster/vm/fstack.d index 5e57949acd..db38ad47b4 100644 --- a/monster/vm/fstack.d +++ b/monster/vm/fstack.d @@ -25,6 +25,7 @@ module monster.vm.fstack; import monster.vm.codestream; import monster.vm.mobject; +import monster.vm.mclass; import monster.vm.stack; import monster.vm.error; import monster.compiler.states; @@ -59,6 +60,7 @@ struct StackPoint SPType ftype; MonsterObject *obj; // "this"-pointer for the function + MonsterClass cls; // class owning the function int afterStack; // Where the stack should be when this function // returns @@ -109,12 +111,13 @@ struct FunctionStack cur.frame = stack.setFrame(); } - // Set the stack point up as a function + // Set the stack point up as a function. Allows obj to be null. void push(Function *func, MonsterObject *obj) { push(obj); cur.ftype = SPType.Function; cur.func = func; + cur.cls = func.owner; // Point the code stream to the byte code, if any. if(func.isNormal) @@ -130,6 +133,9 @@ struct FunctionStack cur.ftype = SPType.State; cur.state = st; + assert(obj !is null); + cur.cls = obj.cls; + // Set up the byte code cur.code.setData(st.bcode, st.lines); } @@ -137,12 +143,17 @@ struct FunctionStack // Native constructor void pushNConst(MonsterObject *obj) { + assert(obj !is null); push(obj); cur.ftype = SPType.NConst; } private void pushIdleCommon(Function *fn, MonsterObject *obj, SPType tp) { + // Not really needed - we will allow static idle functions later + // on. + assert(obj !is null); + push(obj); cur.func = fn; assert(fn.isIdle, fn.name.str ~ "() is not an idle function"); diff --git a/monster/vm/mclass.d b/monster/vm/mclass.d index 10eaec58e6..dc002df389 100644 --- a/monster/vm/mclass.d +++ b/monster/vm/mclass.d @@ -30,15 +30,18 @@ import monster.compiler.tokenizer; import monster.compiler.statement; import monster.compiler.variables; import monster.compiler.states; +import monster.compiler.structs; import monster.compiler.block; +import monster.compiler.enums; -import monster.vm.vm; +import monster.vm.thread; import monster.vm.codestream; import monster.vm.scheduler; import monster.vm.idlefunction; import monster.vm.fstack; import monster.vm.arrays; import monster.vm.error; +import monster.vm.vm; import monster.vm.mobject; import monster.util.flags; @@ -63,6 +66,8 @@ typedef void *MClass; // Pointer to C++ equivalent of MonsterClass. typedef int CIndex; +alias FreeList!(CodeThread) ThreadList; + // Parameter to the constructor. Decides how the class is created. enum MC { @@ -84,6 +89,9 @@ enum CFlags Compiled = 0x08, // Class body has been compiled InScope = 0x10, // We are currently inside the createScope // function + Module = 0x20, // This is a module, not a class + Singleton = 0x40, // This is a singleton. Also set for modules. + Abstract = 0x80, // No objects can be created from this class } // The class that handles 'classes' in Monster. @@ -91,49 +99,11 @@ final class MonsterClass { /*********************************************** * * - * Static path functions * + * Static functions * * * ***********************************************/ - // TODO: These will probably be moved elsewhere. - - // Path to search for script files. Extremely simple at the moment. - private static char[][] includes = [""]; - static void addPath(char[] path) - { - // Make sure the path is slash terminated. - if(!path.ends("/") && !path.ends("\\")) - path ~= '/'; - - includes ~= path; - } - - // Search for a file in the various paths. Returns true if found, - // false otherwise. Changes fname to point to the correct path. - static bool findFile(ref char[] fname) - { - // Check against our include paths. In the future we will replace - // this with a more flexible system, allowing virtual file systems, - // archive files, complete platform independence, improved error - // checking etc. - foreach(path; includes) - { - char[] res = path ~ fname; - if(exists(res)) - { - fname = res; - return true; - } - } - - return false; - } - - /*********************************************** - * * - * Static class functions * - * * - ***********************************************/ + // TODO: These should be moved to vm.vm // Get a class with the given name. It must already be loaded. static MonsterClass get(char[] name) { return global.getClass(name); } @@ -142,6 +112,13 @@ final class MonsterClass // fail if the class cannot be found. static MonsterClass find(char[] name) { return global.findClass(name); } + static bool canParse(TokenArray tokens) + { + return + Block.isNext(tokens, TT.Class) || + Block.isNext(tokens, TT.Module); + } + final: /******************************************************* @@ -150,17 +127,11 @@ final class MonsterClass * * *******************************************************/ - alias FreeList!(CodeThread) ThreadList; alias FreeList!(MonsterObject) ObjectList; // TODO: Put as many of these as possible in the private // section. Ie. move all of them and see what errors you get. - // Contains the entire class tree for this class, always with - // ourselves as the last entry. Any class in the list is always - // preceded by all the classes it inherits from. - MonsterClass tree[]; - // Index within the parent tree. This might become a list at some // point. int treeIndex; @@ -182,6 +153,10 @@ final class MonsterClass bool isResolved() { return flags.has(CFlags.Resolved); } bool isCompiled() { return flags.has(CFlags.Compiled); } + bool isSingleton() { return flags.has(CFlags.Singleton); } + bool isModule() { return flags.has(CFlags.Module); } + bool isAbstract() { return flags.has(CFlags.Abstract); } + // Call whenever you require this function to have its scope in // order. If the scope is missing, this will call createScope if // possible, or fail if the class has not been loaded. @@ -201,12 +176,6 @@ final class MonsterClass // already. void requireCompile() { if(!isCompiled) compileBody(); } - // List of variables and functions declared in this class, ordered - // by index. - Function* functions[]; - Variable* vars[]; - State* states[]; - /******************************************************* * * @@ -237,8 +206,7 @@ final class MonsterClass if(type == MC.String) { - assert(name2 == "", "MC.String only takes one parameter"); - loadString(name1); + loadString(name1, name2); return; } @@ -284,11 +252,12 @@ final class MonsterClass void loadCI(char[] name1, char[] name2 = "", bool usePath=true) { doLoad(name1, name2, false, usePath); } - void loadString(char[] str) + void loadString(char[] str, char[] fname="") { assert(str != ""); auto ms = new MemoryStream(str); - loadStream(ms, "(string)"); + if(fname == "") fname = "(string)"; + loadStream(ms, fname); } // Load a script from a stream. The filename parameter is only used @@ -338,7 +307,20 @@ final class MonsterClass return functions[index]; } - // Find a given callable function. + // Find a virtual function by index. ctree is the tree index of the + // class where the function is defined, findex is the intra-class + // function index. + Function *findVirtualFunc(int ctree, int findex) + { + requireScope(); + assert(ctree >= 0 && ctree <= treeIndex); + assert(findex >= 0 && findex < virtuals[ctree].length); + assert(virtuals[ctree][findex] !is null); + + return virtuals[ctree][findex]; + } + + // Find a given callable function, virtually. Function *findFunction(char[] name) { requireScope(); @@ -493,11 +475,23 @@ final class MonsterClass MonsterObject* getFirst() { return objects.getHead(); } + // Get the singleton object + MonsterObject* getSing() + { + assert(isSingleton()); + requireCompile(); + assert(singObj !is null); + return singObj; + } + // Create a new object, and assign a thread to it. MonsterObject* createObject() { requireCompile(); + if(isAbstract) + fail("Cannot create objects from abstract class " ~ name.str); + // Create the thread CodeThread *trd = threads.getNew(); @@ -652,49 +646,11 @@ final class MonsterClass // Get the global index of this class CIndex getIndex() { requireScope(); return gIndex; } + int getTreeIndex() { requireScope(); return treeIndex; } char[] getName() { assert(name.str != ""); return name.str; } char[] toString() { return getName(); } - /******************************************************* - * * - * Private and lower-level members * - * * - *******************************************************/ - - void reserveStatic(int length) - { - assert(!isResolved); - assert(sdata.length == 0); - - sdSize += length; - } - - AIndex insertStatic(int[] array, int elemSize) - { - assert(isResolved); - - // Allocate data, if it has not been done already. - if(sdata.length == 0 && sdSize != 0) - sdata.length = sdSize; - - assert(array.length <= sdSize, - "Trying to allocate more than reserved size"); - - // How much will be left after inserting this array? - int newSize = sdSize - array.length; - assert(newSize >= 0); - - int[] slice = sdata[$-sdSize..$-newSize]; - sdSize = newSize; - - // Copy the data - slice[] = array[]; - - ArrayRef *arf = arrays.createConst(slice, elemSize); - return arf.getIndex(); - } - private: @@ -704,6 +660,26 @@ final class MonsterClass * * *******************************************************/ + // Contains the entire class tree for this class, always with + // ourselves as the last entry. Any class in the list is always + // preceded by all the classes it inherits from. + MonsterClass tree[]; + + // List of variables and functions declared in this class, ordered + // by index. + Function* functions[]; + Variable* vars[]; + State* states[]; + + // Singleton object - used for singletons and modules only. + MonsterObject *singObj; + + // Function table translation list. Same length as tree[]. For each + // class in the parent tree, this list holds a list equivalent to + // the functions[] list in that class. The difference is that all + // overrided functions have been replaced by their successors. + Function*[][] virtuals; + // The freelists used for allocation of objects and threads. ObjectList objects; ThreadList threads; @@ -711,9 +687,6 @@ final class MonsterClass int[] data; // Contains the initial object data segment int[] sdata; // Static data segment - uint sdSize; // Number of ints reserved for the static data, that - // have not yet been used. - // Size of the data segment uint dataSize; @@ -724,13 +697,18 @@ final class MonsterClass MonsterClass parents[]; Token parentNames[]; + // Used at compile time VarDeclStatement[] vardecs; FuncDeclaration[] funcdecs; StateDeclaration[] statedecs; + StructDeclaration[] structdecs; + EnumDeclaration[] enumdecs; + // Current stage of the loading process MC loadType = MC.None; - // Native constructor type + // Native constructor type. Changed when the actual constructor is + // set. FuncType constType = FuncType.Native; union { @@ -766,20 +744,7 @@ final class MonsterClass int[] val; totSize += size; - // Does this variable have an initializer? - if(vd.init !is null) - { - // And can it be evaluated at compile time? - if(!vd.init.isCTime) - fail("Expression " ~ vd.init.toString ~ - " is not computable at compile time", vd.init.loc); - - val = vd.init.evalCTime(); - } - // Use the default initializer. - else val = vd.var.type.defaultInit(); - - assert(val.length == size, "Size mismatch"); + val = vd.getCTimeValue(); data[vd.var.number..vd.var.number+size] = val[]; } @@ -953,7 +918,7 @@ final class MonsterClass if(!checkFileName()) fail(format("Invalid class name %s (file %s)", cname, fname)); - if(usePath && !findFile(fname)) + if(usePath && !vm.findFile(fname)) fail("Cannot find script file " ~ fname); // Create a temporary file stream and load it @@ -1036,6 +1001,18 @@ final class MonsterClass sd.parse(toks); statedecs ~= sd; } + else if(StructDeclaration.canParse(toks)) + { + auto sd = new StructDeclaration; + sd.parse(toks); + structdecs ~= sd; + } + else if(EnumDeclaration.canParse(toks)) + { + auto sd = new EnumDeclaration; + sd.parse(toks); + enumdecs ~= sd; + } else fail("Illegal type or declaration", toks); } @@ -1044,19 +1021,43 @@ final class MonsterClass void parse(Stream str, char[] fname, int bom) { assert(!isParsed(), "parse() called on a parsed class " ~ name.str); - assert(str !is null); TokenArray tokens = tokenizeStream(fname, str, bom); alias Block.isNext isNext; - if(!isNext(tokens, TT.Class)) - fail("File must begin with a valid class statement"); + // TODO: Check for a list of keywords here. class, module, + // abstract, final. They can come in any order, but only certain + // combinations are legal. For example, class and module cannot + // both be present, and most other keywords only apply to + // classes. 'function' is not allowed at all, but should be + // checked for to make sure we're loading the right kind of + // file. If neither class nor module are found, that is also + // illegal in class files. + + if(isNext(tokens, TT.Module)) + { + flags.set(CFlags.Module); + flags.set(CFlags.Singleton); + } + else if(isNext(tokens, TT.Singleton)) + flags.set(CFlags.Singleton); + else if(!isNext(tokens, TT.Class)) + fail("File must begin with a class or module statement", tokens); if(!isNext(tokens, TT.Identifier, name)) fail("Class statement expected identifier", tokens); + if(isModule) + { + assert(isSingleton); + fail("Modules are not implement yet.", name.loc); + } + + if(isSingleton && isAbstract) + fail("Modules and singletons cannot be abstract", name.loc); + // Insert ourselves into the global scope. This will also // resolve forward references to this class, if any. global.insertClass(this); @@ -1064,6 +1065,9 @@ final class MonsterClass // Get the parent classes, if any if(isNext(tokens, TT.Colon)) { + if(isModule) + fail("Inheritance not allowed for modules."); + Token pName; do { @@ -1125,6 +1129,9 @@ final class MonsterClass fail("Class " ~ name.str ~ " cannot inherit from itself", name.loc); + if(mc.isModule) + fail("Cannot inherit from module " ~ mc.name.str); + // If a parent class is not a forward reference and still // does not have a scope, it means that it is itself running // this function. This can only happen if we are a parent of @@ -1165,10 +1172,22 @@ final class MonsterClass // Set the type objType = new ObjectType(this); + classType = objType.getMeta(); + + // Insert custom types first + foreach(dec; structdecs) + dec.insertType(sc); + foreach(dec; enumdecs) + dec.insertType(sc); + + // Then resolve the headers. + foreach(dec; structdecs) + dec.resolve(sc); + foreach(dec; enumdecs) + dec.resolve(sc); // Resolve variable declarations. They will insert themselves - // into the scope. TODO: Init values will have to be handled as - // part of the body later. + // into the scope. foreach(dec; vardecs) dec.resolve(sc); @@ -1195,6 +1214,60 @@ final class MonsterClass foreach(st; statedecs) states[st.st.index] = st.st; + // Now set up the virtual function table. It's elements + // correspond to the classes in tree[]. + + if(parents.length) + { + // This will get a lot trickier if we allow multiple inheritance + assert(parents.length == 1); + + // Set up the virtuals list + auto pv = parents[0].virtuals; + virtuals.length = pv.length+1; + + // We have to copy every single sublist, since we're not + // allowed to change our parent's data + foreach(i,l; pv) + virtuals[i] = l.dup; + + // Add our own list + virtuals[$-1] = functions; + } + else + virtuals = [functions]; + + assert(virtuals.length == tree.length); + + // Trace all our own functions back to their origin, and replace + // them. Since we've copied our parents list, and assume it is + // all set up, we only have to worry about our own + // functions. (For multiple inheritance this might be a bit more + // troublesome, but definitely doable.) + foreach(fn; functions) + { + auto o = fn.overrides; + + // And we have to loop backwards through the overrides that + // o overrides as well. + while(o !is null) + { + // Find the owner class tree index of the function we're + // overriding + assert(o.owner !is this); + int clsInd = o.owner.treeIndex; + assert(clsInd < tree.length-1); + assert(tree[clsInd] == o.owner); + + // Next, get the function index and replace the pointer + virtuals[clsInd][o.index] = fn; + + // Get the function that o overrides too, and fix that + // one as well. + o = o.overrides; + } + } + // Set the data segment size and the total data size for all // base classes. dataSize = sc.getDataSize(); @@ -1224,6 +1297,12 @@ final class MonsterClass foreach(state; statedecs) state.resolve(sc); + // TODO: Resolve struct functions + /* + foredach(stru; structdecs) + stru.resolveBody(sc); + */ + // Validate all variable types foreach(var; vardecs) var.validate(); @@ -1248,6 +1327,13 @@ final class MonsterClass data = getDataSegment(); flags.set(CFlags.Compiled); + + // If it's a singleton, set up the object. + if(isSingleton) + { + assert(singObj is null); + singObj = createObject(); + } } } diff --git a/monster/vm/mobject.d b/monster/vm/mobject.d index 2a98bbc724..6b315413ce 100644 --- a/monster/vm/mobject.d +++ b/monster/vm/mobject.d @@ -23,7 +23,7 @@ module monster.vm.mobject; -import monster.vm.vm; +import monster.vm.thread; import monster.vm.error; import monster.vm.mclass; import monster.vm.arrays; @@ -155,7 +155,7 @@ struct MonsterObject * * *******************************************************/ - // Template versions first + // This is the work horse for all the set/get functions. T* getPtr(T)(char[] name) { // Find the variable @@ -279,11 +279,11 @@ struct MonsterObject // Call a named function. The function is executed immediately, and // call() returns when the function is finished. The function is - // called virtually, so any sub-class function that overrides it in - // this object will take precedence. + // called virtually, so any child class function that overrides it + // will take precedence. void call(char[] name) { - cls.findFunction(name).call(this); + thread.topObj.cls.findFunction(name).call(this); } // Call a function non-virtually. In other words, ignore @@ -344,12 +344,14 @@ struct MonsterObject MonsterObject *mo = null; if(index < ptree.length) - mo = ptree[index]; + { + mo = ptree[index]; - assert(mo !is this); + assert(mo !is this); - // It's only a match if the classes match - if(mo.cls !is toClass) mo = null; + // It's only a match if the classes match + if(mo.cls !is toClass) mo = null; + } // If no match was found, then the cast failed. if(mo is null) diff --git a/monster/vm/thread.d b/monster/vm/thread.d new file mode 100644 index 0000000000..e1953ffb94 --- /dev/null +++ b/monster/vm/thread.d @@ -0,0 +1,1269 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2007, 2008 Nicolay Korslund + Email: + WWW: http://monster.snaptoad.com/ + + This file (vm.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.vm.thread; + +import std.string; +import std.stdio; +import std.uni; +import std.c.string; + +import monster.compiler.bytecode; +import monster.compiler.linespec; +import monster.compiler.states; +import monster.compiler.functions; +import monster.compiler.scopes; + +import monster.vm.mclass; +import monster.vm.mobject; +import monster.vm.codestream; +import monster.vm.stack; +import monster.vm.scheduler; +import monster.vm.idlefunction; +import monster.vm.arrays; +import monster.vm.iterators; +import monster.vm.error; +import monster.vm.fstack; + +// Used for array copy below. It handles overlapping data for us. +extern(C) void* memmove(void *dest, void *src, size_t n); + +extern(C) double floor(double d); + +// This represents an execution 'thread' in the system. Each object +// has its own thread. The thread contains a link to the object and +// the class, along with some other data. +struct CodeThread +{ + /******************************************************* + * * + * Public variables * + * * + *******************************************************/ + + // The object that "owns" this thread. This can only point to the + // top-most object in the linked parent object chain. + MonsterObject* topObj; + + // Pointer to our current scheduling point. If null, we are not + // currently sceduled. Only applies to state code, not scheduled + // function calls. + CallNode scheduleNode; + + + /******************************************************* + * * + * Public functions * + * * + *******************************************************/ + + void initialize(MonsterObject* top) + { + topObj = top; + + // Initialize other variables + state = null; // Start in the empty state + scheduleNode = null; + isActive = false; + stateChange = false; + } + + State* getState() { return state; } + + // Call state code for this object. 'pos' gives the byte position + // within the bytecode. It is called when a new state is entered, or + // when an idle funtion returns. The state must already be set with + // setState + void callState(int pos) + { + assert(state !is null, "attempted to call the empty state"); + assert(!isActive, + "callState cannot be called when object is already active"); + assert(fstack.isEmpty, + "ctate code can only run at the bottom of the function stack"); + + // Set a bool to indicate that we are now actively running state + // code. + isActive = true; + + // Set up the code stack + fstack.push(state, topObj.upcast(state.sc.getClass())); + + // Set the position + fstack.cur.code.jump(pos); + + // Run the code + execute(); + + // We are no longer active + isActive = false; + + assert(stack.getPos == 0, + format("Stack not returned to zero after state code, __STACK__=", + stack.getPos)); + + fstack.pop(); + } + + /* Set state. Invoked by the statement "state = statename;". This + function can be called in several situations, with various + results: + + + setState called with current state, no label + -> no action is performed + + + setState called with another state + + setState called with current state + a label + -> state is changed normally + + If a state change takes place directly in state code, the code is + aborted immediately. If it takes place in a function called from + state code, then code flow is allowed to return normally back to + the state code level, but is aborted immediately once it reaches + state code. + + State changes outside state code will always unschedule any + previously scheduled code (such as idle functions, or previous + calls to setState.) + */ + void setState(State *st, StateLabel *label) + { + // If no label is specified and we are already in this state, then + // do nothing. + if(st is state && label is null) + return; + + // Does the state actually change? + if(st !is state) + { + // If so, we must handle state functions and other magic here. + } + + // Set the state + state = st; + + // If we are already scheduled (if an idle function has scheduled + // us, or if setState has been called multiple times), + // unschedule. This will automatically cancel any scheduled idle + // functions and call their abort() functions. + if(scheduleNode) + scheduleNode.cancel(); + + // If we are jumping to anything but the empty state, we might + // have to schedule some code. + if(st !is null) + { + // Check that this state is valid + assert(st.sc.getClass().parentOf(topObj), "state '" ~ st.name.str ~ + "' is not part of class " ~ topObj.cls.getName()); + + if(label is null) + // findLabel will return null if the label is not found. + // TODO: The begin label should probably be cached within + // State. + label = st.findLabel("begin"); + + // Reschedule the new state for the next frame, if a label is + // specified. We have to cast to find the right object first + // though. + auto mo = topObj.upcast(st.sc.getClass()); + + if(label !is null) + scheduler.scheduleState(mo, label.offs); + } + + assert(isActive || !stateChange, + "stateChange was set outside active code"); + + // If we are running from state code, signal it that we must now + // abort execution when we reach the state level. + stateChange = isActive; + } + + /******************************************************* + * * + * Private variables * + * * + *******************************************************/ + private: + + bool isActive; // Set to true whenever we are running from state + // code. If we are inside the state itself, this will + // be true and 'next' will be 1. + bool stateChange; // Set to true when a state change is in + // progress. Only used when state is changed from + // within a function in active code. + State *state; // Current state, null is the empty state. + + /******************************************************* + * * + * Private helper functions * + * * + *******************************************************/ + + void fail(char[] msg) + { + int line = -1; + char[] file; + if(fstack.cur !is null) + { + line = fstack.cur.code.getLine(); + file = fstack.cur.cls.name.loc.fname; + } + + .fail(msg, file, line); + } + + // Index version of setState - called from bytecode + void setState(int st, int label, int cls) + { + if(st == -1) + { + assert(label == -1); + setState(null, null); + return; + } + + auto mo = topObj.upcastIndex(cls); + + auto pair = mo.cls.findState(st, label); + setState(pair.state, pair.label); + + assert(pair.state.index == st); + assert(pair.state.sc.getClass().getIndex == cls); + } + + void callIdle() + { + assert(isActive && fstack.isStateCode, + "Byte code attempted to call an idle function outside of state code."); + + CodeStream *code = &fstack.cur.code; + + // Get the correct object + MonsterObject *mo = topObj.upcastIndex(code.getInt()); + + // And the function + Function *fn = mo.cls.findFunction(code.getInt()); + assert(fn !is null && fn.isIdle); + + // The IdleFunction object bound to this function is stored in + // fn.idleFunc + if(fn.idleFunc is null) + fail("Called unimplemented idle function '" ~ fn.name.str ~ "'"); + + // Tell the scheduler that an idle function was called. It + // will reschedule us as needed. + scheduler.callIdle(mo, fn, fstack.cur.code.getPos); + } + + // Pops a pointer off the stack. Null pointers will throw an + // exception. + int *popPtr(MonsterObject *obj) + { + PT type; + int index; + decodePtr(stack.popInt(), type, index); + + // Null pointer? + if(type == PT.Null) + fail("Cannot access value, null pointer"); + + // Local variable? + if(type == PT.Stack) + return stack.getFrameInt(index); + + // Variable in this object + if(type == PT.DataOffs) + return obj.getDataInt(index); + + // This object, but another (parent) class + if(type == PT.DataOffsCls) + { + // We have to pop the class index of the stack as well + return obj.upcastIndex(stack.popInt()).getDataInt(index); + } + + // Far pointer, with offset. Both the class index and the object + // reference is on the stack. + if(type == PT.FarDataOffs) + { + int clsIndex = stack.popInt(); + + // Get the object reference from the stack + MonsterObject *tmp = stack.popObject(); + + // Cast the object to the correct class + tmp = tmp.upcastIndex(clsIndex); + + // Return the correct pointer + return tmp.getDataInt(index); + } + + // Array pointer + if(type == PT.ArrayIndex) + { + assert(index==0); + // Array indices are on the stack, not in the opcode. + index = stack.popInt(); + ArrayRef *arf = stack.popArray(); + assert(!arf.isNull); + if(arf.isConst) + fail("Cannot assign to constant array"); + index *= arf.elemSize; + if(index < 0 || index >= arf.iarr.length) + fail("Array index " ~ .toString(index/arf.elemSize) ~ + " out of bounds (array length " ~ .toString(arf.length) ~ ")"); + return &arf.iarr[index]; + } + + fail("Unable to handle pointer type " ~ toString(cast(int)type)); + } + + bool shouldExitState() + { + if(fstack.isStateCode && stateChange) + { + assert(isActive); + + // The state was changed while in state code. Abort the code. + stateChange = false; + + // There might be dangling stack values + stack.reset(); + return true; + } + return false; + } + + + /******************************************************* + * * + * execute() - main VM function * + * * + *******************************************************/ + public: + + // Execute instructions in the current function stack entry. This is + // the main workhorse of the VM, the "byte-code CPU". The function + // is called (possibly recursively) whenever a byte-code function is + // called, and returns when the function exits. + void execute() + { + // The maximum amount of instructions we execute before assuming + // an infinite loop. + const long limit = 10000000; + + assert(fstack.cur !is null, + "CodeThread.execute called but there is no code on the function stack."); + + // Get some values from the function stack + CodeStream *code = &fstack.cur.code; + MonsterObject *obj = fstack.cur.obj; + MonsterClass cls = fstack.cur.cls; + + // Only an object belonging to this thread can be passed to + // execute() on the function stack. + assert(obj is null || cls.parentOf(topObj)); + + // Reduce or remove as many of these as possible + int *ptr; + long *lptr; + float *fptr; + double *dptr; + int[] iarr; + ArrayRef *arf; + int val, val2; + long lval; + + // Disable this for now. It should be a per-function option, perhaps, + // or at least a compile time option. + //for(long i=0;i 1); + stack.pushBool(stack.popInts(val) == + stack.popInts(val)); + break; + + case BC.IsCaseEqual: + if(toUniLower(stack.popChar) == toUniLower(stack.popChar)) stack.pushInt(1); + else stack.pushInt(0); + break; + + case BC.CmpArray: + stack.pushBool(stack.popArray().iarr == stack.popArray().iarr); + break; + + case BC.ICmpStr: + stack.pushBool(isUniCaseEqual(stack.popString(), + stack.popString())); + break; + + case BC.PreInc: + ptr = popPtr(obj); + stack.pushInt(++(*ptr)); + break; + + case BC.PreDec: + ptr = popPtr(obj); + stack.pushInt(--(*ptr)); + break; + + case BC.PostInc: + ptr = popPtr(obj); + stack.pushInt((*ptr)++); + break; + + case BC.PostDec: + ptr = popPtr(obj); + stack.pushInt((*ptr)--); + break; + + case BC.PreInc8: + lptr = cast(long*)popPtr(obj); + stack.pushLong(++(*lptr)); + break; + + case BC.PreDec8: + lptr = cast(long*)popPtr(obj); + stack.pushLong(--(*lptr)); + break; + + case BC.PostInc8: + lptr = cast(long*)popPtr(obj); + stack.pushLong((*lptr)++); + break; + + case BC.PostDec8: + lptr = cast(long*)popPtr(obj); + stack.pushLong((*lptr)--); + break; + + case BC.Not: + ptr = stack.getInt(0); + if(*ptr == 0) *ptr = 1; + else *ptr = 0; + break; + + case BC.ILess: + val = stack.popInt; + if(stack.popInt < val) stack.pushInt(1); + else stack.pushInt(0); + break; + + case BC.ULess: + val = stack.popInt; + if(stack.popUint < cast(uint)val) stack.pushInt(1); + else stack.pushInt(0); + break; + + case BC.LLess: + lval = stack.popLong; + if(stack.popLong < lval) stack.pushInt(1); + else stack.pushInt(0); + break; + + case BC.ULLess: + lval = stack.popLong; + if(stack.popUlong < cast(ulong)lval) stack.pushInt(1); + else stack.pushInt(0); + break; + + case BC.FLess: + { + float fval = stack.popFloat; + if(stack.popFloat < fval) stack.pushInt(1); + else stack.pushInt(0); + break; + } + + case BC.DLess: + { + double fval = stack.popDouble; + if(stack.popDouble < fval) stack.pushInt(1); + else stack.pushInt(0); + break; + } + + case BC.CastI2L: + if(*stack.getInt(0) < 0) stack.pushInt(-1); + else stack.pushInt(0); + //stack.pushLong(stack.popInt()); + break; + + // Castint to float + case BC.CastI2F: + ptr = stack.getInt(0); + fptr = cast(float*) ptr; + *fptr = *ptr; + break; + + case BC.CastU2F: + ptr = stack.getInt(0); + fptr = cast(float*) ptr; + *fptr = *(cast(uint*)ptr); + break; + + case BC.CastL2F: + stack.pushFloat(stack.popLong); + break; + + case BC.CastUL2F: + stack.pushFloat(stack.popUlong); + break; + + case BC.CastD2F: + stack.pushFloat(stack.popDouble); + break; + + // Castint to double + case BC.CastI2D: + stack.pushDouble(stack.popInt); + break; + + case BC.CastU2D: + stack.pushDouble(stack.popUint); + break; + + case BC.CastL2D: + stack.pushDouble(stack.popLong); + break; + + case BC.CastUL2D: + stack.pushDouble(stack.popUlong); + break; + + case BC.CastF2D: + stack.pushDouble(stack.popFloat); + break; + + case BC.CastI2S: + { + val = code.get(); + char[] res; + if(val == 1) res = .toString(stack.popInt); + else if(val == 2) res = .toString(stack.popUint); + else if(val == 3) res = .toString(stack.popLong); + else if(val == 4) res = .toString(stack.popUlong); + else assert(0); + stack.pushArray(res); + } + break; + + case BC.CastF2S: + { + val = code.get(); + char[] res; + if(val == 1) res = .toString(stack.popFloat); + else if(val == 2) res = .toString(stack.popDouble); + else assert(0); + stack.pushArray(res); + } + break; + + case BC.CastB2S: + stack.pushArray(.toString(stack.popBool)); + break; + + case BC.CastO2S: + { + MIndex idx = stack.popMIndex(); + if(idx != 0) + stack.pushArray(format("%s#%s", getMObject(idx).cls.getName, + cast(int)idx)); + else + stack.pushCArray("(null object)"); + } + break; + + case BC.Upcast: + // TODO: If classes ever get more than one index, it might + // be more sensible to use the global class index here. + stack.pushObject(stack.popObject().upcastIndex(code.getInt())); + break; + + case BC.FetchElem: + // This is not very optimized + val = stack.popInt(); // Index + arf = stack.popArray(); // Get the array + if(val < 0 || val >= arf.length) + fail("Array index " ~ .toString(val) ~ " out of bounds (array length is " + ~ .toString(arf.length) ~ ")"); + val *= arf.elemSize; + for(int i = 0; i arf.iarr.length || val2 > arf.iarr.length || + val > val2) + fail(format("Slice indices [%s..%s] out of range (array length is %s)", + i1, i2, arf.length)); + + // Slices of constant arrays are also constant + if(arf.isConst) + arf = arrays.createConst(arf.iarr[val..val2], arf.elemSize); + else + arf = arrays.create(arf.iarr[val..val2], arf.elemSize); + + stack.pushArray(arf); + break; + } + + case BC.FillArray: + arf = stack.popArray(); + if(arf.isConst) + fail("Cannot fill a constant array"); + val = code.getInt(); // Element size + assert(val == arf.elemSize || arf.isNull); + iarr = stack.popInts(val); // Pop the value + + // Fill the array + assert(arf.iarr.length % val == 0); + for(int i=0; i 2) + { + assert(arf.iarr.length % val2 == 0); + val = arf.length / 2; // Half the number of elements (rounded down) + val *= val2; // Multiplied back up to number of ints + for(int i=0; i= bcToString.length) + fail(format("Invalid command opcode %s", opCode)); + else + fail(format("Unimplemented opcode '%s' (%s)", + bcToString[opCode], opCode)); + } + } + fail(format("Execution unterminated after %s instructions.", limit, + " Possibly an infinite loop, aborting.")); + } +} + +// Helper function for reversing arrays. Swaps the contents of two +// arrays. +void swap(int[] a, int[] b) +{ + const BUF = 32; + + assert(a.length == b.length); + int[BUF] buf; + uint len = a.length; + + while(len >= BUF) + { + buf[] = a[0..BUF]; + a[0..BUF] = b[0..BUF]; + b[0..BUF] = buf[]; + + a = a[BUF..$]; + b = b[BUF..$]; + len -= BUF; + } + + if(len) + { + buf[0..len] = a[]; + a[] = b[]; + b[] = buf[0..len]; + } +} diff --git a/monster/vm/vm.d b/monster/vm/vm.d index f82e1c11c9..1e034d486e 100644 --- a/monster/vm/vm.d +++ b/monster/vm/vm.d @@ -23,1225 +23,72 @@ module monster.vm.vm; -import std.string; -import std.stdio; -import std.uni; -import std.c.string; +import monster.vm.error; +import monster.vm.fstack; +import monster.vm.thread; +import monster.vm.mclass; +import monster.vm.mobject; -import monster.compiler.bytecode; +import monster.compiler.tokenizer; import monster.compiler.linespec; -import monster.compiler.states; import monster.compiler.functions; +import monster.compiler.assembler; import monster.compiler.scopes; -import monster.vm.mclass; -import monster.vm.mobject; -import monster.vm.codestream; -import monster.vm.stack; -import monster.vm.scheduler; -import monster.vm.idlefunction; -import monster.vm.arrays; -import monster.vm.iterators; -import monster.vm.error; -import monster.vm.fstack; +import std.file; +import monster.util.string; -// Used for array copy below. It handles overlapping data for us. -extern(C) void* memmove(void *dest, void *src, size_t n); +VM vm; -extern(C) double floor(double d); - -// This represents an execution 'thread' in the system. Each object -// has its own thread. The thread contains a link to the object and -// the class, along with some other data. -struct CodeThread +struct VM { - /******************************************************* - * * - * Public variables * - * * - *******************************************************/ - - // The object that "owns" this thread. This can only point to the - // top-most object in the linked parent object chain. - MonsterObject* topObj; - - // Pointer to our current scheduling point. If null, we are not - // currently sceduled. Only applies to state code, not scheduled - // function calls. - CallNode scheduleNode; - - - /******************************************************* - * * - * Public functions * - * * - *******************************************************/ - - void initialize(MonsterObject* top) - { - topObj = top; - - // Initialize other variables - state = null; // Start in the empty state - scheduleNode = null; - isActive = false; - stateChange = false; - } - - State* getState() { return state; } - - // Call state code for this object. 'pos' gives the byte position - // within the bytecode. It is called when a new state is entered, or - // when an idle funtion returns. The state must already be set with - // setState - void callState(int pos) - { - assert(state !is null, "attempted to call the empty state"); - assert(!isActive, - "callState cannot be called when object is already active"); - assert(fstack.isEmpty, - "ctate code can only run at the bottom of the function stack"); - - // Set a bool to indicate that we are now actively running state - // code. - isActive = true; - - // Set up the code stack - fstack.push(state, topObj.upcast(state.sc.getClass())); - - // Set the position - fstack.cur.code.jump(pos); - - // Run the code - execute(); - - // We are no longer active - isActive = false; - - assert(stack.getPos == 0, - format("Stack not returned to zero after state code, __STACK__=", - stack.getPos)); - - fstack.pop(); - } - - /* Set state. Invoked by the statement "state = statename;". This - function can be called in several situations, with various - results: - - + setState called with current state, no label - -> no action is performed - - + setState called with another state - + setState called with current state + a label - -> state is changed normally - - If a state change takes place directly in state code, the code is - aborted immediately. If it takes place in a function called from - state code, then code flow is allowed to return normally back to - the state code level, but is aborted immediately once it reaches - state code. - - State changes outside state code will always unschedule any - previously scheduled code (such as idle functions, or previous - calls to setState.) - */ - void setState(State *st, StateLabel *label) + // Run a script file in the context of the object obj. If no object + // is given, an instance of an empty class is used. + void run(char[] file, MonsterObject *obj = null) { - // If no label is specified and we are already in this state, then - // do nothing. - if(st is state && label is null) - return; - - // Does the state actually change? - if(st !is state) + if(obj !is null) { - // If so, we must handle state functions and other magic here. + auto func = Function(file, obj.cls); + func.call(obj); } - - // Set the state - state = st; - - // If we are already scheduled (if an idle function has scheduled - // us, or if setState has been called multiple times), - // unschedule. This will automatically cancel any scheduled idle - // functions and call their abort() functions. - if(scheduleNode) - scheduleNode.cancel(); - - // If we are jumping to anything but the empty state, we might - // have to schedule some code. - if(st !is null) + else { - // Check that this state is valid - assert(st.sc.getClass().parentOf(topObj), "state '" ~ st.name.str ~ - "' is not part of class " ~ topObj.cls.getName()); - - if(label is null) - // findLabel will return null if the label is not found. - // TODO: The begin label should probably be cached within - // State. - label = st.findLabel("begin"); - - // Reschedule the new state for the next frame, if a label is - // specified. We have to cast to find the right object first - // though. - auto mo = topObj.upcast(st.sc.getClass()); - - if(label !is null) - scheduler.scheduleState(mo, label.offs); + auto func = Function(file); + func.call(); } - - assert(isActive || !stateChange, - "stateChange was set outside active code"); - - // If we are running from state code, signal it that we must now - // abort execution when we reach the state level. - stateChange = isActive; } - /******************************************************* - * * - * Private variables * - * * - *******************************************************/ - private: - - bool isActive; // Set to true whenever we are running from state - // code. If we are inside the state itself, this will - // be true and 'next' will be 1. - bool stateChange; // Set to true when a state change is in - // progress. Only used when state is changed from - // within a function in active code. - State *state; // Current state, null is the empty state. - - /******************************************************* - * * - * Private helper functions * - * * - *******************************************************/ + // Path to search for script files. Extremely simple at the moment. + private char[][] includes = [""]; - void fail(char[] msg) + void addPath(char[] path) { - int line = -1; - if(fstack.cur !is null) - line = fstack.cur.code.getLine(); + // Make sure the path is slash terminated. + if(!path.ends("/") && !path.ends("\\")) + path ~= '/'; - .fail(msg, topObj.cls.name.loc.fname, line); + includes ~= path; } - // Index version of setState - called from bytecode - void setState(int st, int label, int cls) + // Search for a file in the various paths. Returns true if found, + // false otherwise. Changes fname to point to the correct path. + bool findFile(ref char[] fname) { - if(st == -1) + // Check against our include paths. In the future we will replace + // this with a more flexible system, allowing virtual file systems, + // archive files, complete platform independence, improved error + // checking etc. + foreach(path; includes) { - assert(label == -1); - setState(null, null); - return; + char[] res = path ~ fname; + if(exists(res)) + { + fname = res; + return true; + } } - auto mo = topObj.upcastIndex(cls); - - auto pair = mo.cls.findState(st, label); - setState(pair.state, pair.label); - - assert(pair.state.index == st); - assert(pair.state.sc.getClass().getIndex == cls); - } - - void callIdle() - { - assert(isActive && fstack.isStateCode, - "Byte code attempted to call an idle function outside of state code."); - - CodeStream *code = &fstack.cur.code; - - // Get the correct object - MonsterObject *mo = topObj.upcastIndex(code.getInt()); - - // And the function - Function *fn = mo.cls.findFunction(code.getInt()); - assert(fn !is null && fn.isIdle); - - // The IdleFunction object bound to this function is stored in - // fn.idleFunc - if(fn.idleFunc is null) - fail("Called unimplemented idle function '" ~ fn.name.str ~ "'"); - - // Tell the scheduler that an idle function was called. It - // will reschedule us as needed. - scheduler.callIdle(mo, fn, fstack.cur.code.getPos); - } - - // Pops a pointer off the stack. Null pointers will throw an - // exception. - int *popPtr(MonsterObject *obj) - { - PT type; - int index; - decodePtr(stack.popInt(), type, index); - - // Null pointer? - if(type == PT.Null) - fail("Cannot access value, null pointer"); - - // Local variable? - if(type == PT.Stack) - return stack.getFrameInt(index); - - // Variable in this object - if(type == PT.DataOffs) - return obj.getDataInt(index); - - // This object, but another (parent) class - if(type == PT.DataOffsCls) - { - // We have to pop the class index of the stack as well - return obj.upcastIndex(stack.popInt()).getDataInt(index); - } - - // Far pointer, with offset. Both the class index and the object - // reference is on the stack. - if(type == PT.FarDataOffs) - { - int clsIndex = stack.popInt(); - - // Get the object reference from the stack - MonsterObject *tmp = stack.popObject(); - - // Cast the object to the correct class - tmp = tmp.upcastIndex(clsIndex); - - // Return the correct pointer - return tmp.getDataInt(index); - } - - // Array pointer - if(type == PT.ArrayIndex) - { - assert(index==0); - // Array indices are on the stack, not in the opcode. - index = stack.popInt(); - ArrayRef *arf = stack.popArray(); - assert(!arf.isNull); - if(arf.isConst) - fail("Cannot assign to constant array"); - index *= arf.elemSize; - if(index < 0 || index >= arf.iarr.length) - fail("Array index " ~ .toString(index/arf.elemSize) ~ - " out of bounds (array length " ~ .toString(arf.length) ~ ")"); - return &arf.iarr[index]; - } - - fail("Unable to handle pointer type " ~ toString(cast(int)type)); - } - - bool shouldExitState() - { - if(fstack.isStateCode && stateChange) - { - assert(isActive); - - // The state was changed while in state code. Abort the code. - stateChange = false; - - // There might be dangling stack values - stack.reset(); - return true; - } return false; } - - /******************************************************* - * * - * execute() - main VM function * - * * - *******************************************************/ - public: - - // Execute instructions in the current function stack entry. This is - // the main workhorse of the VM, the "byte-code CPU". The function - // is called (possibly recursively) whenever a byte-code function is - // called, and returns when the function exits. - void execute() - { - // The maximum amount of instructions we execute before assuming - // an infinite loop. - const long limit = 10000000; - - assert(fstack.cur !is null, - "CodeThread.execute called but there is no code on the function stack."); - - // Get some values from the function stack - CodeStream *code = &fstack.cur.code; - MonsterObject *obj = fstack.cur.obj; - MonsterClass cls = obj.cls; - - // Only an object belonging to this thread can be passed to - // execute() on the function stack. - assert(cls.parentOf(topObj)); - - // Reduce or remove as many of these as possible - int *ptr; - long *lptr; - float *fptr; - double *dptr; - int[] iarr; - ArrayRef *arf; - int val, val2; - long lval; - - // Disable this for now. It should be a per-function option, perhaps, - // or at least a compile time option. - //for(long i=0;i 1); - stack.pushBool(stack.popInts(val) == - stack.popInts(val)); - break; - - case BC.IsCaseEqual: - if(toUniLower(stack.popChar) == toUniLower(stack.popChar)) stack.pushInt(1); - else stack.pushInt(0); - break; - - case BC.CmpArray: - stack.pushBool(stack.popArray().iarr == stack.popArray().iarr); - break; - - case BC.ICmpStr: - stack.pushBool(isUniCaseEqual(stack.popString(), - stack.popString())); - break; - - case BC.PreInc: - ptr = popPtr(obj); - stack.pushInt(++(*ptr)); - break; - - case BC.PreDec: - ptr = popPtr(obj); - stack.pushInt(--(*ptr)); - break; - - case BC.PostInc: - ptr = popPtr(obj); - stack.pushInt((*ptr)++); - break; - - case BC.PostDec: - ptr = popPtr(obj); - stack.pushInt((*ptr)--); - break; - - case BC.PreInc8: - lptr = cast(long*)popPtr(obj); - stack.pushLong(++(*lptr)); - break; - - case BC.PreDec8: - lptr = cast(long*)popPtr(obj); - stack.pushLong(--(*lptr)); - break; - - case BC.PostInc8: - lptr = cast(long*)popPtr(obj); - stack.pushLong((*lptr)++); - break; - - case BC.PostDec8: - lptr = cast(long*)popPtr(obj); - stack.pushLong((*lptr)--); - break; - - case BC.Not: - ptr = stack.getInt(0); - if(*ptr == 0) *ptr = 1; - else *ptr = 0; - break; - - case BC.ILess: - val = stack.popInt; - if(stack.popInt < val) stack.pushInt(1); - else stack.pushInt(0); - break; - - case BC.ULess: - val = stack.popInt; - if(stack.popUint < cast(uint)val) stack.pushInt(1); - else stack.pushInt(0); - break; - - case BC.LLess: - lval = stack.popLong; - if(stack.popLong < lval) stack.pushInt(1); - else stack.pushInt(0); - break; - - case BC.ULLess: - lval = stack.popLong; - if(stack.popUlong < cast(ulong)lval) stack.pushInt(1); - else stack.pushInt(0); - break; - - case BC.FLess: - { - float fval = stack.popFloat; - if(stack.popFloat < fval) stack.pushInt(1); - else stack.pushInt(0); - break; - } - - case BC.DLess: - { - double fval = stack.popDouble; - if(stack.popDouble < fval) stack.pushInt(1); - else stack.pushInt(0); - break; - } - - case BC.CastI2L: - if(*stack.getInt(0) < 0) stack.pushInt(-1); - else stack.pushInt(0); - //stack.pushLong(stack.popInt()); - break; - - // Castint to float - case BC.CastI2F: - ptr = stack.getInt(0); - fptr = cast(float*) ptr; - *fptr = *ptr; - break; - - case BC.CastU2F: - ptr = stack.getInt(0); - fptr = cast(float*) ptr; - *fptr = *(cast(uint*)ptr); - break; - - case BC.CastL2F: - stack.pushFloat(stack.popLong); - break; - - case BC.CastUL2F: - stack.pushFloat(stack.popUlong); - break; - - case BC.CastD2F: - stack.pushFloat(stack.popDouble); - break; - - // Castint to double - case BC.CastI2D: - stack.pushDouble(stack.popInt); - break; - - case BC.CastU2D: - stack.pushDouble(stack.popUint); - break; - - case BC.CastL2D: - stack.pushDouble(stack.popLong); - break; - - case BC.CastUL2D: - stack.pushDouble(stack.popUlong); - break; - - case BC.CastF2D: - stack.pushDouble(stack.popFloat); - break; - - case BC.CastI2S: - { - val = code.get(); - char[] res; - if(val == 1) res = .toString(stack.popInt); - else if(val == 2) res = .toString(stack.popUint); - else if(val == 3) res = .toString(stack.popLong); - else if(val == 4) res = .toString(stack.popUlong); - else assert(0); - stack.pushArray(res); - } - break; - - case BC.CastF2S: - { - val = code.get(); - char[] res; - if(val == 1) res = .toString(stack.popFloat); - else if(val == 2) res = .toString(stack.popDouble); - else assert(0); - stack.pushArray(res); - } - break; - - case BC.CastB2S: - stack.pushArray(.toString(stack.popBool)); - break; - - case BC.CastO2S: - { - MIndex idx = stack.popMIndex(); - if(idx != 0) - stack.pushArray(format("%s#%s", getMObject(idx).cls.getName, - cast(int)idx)); - else - stack.pushCArray("(null object)"); - } - break; - - case BC.Upcast: - // TODO: If classes ever get more than one index, it might - // be more sensible to use the global class index here. - stack.pushObject(stack.popObject().upcastIndex(code.getInt())); - break; - - case BC.FetchElem: - // This is not very optimized - val = stack.popInt(); // Index - arf = stack.popArray(); // Get the array - if(val < 0 || val >= arf.length) - fail("Array index " ~ .toString(val) ~ " out of bounds (array length is " - ~ .toString(arf.length) ~ ")"); - val *= arf.elemSize; - for(int i = 0; i arf.iarr.length || val2 > arf.iarr.length || - val > val2) - fail(format("Slice indices [%s..%s] out of range (array length is %s)", - i1, i2, arf.length)); - - // Slices of constant arrays are also constant - if(arf.isConst) - arf = arrays.createConst(arf.iarr[val..val2], arf.elemSize); - else - arf = arrays.create(arf.iarr[val..val2], arf.elemSize); - - stack.pushArray(arf); - break; - } - - case BC.FillArray: - arf = stack.popArray(); - if(arf.isConst) - fail("Cannot fill a constant array"); - val = code.getInt(); // Element size - assert(val == arf.elemSize || arf.isNull); - iarr = stack.popInts(val); // Pop the value - - // Fill the array - assert(arf.iarr.length % val == 0); - for(int i=0; i 2) - { - assert(arf.iarr.length % val2 == 0); - val = arf.length / 2; // Half the number of elements (rounded down) - val *= val2; // Multiplied back up to number of ints - for(int i=0; i= bcToString.length) - fail(format("Invalid command opcode %s", opCode)); - else - fail(format("Unimplemented opcode '%s' (%s)", - bcToString[opCode], opCode)); - } - } - fail(format("Execution unterminated after %s instructions.", limit, - " Possibly an infinite loop, aborting.")); - } -} - -// Helper function for reversing arrays. Swaps the contents of two -// arrays. -void swap(int[] a, int[] b) -{ - const BUF = 32; - - assert(a.length == b.length); - int[BUF] buf; - uint len = a.length; - - while(len >= BUF) - { - buf[] = a[0..BUF]; - a[0..BUF] = b[0..BUF]; - b[0..BUF] = buf[]; - - a = a[BUF..$]; - b = b[BUF..$]; - len -= BUF; - } - - if(len) - { - buf[0..len] = a[]; - a[] = b[]; - b[] = buf[0..len]; - } } diff --git a/mscripts/config.mn b/mscripts/config.mn index 524d3ddf28..a1d6a057bb 100644 --- a/mscripts/config.mn +++ b/mscripts/config.mn @@ -21,8 +21,7 @@ */ -// TODO: Should be a singleton -class Config : Object; +singleton Config : Object; // Only some config options have been moved into Monster. Key bindings // and other low-level settings are still handled in D. @@ -42,13 +41,13 @@ bool flipMouseY; setMainVolume(float f) { mainVolume = f; - music().updateVolume(); + Music.updateVolume(); } setMusicVolume(float f) { musicVolume = f; - music().updateVolume(); + Music.updateVolume(); } setSfxVolume(float f) diff --git a/mscripts/gameobjects/apparatus.mn b/mscripts/gameobjects/apparatus.mn index 1cfa677aff..f5cb39f286 100644 --- a/mscripts/gameobjects/apparatus.mn +++ b/mscripts/gameobjects/apparatus.mn @@ -24,5 +24,12 @@ // Covers all alchemy apparatus - mortars, retorts, etc class Apparatus : InventoryItem; +enum AppaType + { + MortarPestle = 0 : "Mortar and Pestle", + Albemic = 1, + Calcinator = 2 + } + float quality; int type; diff --git a/mscripts/gameobjects/clothing.mn b/mscripts/gameobjects/clothing.mn index 08bd4bba04..9162017d27 100644 --- a/mscripts/gameobjects/clothing.mn +++ b/mscripts/gameobjects/clothing.mn @@ -25,3 +25,17 @@ class Clothing : EnchantItem; int type; + +enum Type + { + Pants = 0, + Shoes = 1, + Shirt = 2, + Belt = 3, + Robe = 4, + RGlove = 5, + LGlove = 6, + Skirt = 7, + Ring = 8, + Amulet = 9 + } diff --git a/mscripts/gamesettings.mn b/mscripts/gamesettings.mn index cf64e66bc5..f8aa788e12 100644 --- a/mscripts/gamesettings.mn +++ b/mscripts/gamesettings.mn @@ -24,7 +24,7 @@ // Contains all the game settings (GMST) variables of Morrowind, // Tribunal and Bloodmoon. Based on "Morrowind Scripting for Dummies" // (v9). -class GameSettings : Object; +singleton GameSettings : Object; // Most of the comments are copied from MSfD. A bit of cleanup is // still needed. diff --git a/mscripts/object.d b/mscripts/object.d index e9b08f0655..e30f028266 100644 --- a/mscripts/object.d +++ b/mscripts/object.d @@ -34,16 +34,15 @@ import sound.music; void initMonsterScripts() { // Add the script directories - MonsterClass.addPath("mscripts/"); - MonsterClass.addPath("mscripts/gameobjects/"); - MonsterClass.addPath("mscripts/sound/"); + vm.addPath("mscripts/"); + vm.addPath("mscripts/gameobjects/"); + vm.addPath("mscripts/sound/"); // Make sure the Object class is loaded auto mc = new MonsterClass("Object", "object.mn"); - // Create the config object too (only needed here because Object - // refers to Config. This will change.) - config.mo = (new MonsterClass("Config")).createObject; + // Get the Config singleton object + config.mo = (new MonsterClass("Config")).getSing(); // Bind various functions mc.bind("print", { print(); }); @@ -52,10 +51,6 @@ void initMonsterScripts() (stack.popInt,stack.popInt));}); mc.bind("sleep", new IdleSleep); - // Temporary hacks - mc.bind("config", { stack.pushObject(config.mo); }); - mc.bind("music", { stack.pushObject(Music.controlM); }); - // Load and run the test script mc = new MonsterClass("Test"); mc.createObject().call("test"); diff --git a/mscripts/object.mn b/mscripts/object.mn index 6a7798e64a..cfc9634b4e 100644 --- a/mscripts/object.mn +++ b/mscripts/object.mn @@ -24,11 +24,6 @@ // This is the base class of all OpenMW Monster classes. class Object; -// TODO: These are temporary hacks. A more elegant solution will be -// used once the language supports it. -native Config config(); -native Music music(); - // Sleeps a given amount of time idle sleep(float seconds); diff --git a/mscripts/sound/jukebox.mn b/mscripts/sound/jukebox.mn index e191bd414e..0900d5c451 100644 --- a/mscripts/sound/jukebox.mn +++ b/mscripts/sound/jukebox.mn @@ -72,7 +72,7 @@ pause() resume() { - if(!config().useMusic) return; + if(!Config.useMusic) return; if(isPaused) playSound(); else next(); @@ -94,7 +94,7 @@ stop() play() { - if(!config().useMusic) return; + if(!Config.useMusic) return; if(index >= playlist.length) return; @@ -112,7 +112,7 @@ play() // Play the next song in the playlist next() { - if(!config().useMusic) return; + if(!Config.useMusic) return; if(isPlaying) stop(); diff --git a/mscripts/sound/music.mn b/mscripts/sound/music.mn index 713e782ea8..30f0ed8d49 100644 --- a/mscripts/sound/music.mn +++ b/mscripts/sound/music.mn @@ -22,7 +22,7 @@ */ // This class controls all the music. -class Music : Object; +singleton Music : Object; // Create one jukebox for normal music, and one for battle music. This // way we can pause / fade out one while the other resumes / fades in. @@ -98,7 +98,7 @@ setPlaylists(char[][] normal, char[][] battlelist) // have been changed by the user. updateVolume() { - float v = config().calcMusicVolume(); + float v = Config.calcMusicVolume(); jukebox.updateVolume(v); battle.updateVolume(v); } diff --git a/sound/music.d b/sound/music.d index 72fb19c409..4f02104258 100644 --- a/sound/music.d +++ b/sound/music.d @@ -79,7 +79,7 @@ struct Music jukeC.bindConst({new Jukebox(params.obj()); }); controlC = MonsterClass.get("Music"); - controlM = controlC.createObject; + controlM = controlC.getSing(); controlM.call("setup"); }