Updated to Monster 0.12

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

@ -904,6 +904,29 @@ struct Assembler
void castLongToInt() { pop(); } 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) void castIntToFloat(Type fr, Type to)
{ {
assert(fr.isIntegral); assert(fr.isIntegral);
@ -941,6 +964,15 @@ struct Assembler
addi(tindex); 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 // Boolean operators
void isEqual(int s) { cmdmult(BC.IsEqual, BC.IsEqualMulti, s); } void isEqual(int s) { cmdmult(BC.IsEqual, BC.IsEqualMulti, s); }

@ -84,6 +84,13 @@ abstract class Block
return isNext(toks, symbol); 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 ;) // Require either a line break or a given character (default ;)
static void reqSep(ref TokenArray toks, TT symbol = TT.Semicolon) static void reqSep(ref TokenArray toks, TT symbol = TT.Semicolon)
{ {

@ -244,21 +244,36 @@ enum BC
CastI2L, // int to long (signed) CastI2L, // int to long (signed)
CastD2F, // double to float
CastF2D, // float to double
CastI2F, // int to float CastI2F, // int to float
CastU2F, // uint to float CastU2F, // uint to float
CastL2F, // long to float CastL2F, // long to float
CastUL2F, // ulong to float CastUL2F, // ulong to float
CastD2F, // double to float
CastI2D, // int to double CastI2D, // int to double
CastU2D, // uint to double CastU2D, // uint to double
CastL2D, // long to double CastL2D, // long to double
CastUL2D, // ulong 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 CastT2S, // cast any type to string. Takes the type
// index (int) as a parameter // 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 FetchElem, // Get an element from an array. Pops the
// index, then the array reference, then // index, then the array reference, then
// pushes the value. The element size is // pushes the value. The element size is
@ -611,7 +626,16 @@ char[][] bcToString =
BC.CastL2D: "CastL2D", BC.CastL2D: "CastL2D",
BC.CastUL2D: "CastUL2D", BC.CastUL2D: "CastUL2D",
BC.CastF2D: "CastF2D", 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.CastT2S: "CastT2S",
BC.DownCast: "DownCast",
BC.PopToArray: "PopToArray", BC.PopToArray: "PopToArray",
BC.NewArray: "NewArray", BC.NewArray: "NewArray",
BC.CopyArray: "CopyArray", BC.CopyArray: "CopyArray",

@ -65,9 +65,9 @@ abstract class Expression : Block
Expression b; Expression b;
Floc ln; Floc ln;
// These are allowed for members (eg. hello().to.you()) // Only identifiers (variables, properties, etc) are allowed as
if(FunctionCallExpr.canParse(toks)) b = new FunctionCallExpr; // members.
else if(VariableExpr.canParse(toks)) b = new VariableExpr; if(MemberExpr.canParse(toks)) b = new MemberExpr;
else if(isMember) fail(toks[0].str ~ " can not be a member", toks[0].loc); 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 // 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); 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; else break;
} }
// Finally, check for a single ++ or -- following an expression. // Finally, check for a single ++ or -- following an expression.
@ -477,10 +484,11 @@ class NewExpression : Expression
// Parameters? // Parameters?
ExprArray exps; ExprArray exps;
FunctionCallExpr.getParams(toks, exps, params); if(isNext(toks, TT.LeftParen))
FunctionCallExpr.getParams(toks, exps, params);
if(exps.length != 0) if(exps.length != 0)
fail("'new' can only take names parameters", loc); fail("'new' can only take named parameters", loc);
} }
char[] toString() 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 // Expression that handles conversion from one type to another. This
// is used for implisit casts (eg. when using ints and floats // is used for implisit casts (eg. when using ints and floats
// together) and in the future it will also parse explisit casts. It // together) and in the future it will also parse explisit casts. It
@ -1040,13 +1025,13 @@ class CastExpression : Expression
type = newType; type = newType;
assert(type !is null); 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 // These would only be used if typecasts got their own unique
// "cast(Type)" expressions are implemented (if ever) // syntax, eg. "cast(Type) expression" like in D.
void parse(ref TokenArray) { assert(0, "Cannot parse casts yet"); } void parse(ref TokenArray) { assert(0, "Cannot parse casts"); }
void resolve(Scope sc) { assert(0, "Cannot resolve casts yet"); } void resolve(Scope sc) { assert(0, "Cannot resolve casts"); }
bool isCTime() bool isCTime()
{ {
@ -1055,6 +1040,8 @@ class CastExpression : Expression
int[] evalCTime() int[] evalCTime()
{ {
assert(isCTime());
// Let the type do the conversion // Let the type do the conversion
int[] res = type.typeCastCTime(orig, ""); int[] res = type.typeCastCTime(orig, "");
@ -1080,7 +1067,52 @@ class CastExpression : Expression
// import x; // import x;
// y = 3; // refers to x.y // y = 3; // refers to x.y
// y is resolved as x.y, where the owner (x) is an import holder. // 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; MonsterClass mc;
@ -1101,25 +1133,24 @@ class ImportHolder : Expression
assert(type !is null); assert(type !is null);
} }
// All lookups in this import is done through this function. Can be override:
// used to filter lookups later on.
ScopeLookup lookup(Token name) char[] getName() { return mc.name.str; }
ScopeLookup lookup(Token name, bool autoLoad=false)
{ {
assert(mc !is null); assert(mc !is null);
mc.requireScope(); mc.requireScope();
return mc.sc.lookup(name); return mc.sc.lookup(name, autoLoad);
} }
override:
void parse(ref TokenArray) { assert(0); } char[] toString() { return "imported class " ~ mc.name.str; }
void resolve(Scope sc) {}
void evalDest() { assert(0); }
char[] toString() { return "imported class " ~ mc.name.str ~ ""; }
void evalAsm() void evalAsm()
{ {
mc.requireCompile(); assert(mc !is null);
mc.requireScope();
if(mc.isSingleton) if(mc.isSingleton)
{ {
assert(type.isObject); assert(type.isObject);

@ -58,8 +58,8 @@ import std.stdio;
import std.stream; import std.stream;
import std.string; import std.string;
// TODO/FIXME: Make tango compatible before release version(Tango) import tango.core.Traits;
import std.traits; else import std.traits;
// One problem with these split compiler / vm classes is that we // 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 // 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); assert(isNative, "cannot bind to non-native function " ~ name.str);
alias ParameterTypeTuple!(func) P; version(Tango)
alias ReturnType!(func) R; {
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)); 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 // 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 ExprArray params; // Normal (non-named) parameters
NamedParam[] named; // Named parameters NamedParam[] named; // Named parameters
@ -930,18 +938,14 @@ class FunctionCallExpr : MemberExpression
// used for vararg functions. // used for vararg functions.
Function* fd; Function* fd;
bool isVararg; bool isCast; // If true, this is an explicit typecast, not a
// function call.
// Used to simulate a member for imported variables
DotOperator dotImport;
bool recurse = true;
static bool canParse(TokenArray toks) bool isVararg;
{
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, static void getParams(ref TokenArray toks,
out ExprArray parms, out ExprArray parms,
out NamedParam[] named) out NamedParam[] named)
@ -949,8 +953,6 @@ class FunctionCallExpr : MemberExpression
parms = null; parms = null;
named = null; named = null;
if(!isNext(toks, TT.LeftParen)) return;
// No parameters? // No parameters?
if(isNext(toks, TT.RightParen)) return; if(isNext(toks, TT.RightParen)) return;
@ -987,51 +989,80 @@ class FunctionCallExpr : MemberExpression
fail("Parameter list expected ')'", toks); fail("Parameter list expected ')'", toks);
} }
void parse(ref TokenArray toks)
this(Expression func, ref TokenArray toks)
{ {
name = next(toks); assert(func !is null);
loc = name.loc; fname = func;
loc = fname.loc;
// Parse the parameter list
getParams(toks, params, named); 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[] toString()
{ {
char[] result = name.str ~ "("; char[] result = fname.toString ~ "(";
foreach(b; params) foreach(b; params)
result ~= b.toString ~" "; result ~= b.toString ~", ";
foreach(b; named)
result ~= b.name.str ~ "=" ~ b.value.toString ~ ", ";
return result ~ ")"; return result ~ ")";
} }
char[] name() { assert(fname !is null); return fname.toString(); }
void resolve(Scope sc) void resolve(Scope sc)
{ {
if(isMember) // Are we called as a member? // Resolve the function lookup first
{ fname.resolve(sc);
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);
// For imported functions, we have to do some funky magic // Is the 'function' really a type name?
if(l.isImport) if(fname.type.isMeta)
{ {
assert(l.imphold !is null); // If so, it's a type cast! Get the type we're casting to.
dotImport = new DotOperator(l.imphold, this, loc); type = fname.type.getBase();
dotImport.resolve(sc); assert(type !is null);
return;
}
fd = l.func; // Only one (non-named) parameter is allowed
if(!l.isFunc) if(params.length != 1 || named.length != 0)
fail("Undefined function "~name.str, name.loc); 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; isVararg = fd.isVararg;
type = fd.type; type = fd.type;
@ -1043,8 +1074,8 @@ class FunctionCallExpr : MemberExpression
// arguments, including zero. // arguments, including zero.
if(params.length < fd.params.length-1) if(params.length < fd.params.length-1)
fail(format("%s() expected at least %s parameters, got %s", fail(format("%s() expected at least %s parameters, got %s",
name.str, fd.params.length-1, params.length), name, fd.params.length-1, params.length),
name.loc); loc);
// Check parameter types except for the vararg parameter // Check parameter types except for the vararg parameter
foreach(int i, par; fd.params[0..$-1]) foreach(int i, par; fd.params[0..$-1])
@ -1108,7 +1139,7 @@ class FunctionCallExpr : MemberExpression
} }
if(index == -1) if(index == -1)
fail(format("Function %s() has no paramter named %s", fail(format("Function %s() has no paramter named %s",
name.str, p.name.str), name, p.name.str),
p.name.loc); p.name.loc);
assert(index<parNum); assert(index<parNum);
@ -1128,8 +1159,8 @@ class FunctionCallExpr : MemberExpression
foreach(i, cv; coverage) foreach(i, cv; coverage)
if(cv is null && fd.defaults[i].length == 0) if(cv is null && fd.defaults[i].length == 0)
fail(format("Non-optional parameter %s is missing in call to %s()", fail(format("Non-optional parameter %s is missing in call to %s()",
fd.params[i].name.str, name.str), fd.params[i].name.str, name),
name.loc); loc);
// Check parameter types // Check parameter types
foreach(int i, ref cov; coverage) foreach(int i, ref cov; coverage)
@ -1144,17 +1175,9 @@ class FunctionCallExpr : MemberExpression
} }
} }
// Used in cases where the parameters need to be evaluated // Evaluate the parameters
// 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;
void evalParams() void evalParams()
{ {
assert(pdone == false);
pdone = true;
// Again, let's handle the vararg case separately // Again, let's handle the vararg case separately
if(isVararg) if(isVararg)
{ {
@ -1228,15 +1251,30 @@ class FunctionCallExpr : MemberExpression
void evalAsm() void evalAsm()
{ {
if(dotImport !is null && recurse) if(isCast)
{ {
recurse = false; // Just evaluate the expression. CastExpression takes care
dotImport.evalAsm(); // of everything automatically.
recurse = true;
assert(params.length == 1);
assert(params[0] !is null);
params[0].eval();
return; 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(); setLine();
assert(fd.owner !is null); assert(fd.owner !is null);
@ -1244,8 +1282,5 @@ class FunctionCallExpr : MemberExpression
tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember); tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember);
else else
tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember); 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.types;
import monster.compiler.functions; import monster.compiler.functions;
import monster.compiler.enums; import monster.compiler.enums;
import monster.compiler.variables;
import monster.vm.error; import monster.vm.error;
import monster.vm.arrays; import monster.vm.arrays;
@ -484,7 +485,7 @@ class DotOperator : OperatorExpr
{ {
// owner.member // owner.member
Expression owner; Expression owner;
MemberExpression member; MemberExpr member;
this(Expression own, Expression memb, Floc loc) this(Expression own, Expression memb, Floc loc)
{ {
@ -493,7 +494,7 @@ class DotOperator : OperatorExpr
assert(own !is null, "owner cannot be null"); assert(own !is null, "owner cannot be null");
assert(memb !is null); assert(memb !is null);
member = cast(MemberExpression)memb; member = cast(MemberExpr)memb;
assert(member !is null); assert(member !is null);
this.loc = loc; this.loc = loc;
@ -567,12 +568,6 @@ class DotOperator : OperatorExpr
void evalCommon() 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 // 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 // staic or evaluatable at compile time, eg a type name, part of
// an enum, a static function or a static property. So if you // 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= // Boolean operators: ==, !=, <, >, <=, >=, &&, ||, =i=, =I=, !=i=, !=I=
class BooleanOperator : BinaryOperator class BooleanOperator : BinaryOperator
{ {

@ -44,6 +44,8 @@ import monster.vm.mclass;
import monster.vm.error; import monster.vm.error;
import monster.vm.vm; import monster.vm.vm;
//debug=lookupTrace;
// The global scope // The global scope
RootScope global; RootScope global;
@ -65,6 +67,7 @@ enum LType
LoopLabel, LoopLabel,
Property, Property,
Import, Import,
Package,
} }
const char[][] LTypeName = const char[][] LTypeName =
@ -78,6 +81,7 @@ const char[][] LTypeName =
LType.StateLabel: "state label", LType.StateLabel: "state label",
LType.LoopLabel: "loop label", LType.LoopLabel: "loop label",
LType.Property: "property", LType.Property: "property",
LType.Package: "package",
]; ];
struct ScopeLookup struct ScopeLookup
@ -107,6 +111,7 @@ struct ScopeLookup
bool isState() { return ltype == LType.State; } bool isState() { return ltype == LType.State; }
bool isNone() { return ltype == LType.None; } bool isNone() { return ltype == LType.None; }
bool isImport() { return ltype == LType.Import; } bool isImport() { return ltype == LType.Import; }
bool isPackage() { return ltype == LType.Package; }
bool isProperty() bool isProperty()
{ {
bool ret = (ltype == LType.Property); bool ret = (ltype == LType.Property);
@ -202,10 +207,12 @@ abstract class Scope
// error. Recurses through parent scopes. // error. Recurses through parent scopes.
final void clearId(Token name) 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 // Lookup checks all parent scopes so we only have to call it
// once. // once.
auto sl = lookup(name); auto sl = lookup(name, false);
assert(sl.name.str == name.str);
if(!sl.isNone) if(!sl.isNone)
{ {
@ -213,10 +220,18 @@ abstract class Scope
fail(name.str ~ " is a property and cannot be redeclared", fail(name.str ~ " is a property and cannot be redeclared",
name.loc); name.loc);
fail(format("%s is already declared (at %s) as a %s", char[] oldLoc = name.loc.toString;
name.str, name.loc, LTypeName[sl.ltype]),
// 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); name.loc);
} }
assert(sl.name.str == name.str);
} }
// Is this the root scope? // Is this the root scope?
@ -266,6 +281,12 @@ abstract class Scope
return parent.getArray(); return parent.getArray();
} }
PackageScope getPackage()
{
assert(!isRoot(), "getPackage called on the wrong scope type");
return parent.getPackage();
}
int getLoopStack() int getLoopStack()
{ {
assert(!isRoot(), "getLoopStack called on wrong scope type"); assert(!isRoot(), "getLoopStack called on wrong scope type");
@ -278,19 +299,38 @@ abstract class Scope
LabelStatement getBreak(char[] name = "") { return null; } LabelStatement getBreak(char[] name = "") { return null; }
LabelStatement getContinue(char[] name = "") { return null; } LabelStatement getContinue(char[] name = "") { return null; }
// Lookup a string
final ScopeLookup lookupName(char[] name) final ScopeLookup lookupName(char[] name)
{ return lookup(Token(name, Floc.init)); } { 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); 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. // Look up a token in this scope. If the token is not found, try the
ScopeLookup lookupImport(Token name) // 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; if(!l.isNone) return l;
// Nuttin' was found, try the imports // Nuttin' was found, try the imports
@ -298,7 +338,7 @@ abstract class Scope
auto old = l; auto old = l;
foreach(imp; importList) foreach(imp; importList)
{ {
l = imp.lookup(name); l = imp.lookup(name, second);
// Only accept types, classes, variables and functions // Only accept types, classes, variables and functions
if(l.isType || l.isClass || l.isVar || l.isFunc) if(l.isType || l.isClass || l.isVar || l.isFunc)
@ -307,8 +347,8 @@ abstract class Scope
if(found && l.sc !is old.sc) if(found && l.sc !is old.sc)
fail(format( fail(format(
"%s matches both %s.%s (at %s) and %s.%s (at %s)", name.str, "%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, old.imphold.getName(), old.name.str, old.name.loc,
imp.mc.name.str, l.name.str, l.name.loc), imp.getName(), l.name.str, l.name.loc),
name.loc); name.loc);
// First match // First match
@ -319,7 +359,14 @@ abstract class Scope
} }
if(!found) 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 // Tell the caller that this is an import. We override the
// lookup struct, but it doesn't matter since the lookup will // 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 // imports. Eg. global.registerImport(myclass) -> makes myclass
// available in ALL classes. // available in ALL classes.
void registerImport(MonsterClass mc) void registerImport(MonsterClass mc)
{ registerImport(new ImportHolder(mc)); } { registerImport(new ClassImpHolder(mc)); }
// Even more user-friendly version. Takes a list of class names. // Even more user-friendly version. Takes a list of class names.
void registerImport(char[][] cls ...) void registerImport(char[][] cls ...)
@ -420,8 +467,11 @@ final class StateScope : Scope
} }
override: 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 != ""); assert(name.str != "");
// Check against state labels // Check against state labels
@ -429,7 +479,7 @@ final class StateScope : Scope
if(st.labels.inList(name.str, lb)) if(st.labels.inList(name.str, lb))
return ScopeLookup(lb.name, LType.StateLabel, null, this, lb); return ScopeLookup(lb.name, LType.StateLabel, null, this, lb);
return super.lookup(name); return super.lookup(name, autoLoad);
} }
State* getState() { return st; } State* getState() { return st; }
@ -456,7 +506,7 @@ final class RootScope : PackageScope
CIndex next = 1; CIndex next = 1;
public: public:
this() { super(null, "global"); } this() { super(null, "global", ""); }
bool allowRoot() { return true; } bool allowRoot() { return true; }
// Get a class by index // Get a class by index
@ -475,10 +525,6 @@ final class RootScope : PackageScope
// it will get the same index. // it will get the same index.
CIndex getForwardIndex(char[] name) 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 // Called when an existing forward does not exist
void inserter(ref CIndex v) void inserter(ref CIndex v)
{ v = next++; } { v = next++; }
@ -493,6 +539,24 @@ final class RootScope : PackageScope
{ {
return indexList.inList(ci); 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. // A package scope is a scope that can contain classes.
@ -503,16 +567,86 @@ class PackageScope : Scope
// can look up file names too. // can look up file names too.
HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes; HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes;
// List of sub-packages
HashTable!(char[], PackageScope, GCAlloc, CITextHash) children;
char[] path;
public: 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); 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; } 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 // Insert a new class into the scope. The class is given a unique
// global index. If the class was previously forward referenced, the // global index. If the class was previously forward referenced, the
// forward is replaced and the previously assigned forward index is // 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(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 // ci is set, remove the entry from the forwards hashmap
assert(ci != 0); assert(ci != 0);
global.forwards.remove(cls.name.str); global.forwards.remove(cls.name.str);
@ -570,46 +707,62 @@ class PackageScope : Scope
// before the actual class is loaded. // before the actual class is loaded.
bool ciInList(char[] name) bool ciInList(char[] name)
{ return classes.inList(name); } { return classes.inList(name); }
bool ciInList(char[] name, ref MonsterClass cb) bool ciInList(char[] name, out MonsterClass cb)
{ return classes.inList(name, cb); } { return classes.inList(name, cb); }
// Case sensitive versions. If a class is found that does not match // Case sensitive versions. If a class is found that does not match
// in case, it is an error. // 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 override ScopeLookup lookup(Token name, bool autoLoad=false)
// will set up the class scope if this is not already done.
MonsterClass getClass(char[] name)
{ {
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; MonsterClass mc;
if(!csInList(name, mc)) if(!csInList(name.str, mc) && autoLoad)
{ {
char[] msg = "Class '" ~ name ~ "' not found."; // Load the class from file, if it exists
if(ciInList(name, mc)) mc = vm.loadNoFail(name.str,
msg ~= " (Perhaps you meant " ~ mc.name.str ~ "?)"; tolower(getPath(name.str))~".mn");
fail(msg);
} }
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(mc !is null)
if(csInList(name.str, mc))
return ScopeLookup(mc.name, LType.Class, null, this, mc); return ScopeLookup(mc.name, LType.Class, null, this, mc);
// No parents to check // Unlike other scopes, a package scope doesn't check through
assert(isRoot()); // all its parent packages. That would mean that a name search
return super.lookup(name); // 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: 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 != ""); assert(name.str != "");
Variable *vd; Variable *vd;
if(variables.inList(name.str, vd)) if(variables.inList(name.str, vd))
return ScopeLookup(vd.name, LType.Variable, vd.type, this, 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: 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 != ""); assert(name.str != "");
Function* fd; Function* fd;
@ -695,7 +854,7 @@ class FVScope : VarScope
return ScopeLookup(fd.name, LType.Function, fd.type, this, fd); return ScopeLookup(fd.name, LType.Function, fd.type, this, fd);
// Let VarScope handle variables // Let VarScope handle variables
return super.lookup(name); return super.lookup(name, autoLoad);
} }
} }
@ -729,8 +888,11 @@ class TFVScope : FVScope
} }
override: 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 != ""); assert(name.str != "");
StructType sd; StructType sd;
@ -744,7 +906,7 @@ class TFVScope : FVScope
return ScopeLookup(Token(tp.name, tp.loc), LType.Type, tp, this); return ScopeLookup(Token(tp.name, tp.loc), LType.Type, tp, this);
// Pass it on to the parent // 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; 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 != ""); assert(name.str != "");
State* sd; State* sd;
@ -945,7 +1110,7 @@ final class ClassScope : TFVScope
return sl; return sl;
// Let the parent handle everything else // Let the parent handle everything else
return super.lookup(name); return super.lookup(name, autoLoad);
} }
// Get total data segment size // Get total data segment size
@ -1004,6 +1169,13 @@ abstract class StackScope : VarScope
return tmp; return tmp;
} }
// Reset the stack counters. Used from the console.
void reset()
{
locals = 0;
sumLocals = 0;
}
/* /*
void push(int i) { expStack += i; } void push(int i) { expStack += i; }
void push(Type t) { push(t.getSize); } void push(Type t) { push(t.getSize); }
@ -1108,14 +1280,17 @@ abstract class PropertyScope : Scope
bool isProperty() { return true; } bool isProperty() { return true; }
bool allowRoot() { 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? // Does this scope contain the property?
if(hasProperty(name.str)) if(hasProperty(name.str))
return ScopeLookup(name, LType.Property, null, this); return ScopeLookup(name, LType.Property, null, this);
// Check the parent scope // Check the parent scope
return super.lookup(name); return super.lookup(name, autoLoad);
} }
} }
@ -1184,15 +1359,18 @@ class LoopScope : CodeScope
continueLabel = new LabelStatement(getTotLocals()); 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 != ""); assert(name.str != "");
// Check for loop labels // Check for loop labels
if(loopName.str == name.str) if(loopName.str == name.str)
return ScopeLookup(loopName, LType.LoopLabel, null, this); 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 // Get the break or continue label for the given named loop, or the

@ -125,14 +125,24 @@ class ImportStatement : Statement
if(type.isReplacer) if(type.isReplacer)
type = type.getBase(); type = type.getBase();
if(!type.isObject) if(type.isObject)
fail("Can only import from classes", type.loc); {
auto t = cast(ObjectType)type;
assert(t !is null);
mc = t.getClass(type.loc);
auto t = cast(ObjectType)type; sc.registerImport(new ClassImpHolder(mc));
assert(t !is null); }
mc = t.getClass(type.loc); 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 // Resolve the statement if present

@ -53,6 +53,20 @@ bool validFirstIdentChar(char c)
return false; 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) bool numericalChar(char c)
{ {
return c >= '0' && c <= '9'; return c >= '0' && c <= '9';
@ -365,7 +379,7 @@ class Tokenizer
this.inf = inf; this.inf = inf;
this.fname = fname; this.fname = fname;
empty.type = TT.EMPTY; this();
} }
// This is used for single-line mode, such as in a console. // This is used for single-line mode, such as in a console.
@ -695,6 +709,8 @@ class Tokenizer
} }
Token tt = getNextFromLine(); Token tt = getNextFromLine();
// Skip empty lines, don't return them into the token list.
if(tt.type == TT.EMPTY) if(tt.type == TT.EMPTY)
goto restart; goto restart;

@ -36,8 +36,11 @@ import monster.compiler.states;
import monster.compiler.structs; import monster.compiler.structs;
import monster.vm.arrays; import monster.vm.arrays;
import monster.vm.mclass; import monster.vm.mclass;
import monster.vm.mobject;
import monster.vm.error; import monster.vm.error;
import monster.options;
import std.stdio; import std.stdio;
import std.utf; import std.utf;
import std.string; import std.string;
@ -54,6 +57,8 @@ import std.string;
ArrayType ArrayType
StructType StructType
EnumType EnumType
FunctionType
PackageType
UserType (replacer for identifier-named types like structs and UserType (replacer for identifier-named types like structs and
classes) classes)
TypeofType (replacer for typeof(x) when used as a type) TypeofType (replacer for typeof(x) when used as a type)
@ -76,6 +81,7 @@ abstract class Type : Block
// tokens.) // tokens.)
static bool canParseRem(ref TokenArray toks) static bool canParseRem(ref TokenArray toks)
{ {
// We allow typeof(expression) as a type
if(isNext(toks, TT.Typeof)) if(isNext(toks, TT.Typeof))
{ {
reqNext(toks, TT.LeftParen); reqNext(toks, TT.LeftParen);
@ -84,10 +90,17 @@ abstract class Type : Block
return true; return true;
} }
// The 'var' keyword can be used as a type
if(isNext(toks, TT.Var)) return true; if(isNext(toks, TT.Var)) return true;
// Allow typename
if(!isNext(toks, TT.Identifier)) return false; 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)) while(isNext(toks,TT.LeftSquare))
if(!isNext(toks,TT.RightSquare)) if(!isNext(toks,TT.RightSquare))
return false; return false;
@ -184,8 +197,10 @@ abstract class Type : Block
bool isArray() { return arrays() != 0; } bool isArray() { return arrays() != 0; }
bool isObject() { return false; } bool isObject() { return false; }
bool isPackage() { return false; }
bool isStruct() { return false; } bool isStruct() { return false; }
bool isEnum() { return false; } bool isEnum() { return false; }
bool isFunc() { return false; }
bool isReplacer() { return false; } bool isReplacer() { return false; }
@ -310,7 +325,8 @@ abstract class Type : Block
final char[] toString() { return name; } final char[] toString() { return name; }
// Cast the expression orig to this type. Uses canCastTo to // 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) final void typeCast(ref Expression orig, char[] to)
{ {
if(orig.type == this) return; if(orig.type == this) return;
@ -321,11 +337,25 @@ abstract class Type : Block
if(orig.type.canCastTo(this)) if(orig.type.canCastTo(this))
orig = new CastExpression(orig, this); orig = new CastExpression(orig, this);
else 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, orig.toString, orig.typeString,
to, this), orig.loc); 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 // Do compile-time type casting. Gets orig.evalCTime() and returns
// the converted result. // the converted result.
final int[] typeCastCTime(Expression orig, char[] to) final int[] typeCastCTime(Expression orig, char[] to)
@ -334,10 +364,10 @@ abstract class Type : Block
assert(res.length == orig.type.getSize); assert(res.length == orig.type.getSize);
if(orig.type.canCastTo(this)) if(orig.type.canCastCTime(this))
res = orig.type.doCastCTime(res, this); res = orig.type.doCastCTime(res, this);
else 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, orig.toString, orig.typeString,
to, this), orig.loc); to, this), orig.loc);
@ -353,10 +383,17 @@ abstract class Type : Block
return false; // By default we can't cast anything 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 // 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) bool canCastCTime(Type to)
{ return canCastTo(to); } { return canCastTo(to) || canCastToExplicit(to); }
// Returns true if the types are equal or if canCastTo returns true. // Returns true if the types are equal or if canCastTo returns true.
final bool canCastOrEqual(Type to) final bool canCastOrEqual(Type to)
@ -364,13 +401,15 @@ abstract class Type : Block
return to == this || canCastTo(to); 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) void evalCastTo(Type to)
{ {
assert(0, "evalCastTo not implemented for type " ~ toString); 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) int[] doCastCTime(int[] data, Type to)
{ {
assert(0, "doCastCTime not implemented for type " ~ toString); assert(0, "doCastCTime not implemented for type " ~ toString);
@ -443,7 +482,7 @@ abstract class Type : Block
// Find the common type // Find the common type
if(t1.canCastTo(t2)) common = t2; else if(t1.canCastTo(t2)) common = t2; else
if(t2.canCastTo(t1)) common = t1; 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. // 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, // Internal types are types that are used internally by the compiler,
// eg. for type conversion or for special syntax. They can not be used // 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 abstract class InternalType : Type
{ {
final: final:
@ -463,6 +502,28 @@ abstract class InternalType : Type
int getSize() { return 0; } 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 // Handles the 'null' literal. This type is only used for
// conversions. You cannot declare variables of the nulltype. // conversions. You cannot declare variables of the nulltype.
class NullType : InternalType class NullType : InternalType
@ -529,10 +590,11 @@ class BasicType : Type
// be created. // be created.
this(char[] tn) this(char[] tn)
{ {
if(!isBasic(tn))
fail("BasicType does not support type " ~ tn);
name = tn; name = tn;
if(name == "") name = "(none)";
if(!isBasic(name))
fail("BasicType does not support type " ~ tn);
// Cache the class to save some overhead // Cache the class to save some overhead
store[tn] = this; store[tn] = this;
@ -582,7 +644,7 @@ class BasicType : Type
static bool isBasic(char[] tn) 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 == "bool" || tn == "uint" || tn == "long" ||
tn == "ulong" || tn == "double"); tn == "ulong" || tn == "double");
} }
@ -605,6 +667,8 @@ class BasicType : Type
// Get the name and the line from the token // Get the name and the line from the token
name = t.str; name = t.str;
loc = t.loc; loc = t.loc;
if(name == "") name = "(none)";
} }
bool isInt() { return name == "int"; } bool isInt() { return name == "int"; }
@ -615,7 +679,7 @@ class BasicType : Type
bool isBool() { return name == "bool"; } bool isBool() { return name == "bool"; }
bool isFloat() { return name == "float"; } bool isFloat() { return name == "float"; }
bool isDouble() { return name == "double"; } bool isDouble() { return name == "double"; }
bool isVoid() { return name == ""; } bool isVoid() { return name == "(none)"; }
Scope getMemberScope() Scope getMemberScope()
{ {
@ -636,6 +700,14 @@ class BasicType : Type
// List the implisit conversions that are possible // List the implisit conversions that are possible
bool canCastTo(Type to) 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 // We can convert between all integral types
if(to.isIntegral) return isIntegral; if(to.isIntegral) return isIntegral;
@ -649,6 +721,14 @@ class BasicType : Type
return false; return false;
} }
bool canCastToExplicit(Type to)
{
if(isFloating && to.isIntegral)
return true;
return false;
}
void evalCastTo(Type to) void evalCastTo(Type to)
{ {
assert(this != to); assert(this != to);
@ -658,7 +738,9 @@ class BasicType : Type
int toSize = to.getSize(); int toSize = to.getSize();
bool fromSign = isInt || isLong || isFloat || isBool; 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); assert(isIntegral);
if(isLong || isUlong) if(isLong || isUlong)
@ -714,17 +796,49 @@ class BasicType : Type
assert(data.length == fromSize); assert(data.length == fromSize);
bool fromSign = isInt || isLong || isFloat || isBool; 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); assert(isIntegral);
data = data[0..1]; // Just pick out the least significant int
toData[] = data[0..1];
} }
else if(to.isLong || to.isUlong) else if(to.isLong || to.isUlong)
{ {
if(isInt || isUint) if(isInt || isUint)
{ {
if(fromSign && to.isLong && data[0] < 0) data ~= -1; toData[0] = data[0];
else data ~= 0; if(fromSign && to.isLong && data[0] < 0) toData[1] = -1;
else toData[1] = 0;
} }
else assert(isUlong || isLong); else assert(isUlong || isLong);
} }
@ -732,7 +846,7 @@ class BasicType : Type
{ {
assert(isNumerical); assert(isNumerical);
float *fptr = cast(float*)data.ptr; float *fptr = cast(float*)toData.ptr;
if(isInt) *fptr = data[0]; if(isInt) *fptr = data[0];
else if(isUint) *fptr = cast(uint)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(isUlong) *fptr = *(cast(ulong*)data.ptr);
else if(isDouble) *fptr = *(cast(double*)data.ptr); else if(isDouble) *fptr = *(cast(double*)data.ptr);
else assert(0); else assert(0);
data = data[0..1];
} }
else if(to.isDouble) else if(to.isDouble)
{ {
assert(isNumerical); assert(isNumerical);
if(data.length < 2) data.length = 2; assert(toData.length == 2);
double *fptr = cast(double*)data.ptr; double *fptr = cast(double*)toData.ptr;
if(isInt) *fptr = data[0]; if(isInt) *fptr = data[0];
else if(isUint) *fptr = cast(uint)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(isUlong) *fptr = *(cast(ulong*)data.ptr);
else if(isFloat) *fptr = *(cast(float*)data.ptr); else if(isFloat) *fptr = *(cast(float*)data.ptr);
else assert(0); else assert(0);
data = data[0..2];
} }
else else
fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~ fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~
" not implemented."); " not implemented.");
assert(data.length == toSize); assert(toData.length == toSize);
return data; assert(toData.ptr !is data.ptr);
return toData;
} }
int getSize() int getSize()
@ -804,10 +917,7 @@ class BasicType : Type
} }
} }
// Represents a normal class name. The reason this is called // Represents a normal class name.
// "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.
class ObjectType : Type class ObjectType : Type
{ {
final: final:
@ -859,8 +969,11 @@ class ObjectType : Type
char[] valToString(int[] data) char[] valToString(int[] data)
{ {
// Use the object's toString function
assert(data.length == 1); 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; } int getSize() { return 1; }
@ -869,6 +982,19 @@ class ObjectType : Type
bool canCastCTime(Type to) { return false; } 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) bool canCastTo(Type type)
{ {
assert(clsIndex != 0); assert(clsIndex != 0);
@ -877,31 +1003,58 @@ class ObjectType : Type
if(type.isObject) if(type.isObject)
{ {
auto ot = cast(ObjectType)type;
assert(ot !is null);
MonsterClass us = getClass(); 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 other.parentOf(us);
} }
return false; 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) void evalCastTo(Type to)
{ {
assert(clsIndex != 0); assert(clsIndex != 0);
assert(canCastTo(to)); assert(canCastTo(to) || canCastToExplicit(to));
if(to.isObject) if(to.isObject)
{ {
auto tt = cast(ObjectType)to; auto us = getClass();
assert(tt !is null); auto other = getOther(to);
assert(clsIndex !is tt.clsIndex);
// 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; return;
} }
@ -923,7 +1076,12 @@ class ObjectType : Type
// body (not just the header) is being resolved. // body (not just the header) is being resolved.
void resolve(Scope sc) 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); assert(clsIndex != 0);
} }
} }
@ -980,7 +1138,6 @@ class ArrayType : Type
int[] doCastCTime(int[] data, Type to) int[] doCastCTime(int[] data, Type to)
{ {
assert(to.isString); assert(to.isString);
return [valToStringIndex(data)]; 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 class EnumType : Type
{ {
// Enum entries // Enum entries
@ -1166,20 +1343,111 @@ class EnumType : Type
} }
} }
// Can only cast to string for now
bool canCastTo(Type to) 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) void evalCastTo(Type to)
{ {
assert(to.isString); // Convert the enum name to a string
tasm.castToString(tIndex); 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) int[] doCastCTime(int[] data, Type to)
{ {
assert(to.isString); if(to.isString)
return [valToStringIndex(data)]; 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, // TODO: In this case, we could override valToStringIndex as well,
@ -1293,7 +1561,7 @@ abstract class ReplacerType : InternalType
class UserType : ReplacerType class UserType : ReplacerType
{ {
private: private:
Token id; Token ids[];
public: public:
@ -1305,19 +1573,66 @@ class UserType : ReplacerType
void parse(ref TokenArray toks) void parse(ref TokenArray toks)
{ {
Token id;
reqNext(toks, TT.Identifier, 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)"; name = id.str~"(replacer)";
loc = id.loc; 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) 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) 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? // Is it a type?
if(sl.isType) if(sl.isType)
@ -1326,21 +1641,30 @@ class UserType : ReplacerType
// If not, maybe a class? // If not, maybe a class?
else if(sl.isClass) 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? // Was anything found at all?
else if(!sl.isNone) else if(!sl.isNone)
// Ouch, something was found that's not a type or class. // Ouch, something was found that's not a type or class.
fail("Cannot use " ~ sl.name.str ~ " (which is a " ~ 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) if(realType is null)
{ {
// Nothing was found. Assume it's a forward reference to a // Nothing was found. Assume it's a forward reference to a
// class. These are handled later on. // class. These are handled later on.
realType = new ObjectType(id); realType = new ObjectType(sl.name);
realType.resolve(sc); assert(lastScope !is null);
realType.resolve(lastScope);
} }
assert(realType !is this); assert(realType !is this);
@ -1462,15 +1786,3 @@ class MetaType : InternalType
Scope getMemberScope() { return base.getMemberScope(); } Scope getMemberScope() { return base.getMemberScope(); }
Type getBase() { return base; } 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 // We can't have var at this point
if(var.type.isVar) 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 // Illegal types are illegal
if(!var.type.isLegal) 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) if(!allowConst && var.isConst)
fail("'const' is not allowed here", loc); fail("'const' is not allowed here", loc);
@ -533,13 +537,11 @@ class ClassVarSet : Block
} }
} }
// Represents a reference to a variable. Simply stores the token // Represents a reference to an identifier member, such as a variable,
// representing the identifier. Evaluation is handled by the variable // function or property. Can also refer to type names. Simply stores
// declaration itself. This allows us to use this class for local and // the token representing the identifier. Special names (currently
// global variables as well as for properties, without handling each // only __STACK__) are also handled.
// case separately. The special names (currently __STACK__) are class MemberExpr : Expression
// handled internally.
class VariableExpr : MemberExpression
{ {
Token name; Token name;
@ -558,7 +560,9 @@ class VariableExpr : MemberExpression
FarOtherVar, // Another class, another object FarOtherVar, // Another class, another object
Property, // Property (like .length of arrays) Property, // Property (like .length of arrays)
Special, // Special name (like __STACK__) Special, // Special name (like __STACK__)
Function, // Function
Type, // Typename Type, // Typename
Package, // Package
} }
VType vtype; VType vtype;
@ -596,6 +600,152 @@ class VariableExpr : MemberExpression
} }
bool isSpecial() { return vtype == VType.Special; } 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: override:
char[] toString() { return name.str; } char[] toString() { return name.str; }
@ -615,20 +765,6 @@ class VariableExpr : MemberExpression
return true; 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; } bool isCTime() { return isType; }
void parse(ref TokenArray toks) void parse(ref TokenArray toks)
@ -651,49 +787,6 @@ class VariableExpr : MemberExpression
} }
body 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. // Look for reserved names first.
if(name.str == "__STACK__") if(name.str == "__STACK__")
{ {
@ -705,8 +798,8 @@ class VariableExpr : MemberExpression
if(name.type == TT.Const || name.type == TT.Clone) if(name.type == TT.Const || name.type == TT.Clone)
fail("Cannot use " ~ name.str ~ " as a variable", name.loc); fail("Cannot use " ~ name.str ~ " as a variable", name.loc);
// Not a member or a special name. Look ourselves up in the // Look ourselves up in the local scope, and include imported
// local scope, and include imported scopes. // scopes.
look = sc.lookupImport(name); look = sc.lookupImport(name);
if(look.isImport) if(look.isImport)
@ -714,9 +807,9 @@ class VariableExpr : MemberExpression
// We're imported from another scope. This means we're // We're imported from another scope. This means we're
// essentially a member variable. Let DotOperator handle // essentially a member variable. Let DotOperator handle
// this. // this.
dotImport = new DotOperator(look.imphold, this, loc); dotImport = new DotOperator(look.imphold, this, loc);
dotImport.resolve(sc); dotImport.resolve(sc);
assert(dotImport.type is type);
return; return;
} }
@ -733,75 +826,14 @@ class VariableExpr : MemberExpression
assert(look.isProperty, name.str ~ " expression not implemented yet"); assert(look.isProperty, name.str ~ " expression not implemented yet");
vtype = VType.Property; vtype = VType.Property;
type = look.getPropType(ownerType); assert(0); // FIXME: This can't be right!? Was ownerType.
type = look.getPropType(null);
return; return;
} }
type = look.type; type = look.type;
if(look.isVar) resolveCommon(null, sc);
{
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();
}
} }
int[] evalCTime() int[] evalCTime()
@ -824,6 +856,8 @@ class VariableExpr : MemberExpression
if(isType) return; if(isType) return;
if(type.isFunc) return;
setLine(); setLine();
// Special name // Special name

@ -95,7 +95,7 @@ class Console
char[] cmt_prompt = "(comment) "; char[] cmt_prompt = "(comment) ";
public: public:
bool allowVar = false; bool allowVar = true;
this(MonsterObject *ob = null) this(MonsterObject *ob = null)
{ {
@ -134,9 +134,12 @@ class Console
void putln(char[] str) { put(str, true); } void putln(char[] str) { put(str, true); }
Statement parse(TokenArray toks) Statement[] parse(TokenArray toks)
{ {
Statement b = null; Statement b = null;
Statement[] res;
repeat:
if(VarDeclStatement.canParse(toks)) if(VarDeclStatement.canParse(toks))
{ {
@ -156,7 +159,13 @@ class Console
else b = new ExprStatement; else b = new ExprStatement;
b.parse(toks); b.parse(toks);
return b; res ~= b;
// Are there more tokens waiting for us?
if(toks.length > 0)
goto repeat;
return res;
} }
int sumParen() int sumParen()
@ -208,6 +217,8 @@ class Console
// Restore the previous thread (if any) // Restore the previous thread (if any)
if(store !is null) if(store !is null)
store.foreground(); store.foreground();
store = null;
} }
// Push the input into the compiler and run it // Push the input into the compiler and run it
@ -260,38 +271,9 @@ class Console
// Phase II, parse // Phase II, parse
TokenArray toks = tokArr.arrayCopy(); TokenArray toks = tokArr.arrayCopy();
Statement st = parse(toks); Statement[] sts = parse(toks);
delete toks; delete toks;
assert(sts.length >= 1);
// 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
// First, background the current thread (if any) and bring up // First, background the current thread (if any) and bring up
// our own. // our own.
@ -305,41 +287,82 @@ class Console
// will see that it's empty and kill the thread upon exit // will see that it's empty and kill the thread upon exit
trd.fstack.pushExt("Console"); 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 // Is it an expression?
if(es !is null) auto es = cast(ExprStatement)st;
putln(stack.popString8()); if(es !is null)
// 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)
{ {
varList ~= v.var; // Yes. But is the type usable?
if(es.left.type.canCastTo(ArrayType.getString())
// Add the size as well && es.right is null)
varSize += v.var.type.getSize; {
// 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 // No expression is being used, so compile the statement
int place = 0; if(es is null)
foreach_reverse(v; varList) 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; // Add the new vars to the list
v.number = place; 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 the console to a usable state
reset(); reset();

@ -36,26 +36,21 @@ abstract class VFS
// Return true if a file exists. Should not return true for // Return true if a file exists. Should not return true for
// directories. // directories.
abstract bool has(char[] file); abstract bool has(char[] file);
abstract bool hasDir(char[] dir);
// Open the given file and return it as a stream. // Open the given file and return it as a stream.
abstract Stream open(char[] file); 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) if(file.begins("/") || file.begins("\\"))
{ fail("Filename " ~ file ~ " cannot begin with a path separator");
version(Win32) if(file.find(":") != -1)
{ fail("Filename " ~ file ~ " cannot contain colons");
if(c == ':' || c == '\\' || c == '/') if(file.find("..") != -1)
return fullname[i+1..$]; fail("Filename " ~ file ~ " cannot contain '..'");
}
version(Posix)
{
if (fullname[i] == '/')
return fullname[i+1..$];
}
}
return fullname;
} }
} }
@ -82,6 +77,13 @@ class ListVFS : VFS
return false; return false;
} }
bool hasDir(char[] file)
{
foreach(l; list)
if(l.hasDir(file)) return true;
return false;
}
Stream open(char[] file) Stream open(char[] file)
{ {
foreach(l; list) foreach(l; list)
@ -109,14 +111,7 @@ class FileVFS : VFS
if(buffer.length < file.length+sysPath.length) if(buffer.length < file.length+sysPath.length)
buffer.length = file.length + sysPath.length + 50; buffer.length = file.length + sysPath.length + 50;
// Check for invalid file names. This makes sure the caller checkForEscape(file);
// 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 '..'");
// Copy the file name over // Copy the file name over
buffer[sysPath.length .. sysPath.length+file.length] buffer[sysPath.length .. sysPath.length+file.length]
@ -172,7 +167,16 @@ class FileVFS : VFS
} }
bool has(char[] file) 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) Stream open(char[] file)
{ return new BufferedFile(getPath(file)); } { return new BufferedFile(getPath(file)); }

@ -60,8 +60,16 @@ bool ciStringOps = true;
// Skip lines beginning with a hash character '#' // Skip lines beginning with a hash character '#'
bool skipHashes = true; 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 '#' // Skip lines beginning with a hash character '#'
bool skipHashes = true; 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 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.modules.all;
import monster.options; import monster.options;
version(Tango)
{}
else
{
// D runtime stuff // D runtime stuff
version(Posix) version(Posix)
{ {
@ -56,6 +61,9 @@ extern (C) void _moduleUnitTests();
//extern (C) bool no_catch_exceptions; //extern (C) bool no_catch_exceptions;
} // end version(Tango) .. else
bool initHasRun = false; bool initHasRun = false;
bool stHasRun = false; bool stHasRun = false;
@ -83,21 +91,30 @@ void doMonsterInit()
// Nope. This is normal though if we're running as a C++ // Nope. This is normal though if we're running as a C++
// library. We have to init the D runtime manually. // 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(); assert(0, "tango-compiled C++ library not supported yet");
_STI_critical_init();
} }
else
{
gc_init(); version (Posix)
{
_STI_monitor_staticctor();
_STI_critical_init();
}
version (Win32) gc_init();
{
_minit(); version (Win32)
} {
_minit();
}
_moduleCtor(); _moduleCtor();
_moduleUnitTests(); _moduleUnitTests();
}
} }
assert(stHasRun, "D library initializion failed"); assert(stHasRun, "D library initializion failed");
@ -107,10 +124,12 @@ void doMonsterInit()
// Initialize compiler constructs // Initialize compiler constructs
initTokenizer(); initTokenizer();
initProperties(); initProperties();
initScope();
// Initialize VM // initScope depends on doVMInit setting vm.vfs
vm.doVMInit(); vm.doVMInit();
initScope();
// The rest of the VM
scheduler.init(); scheduler.init();
stack.init(); stack.init();
arrays.initialize(); arrays.initialize();

@ -113,6 +113,10 @@ final class MonsterClass
Type classType; // Type for class references to this class (not Type classType; // Type for class references to this class (not
// implemented yet) // 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: private:
// List of objects of this class. Includes objects of all subclasses // List of objects of this class. Includes objects of all subclasses
// as well. // as well.
@ -150,11 +154,15 @@ final class MonsterClass
// already. // already.
void requireCompile() { if(!isCompiled) compileBody(); } void requireCompile() { if(!isCompiled) compileBody(); }
// Constructor that only exists to keep people from using it. It's // Create a class belonging to the given package scope. Do not call
// much safer to use the vm.load functions, since these check if the // this yourself, use vm.load* to load classes.
// class already exists. this(PackageScope psc = null)
this(int internal = 0) { assert(internal == -14, {
"Don't create MonsterClasses directly, use vm.load()"); } 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 // Insert ourselves into the global scope. This will also
// resolve forward references to this class, if any. // resolve forward references to this class, if any.
global.insertClass(this); pack.insertClass(this);
// Get the parent classes, if any // Get the parent classes, if any
if(isNext(tokens, TT.Colon)) if(isNext(tokens, TT.Colon))
@ -1005,11 +1013,11 @@ final class MonsterClass
assert(tree[treeIndex] is this); assert(tree[treeIndex] is this);
// The parent scope is the scope of the parent class, or the // 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; Scope parSc;
if(parents.length != 0) parSc = parents[0].sc; if(parents.length != 0) parSc = parents[0].sc;
// TODO: Should only be allowed for Object // TODO: Should only be allowed for Object
else parSc = global; else parSc = pack;
assert(parSc !is null); assert(parSc !is null);
@ -1189,9 +1197,12 @@ final class MonsterClass
assert(totSize == dataSize, "Data size mismatch in scope"); assert(totSize == dataSize, "Data size mismatch in scope");
} }
bool compiling = false;
void compileBody() void compileBody()
{ {
assert(!isCompiled, getName() ~ " is already compiled"); assert(!isCompiled, getName() ~ " is already compiled");
assert(!compiling, "compileBody called recursively");
compiling = true;
// Resolve the class body if it's not already done // Resolve the class body if it's not already done
if(!isResolved) resolveBody(); if(!isResolved) resolveBody();
@ -1297,5 +1308,6 @@ final class MonsterClass
assert(singObj is null); assert(singObj is null);
singObj = createObject(); singObj = createObject();
} }
compiling = false;
} }
} }

@ -230,9 +230,9 @@ struct MonsterObject
int *getDataInt(int treeIndex, int pos) int *getDataInt(int treeIndex, int pos)
{ {
assert(treeIndex >= 0 && treeIndex < data.length, 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, 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]; return &data[treeIndex][pos];
} }
@ -241,10 +241,10 @@ struct MonsterObject
{ {
assert(len > 0); assert(len > 0);
assert(treeIndex >= 0 && treeIndex < data.length, 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, assert(pos >= 0 && (pos+len)<=data[treeIndex].length,
"data pointer out of range: pos=" ~ toString(pos) ~ "data pointer out of range: pos=" ~ .toString(pos) ~
", len=" ~toString(len)); ", len=" ~.toString(len));
return data[treeIndex][pos..pos+len]; return data[treeIndex][pos..pos+len];
} }
@ -454,6 +454,11 @@ struct MonsterObject
auto stl = cls.findState(name, label); auto stl = cls.findState(name, label);
setState(stl.state, stl.label); setState(stl.state, stl.label);
} }
char[] toString()
{
return cls.toString ~ "#" ~ .toString(cast(int)getIndex());
}
} }
alias FreeList!(MonsterObject) ObjectList; alias FreeList!(MonsterObject) ObjectList;

@ -54,6 +54,7 @@ import std.math : floor;
// Used for array copy below. It handles overlapping data for us. // Used for array copy below. It handles overlapping data for us.
extern(C) void* memmove(void *dest, void *src, size_t n); extern(C) void* memmove(void *dest, void *src, size_t n);
// Enable this to print bytecode instructions to stdout
//debug=traceOps; //debug=traceOps;
import monster.util.list; import monster.util.list;
@ -625,8 +626,8 @@ struct Thread
debug(traceOps) debug(traceOps)
{ {
writefln("exec: %s", bcToString[opCode]); writefln("exec: %s (at stack %s)",
writefln("stack=", stack.getPos); bcToString[opCode], stack.getPos);
} }
switch(opCode) switch(opCode)
@ -838,10 +839,19 @@ struct Thread
case BC.PushData: case BC.PushData:
stack.pushInt(code.getInt()); stack.pushInt(code.getInt());
debug(traceOps) writefln(" Data: %s", *stack.getInt(0));
break; break;
case BC.PushLocal: 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; break;
case BC.PushClassVar: case BC.PushClassVar:
@ -1232,7 +1242,7 @@ struct Thread
//stack.pushLong(stack.popInt()); //stack.pushLong(stack.popInt());
break; break;
// Castint to float // Cast int to float
case BC.CastI2F: case BC.CastI2F:
ptr = stack.getInt(0); ptr = stack.getInt(0);
fptr = cast(float*) ptr; fptr = cast(float*) ptr;
@ -1257,7 +1267,7 @@ struct Thread
stack.pushFloat(stack.popDouble); stack.pushFloat(stack.popDouble);
break; break;
// Castint to double // Cast int to double
case BC.CastI2D: case BC.CastI2D:
stack.pushDouble(stack.popInt); stack.pushDouble(stack.popInt);
break; break;
@ -1278,6 +1288,43 @@ struct Thread
stack.pushDouble(stack.popFloat); stack.pushDouble(stack.popFloat);
break; 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: case BC.CastT2S:
{ {
// Get the type to cast from // Get the type to cast from
@ -1292,6 +1339,18 @@ struct Thread
} }
break; 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: case BC.FetchElem:
// This is not very optimized // This is not very optimized
val = stack.popInt(); // Index val = stack.popInt(); // Index

@ -86,51 +86,7 @@ struct VM
scheduler.doFrame(); scheduler.doFrame();
} }
// Execute a single statement. Context-dependent statements such as // Load a class based on class name, file name, or both.
// '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.
MonsterClass load(char[] nam1, char[] nam2 = "") MonsterClass load(char[] nam1, char[] nam2 = "")
{ return doLoad(nam1, nam2, true, true); } { return doLoad(nam1, nam2, true, true); }
@ -139,7 +95,7 @@ struct VM
{ return doLoad(nam1, nam2, false, true); } { return doLoad(nam1, nam2, false, true); }
// Does not fail if the class is not found, just returns null. It // 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 = "") MonsterClass loadNoFail(char[] nam1, char[] nam2 = "")
{ return doLoad(nam1, nam2, true, false); } { return doLoad(nam1, nam2, true, false); }
@ -150,7 +106,7 @@ struct VM
init(); init();
assert(s !is null, "Cannot load from null stream"); assert(s !is null, "Cannot load from null stream");
auto mc = new MonsterClass(-14); auto mc = new MonsterClass(global);
mc.parse(s, name, bom); mc.parse(s, name, bom);
return mc; return mc;
} }
@ -161,7 +117,7 @@ struct VM
{ {
init(); init();
auto mc = new MonsterClass(-14); auto mc = new MonsterClass(global);
mc.parse(toks, name); mc.parse(toks, name);
return mc; return mc;
} }
@ -276,6 +232,9 @@ struct VM
char[] fname, cname; char[] fname, cname;
MonsterClass mc; MonsterClass mc;
PackageScope pack = global;
assert(pack !is null);
if(name1 == "") if(name1 == "")
fail("Cannot give empty first parameter to load()"); fail("Cannot give empty first parameter to load()");
@ -304,42 +263,85 @@ struct VM
// Was a filename given? // Was a filename given?
if(fname != "") if(fname != "")
{ {
// Derive the class name from the file name // Derive the class and package names from the given file name.
char[] nameTmp = vfs.getBaseName(fname); VFS.checkForEscape(fname);
assert(fname.iEnds(".mn"));
nameTmp = fname[0..$-3]; 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) if(!cNameSet)
// No class name given, set it to the derived name // No class name given, set it to the derived name
cname = nameTmp; cname = file;
else else
// Both names were given, make sure they match {
if(icmp(nameTmp,cname) != 0) // Both names were given, make sure they match
fail(format("Class name %s does not match file name %s", if(cname.find('.') != -1)
cname, fname)); 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 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) // Pick out the package part of the class name.
return false; char[] pname = cname;
while(true)
if(!validFirstIdentChar(cname[0])) {
return false; 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) cname = pname;
if(!validIdentChar(c)) return false;
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)); fail(format("Invalid class name %s (file %s)", cname, fname));
// At this point, check if the class already exists. // At this point, check if the class already exists.
@ -369,7 +371,7 @@ struct VM
auto bf = vfs.open(fname); auto bf = vfs.open(fname);
auto ef = new EndianStream(bf); auto ef = new EndianStream(bf);
int bom = ef.readBOM(); int bom = ef.readBOM();
mc = new MonsterClass(-14); mc = new MonsterClass(pack);
mc.parse(ef, fname, bom); mc.parse(ef, fname, bom);
delete bf; delete bf;

@ -231,8 +231,8 @@ void getHeight()
void setupGUIScripts() void setupGUIScripts()
{ {
vm.addPath("mscripts/gui/"); vm.addPath("mscripts/guiscripts/");
vm.addPath("mscripts/gui/module/"); vm.addPath("mscripts/guiscripts/module/");
gmc = vm.load("gui", "gui.mn"); gmc = vm.load("gui", "gui.mn");
wid_mc = vm.load("Widget", "widget.mn"); wid_mc = vm.load("Widget", "widget.mn");
/* /*

Loading…
Cancel
Save