Updated to Monster 0.12

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@100 ea6a568a-9f4f-0410-981a-c910a81bb256
actorid
nkorslund 16 years ago
parent 4b70fd9a06
commit cc1f7f02e9

@ -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); }

@ -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)
{

@ -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",

@ -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);

@ -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 isVararg;
// Used to simulate a member for imported variables
DotOperator dotImport;
bool recurse = true;
bool isCast; // If true, this is an explicit typecast, not a
// function call.
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
}
bool isVararg;
// 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<parNum);
@ -1128,8 +1159,8 @@ class FunctionCallExpr : MemberExpression
foreach(i, cv; coverage)
if(cv is null && fd.defaults[i].length == 0)
fail(format("Non-optional parameter %s is missing in call to %s()",
fd.params[i].name.str, name.str),
name.loc);
fd.params[i].name.str, name),
loc);
// Check parameter types
foreach(int i, ref cov; coverage)
@ -1144,17 +1175,9 @@ class FunctionCallExpr : MemberExpression
}
}
// 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.
bool pdone;
// Evaluate the parameters
void evalParams()
{
assert(pdone == false);
pdone = true;
// Again, let's handle the vararg case separately
if(isVararg)
{
@ -1228,15 +1251,30 @@ class FunctionCallExpr : MemberExpression
void evalAsm()
{
if(dotImport !is null && recurse)
if(isCast)
{
recurse = false;
dotImport.evalAsm();
recurse = true;
// Just evaluate the expression. CastExpression takes care
// of everything automatically.
assert(params.length == 1);
assert(params[0] !is null);
params[0].eval();
return;
}
if(!pdone) evalParams();
// Push parameters first
evalParams();
// Then push the function expression, to get the object (if any)
// and later to get the function pointer value well.
assert(fname.type.isFunc);
fname.eval();
auto ft = cast(FunctionType)fname.type;
assert(ft !is null);
bool isMember = ft.isMember;
setLine();
assert(fd.owner !is null);
@ -1244,8 +1282,5 @@ class FunctionCallExpr : MemberExpression
tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember);
else
tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember);
// Reset pdone incase the expression is evaluated again
pdone = false;
}
}

@ -31,6 +31,7 @@ import monster.compiler.scopes;
import monster.compiler.types;
import monster.compiler.functions;
import monster.compiler.enums;
import monster.compiler.variables;
import monster.vm.error;
import monster.vm.arrays;
@ -484,7 +485,7 @@ class DotOperator : OperatorExpr
{
// owner.member
Expression owner;
MemberExpression member;
MemberExpr member;
this(Expression own, Expression memb, Floc loc)
{
@ -493,7 +494,7 @@ class DotOperator : OperatorExpr
assert(own !is null, "owner cannot be null");
assert(memb !is null);
member = cast(MemberExpression)memb;
member = cast(MemberExpr)memb;
assert(member !is null);
this.loc = loc;
@ -567,12 +568,6 @@ class DotOperator : OperatorExpr
void evalCommon()
{
// If the the expression is a function call, we must push the
// parameters first
auto fc = cast(FunctionCallExpr)member;
if(fc !is null)
fc.evalParams();
// Ask the rhs if it needs the lhs. It says no if the rhs is
// staic or evaluatable at compile time, eg a type name, part of
// an enum, a static function or a static property. So if you
@ -584,14 +579,6 @@ class DotOperator : OperatorExpr
}
}
// KILLME
/*
void evalAsm()
{
}
}
// */
// Boolean operators: ==, !=, <, >, <=, >=, &&, ||, =i=, =I=, !=i=, !=I=
class BooleanOperator : BinaryOperator
{

@ -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

@ -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

@ -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;

@ -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.
*/

@ -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

@ -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);
// The rest must be performed separately for each statement on
// the line.
foreach(st; sts)
{
// Phase III, resolve
st.resolve(sc);
trd.fstack.pop();
// Phase IV, compile
tasm.newFunc();
// 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)
{
// Add the new vars to the list
foreach(v; vs.vars)
// Is it an expression?
auto es = cast(ExprStatement)st;
if(es !is null)
{
varList ~= v.var;
// Add the size as well
varSize += v.var.type.getSize;
// 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();

@ -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)); }

@ -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;
/*********************************************************

@ -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;
/*********************************************************

@ -10,4 +10,4 @@ done
svn st
svn diff options.openmw options.d
diff options.openmw options.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");
}
else
{
gc_init();
version (Posix)
{
_STI_monitor_staticctor();
_STI_critical_init();
}
version (Win32)
{
_minit();
}
gc_init();
version (Win32)
{
_minit();
}
_moduleCtor();
_moduleUnitTests();
_moduleCtor();
_moduleUnitTests();
}
}
assert(stHasRun, "D library initializion failed");
@ -107,10 +124,12 @@ void doMonsterInit()
// Initialize compiler constructs
initTokenizer();
initProperties();
initScope();
// Initialize VM
// initScope depends on doVMInit setting vm.vfs
vm.doVMInit();
initScope();
// The rest of the VM
scheduler.init();
stack.init();
arrays.initialize();

@ -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;
}
}

@ -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<data[treeIndex].length,
"data pointer out of range: " ~ toString(pos));
"data pointer out of range: " ~ .toString(pos));
return &data[treeIndex][pos];
}
@ -241,10 +241,10 @@ struct MonsterObject
{
assert(len > 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;

@ -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

@ -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";
assert(cname != "" && !cname.iEnds(".mn"));
assert(fname.iEnds(".mn"));
bool checkFileName()
{
if(cname.length == 0)
return false;
if(!validFirstIdentChar(cname[0]))
return false;
// 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;
}
foreach(char c; cname)
if(!validIdentChar(c)) return false;
cname = pname;
return true;
// Derive the file name from the given class name.
fname = pack.getPath(tolower(cname)) ~ ".mn";
}
if(!checkFileName())
assert(cname != "" && !cname.iEnds(".mn"));
assert(fname.iEnds(".mn"));
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;

@ -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");
/*

Loading…
Cancel
Save