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