From cc1f7f02e95ab448cb093003dfe2c3f53b04a2a7 Mon Sep 17 00:00:00 2001 From: nkorslund Date: Mon, 4 May 2009 12:20:18 +0000 Subject: [PATCH] Updated to Monster 0.12 git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@100 ea6a568a-9f4f-0410-981a-c910a81bb256 --- monster/compiler/assembler.d | 32 ++ monster/compiler/block.d | 7 + monster/compiler/bytecode.d | 28 +- monster/compiler/expression.d | 119 +++-- monster/compiler/functions.d | 175 ++++--- monster/compiler/operators.d | 19 +- monster/compiler/scopes.d | 300 +++++++++--- monster/compiler/statement.d | 22 +- monster/compiler/tokenizer.d | 18 +- monster/compiler/types.d | 454 +++++++++++++++--- monster/compiler/variables.d | 300 +++++++----- monster/modules/console.d | 145 +++--- monster/modules/vfs.d | 52 +- monster/options.d | 10 +- monster/options.openmw | 10 +- monster/update.sh | 2 +- monster/vm/init.d | 45 +- monster/vm/mclass.d | 28 +- monster/vm/mobject.d | 15 +- monster/vm/thread.d | 69 ++- monster/vm/vm.d | 152 +++--- mscripts/{gui => guiscripts}/makegui.mn | 0 mscripts/{gui => guiscripts}/module/gui.mn | 0 mscripts/{gui => guiscripts}/module/widget.mn | 0 ogre/gui.d | 4 +- 25 files changed, 1406 insertions(+), 600 deletions(-) rename mscripts/{gui => guiscripts}/makegui.mn (100%) rename mscripts/{gui => guiscripts}/module/gui.mn (100%) rename mscripts/{gui => guiscripts}/module/widget.mn (100%) diff --git a/monster/compiler/assembler.d b/monster/compiler/assembler.d index a4e317cb94..798f42653b 100644 --- a/monster/compiler/assembler.d +++ b/monster/compiler/assembler.d @@ -904,6 +904,29 @@ struct Assembler void castLongToInt() { pop(); } + void castFloatToInt(Type fr, Type to) + { + assert(fr.isFloating); + assert(to.isIntegral); + if(fr.isFloat) + { + if(to.isInt) cmd(BC.CastF2I); + else if(to.isUint) cmd(BC.CastF2U); + else if(to.isLong) cmd(BC.CastF2L); + else if(to.isUlong) cmd(BC.CastF2UL); + else assert(0); + } + else + { + assert(fr.isDouble); + if(to.isInt) cmd(BC.CastD2I); + else if(to.isUint) cmd(BC.CastD2U); + else if(to.isLong) cmd(BC.CastD2L); + else if(to.isUlong) cmd(BC.CastD2UL); + else assert(0); + } + } + void castIntToFloat(Type fr, Type to) { assert(fr.isIntegral); @@ -941,6 +964,15 @@ struct Assembler addi(tindex); } + // Cast an object to a given class. Takes a global class index. The + // object on the stack isn't changed in any way, but the VM checks + // if a cast is valid. + void downCast(int cindex) + { + cmd(BC.DownCast); + addi(cindex); + } + // Boolean operators void isEqual(int s) { cmdmult(BC.IsEqual, BC.IsEqualMulti, s); } diff --git a/monster/compiler/block.d b/monster/compiler/block.d index 121ff73190..c2859d3f6b 100644 --- a/monster/compiler/block.d +++ b/monster/compiler/block.d @@ -84,6 +84,13 @@ abstract class Block return isNext(toks, symbol); } + // Returns true if the next token is on a new line. Does not remove + // any tokens. + static bool isNewline(ref TokenArray toks) + // TT.EMPTY never occurs in the stream, so it's safe to check for + // it. + { return isSep(toks, TT.EMPTY); } + // Require either a line break or a given character (default ;) static void reqSep(ref TokenArray toks, TT symbol = TT.Semicolon) { diff --git a/monster/compiler/bytecode.d b/monster/compiler/bytecode.d index f7376ed67d..76d78e6238 100644 --- a/monster/compiler/bytecode.d +++ b/monster/compiler/bytecode.d @@ -244,21 +244,36 @@ enum BC CastI2L, // int to long (signed) + CastD2F, // double to float + CastF2D, // float to double + CastI2F, // int to float CastU2F, // uint to float CastL2F, // long to float CastUL2F, // ulong to float - CastD2F, // double to float CastI2D, // int to double CastU2D, // uint to double CastL2D, // long to double CastUL2D, // ulong to double - CastF2D, // float to double + + CastF2I, // float to int + CastF2U, // float to uint + CastF2L, // float to long + CastF2UL, // float to ulong + + CastD2I, // double to int + CastD2U, // double to uint + CastD2L, // double to long + CastD2UL, // double to ulong CastT2S, // cast any type to string. Takes the type // index (int) as a parameter + DownCast, // Takes a global class index (int). Checks if + // the object on the stack is an instance of + // the given class. + FetchElem, // Get an element from an array. Pops the // index, then the array reference, then // pushes the value. The element size is @@ -611,7 +626,16 @@ char[][] bcToString = BC.CastL2D: "CastL2D", BC.CastUL2D: "CastUL2D", BC.CastF2D: "CastF2D", + BC.CastF2I: "CastF2I", + BC.CastF2U: "CastF2U", + BC.CastF2L: "CastF2L", + BC.CastF2UL: "CastF2UL", + BC.CastD2I: "CastD2I", + BC.CastD2U: "CastD2U", + BC.CastD2L: "CastD2L", + BC.CastD2UL: "CastD2UL", BC.CastT2S: "CastT2S", + BC.DownCast: "DownCast", BC.PopToArray: "PopToArray", BC.NewArray: "NewArray", BC.CopyArray: "CopyArray", diff --git a/monster/compiler/expression.d b/monster/compiler/expression.d index 146a4f8adb..13dfb93704 100644 --- a/monster/compiler/expression.d +++ b/monster/compiler/expression.d @@ -65,9 +65,9 @@ abstract class Expression : Block Expression b; Floc ln; - // These are allowed for members (eg. hello().to.you()) - if(FunctionCallExpr.canParse(toks)) b = new FunctionCallExpr; - else if(VariableExpr.canParse(toks)) b = new VariableExpr; + // Only identifiers (variables, properties, etc) are allowed as + // members. + if(MemberExpr.canParse(toks)) b = new MemberExpr; else if(isMember) fail(toks[0].str ~ " can not be a member", toks[0].loc); // The rest are not allowed for members (eg. con.(a+b) is not @@ -145,6 +145,13 @@ abstract class Expression : Block fail("Array expected closing ]", toks); } } + // Any expression followed by a left parenthesis is + // interpreted as a function call. We do not allow it to + // be on the next line however, as this could be + // gramatically ambiguous. + else if(!isNewline(toks) && isNext(toks,TT.LeftParen)) + b = new FunctionCallExpr(b, toks); + else break; } // Finally, check for a single ++ or -- following an expression. @@ -477,10 +484,11 @@ class NewExpression : Expression // Parameters? ExprArray exps; - FunctionCallExpr.getParams(toks, exps, params); + if(isNext(toks, TT.LeftParen)) + FunctionCallExpr.getParams(toks, exps, params); if(exps.length != 0) - fail("'new' can only take names parameters", loc); + fail("'new' can only take named parameters", loc); } char[] toString() @@ -997,29 +1005,6 @@ class LiteralExpr : Expression } } -// Expressions that can be members of other types. Can be variables, -// types or functions. -abstract class MemberExpression : Expression -{ - Scope leftScope; - bool isMember = false; - Type ownerType; - - void resolveMember(Scope sc, Type ownerT) - { - assert(ownerT !is null); - leftScope = ownerT.getMemberScope(); - ownerType = ownerT; - isMember = true; - - resolve(sc); - } - - // Static members. These can be evaluated without needing the owner - // pushed onto the stack. - bool isStatic() { return false; } -} - // 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 @@ -1040,13 +1025,13 @@ class CastExpression : Expression type = newType; assert(type !is null); - assert(orig.type.canCastTo(type)); + assert(orig.type.canCastTo(type) || orig.type.canCastToExplicit(type)); } - // These are only needed for explisit casts, and will be used when - // "cast(Type)" expressions are implemented (if ever) - void parse(ref TokenArray) { assert(0, "Cannot parse casts yet"); } - void resolve(Scope sc) { assert(0, "Cannot resolve casts yet"); } + // These would only be used if typecasts got their own unique + // syntax, eg. "cast(Type) expression" like in D. + void parse(ref TokenArray) { assert(0, "Cannot parse casts"); } + void resolve(Scope sc) { assert(0, "Cannot resolve casts"); } bool isCTime() { @@ -1055,6 +1040,8 @@ class CastExpression : Expression int[] evalCTime() { + assert(isCTime()); + // Let the type do the conversion int[] res = type.typeCastCTime(orig, ""); @@ -1080,7 +1067,52 @@ class CastExpression : Expression // import x; // y = 3; // refers to x.y // y is resolved as x.y, where the owner (x) is an import holder. -class ImportHolder : Expression +abstract class ImportHolder : Expression +{ + // All lookups in this import is done through this function. + abstract ScopeLookup lookup(Token name, bool autoLoad=false); + + // Get a short name (for error messages) + abstract char[] getName(); + + override: + + // Override these + char[] toString() {assert(0);} + void evalAsm() {} + + // These aren't needed + final: + void parse(ref TokenArray) { assert(0); } + void resolve(Scope sc) {} + void evalDest() { assert(0); } +} + +// Import holder for packages +class PackageImpHolder : ImportHolder +{ + PackageScope sc; + + this(PackageScope psc) + { + sc = psc; + assert(sc !is null); + } + + override: + + ScopeLookup lookup(Token name, bool autoLoad=false) + { + return sc.lookup(name, autoLoad); + } + + char[] getName() { return sc.toString(); } + + char[] toString() { return "imported package " ~ sc.toString(); } +} + +// Import holder for classes +class ClassImpHolder : ImportHolder { MonsterClass mc; @@ -1101,25 +1133,24 @@ class ImportHolder : Expression assert(type !is null); } - // All lookups in this import is done through this function. Can be - // used to filter lookups later on. - ScopeLookup lookup(Token name) + override: + + char[] getName() { return mc.name.str; } + + ScopeLookup lookup(Token name, bool autoLoad=false) { assert(mc !is null); mc.requireScope(); - return mc.sc.lookup(name); + return mc.sc.lookup(name, autoLoad); } - override: - void parse(ref TokenArray) { assert(0); } - void resolve(Scope sc) {} - void evalDest() { assert(0); } - char[] toString() { return "imported class " ~ mc.name.str ~ ""; } + char[] toString() { return "imported class " ~ mc.name.str; } void evalAsm() { - mc.requireCompile(); + assert(mc !is null); + mc.requireScope(); if(mc.isSingleton) { assert(type.isObject); diff --git a/monster/compiler/functions.d b/monster/compiler/functions.d index f1eeffcd94..c22e6bf747 100644 --- a/monster/compiler/functions.d +++ b/monster/compiler/functions.d @@ -58,8 +58,8 @@ import std.stdio; import std.stream; import std.string; -// TODO/FIXME: Make tango compatible before release -import std.traits; +version(Tango) import tango.core.Traits; +else import std.traits; // 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 @@ -138,8 +138,16 @@ struct Function { assert(isNative, "cannot bind to non-native function " ~ name.str); - alias ParameterTypeTuple!(func) P; - alias ReturnType!(func) R; + version(Tango) + { + alias ParameterTypleOf!(func) P; + alias ReturnTypeOf!(func) R; + } + else + { + alias ParameterTypeTuple!(func) P; + alias ReturnType!(func) R; + } assert(P.length == params.length, format("function %s has %s parameters, but binding function has %s", name.str, params.length, P.length)); @@ -918,9 +926,9 @@ struct NamedParam } // Expression representing a function call -class FunctionCallExpr : MemberExpression +class FunctionCallExpr : Expression { - Token name; + Expression fname; // Function name or pointer value ExprArray params; // Normal (non-named) parameters NamedParam[] named; // Named parameters @@ -930,18 +938,14 @@ class FunctionCallExpr : MemberExpression // used for vararg functions. Function* fd; + bool isCast; // If true, this is an explicit typecast, not a + // function call. + bool isVararg; - // Used to simulate a member for imported variables - DotOperator dotImport; - bool recurse = true; - - static bool canParse(TokenArray toks) - { - return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen); - } - - // Read a function parameter list (a,b,v1=c,v2=d,...) + // Read a function parameter list (a,b,v1=c,v2=d,...). The function + // expects that you have already removed the initial left paren '(' + // token. static void getParams(ref TokenArray toks, out ExprArray parms, out NamedParam[] named) @@ -949,8 +953,6 @@ class FunctionCallExpr : MemberExpression parms = null; named = null; - if(!isNext(toks, TT.LeftParen)) return; - // No parameters? if(isNext(toks, TT.RightParen)) return; @@ -987,51 +989,80 @@ class FunctionCallExpr : MemberExpression fail("Parameter list expected ')'", toks); } - void parse(ref TokenArray toks) + + this(Expression func, ref TokenArray toks) { - name = next(toks); - loc = name.loc; + assert(func !is null); + fname = func; + loc = fname.loc; + // Parse the parameter list getParams(toks, params, named); } + /* Might be used for D-like implicit function calling, eg. someFunc; + or (obj.prop) + this(Expression func) {} + + We might also allow a special free-form function call syntax, eg. + func 1 2 3 + + where parens and commas are optional, and the list is terminated by + isSep (newline or ;). This is useful mostly for console mode. + */ + + void parse(ref TokenArray toks) { assert(0); } + char[] toString() { - char[] result = name.str ~ "("; + char[] result = fname.toString ~ "("; foreach(b; params) - result ~= b.toString ~" "; + result ~= b.toString ~", "; + foreach(b; named) + result ~= b.name.str ~ "=" ~ b.value.toString ~ ", "; return result ~ ")"; } + char[] name() { assert(fname !is null); return fname.toString(); } + void resolve(Scope sc) { - if(isMember) // Are we called as a member? - { - assert(leftScope !is null); - auto l = leftScope.lookup(name); - fd = l.func; - if(!l.isFunc) - fail(name.str ~ " is not a member function of " - ~ leftScope.toString, loc); - } - else - { - assert(leftScope is null); - auto l = sc.lookupImport(name); + // Resolve the function lookup first + fname.resolve(sc); - // For imported functions, we have to do some funky magic - if(l.isImport) - { - assert(l.imphold !is null); - dotImport = new DotOperator(l.imphold, this, loc); - dotImport.resolve(sc); - return; - } + // Is the 'function' really a type name? + if(fname.type.isMeta) + { + // If so, it's a type cast! Get the type we're casting to. + type = fname.type.getBase(); + assert(type !is null); - fd = l.func; - if(!l.isFunc) - fail("Undefined function "~name.str, name.loc); - } + // Only one (non-named) parameter is allowed + if(params.length != 1 || named.length != 0) + fail("Invalid parameter list to type cast", loc); + + isCast = true; + + // Resolve the expression we're converting + params[0].resolve(sc); + + // Cast it + type.typeCastExplicit(params[0]); + + return; + } + + // TODO: Do typecasting here. That will take care of polysemous + // types later as well. + if(!fname.type.isFunc) + fail(format("Expression '%s' of type %s is not a function", + fname, fname.typeString), loc); + + // In the future, we will probably use a global function + // index. Right now, just get the function from the type. + auto ft = cast(FunctionType)fname.type; + assert(ft !is null); + fd = ft.func; isVararg = fd.isVararg; type = fd.type; @@ -1043,8 +1074,8 @@ class FunctionCallExpr : MemberExpression // 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); + name, fd.params.length-1, params.length), + loc); // Check parameter types except for the vararg parameter foreach(int i, par; fd.params[0..$-1]) @@ -1108,7 +1139,7 @@ class FunctionCallExpr : MemberExpression } if(index == -1) fail(format("Function %s() has no paramter named %s", - name.str, p.name.str), + name, p.name.str), p.name.loc); assert(index, <=, >=, &&, ||, =i=, =I=, !=i=, !=I= class BooleanOperator : BinaryOperator { diff --git a/monster/compiler/scopes.d b/monster/compiler/scopes.d index 9aa2f4e120..743be74331 100644 --- a/monster/compiler/scopes.d +++ b/monster/compiler/scopes.d @@ -44,6 +44,8 @@ import monster.vm.mclass; import monster.vm.error; import monster.vm.vm; +//debug=lookupTrace; + // The global scope RootScope global; @@ -65,6 +67,7 @@ enum LType LoopLabel, Property, Import, + Package, } const char[][] LTypeName = @@ -78,6 +81,7 @@ const char[][] LTypeName = LType.StateLabel: "state label", LType.LoopLabel: "loop label", LType.Property: "property", + LType.Package: "package", ]; struct ScopeLookup @@ -107,6 +111,7 @@ struct ScopeLookup bool isState() { return ltype == LType.State; } bool isNone() { return ltype == LType.None; } bool isImport() { return ltype == LType.Import; } + bool isPackage() { return ltype == LType.Package; } bool isProperty() { bool ret = (ltype == LType.Property); @@ -202,10 +207,12 @@ abstract class Scope // error. Recurses through parent scopes. final void clearId(Token name) { + debug(lookupTrace) + writefln("ClearId %s in %s (line %s)", name, this, __LINE__); + // Lookup checks all parent scopes so we only have to call it // once. - auto sl = lookup(name); - assert(sl.name.str == name.str); + auto sl = lookup(name, false); if(!sl.isNone) { @@ -213,10 +220,18 @@ abstract class Scope fail(name.str ~ " is a property and cannot be redeclared", name.loc); - fail(format("%s is already declared (at %s) as a %s", - name.str, name.loc, LTypeName[sl.ltype]), + char[] oldLoc = name.loc.toString; + + // There might be a case insensitive match. In that case we + // should print the original name as well. + if(sl.name.str != name.str) + oldLoc ~= " as " ~ sl.name.str; + + fail(format("%s is already declared as a %s (at %s)", + name.str, LTypeName[sl.ltype], oldLoc), name.loc); } + assert(sl.name.str == name.str); } // Is this the root scope? @@ -266,6 +281,12 @@ abstract class Scope return parent.getArray(); } + PackageScope getPackage() + { + assert(!isRoot(), "getPackage called on the wrong scope type"); + return parent.getPackage(); + } + int getLoopStack() { assert(!isRoot(), "getLoopStack called on wrong scope type"); @@ -278,19 +299,38 @@ abstract class Scope LabelStatement getBreak(char[] name = "") { return null; } LabelStatement getContinue(char[] name = "") { return null; } + // Lookup a string final ScopeLookup lookupName(char[] name) { return lookup(Token(name, Floc.init)); } - ScopeLookup lookup(Token name) + // Look up a token in this scope. If autoLoad is true, load classes + // automatically if a file exists. + ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); if(isRoot()) return ScopeLookup(name, LType.None, null, null); - else return parent.lookup(name); + else return parent.lookup(name, autoLoad); } - // Look up an identifier, and check imported scopes as well. - ScopeLookup lookupImport(Token name) + // Look up a token in this scope. If the token is not found, try the + // lookup again and check the file system for classes. + ScopeLookup lookupClass(Token name) { - auto l = lookup(name); + auto sl = lookup(name); + if(sl.isNone) + sl = lookup(name, true); + return sl; + } + + // Look up an identifier, and check imported scopes as well. If the + // token is not found, do the lookup again but this time check the + // file system for classes as well. + ScopeLookup lookupImport(Token name, bool second=false) + { + debug(lookupTrace) + writefln("LookupImport %s in %s (line %s)", name, this, __LINE__); + auto l = lookup(name, second); if(!l.isNone) return l; // Nuttin' was found, try the imports @@ -298,7 +338,7 @@ abstract class Scope auto old = l; foreach(imp; importList) { - l = imp.lookup(name); + l = imp.lookup(name, second); // Only accept types, classes, variables and functions if(l.isType || l.isClass || l.isVar || l.isFunc) @@ -307,8 +347,8 @@ abstract class Scope if(found && l.sc !is old.sc) fail(format( "%s matches both %s.%s (at %s) and %s.%s (at %s)", name.str, - old.imphold.mc.name.str, old.name.str, old.name.loc, - imp.mc.name.str, l.name.str, l.name.loc), + old.imphold.getName(), old.name.str, old.name.loc, + imp.getName(), l.name.str, l.name.loc), name.loc); // First match @@ -319,7 +359,14 @@ abstract class Scope } if(!found) - return ScopeLookup(name, LType.None, null, null); + { + // If we haven't tried searching the file system for + // classes, try that. + if(!second) + return lookupImport(name, true); + + return ScopeLookup(name, LType.None, null, null); + } // Tell the caller that this is an import. We override the // lookup struct, but it doesn't matter since the lookup will @@ -336,7 +383,7 @@ abstract class Scope // imports. Eg. global.registerImport(myclass) -> makes myclass // available in ALL classes. void registerImport(MonsterClass mc) - { registerImport(new ImportHolder(mc)); } + { registerImport(new ClassImpHolder(mc)); } // Even more user-friendly version. Takes a list of class names. void registerImport(char[][] cls ...) @@ -420,8 +467,11 @@ final class StateScope : Scope } override: - ScopeLookup lookup(Token name) + ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + assert(name.str != ""); // Check against state labels @@ -429,7 +479,7 @@ final class StateScope : Scope if(st.labels.inList(name.str, lb)) return ScopeLookup(lb.name, LType.StateLabel, null, this, lb); - return super.lookup(name); + return super.lookup(name, autoLoad); } State* getState() { return st; } @@ -456,7 +506,7 @@ final class RootScope : PackageScope CIndex next = 1; public: - this() { super(null, "global"); } + this() { super(null, "global", ""); } bool allowRoot() { return true; } // Get a class by index @@ -475,10 +525,6 @@ final class RootScope : PackageScope // it will get the same index. CIndex getForwardIndex(char[] name) { - MonsterClass mc; - mc = vm.loadNoFail(name); - if(mc !is null) return mc.getIndex(); - // Called when an existing forward does not exist void inserter(ref CIndex v) { v = next++; } @@ -493,6 +539,24 @@ final class RootScope : PackageScope { return indexList.inList(ci); } + + override ScopeLookup lookup(Token name, bool autoLoad=false) + { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + + // Basic types are looked up here + if(BasicType.isBasic(name.str)) + return ScopeLookup(name, LType.Type, BasicType.get(name.str), this); + + // Return ourselves as the package named 'global' + if(name.str == "global") + return ScopeLookup(name, LType.Package, type, this); + + // No parents to check + assert(isRoot()); + return super.lookup(name, autoLoad); + } } // A package scope is a scope that can contain classes. @@ -503,16 +567,86 @@ class PackageScope : Scope // can look up file names too. HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes; + // List of sub-packages + HashTable!(char[], PackageScope, GCAlloc, CITextHash) children; + + char[] path; + public: - this(Scope last, char[] name) + // The type is used for expression lookups, eg. packname.Class(obj); + PackageType type; + + this(Scope last, char[] name, char[] dir) { super(last, name); - assert(last is null); + auto ps = cast(PackageScope) last; + assert(last is null || ps !is null, + "Packages can only have other packages as parent scopes"); + + path = dir; + assert(!path.begins("/")); + + assert(vm.vfs !is null); + if(dir != "" && !vm.vfs.hasDir(dir)) + fail("Cannot create package "~toString~": directory " + ~dir~" not found"); + + type = new PackageType(this); } bool isPackage() { return true; } + PackageScope getPackage() + { + return this; + } + + char[] getPath(char[] file) + { + if(path == "") return file; + + file = path ~ "/" ~ file; + assert(file[0] != '/'); + return file; + } + + // Insert a new sub-package, or find an existing one. All package + // names are case insensitive. Returns the package scope. Fails if + // there are naming conflicts with other identifiers. + PackageScope insertPackage(char[] name) + { + auto sl = lookupName(name); + + if(!sl.isPackage) + fail(format("Package name %s is already declared (at %s) as a %s", + sl.name.str, sl.name.loc, LTypeName[sl.ltype])); + if(sl.isNone) + fail("Cannot find package " ~ name); + + auto ps = cast(PackageScope)sl.sc; + assert(ps !is null); + return ps; + } + + // Used internally from lookup() + private PackageScope makeSubPackage(char[] name) + { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + + if(!isValidIdent(name)) + fail("Cannot create package " ~ name ~ ": not a valid identifier"); + + assert(!children.inList(name)); + + PackageScope ps; + ps = new PackageScope(this, name, getPath(name)); + children[name] = ps; + + return ps; + } + // Insert a new class into the scope. The class is given a unique // global index. If the class was previously forward referenced, the // forward is replaced and the previously assigned forward index is @@ -548,6 +682,9 @@ class PackageScope : Scope if(global.forwards.inList(cls.name.str, ci)) { + if(this !is global) + fail("Class " ~ cls.name.str ~ " was forward referenced and can only be added to the 'global' package, not to " ~ toString); + // ci is set, remove the entry from the forwards hashmap assert(ci != 0); global.forwards.remove(cls.name.str); @@ -570,46 +707,62 @@ class PackageScope : Scope // before the actual class is loaded. bool ciInList(char[] name) { return classes.inList(name); } - bool ciInList(char[] name, ref MonsterClass cb) + bool ciInList(char[] name, out MonsterClass cb) { return classes.inList(name, cb); } // Case sensitive versions. If a class is found that does not match // in case, it is an error. - bool csInList(char[] name, ref MonsterClass cb) + bool csInList(char[] name, out MonsterClass cb) { - return ciInList(name, cb) && cb.name.str == name; + bool result = ciInList(name, cb) && cb.name.str == name; + + // The caller depends on the output to be null if the search + // fails. + if(!result) cb = null; + + return result; } - // Get the class. It must exist and the case must match. getClass - // will set up the class scope if this is not already done. - MonsterClass getClass(char[] name) + override ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + + // Look up packages + PackageScope psc; + if(!children.inList(name.str, psc)) + // Check the file system if there is a matching directory + if(vm.vfs.hasDir(getPath(name.str))) + psc = makeSubPackage(name.str); + + if(psc !is null) + return ScopeLookup(name, LType.Package, psc.type, psc); + + // Look up classes MonsterClass mc; - if(!csInList(name, mc)) + if(!csInList(name.str, mc) && autoLoad) { - char[] msg = "Class '" ~ name ~ "' not found."; - if(ciInList(name, mc)) - msg ~= " (Perhaps you meant " ~ mc.name.str ~ "?)"; - fail(msg); + // Load the class from file, if it exists + mc = vm.loadNoFail(name.str, + tolower(getPath(name.str))~".mn"); } - mc.requireScope(); - return mc; - } - override ScopeLookup lookup(Token name) - { - // Type names can never be overwritten, so we check the class - // list and the built-in types. - if(BasicType.isBasic(name.str)) - return ScopeLookup(name, LType.Type, BasicType.get(name.str), this); - - MonsterClass mc; - if(csInList(name.str, mc)) + if(mc !is null) return ScopeLookup(mc.name, LType.Class, null, this, mc); - // No parents to check - assert(isRoot()); - return super.lookup(name); + // Unlike other scopes, a package scope doesn't check through + // all its parent packages. That would mean that a name search + // for a package or a class could 'leak' out of a directory and + // find the wrong match. Instead we jump directly to the root + // scope. + + // If we're the root scope, super.lookup will return an empty + // result. + if(isRoot()) return super.lookup(name, autoLoad); + + // Otherwise, jump directly to root, do not collect 200 dollars. + assert(global !is this); + return global.lookup(name, autoLoad); } } @@ -637,15 +790,18 @@ abstract class VarScope : Scope override: - ScopeLookup lookup(Token name) + ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + assert(name.str != ""); Variable *vd; if(variables.inList(name.str, vd)) return ScopeLookup(vd.name, LType.Variable, vd.type, this, vd); - return super.lookup(name); + return super.lookup(name, autoLoad); } } @@ -685,8 +841,11 @@ class FVScope : VarScope } override: - ScopeLookup lookup(Token name) + ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + assert(name.str != ""); Function* fd; @@ -695,7 +854,7 @@ class FVScope : VarScope return ScopeLookup(fd.name, LType.Function, fd.type, this, fd); // Let VarScope handle variables - return super.lookup(name); + return super.lookup(name, autoLoad); } } @@ -729,8 +888,11 @@ class TFVScope : FVScope } override: - ScopeLookup lookup(Token name) + ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + assert(name.str != ""); StructType sd; @@ -744,7 +906,7 @@ class TFVScope : FVScope return ScopeLookup(Token(tp.name, tp.loc), LType.Type, tp, this); // Pass it on to the parent - return super.lookup(name); + return super.lookup(name, autoLoad); } } @@ -930,8 +1092,11 @@ final class ClassScope : TFVScope return tmp; } - override ScopeLookup lookup(Token name) + override ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + assert(name.str != ""); State* sd; @@ -945,7 +1110,7 @@ final class ClassScope : TFVScope return sl; // Let the parent handle everything else - return super.lookup(name); + return super.lookup(name, autoLoad); } // Get total data segment size @@ -1004,6 +1169,13 @@ abstract class StackScope : VarScope return tmp; } + // Reset the stack counters. Used from the console. + void reset() + { + locals = 0; + sumLocals = 0; + } + /* void push(int i) { expStack += i; } void push(Type t) { push(t.getSize); } @@ -1108,14 +1280,17 @@ abstract class PropertyScope : Scope bool isProperty() { return true; } bool allowRoot() { return true; } - ScopeLookup lookup(Token name) + ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + // Does this scope contain the property? if(hasProperty(name.str)) return ScopeLookup(name, LType.Property, null, this); // Check the parent scope - return super.lookup(name); + return super.lookup(name, autoLoad); } } @@ -1184,15 +1359,18 @@ class LoopScope : CodeScope continueLabel = new LabelStatement(getTotLocals()); } - override ScopeLookup lookup(Token name) + override ScopeLookup lookup(Token name, bool autoLoad=false) { + debug(lookupTrace) + writefln("Lookup %s in %s (line %s)", name, this, __LINE__); + assert(name.str != ""); // Check for loop labels if(loopName.str == name.str) return ScopeLookup(loopName, LType.LoopLabel, null, this); - return super.lookup(name); + return super.lookup(name, autoLoad); } // Get the break or continue label for the given named loop, or the diff --git a/monster/compiler/statement.d b/monster/compiler/statement.d index ffc1aa8871..7bb48e58c0 100644 --- a/monster/compiler/statement.d +++ b/monster/compiler/statement.d @@ -125,14 +125,24 @@ class ImportStatement : Statement if(type.isReplacer) type = type.getBase(); - if(!type.isObject) - fail("Can only import from classes", type.loc); + if(type.isObject) + { + auto t = cast(ObjectType)type; + assert(t !is null); + mc = t.getClass(type.loc); - auto t = cast(ObjectType)type; - assert(t !is null); - mc = t.getClass(type.loc); + sc.registerImport(new ClassImpHolder(mc)); + } + else if(type.isPackage) + { + auto t = cast(PackageType)type; + assert(t !is null); + auto psc = t.sc; - sc.registerImport(new ImportHolder(mc)); + sc.registerImport(new PackageImpHolder(psc)); + } + else + fail("Can only import from classes and packages", type.loc); } // Resolve the statement if present diff --git a/monster/compiler/tokenizer.d b/monster/compiler/tokenizer.d index 2e8f075a57..deb681174d 100644 --- a/monster/compiler/tokenizer.d +++ b/monster/compiler/tokenizer.d @@ -53,6 +53,20 @@ bool validFirstIdentChar(char c) return false; } +bool isValidIdent(char[] iname) +{ + if(iname.length == 0) + return false; + + if(!validFirstIdentChar(iname[0])) + return false; + + foreach(char c; iname) + if(!validIdentChar(c)) return false; + + return true; +} + bool numericalChar(char c) { return c >= '0' && c <= '9'; @@ -365,7 +379,7 @@ class Tokenizer this.inf = inf; this.fname = fname; - empty.type = TT.EMPTY; + this(); } // This is used for single-line mode, such as in a console. @@ -695,6 +709,8 @@ class Tokenizer } Token tt = getNextFromLine(); + + // Skip empty lines, don't return them into the token list. if(tt.type == TT.EMPTY) goto restart; diff --git a/monster/compiler/types.d b/monster/compiler/types.d index 7020f4ee95..5316899f3f 100644 --- a/monster/compiler/types.d +++ b/monster/compiler/types.d @@ -36,8 +36,11 @@ import monster.compiler.states; import monster.compiler.structs; import monster.vm.arrays; import monster.vm.mclass; +import monster.vm.mobject; import monster.vm.error; +import monster.options; + import std.stdio; import std.utf; import std.string; @@ -54,6 +57,8 @@ import std.string; ArrayType StructType EnumType + FunctionType + PackageType UserType (replacer for identifier-named types like structs and classes) TypeofType (replacer for typeof(x) when used as a type) @@ -76,6 +81,7 @@ abstract class Type : Block // tokens.) static bool canParseRem(ref TokenArray toks) { + // We allow typeof(expression) as a type if(isNext(toks, TT.Typeof)) { reqNext(toks, TT.LeftParen); @@ -84,10 +90,17 @@ abstract class Type : Block return true; } + // The 'var' keyword can be used as a type if(isNext(toks, TT.Var)) return true; + // Allow typename if(!isNext(toks, TT.Identifier)) return false; + // Allow a.b.c + while(isNext(toks, TT.Dot)) + if(!isNext(toks, TT.Identifier)) return false; + + // Allow a[][] while(isNext(toks,TT.LeftSquare)) if(!isNext(toks,TT.RightSquare)) return false; @@ -184,8 +197,10 @@ abstract class Type : Block bool isArray() { return arrays() != 0; } bool isObject() { return false; } + bool isPackage() { return false; } bool isStruct() { return false; } bool isEnum() { return false; } + bool isFunc() { return false; } bool isReplacer() { return false; } @@ -310,7 +325,8 @@ abstract class Type : Block final char[] toString() { return name; } // Cast the expression orig to this type. Uses canCastTo to - // determine if a cast is possible. + // determine if a cast is possible. The 'to' parameter is used in + // error messages to describe the destination. final void typeCast(ref Expression orig, char[] to) { if(orig.type == this) return; @@ -321,11 +337,25 @@ abstract class Type : Block if(orig.type.canCastTo(this)) orig = new CastExpression(orig, this); else - fail(format("Cannot cast %s of type %s to %s of type %s", + fail(format("Cannot implicitly cast %s of type %s to %s of type %s", orig.toString, orig.typeString, to, this), orig.loc); } + // Do explicit type casting. This allows for a wider range of type + // conversions. + final void typeCastExplicit(ref Expression orig) + { + if(orig.type == this) return; + + if(orig.type.canCastToExplicit(this) || orig.type.canCastTo(this)) + orig = new CastExpression(orig, this); + else + fail(format("Cannot cast %s of type %s to type %s", + orig.toString, orig.typeString, + this), orig.loc); + } + // Do compile-time type casting. Gets orig.evalCTime() and returns // the converted result. final int[] typeCastCTime(Expression orig, char[] to) @@ -334,10 +364,10 @@ abstract class Type : Block assert(res.length == orig.type.getSize); - if(orig.type.canCastTo(this)) + if(orig.type.canCastCTime(this)) res = orig.type.doCastCTime(res, this); else - fail(format("Cannot cast %s of type %s to %s of type %s", + fail(format("Cannot cast %s of type %s to %s of type %s (at compile time)", orig.toString, orig.typeString, to, this), orig.loc); @@ -353,10 +383,17 @@ abstract class Type : Block return false; // By default we can't cast anything } + // Can the type be explicitly cast? Is not required to handle cases + // where canCastTo is true. + bool canCastToExplicit(Type to) + { + return false; + } + // Returns true if the cast can be performed at compile time. This - // is usually true. + // is usually true if we can cast at runtime. bool canCastCTime(Type to) - { return canCastTo(to); } + { return canCastTo(to) || canCastToExplicit(to); } // Returns true if the types are equal or if canCastTo returns true. final bool canCastOrEqual(Type to) @@ -364,13 +401,15 @@ abstract class Type : Block return to == this || canCastTo(to); } - // Do the cast in the assembler. Rename to compileCastTo, perhaps. + // Do the cast in the assembler. Must handle both implicit and + // explicit casts. void evalCastTo(Type to) { assert(0, "evalCastTo not implemented for type " ~ toString); } - // Do the cast in the compiler. + // Do the cast in the compiler. Must handle both implicit and + // explicit casts. int[] doCastCTime(int[] data, Type to) { assert(0, "doCastCTime not implemented for type " ~ toString); @@ -443,7 +482,7 @@ abstract class Type : Block // Find the common type if(t1.canCastTo(t2)) common = t2; else if(t2.canCastTo(t1)) common = t1; else - fail(format("Cannot cast %s of type %s to %s of type %s, or vice versa.", e1, t1, e2, t2), e1.loc); + fail(format("Cannot implicitly cast %s of type %s to %s of type %s, or vice versa.", e1, t1, e2, t2), e1.loc); } // Wrap the expressions in CastExpression blocks if necessary. @@ -454,7 +493,7 @@ abstract class Type : Block // Internal types are types that are used internally by the compiler, // eg. for type conversion or for special syntax. They can not be used -// for variables, neither directly nor indirectly. +// for variables or values, neither directly nor indirectly. abstract class InternalType : Type { final: @@ -463,6 +502,28 @@ abstract class InternalType : Type int getSize() { return 0; } } +// Handles package names, in expressions like Package.ClassName. It is +// only used for these expressions and never for anything else. +class PackageType : InternalType +{ + PackageScope sc; + + this(PackageScope _sc) + { + sc = _sc; + name = sc.toString() ~ " (package)"; + } + + override: + Scope getMemberScope() + { + assert(sc !is null); + return sc; + } + + bool isPackage() { return true; } +} + // Handles the 'null' literal. This type is only used for // conversions. You cannot declare variables of the nulltype. class NullType : InternalType @@ -529,10 +590,11 @@ class BasicType : Type // be created. this(char[] tn) { - if(!isBasic(tn)) - fail("BasicType does not support type " ~ tn); - name = tn; + if(name == "") name = "(none)"; + + if(!isBasic(name)) + fail("BasicType does not support type " ~ tn); // Cache the class to save some overhead store[tn] = this; @@ -582,7 +644,7 @@ class BasicType : Type static bool isBasic(char[] tn) { - return (tn == "" || tn == "int" || tn == "float" || tn == "char" || + return (tn == "(none)" || tn == "int" || tn == "float" || tn == "char" || tn == "bool" || tn == "uint" || tn == "long" || tn == "ulong" || tn == "double"); } @@ -605,6 +667,8 @@ class BasicType : Type // Get the name and the line from the token name = t.str; loc = t.loc; + + if(name == "") name = "(none)"; } bool isInt() { return name == "int"; } @@ -615,7 +679,7 @@ class BasicType : Type bool isBool() { return name == "bool"; } bool isFloat() { return name == "float"; } bool isDouble() { return name == "double"; } - bool isVoid() { return name == ""; } + bool isVoid() { return name == "(none)"; } Scope getMemberScope() { @@ -636,6 +700,14 @@ class BasicType : Type // List the implisit conversions that are possible bool canCastTo(Type to) { + // If options.d allow it, we can implicitly convert back from + // floats to integral types. + static if(implicitTruncate) + { + if(isFloating && to.isIntegral) + return true; + } + // We can convert between all integral types if(to.isIntegral) return isIntegral; @@ -649,6 +721,14 @@ class BasicType : Type return false; } + bool canCastToExplicit(Type to) + { + if(isFloating && to.isIntegral) + return true; + + return false; + } + void evalCastTo(Type to) { assert(this != to); @@ -658,7 +738,9 @@ class BasicType : Type int toSize = to.getSize(); bool fromSign = isInt || isLong || isFloat || isBool; - if(to.isInt || to.isUint) + if(isFloating && to.isIntegral) + tasm.castFloatToInt(this, to); + else if(to.isInt || to.isUint) { assert(isIntegral); if(isLong || isUlong) @@ -714,17 +796,49 @@ class BasicType : Type assert(data.length == fromSize); bool fromSign = isInt || isLong || isFloat || isBool; - if(to.isInt || to.isUint) + // Set up a new array to hold the result + int[] toData = new int[toSize]; + + if(isFloating && to.isIntegral) + { + int *iptr = cast(int*)toData.ptr; + uint *uptr = cast(uint*)toData.ptr; + long *lptr = cast(long*)toData.ptr; + ulong *ulptr = cast(ulong*)toData.ptr; + + if(isFloat) + { + float f = *(cast(float*)data.ptr); + if(to.isInt) *iptr = cast(int) f; + else if(to.isUint) *uptr = cast(uint) f; + else if(to.isLong) *lptr = cast(long) f; + else if(to.isUlong) *ulptr = cast(ulong) f; + else assert(0); + } + else if(isDouble) + { + double f = *(cast(double*)data.ptr); + if(to.isInt) *iptr = cast(int) f; + else if(to.isUint) *uptr = cast(uint) f; + else if(to.isLong) *lptr = cast(long) f; + else if(to.isUlong) *ulptr = cast(ulong) f; + else assert(0); + } + else assert(0); + } + else if(to.isInt || to.isUint) { assert(isIntegral); - data = data[0..1]; + // Just pick out the least significant int + toData[] = data[0..1]; } else if(to.isLong || to.isUlong) { if(isInt || isUint) { - if(fromSign && to.isLong && data[0] < 0) data ~= -1; - else data ~= 0; + toData[0] = data[0]; + if(fromSign && to.isLong && data[0] < 0) toData[1] = -1; + else toData[1] = 0; } else assert(isUlong || isLong); } @@ -732,7 +846,7 @@ class BasicType : Type { assert(isNumerical); - float *fptr = cast(float*)data.ptr; + float *fptr = cast(float*)toData.ptr; if(isInt) *fptr = data[0]; else if(isUint) *fptr = cast(uint)data[0]; @@ -740,14 +854,13 @@ class BasicType : Type else if(isUlong) *fptr = *(cast(ulong*)data.ptr); else if(isDouble) *fptr = *(cast(double*)data.ptr); else assert(0); - data = data[0..1]; } else if(to.isDouble) { assert(isNumerical); - if(data.length < 2) data.length = 2; - double *fptr = cast(double*)data.ptr; + assert(toData.length == 2); + double *fptr = cast(double*)toData.ptr; if(isInt) *fptr = data[0]; else if(isUint) *fptr = cast(uint)data[0]; @@ -755,14 +868,14 @@ class BasicType : Type else if(isUlong) *fptr = *(cast(ulong*)data.ptr); else if(isFloat) *fptr = *(cast(float*)data.ptr); else assert(0); - data = data[0..2]; } else fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~ " not implemented."); - assert(data.length == toSize); - return data; + assert(toData.length == toSize); + assert(toData.ptr !is data.ptr); + return toData; } int getSize() @@ -804,10 +917,7 @@ class BasicType : Type } } -// Represents a normal class name. The reason this is called -// "ObjectType" is because an actual variable (of this type) points to -// an object, not to a class. The meta-type ClassType should be used -// for variables that point to classes. +// Represents a normal class name. class ObjectType : Type { final: @@ -859,8 +969,11 @@ class ObjectType : Type char[] valToString(int[] data) { + // Use the object's toString function assert(data.length == 1); - return format("%s#%s", name, data[0]); + if(data[0] == 0) return "(null object)"; + auto mo = getMObject(cast(MIndex)data[0]); + return mo.toString(); } int getSize() { return 1; } @@ -869,6 +982,19 @@ class ObjectType : Type bool canCastCTime(Type to) { return false; } + // Used internally below, to get the class from another object type. + private MonsterClass getOther(Type other) + { + assert(other !is this); + assert(other.isObject); + auto ot = cast(ObjectType)other; + assert(ot !is null); + auto cls = ot.getClass(); + assert(cls !is null); + assert(cls !is getClass()); + return cls; + } + bool canCastTo(Type type) { assert(clsIndex != 0); @@ -877,31 +1003,58 @@ class ObjectType : Type if(type.isObject) { - auto ot = cast(ObjectType)type; - assert(ot !is null); - MonsterClass us = getClass(); - MonsterClass other = ot.getClass(); + MonsterClass other = getOther(type); - assert(us != other); + // Allow implicit downcasting if options.d says so. + static if(implicitDowncast) + { + if(other.childOf(us)) + return true; + } - // We can only upcast + // We can always upcast implicitly return other.parentOf(us); } return false; } + bool canCastToExplicit(Type type) + { + // We can explicitly cast to a child class, and let the VM + // handle any errors. + if(type.isObject) + { + auto us = getClass(); + auto other = getOther(type); + + return other.childOf(us); + } + } + void evalCastTo(Type to) { assert(clsIndex != 0); - assert(canCastTo(to)); + assert(canCastTo(to) || canCastToExplicit(to)); if(to.isObject) { - auto tt = cast(ObjectType)to; - assert(tt !is null); - assert(clsIndex !is tt.clsIndex); + auto us = getClass(); + auto other = getOther(to); + + // Upcasting doesn't require any action + if(other.parentOf(us)) {} + + // Downcasting (from parent to child) requires that VM + // checks the runtime type of the object. + else if(other.childOf(us)) + // Use the global class index + tasm.downCast(other.getIndex()); + + // We should never get here + else assert(0); + return; } @@ -923,7 +1076,12 @@ class ObjectType : Type // body (not just the header) is being resolved. void resolve(Scope sc) { - clsIndex = global.getForwardIndex(name); + // Insert the class into the global scope as a forward + // reference. Note that this means the class may not be part of + // any other package than 'global' when it is loaded later. + if(clsIndex == 0) + clsIndex = global.getForwardIndex(name); + assert(clsIndex != 0); } } @@ -980,7 +1138,6 @@ class ArrayType : Type int[] doCastCTime(int[] data, Type to) { assert(to.isString); - return [valToStringIndex(data)]; } @@ -1027,6 +1184,26 @@ class ArrayType : Type } } +// Type used for references to functions. Will later contain type +// information, but right now it's just used as an internal flag. +class FunctionType : InternalType +{ + Function *func; + bool isMember; + + this(Function *fn, bool isMemb) + { + func = fn; + assert(fn !is null); + isMember = isMemb; + + name="Function"; + } + + override: + bool isFunc() { return true; } +} + class EnumType : Type { // Enum entries @@ -1166,20 +1343,111 @@ class EnumType : Type } } - // Can only cast to string for now bool canCastTo(Type to) - { return to.isString; } + { + // Can always cast to string + if(to.isString) return true; + + // The value is a long, so we can always cast to types that long + // can be cast to. + if(BasicType.getLong().canCastOrEqual(to)) return true; + + // Check each field from left to right. If the field can be cast + // to the given type, then it's ok. + foreach(f; fields) + if(f.type.canCastOrEqual(to)) + return true; + + return false; + } void evalCastTo(Type to) { - assert(to.isString); - tasm.castToString(tIndex); + // Convert the enum name to a string + if(to.isString) + { + tasm.castToString(tIndex); + return; + } + + auto lng = BasicType.getLong(); + if(lng.canCastOrEqual(to)) + { + // Get the value + tasm.getEnumValue(tIndex); + // Cast it if necessary + if(to != lng) + lng.evalCastTo(to); + return; + } + + // Check the fields + foreach(i, f; fields) + if(f.type.canCastOrEqual(to)) + { + // Get the field value from the enum + tasm.getEnumValue(tIndex, i); + + // If the type doesn't match exactly, convert it. + if(f.type != to) + f.type.evalCastTo(to); + + return; + } + + assert(0); } int[] doCastCTime(int[] data, Type to) { - assert(to.isString); - return [valToStringIndex(data)]; + if(to.isString) + return [valToStringIndex(data)]; + + // This code won't run yet, because the enum fields are + // properties and we haven't implemented ctime property reading + // yet. Leave this assert in here so that we remember to test it + // later. + assert(0, "finished, but not tested"); + + // Get the enum index + assert(data.length == 1); + int v = data[0]; + + // Check that we were not given a zero index + if(v-- == 0) + fail("Cannot get value of fields from an empty Enum variable."); + + // Get the entry + assert(v >= 0 && v < entries.length); + auto ent = &entries[v]; + + auto lng = BasicType.getLong(); + if(lng.canCastOrEqual(to)) + { + // Get the value + int[] val = (cast(int*)&ent.value)[0..2]; + // Cast it if necessary + if(to != lng) + val = lng.doCastCTime(val, to); + + return val; + } + + // Check the fields + foreach(i, f; fields) + if(f.type.canCastOrEqual(to)) + { + // Get the field value from the enum + int[] val = ent.fields[i]; + + // If the type doesn't match exactly, convert it. + if(f.type != to) + val = f.type.doCastCTime(val, to); + + return val; + } + + assert(0); } // TODO: In this case, we could override valToStringIndex as well, @@ -1293,7 +1561,7 @@ abstract class ReplacerType : InternalType class UserType : ReplacerType { private: - Token id; + Token ids[]; public: @@ -1305,19 +1573,66 @@ class UserType : ReplacerType void parse(ref TokenArray toks) { + Token id; reqNext(toks, TT.Identifier, id); - // Get the name and the line from the token + // Get the name and the line from the first token name = id.str~"(replacer)"; loc = id.loc; + + ids ~= id; + + // Parse any following identifiers, separated by dots. + while(isNext(toks,TT.Dot)) + { + reqNext(toks, TT.Identifier, id); + ids ~= id; + } } void resolve(Scope sc) { - auto sl = sc.lookupImport(id); + assert(ids.length >= 1); + // The top-most identifier is looked up in imported scopes + auto first = ids[0]; + auto sl = sc.lookupImport(first); if(sl.isImport) - sl = sl.imphold.mc.sc.lookup(id); + sl = sl.imphold.lookup(first); + + // The scope in which to resolve the class lookup. + Scope lastScope = sc; + + // Loop through the list and look up each member. + foreach(int ind, idt; ids) + { + if(ind == 0) continue; + + if(sl.isClass) + { + // Look up the next identifier in the class scope + assert(sl.mc !is null); + sl.mc.requireScope(); + assert(sl.mc.sc !is null); + sl = sl.mc.sc.lookupClass(idt); + } + else if(sl.isPackage) + { + lastScope = sl.sc; + assert(sl.sc.isPackage); + sl = sl.sc.lookupClass(idt); + } + // Was anything found at all? + else if(!sl.isNone) + fail(sl.name.str ~ " (which is a " ~ LTypeName[sl.ltype] + ~ ") does not have a type member called " ~ idt.str, + idt.loc); + else + fail("Unknown type " ~ sl.name.str, sl.name.loc); + } + + // sl should now contain the lookup result of the last + // identifier in the list. // Is it a type? if(sl.isType) @@ -1326,21 +1641,30 @@ class UserType : ReplacerType // If not, maybe a class? else if(sl.isClass) - // Splendid - realType = sl.mc.objType; + { + // Splendid + sl.mc.requireScope(); + realType = sl.mc.objType; + assert(realType !is null); + } + + // Allow packages used as type names in some situations + else if(sl.isPackage) + realType = sl.sc.getPackage().type; // Was anything found at all? else if(!sl.isNone) // Ouch, something was found that's not a type or class. fail("Cannot use " ~ sl.name.str ~ " (which is a " ~ - LTypeName[sl.ltype] ~ ") as a type!", id.loc); + LTypeName[sl.ltype] ~ ") as a type!", sl.name.loc); if(realType is null) { // Nothing was found. Assume it's a forward reference to a // class. These are handled later on. - realType = new ObjectType(id); - realType.resolve(sc); + realType = new ObjectType(sl.name); + assert(lastScope !is null); + realType.resolve(lastScope); } assert(realType !is this); @@ -1462,15 +1786,3 @@ class MetaType : InternalType Scope getMemberScope() { return base.getMemberScope(); } Type getBase() { return base; } } - -/* - Types we might add later: - - 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 - function pointers are in effect delegates. - -*/ diff --git a/monster/compiler/variables.d b/monster/compiler/variables.d index 57206d8fb2..fa240020fe 100644 --- a/monster/compiler/variables.d +++ b/monster/compiler/variables.d @@ -317,11 +317,15 @@ class VarDeclaration : Block // We can't have var at this point if(var.type.isVar) - fail("cannot implicitly determine type", loc); + fail("Cannot implicitly determine type", loc); + + // Nor can we have void types + if(var.type.isVoid) + fail("Cannot declare variables with no type", loc); // Illegal types are illegal if(!var.type.isLegal) - fail("Cannot create variables of type " ~ var.type.toString, loc); + fail("Cannot create variables of type '" ~ var.type.toString ~ "'", loc); if(!allowConst && var.isConst) fail("'const' is not allowed here", loc); @@ -533,13 +537,11 @@ class ClassVarSet : Block } } -// 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 +// Represents a reference to an identifier member, such as a variable, +// function or property. Can also refer to type names. Simply stores +// the token representing the identifier. Special names (currently +// only __STACK__) are also handled. +class MemberExpr : Expression { Token name; @@ -558,7 +560,9 @@ class VariableExpr : MemberExpression FarOtherVar, // Another class, another object Property, // Property (like .length of arrays) Special, // Special name (like __STACK__) + Function, // Function Type, // Typename + Package, // Package } VType vtype; @@ -596,6 +600,152 @@ class VariableExpr : MemberExpression } bool isSpecial() { return vtype == VType.Special; } + bool isPackage() { return vtype == VType.Package; } + + // Is this a static member + bool isStatic() + { + // Properties can be static + if(isProperty) + return look.isPropStatic; + + // Type names are always static. + if(isType) + return true; + + // Ditto for packages + if(isPackage) + return true; + + // Currently no other static variables + return false; + } + + void resolveMember(Scope sc, Type ownerType) + { + assert(ownerType !is null); + assert(sc !is null); + + Scope leftScope; + + leftScope = ownerType.getMemberScope(); + + // Look up the name in the scope belonging to the owner + assert(leftScope !is null); + look = leftScope.lookupClass(name); + + type = look.type; + + // Check for member properties + if(look.isProperty) + { + // TODO: Need to account for ownerType here somehow - + // rewrite the property system + vtype = VType.Property; + type = look.getPropType(ownerType); + return; + } + + // The rest is common + resolveCommon(ownerType, leftScope); + } + + // Common parts for members and non-members + void resolveCommon(Type ownerType, Scope sc) + { + bool isMember = (ownerType !is null); + + // Package name? + if(look.isPackage) + { + vtype = VType.Package; + return; + } + + // Variable? + if(look.isVar) + { + assert(look.sc !is null); + assert(look.sc is look.var.sc); + + if(isMember) + { + // We are a class member variable. + vtype = VType.FarOtherVar; + assert(look.sc.isClass); + } + // This/parent class variable? + else if(look.sc.isClass) + { + // Check if it's in THIS class, which is a common + // case. If so, we can use a simplified instruction that + // doesn't have to look up the class. + if(look.sc.getClass is sc.getClass) + vtype = VType.ThisVar; + else + { + // It's another class. For non-members this can only + // mean a parent class. + vtype = VType.ParentVar; + } + } + else + // Local stack variable + vtype = VType.LocalVar; + } + + // Function? + else if(look.isFunc) + { + // The Function* is stored in the lookup variable + type = new FunctionType(look.func, isMember); + vtype = VType.Function; + + // TODO: Make the scope set the type for us. In fact, the + // type should contain the param/return type information + // rather than the Function itself, the function should just + // refer to it. This would make checking type compatibility + // easy. + } + // Class name? + else if(look.isClass) + { + if(isMember && !ownerType.isPackage) + fail(format("%s cannot have class member %s", + ownerType, name), loc); + + assert(look.mc !is null); + look.mc.requireScope(); + + type = look.mc.classType; + vtype = VType.Type; + + // Singletons are treated differently - the class name can + // be used to access the singleton object + if(look.mc.isSingleton) + { + type = look.mc.objType; + singCls = look.mc.getIndex(); + } + } + // Type name? + else if(look.isType) + { + assert(look.isType); + assert(look.type !is null); + type = look.type.getMeta(); + vtype = VType.Type; + } + else + { + // Nothing useful was found. + if(isMember) + fail(name.str ~ " is not a member of " ~ ownerType.toString, + loc); + else + fail("Undefined identifier "~name.str, name.loc); + } + } override: char[] toString() { return name.str; } @@ -615,20 +765,6 @@ class VariableExpr : MemberExpression return true; } - bool isStatic() - { - // Properties can be static - if(isProperty) - return look.isPropStatic; - - // Type names are always static. - if(isType) - return true; - - // Currently no other static variables - return false; - } - bool isCTime() { return isType; } void parse(ref TokenArray toks) @@ -651,49 +787,6 @@ class VariableExpr : MemberExpression } body { - if(isMember) // Are we called as a member? - { - // Look up the name in the scope belonging to the owner - assert(leftScope !is null); - look = leftScope.lookup(name); - - type = look.type; - - // Check first if this is a variable - if(look.isVar) - { - // We are a class member variable. - vtype = VType.FarOtherVar; - assert(look.sc.isClass); - - return; - } - - // Check for properties - if(look.isProperty) - { - // TODO: Need to account for ownerType here somehow - - // rewrite the property system - vtype = VType.Property; - type = look.getPropType(ownerType); - return; - } - - // Check types too - if(look.isType) - { - vtype = VType.Type; - type = look.type.getMeta(); - return; - } - - // No match - fail(name.str ~ " is not a variable member of " ~ ownerType.toString, - loc); - } - - // Not a member - // Look for reserved names first. if(name.str == "__STACK__") { @@ -705,8 +798,8 @@ class VariableExpr : MemberExpression if(name.type == TT.Const || name.type == TT.Clone) fail("Cannot use " ~ name.str ~ " as a variable", name.loc); - // Not a member or a special name. Look ourselves up in the - // local scope, and include imported scopes. + // Look ourselves up in the local scope, and include imported + // scopes. look = sc.lookupImport(name); if(look.isImport) @@ -714,9 +807,9 @@ class VariableExpr : MemberExpression // We're imported from another scope. This means we're // essentially a member variable. Let DotOperator handle // this. - dotImport = new DotOperator(look.imphold, this, loc); dotImport.resolve(sc); + assert(dotImport.type is type); return; } @@ -733,75 +826,14 @@ class VariableExpr : MemberExpression assert(look.isProperty, name.str ~ " expression not implemented yet"); vtype = VType.Property; - type = look.getPropType(ownerType); + assert(0); // FIXME: This can't be right!? Was ownerType. + type = look.getPropType(null); return; } type = look.type; - if(look.isVar) - { - assert(look.sc !is null); - assert(look.sc is look.var.sc); - - // Class variable? - if(look.sc.isClass) - { - // Check if it's in THIS class, which is a common - // case. If so, we can use a simplified instruction that - // doesn't have to look up the class. - if(look.sc.getClass is sc.getClass) - vtype = VType.ThisVar; - else - { - // It's another class. For non-members this can only - // mean a parent class. - vtype = VType.ParentVar; - } - } - else - vtype = VType.LocalVar; - - return; - } - - // We are not a variable. Last chance is a type name / class. - if(!look.isType && !look.isClass) - { - // Still no match. Might be an unloaded class however, - // lookup() doesn't load classes. Try loading it. - if(vm.loadNoFail(name.str) is null) - // No match at all. - fail("Undefined identifier "~name.str, name.loc); - - // We found a class! Check that we can look it up now - look = sc.lookup(name); - assert(look.isClass); - } - - vtype = VType.Type; - - // Class name? - if(look.isClass) - { - assert(look.mc !is null); - look.mc.requireScope(); - - type = look.mc.classType; - - // Singletons are treated differently - the class name can - // be used to access the singleton object - if(look.mc.isSingleton) - { - type = look.mc.objType; - singCls = look.mc.getIndex(); - } - } - else - { - assert(look.type !is null); - type = look.type.getMeta(); - } + resolveCommon(null, sc); } int[] evalCTime() @@ -824,6 +856,8 @@ class VariableExpr : MemberExpression if(isType) return; + if(type.isFunc) return; + setLine(); // Special name diff --git a/monster/modules/console.d b/monster/modules/console.d index 0c8011d528..a2f79fcfda 100644 --- a/monster/modules/console.d +++ b/monster/modules/console.d @@ -95,7 +95,7 @@ class Console char[] cmt_prompt = "(comment) "; public: - bool allowVar = false; + bool allowVar = true; this(MonsterObject *ob = null) { @@ -134,9 +134,12 @@ class Console void putln(char[] str) { put(str, true); } - Statement parse(TokenArray toks) + Statement[] parse(TokenArray toks) { Statement b = null; + Statement[] res; + + repeat: if(VarDeclStatement.canParse(toks)) { @@ -156,7 +159,13 @@ class Console else b = new ExprStatement; b.parse(toks); - return b; + res ~= b; + + // Are there more tokens waiting for us? + if(toks.length > 0) + goto repeat; + + return res; } int sumParen() @@ -208,6 +217,8 @@ class Console // Restore the previous thread (if any) if(store !is null) store.foreground(); + + store = null; } // Push the input into the compiler and run it @@ -260,38 +271,9 @@ class Console // Phase II, parse TokenArray toks = tokArr.arrayCopy(); - Statement st = parse(toks); + Statement[] sts = parse(toks); delete toks; - - // Phase III, resolve - st.resolve(sc); - - // Phase IV, compile - tasm.newFunc(); - - // Is it an expression? - auto es = cast(ExprStatement)st; - if(es !is null) - { - // Yes. But is the type usable? - if(es.left.type.canCastTo(ArrayType.getString()) - && es.right is null) - { - // Yup. Get the type, and cast the expression to string. - scope auto ce = new CastExpression(es.left, ArrayType.getString()); - ce.eval(); - } - else es = null; - } - - // No expression is being used, so compile the statement - if(es is null) - st.compile(); - - fn.bcode = tasm.assemble(fn.lines); - fn.bcode ~= cast(ubyte)BC.Exit; // Non-optimal hack - - // Phase V, call the function + assert(sts.length >= 1); // First, background the current thread (if any) and bring up // our own. @@ -305,41 +287,82 @@ class Console // will see that it's empty and kill the thread upon exit trd.fstack.pushExt("Console"); - fn.call(obj); - - trd.fstack.pop(); - - // Finally, get the expression result, if any, and print it - if(es !is null) - putln(stack.popString8()); - - // In the case of new variable declarations, we have to make - // sure they are accessible to any subsequent calls to the - // function. Since the stack frame gets set at each call, we - // have to access the variables outside the function frame, - // ie. the same way we treat function parameters. We do this by - // giving the variables negative indices. - auto vs = cast(VarDeclStatement)st; - if(vs !is null) + // The rest must be performed separately for each statement on + // the line. + foreach(st; sts) { - // Add the new vars to the list - foreach(v; vs.vars) - { - varList ~= v.var; + // Phase III, resolve + st.resolve(sc); - // Add the size as well - varSize += v.var.type.getSize; + // Phase IV, compile + tasm.newFunc(); + + // Is it an expression? + auto es = cast(ExprStatement)st; + if(es !is null) + { + // Yes. But is the type usable? + if(es.left.type.canCastTo(ArrayType.getString()) + && es.right is null) + { + // Yup. Get the type, and cast the expression to string. + scope auto ce = new + CastExpression(es.left, ArrayType.getString()); + ce.eval(); + } + else es = null; } - // Recalculate all the indices backwards from zero - int place = 0; - foreach_reverse(v; varList) + // No expression is being used, so compile the statement + if(es is null) + st.compile(); + + // Gather all the statements into one function and get the + // bytecode. + fn.bcode = tasm.assemble(fn.lines); + fn.bcode ~= cast(ubyte)BC.Exit; // Non-optimal hack + + // Phase V, call the function + fn.call(obj); + + // Finally, get the expression result, if any, and print it. + if(es !is null) + putln(stack.popString8()); + + // In the case of new a variable declaration, we have to + // make sure they are accessible to any subsequent calls to + // the function. Since the stack frame gets set at each + // call, we have to access the variables outside the + // function frame, ie. the same way we treat function + // parameters. We do this by giving the variables negative + // indices. + auto vs = cast(VarDeclStatement)st; + if(vs !is null) { - place -= v.type.getSize; - v.number = place; + // Add the new vars to the list + foreach(v; vs.vars) + { + varList ~= v.var; + + // Add the size as well + varSize += v.var.type.getSize; + } + + // Recalculate all the indices backwards from zero + int place = 0; + foreach_reverse(v; varList) + { + place -= v.type.getSize; + v.number = place; + } + + // Reset the scope stack counters + sc.reset(); } } + trd.fstack.pop(); + // Reset the console to a usable state reset(); diff --git a/monster/modules/vfs.d b/monster/modules/vfs.d index 4af931ddbe..1d7dfa7fb9 100644 --- a/monster/modules/vfs.d +++ b/monster/modules/vfs.d @@ -36,26 +36,21 @@ abstract class VFS // Return true if a file exists. Should not return true for // directories. abstract bool has(char[] file); + abstract bool hasDir(char[] dir); // Open the given file and return it as a stream. abstract Stream open(char[] file); - static final char[] getBaseName(char[] fullname) + // Check for invalid file names. This makes sure the caller cannot + // read files outside the designated subdirectory. + final static void checkForEscape(char[] file) { - foreach_reverse(i, c; fullname) - { - version(Win32) - { - if(c == ':' || c == '\\' || c == '/') - return fullname[i+1..$]; - } - version(Posix) - { - if (fullname[i] == '/') - return fullname[i+1..$]; - } - } - return fullname; + if(file.begins("/") || file.begins("\\")) + fail("Filename " ~ file ~ " cannot begin with a path separator"); + if(file.find(":") != -1) + fail("Filename " ~ file ~ " cannot contain colons"); + if(file.find("..") != -1) + fail("Filename " ~ file ~ " cannot contain '..'"); } } @@ -82,6 +77,13 @@ class ListVFS : VFS return false; } + bool hasDir(char[] file) + { + foreach(l; list) + if(l.hasDir(file)) return true; + return false; + } + Stream open(char[] file) { foreach(l; list) @@ -109,14 +111,7 @@ class FileVFS : VFS if(buffer.length < file.length+sysPath.length) buffer.length = file.length + sysPath.length + 50; - // Check for invalid file names. This makes sure the caller - // cannot read files outside the designated subdirectory. - if(file.begins([from]) || file.begins([to])) - fail("Filename " ~ file ~ " cannot begin with a path separator"); - if(file.find(":") != -1) - fail("Filename " ~ file ~ " cannot contain colons"); - if(file.find("..") != -1) - fail("Filename " ~ file ~ " cannot contain '..'"); + checkForEscape(file); // Copy the file name over buffer[sysPath.length .. sysPath.length+file.length] @@ -172,7 +167,16 @@ class FileVFS : VFS } bool has(char[] file) - { return exists(getPath(file)) != 0; } + { + char[] pt = getPath(file); + return exists(pt) && isfile(pt); + } + + bool hasDir(char[] file) + { + char[] pt = getPath(file); + return exists(pt) && isdir(pt); + } Stream open(char[] file) { return new BufferedFile(getPath(file)); } diff --git a/monster/options.d b/monster/options.d index 53ed6caeed..5f18932d54 100644 --- a/monster/options.d +++ b/monster/options.d @@ -60,8 +60,16 @@ bool ciStringOps = true; // Skip lines beginning with a hash character '#' bool skipHashes = true; +// Do we allow implicit downcasting of classes? Downcasting means +// casting from a parent class to a child class. The actual object +// type is checked at runtime. In any case you can always downcast +// explicitly, using ClassName(obj). +bool implicitDowncast = true; - +// Allow implicit conversion from float to int (and similar +// conversions). If false, you must use explicit casting, +// ie. int(value) +bool implicitTruncate = false; /********************************************************* diff --git a/monster/options.openmw b/monster/options.openmw index 53ed6caeed..5f18932d54 100644 --- a/monster/options.openmw +++ b/monster/options.openmw @@ -60,8 +60,16 @@ bool ciStringOps = true; // Skip lines beginning with a hash character '#' bool skipHashes = true; +// Do we allow implicit downcasting of classes? Downcasting means +// casting from a parent class to a child class. The actual object +// type is checked at runtime. In any case you can always downcast +// explicitly, using ClassName(obj). +bool implicitDowncast = true; - +// Allow implicit conversion from float to int (and similar +// conversions). If false, you must use explicit casting, +// ie. int(value) +bool implicitTruncate = false; /********************************************************* diff --git a/monster/update.sh b/monster/update.sh index f8a9810115..0fb5a7d321 100755 --- a/monster/update.sh +++ b/monster/update.sh @@ -10,4 +10,4 @@ done svn st -svn diff options.openmw options.d +diff options.openmw options.d diff --git a/monster/vm/init.d b/monster/vm/init.d index ed1f31eaa3..8ef57ea071 100644 --- a/monster/vm/init.d +++ b/monster/vm/init.d @@ -36,6 +36,11 @@ import monster.vm.vm; import monster.modules.all; import monster.options; +version(Tango) +{} +else +{ + // D runtime stuff version(Posix) { @@ -56,6 +61,9 @@ extern (C) void _moduleUnitTests(); //extern (C) bool no_catch_exceptions; +} // end version(Tango) .. else + + bool initHasRun = false; bool stHasRun = false; @@ -83,21 +91,30 @@ void doMonsterInit() // Nope. This is normal though if we're running as a C++ // library. We have to init the D runtime manually. - version (Posix) + // But this is not supported in Tango at the moment. + version(Tango) { - _STI_monitor_staticctor(); - _STI_critical_init(); + assert(0, "tango-compiled C++ library not supported yet"); } - - gc_init(); - - version (Win32) + else { - _minit(); - } - _moduleCtor(); - _moduleUnitTests(); + version (Posix) + { + _STI_monitor_staticctor(); + _STI_critical_init(); + } + + gc_init(); + + version (Win32) + { + _minit(); + } + + _moduleCtor(); + _moduleUnitTests(); + } } assert(stHasRun, "D library initializion failed"); @@ -107,10 +124,12 @@ void doMonsterInit() // Initialize compiler constructs initTokenizer(); initProperties(); + + // initScope depends on doVMInit setting vm.vfs + vm.doVMInit(); initScope(); - // Initialize VM - vm.doVMInit(); + // The rest of the VM scheduler.init(); stack.init(); arrays.initialize(); diff --git a/monster/vm/mclass.d b/monster/vm/mclass.d index e1ac73479f..9b8cc397b2 100644 --- a/monster/vm/mclass.d +++ b/monster/vm/mclass.d @@ -113,6 +113,10 @@ final class MonsterClass Type classType; // Type for class references to this class (not // implemented yet) + // Pointer to the C++ wrapper class, if any. Could be used for other + // wrapper languages at well, but only one at a time. + MClass cppClassPtr; + private: // List of objects of this class. Includes objects of all subclasses // as well. @@ -150,11 +154,15 @@ final class MonsterClass // already. void requireCompile() { if(!isCompiled) compileBody(); } - // Constructor that only exists to keep people from using it. It's - // much safer to use the vm.load functions, since these check if the - // class already exists. - this(int internal = 0) { assert(internal == -14, - "Don't create MonsterClasses directly, use vm.load()"); } + // Create a class belonging to the given package scope. Do not call + // this yourself, use vm.load* to load classes. + this(PackageScope psc = null) + { + assert(psc !is null, + "Don't create MonsterClasses directly, use vm.load()"); + + pack = psc; + } /******************************************************* * * @@ -727,7 +735,7 @@ final class MonsterClass // Insert ourselves into the global scope. This will also // resolve forward references to this class, if any. - global.insertClass(this); + pack.insertClass(this); // Get the parent classes, if any if(isNext(tokens, TT.Colon)) @@ -1005,11 +1013,11 @@ final class MonsterClass assert(tree[treeIndex] is this); // The parent scope is the scope of the parent class, or the - // global scope if there is no parent. + // package scope if there is no parent. Scope parSc; if(parents.length != 0) parSc = parents[0].sc; // TODO: Should only be allowed for Object - else parSc = global; + else parSc = pack; assert(parSc !is null); @@ -1189,9 +1197,12 @@ final class MonsterClass assert(totSize == dataSize, "Data size mismatch in scope"); } + bool compiling = false; void compileBody() { assert(!isCompiled, getName() ~ " is already compiled"); + assert(!compiling, "compileBody called recursively"); + compiling = true; // Resolve the class body if it's not already done if(!isResolved) resolveBody(); @@ -1297,5 +1308,6 @@ final class MonsterClass assert(singObj is null); singObj = createObject(); } + compiling = false; } } diff --git a/monster/vm/mobject.d b/monster/vm/mobject.d index 0e0e67d1cb..a947c9417f 100644 --- a/monster/vm/mobject.d +++ b/monster/vm/mobject.d @@ -230,9 +230,9 @@ struct MonsterObject int *getDataInt(int treeIndex, int pos) { assert(treeIndex >= 0 && treeIndex < data.length, - "tree index out of range: " ~ toString(treeIndex)); + "tree index out of range: " ~ .toString(treeIndex)); assert(pos >= 0 && pos 0); assert(treeIndex >= 0 && treeIndex < data.length, - "tree index out of range: " ~ toString(treeIndex)); + "tree index out of range: " ~ .toString(treeIndex)); assert(pos >= 0 && (pos+len)<=data[treeIndex].length, - "data pointer out of range: pos=" ~ toString(pos) ~ - ", len=" ~toString(len)); + "data pointer out of range: pos=" ~ .toString(pos) ~ + ", len=" ~.toString(len)); return data[treeIndex][pos..pos+len]; } @@ -454,6 +454,11 @@ struct MonsterObject auto stl = cls.findState(name, label); setState(stl.state, stl.label); } + + char[] toString() + { + return cls.toString ~ "#" ~ .toString(cast(int)getIndex()); + } } alias FreeList!(MonsterObject) ObjectList; diff --git a/monster/vm/thread.d b/monster/vm/thread.d index 7c2fb9f7e5..6d72a0fb73 100644 --- a/monster/vm/thread.d +++ b/monster/vm/thread.d @@ -54,6 +54,7 @@ import std.math : floor; // Used for array copy below. It handles overlapping data for us. extern(C) void* memmove(void *dest, void *src, size_t n); +// Enable this to print bytecode instructions to stdout //debug=traceOps; import monster.util.list; @@ -625,8 +626,8 @@ struct Thread debug(traceOps) { - writefln("exec: %s", bcToString[opCode]); - writefln("stack=", stack.getPos); + writefln("exec: %s (at stack %s)", + bcToString[opCode], stack.getPos); } switch(opCode) @@ -838,10 +839,19 @@ struct Thread case BC.PushData: stack.pushInt(code.getInt()); + debug(traceOps) writefln(" Data: %s", *stack.getInt(0)); break; case BC.PushLocal: - stack.pushInt(*stack.getFrameInt(code.getInt())); + debug(traceOps) + { + auto p = code.getInt(); + auto v = *stack.getFrameInt(p); + stack.pushInt(v); + writefln(" Pushed %s from position %s",v,p); + } + else + stack.pushInt(*stack.getFrameInt(code.getInt())); break; case BC.PushClassVar: @@ -1232,7 +1242,7 @@ struct Thread //stack.pushLong(stack.popInt()); break; - // Castint to float + // Cast int to float case BC.CastI2F: ptr = stack.getInt(0); fptr = cast(float*) ptr; @@ -1257,7 +1267,7 @@ struct Thread stack.pushFloat(stack.popDouble); break; - // Castint to double + // Cast int to double case BC.CastI2D: stack.pushDouble(stack.popInt); break; @@ -1278,6 +1288,43 @@ struct Thread stack.pushDouble(stack.popFloat); break; + // Cast floating point types back to integral ones + case BC.CastF2I: + ptr = stack.getInt(0); + fptr = cast(float*) ptr; + *ptr = cast(int)*fptr; + break; + + case BC.CastF2U: + ptr = stack.getInt(0); + fptr = cast(float*) ptr; + *(cast(uint*)ptr) = cast(uint)*fptr; + break; + + case BC.CastF2L: + stack.pushLong(cast(long)stack.popFloat); + break; + + case BC.CastF2UL: + stack.pushUlong(cast(ulong)stack.popFloat); + break; + + case BC.CastD2I: + stack.pushInt(cast(int)stack.popDouble); + break; + + case BC.CastD2U: + stack.pushUint(cast(uint)stack.popDouble); + break; + + case BC.CastD2L: + stack.pushLong(cast(long)stack.popDouble); + break; + + case BC.CastD2UL: + stack.pushUlong(cast(ulong)stack.popDouble); + break; + case BC.CastT2S: { // Get the type to cast from @@ -1292,6 +1339,18 @@ struct Thread } break; + case BC.DownCast: + { + // Get the object on the stack + auto mo = getMObject(cast(MIndex)*stack.getInt(0)); + // And the class we're checking against + auto mc = global.getClass(cast(CIndex)code.getInt()); + + if(!mc.parentOf(mo)) + fail("Cannot cast object " ~ mo.toString ~ " to class " ~ mc.toString); + } + break; + case BC.FetchElem: // This is not very optimized val = stack.popInt(); // Index diff --git a/monster/vm/vm.d b/monster/vm/vm.d index 68d4274a77..818a6d6dcb 100644 --- a/monster/vm/vm.d +++ b/monster/vm/vm.d @@ -86,51 +86,7 @@ struct VM scheduler.doFrame(); } - // Execute a single statement. Context-dependent statements such as - // 'return' or 'goto' are not allowed, and neither are - // declarations. Any return value (in case of expressions) is - // discarded. An optional monster object may be given as context. - void execute(char[] statm, MonsterObject *mo = null) - { - init(); - - // Get an empty object if none was specified - if(mo is null) - mo = Function.getIntMO(); - - // Push a dummy function on the stack, tokenize the statement, and - // parse the various statement types we allow. Assemble it and - // store the code in the dummy function. The VM handles the rest. - assert(0, "not done"); - } - - // This doesn't cover the 'console mode', or 'line mode', which is a - // bit more complicated. (It would search for matching brackets in - // the token list, print values back out, possibly allow variable - // declarations, etc.) I think we need a separate Console class to - // handle this, especially to store temporary values for - // optimization. - - // Execute an expression and return the value. The template - // parameter gives the desired type, which must match the type of - // the expression. An optional object may be given as context. - T expressionT(T)(char[] expr, MonsterObject *mo = null) - { - init(); - - // Get an empty object if none was specified - if(mo is null) - mo = Function.getIntMO(); - - // Push a dummy function on the stack, tokenize and parse the - // expression. Get the type after resolving, and check it against - // the template parameter. Execute like for statements, and pop - // the return value. - assert(0, "not done"); - } - - // TODO: These have to check for existing classes first. Simply move - // doLoad over here and fix it up. + // Load a class based on class name, file name, or both. MonsterClass load(char[] nam1, char[] nam2 = "") { return doLoad(nam1, nam2, true, true); } @@ -139,7 +95,7 @@ struct VM { return doLoad(nam1, nam2, false, true); } // Does not fail if the class is not found, just returns null. It - // will still fail if the class exists and contains errors though. + // will still fail if the class exists and contains errors. MonsterClass loadNoFail(char[] nam1, char[] nam2 = "") { return doLoad(nam1, nam2, true, false); } @@ -150,7 +106,7 @@ struct VM init(); assert(s !is null, "Cannot load from null stream"); - auto mc = new MonsterClass(-14); + auto mc = new MonsterClass(global); mc.parse(s, name, bom); return mc; } @@ -161,7 +117,7 @@ struct VM { init(); - auto mc = new MonsterClass(-14); + auto mc = new MonsterClass(global); mc.parse(toks, name); return mc; } @@ -276,6 +232,9 @@ struct VM char[] fname, cname; MonsterClass mc; + PackageScope pack = global; + assert(pack !is null); + if(name1 == "") fail("Cannot give empty first parameter to load()"); @@ -304,42 +263,85 @@ struct VM // Was a filename given? if(fname != "") { - // Derive the class name from the file name - char[] nameTmp = vfs.getBaseName(fname); - assert(fname.iEnds(".mn")); - nameTmp = fname[0..$-3]; + // Derive the class and package names from the given file name. + VFS.checkForEscape(fname); + + char[] file = fname; + + while(true) + { + // Find a path separator + int ind = file.find('/'); + if(ind == -1) + ind = file.find('\\'); + + if(ind == -1) break; + + // The file is in a directory. Add it as a package. + char[] packname = file[0..ind]; + file = file[ind+1..$]; + + // Empty directory name (eg. dir//file.mn or + // dir/./file.mn) should not be added as packages. + if(packname != "" && packname != ".") + pack = pack.insertPackage(packname); + + // Did we end with a path separator? + if(file == "") + fail("File name " ~ fname ~ " is a directory"); + } + + // 'file' now contains the base filename, without the + // directory + assert(file.iEnds(".mn")); + + // Pick away the extension + file = file[0..$-3]; if(!cNameSet) // No class name given, set it to the derived name - cname = nameTmp; + cname = file; else - // Both names were given, make sure they match - if(icmp(nameTmp,cname) != 0) - fail(format("Class name %s does not match file name %s", - cname, fname)); + { + // Both names were given, make sure they match + if(cname.find('.') != -1) + fail(format("Don't use a package specifier in the class name when the file name is also given (class %s, file %s)", + cname, fname)); + + if(icmp(file,cname) != 0) + fail(format("Class name %s does not match file name %s", + cname, fname)); + } } else - // No filename given. Derive it from the given class name. - fname = tolower(cname) ~ ".mn"; + { + // Pick out the package part of the class name. + char[] pname = cname; + while(true) + { + int ind = find(pname, '.'); + if(ind != -1) + { + // Found a package name separator. Insert the package. + pack = pack.insertPackage(pname[0..ind]); + pname = pname[ind+1..$]; + + if(pname == "") + fail("Class name cannot end with a period: " ~ cname); + } + else break; + } + + cname = pname; + + // Derive the file name from the given class name. + fname = pack.getPath(tolower(cname)) ~ ".mn"; + } assert(cname != "" && !cname.iEnds(".mn")); assert(fname.iEnds(".mn")); - bool checkFileName() - { - if(cname.length == 0) - return false; - - if(!validFirstIdentChar(cname[0])) - return false; - - foreach(char c; cname) - if(!validIdentChar(c)) return false; - - return true; - } - - if(!checkFileName()) + if(!isValidIdent(cname)) fail(format("Invalid class name %s (file %s)", cname, fname)); // At this point, check if the class already exists. @@ -369,7 +371,7 @@ struct VM auto bf = vfs.open(fname); auto ef = new EndianStream(bf); int bom = ef.readBOM(); - mc = new MonsterClass(-14); + mc = new MonsterClass(pack); mc.parse(ef, fname, bom); delete bf; diff --git a/mscripts/gui/makegui.mn b/mscripts/guiscripts/makegui.mn similarity index 100% rename from mscripts/gui/makegui.mn rename to mscripts/guiscripts/makegui.mn diff --git a/mscripts/gui/module/gui.mn b/mscripts/guiscripts/module/gui.mn similarity index 100% rename from mscripts/gui/module/gui.mn rename to mscripts/guiscripts/module/gui.mn diff --git a/mscripts/gui/module/widget.mn b/mscripts/guiscripts/module/widget.mn similarity index 100% rename from mscripts/gui/module/widget.mn rename to mscripts/guiscripts/module/widget.mn diff --git a/ogre/gui.d b/ogre/gui.d index f66021d0b6..08a62998f8 100644 --- a/ogre/gui.d +++ b/ogre/gui.d @@ -231,8 +231,8 @@ void getHeight() void setupGUIScripts() { - vm.addPath("mscripts/gui/"); - vm.addPath("mscripts/gui/module/"); + vm.addPath("mscripts/guiscripts/"); + vm.addPath("mscripts/guiscripts/module/"); gmc = vm.load("gui", "gui.mn"); wid_mc = vm.load("Widget", "widget.mn"); /*