Updated to Monster 0.14 and fixed console bugs

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

File diff suppressed because it is too large Load Diff

@ -117,6 +117,35 @@ abstract class Block
reqNext(toks, type, t);
}
// Skip any matching set of parens (), [] or {}, and anything inside
// it.
static void skipParens(ref TokenArray toks, TT type = TT.LeftParen)
{
TT endType;
if(type == TT.LeftParen)
endType = TT.RightParen;
else if(type == TT.LeftCurl)
endType = TT.RightCurl;
else if(type == TT.LeftSquare)
endType = TT.RightSquare;
else assert(0);
int count = 0;
while(toks.length != 0)
{
if(toks[0].type == type)
count++;
else if(toks[0].type == endType)
count--;
toks = toks[1..$];
if(count <= 0)
return;
}
}
// Sets the assembler debug line to the line belonging to this
// block.
final void setLine() { tasm.setLine(loc.line); }

@ -29,20 +29,12 @@ enum BC
{
Exit = 1, // Exit function.
Call, // Call function in this object. Takes a class
// tree index and a function index, both ints.
Call, // Call function in this object. Takes a
// global function index on the stack, int.
CallFar, // Call function in another object. Takes a
// class index tree and a function index. The
// object must be pushed on the stack.
CallIdle, // Calls an idle function in this object.
// Takes a class tree index and a function
// index.
CallIdleFar, // Calls an idle function in another
// object. Also takes an object index from the
// stack.
CallFar, // Call function in another object. The object
// must be pushed on the stack, followed by
// the global function index.
Return, // Takes a parameter nr (int). Equivalent to:
// POPN nr (remove nr values of the stack)
@ -267,6 +259,8 @@ enum BC
CastD2L, // double to long
CastD2UL, // double to ulong
CastS2C, // string to char - string must have length 1
CastT2S, // cast any type to string. Takes the type
// index (int) as a parameter
@ -274,6 +268,9 @@ enum BC
// the object on the stack is an instance of
// the given class.
RefFunc, // Pop an array reference (two ints) and push
// the corresponding function name as a string
FetchElem, // Get an element from an array. Pops the
// index, then the array reference, then
// pushes the value. The element size is
@ -397,7 +394,7 @@ enum BC
// (byte) defined below. The library user will
// later be able to choose whether this halts
// execution entirely or just kills the
// offending object.
// offending vthread.
Last
}
@ -418,13 +415,13 @@ enum Err
// Used for coded pointers. The first byte in a coded pointer gives
// the pointer type, and the remaining 24 bits gives an index whose
// meaning is determined by the type. The pointers can be used both
// for variables and for functions.
// for variables and for functions. All pointers are 3 ints in size on
// the stack. The comments below defines what the indices mean - the
// ones that are not mentioned are zero.
enum PT
{
Null = 0, // Null pointer. The index must also be zero.
// Variable pointers
Stack = 1, // Index is relative to function stack
// frame. Used for local variables.
@ -532,7 +529,6 @@ char[][] bcToString =
BC.Exit: "Exit",
BC.Call: "Call",
BC.CallFar: "CallFar",
BC.CallIdle: "CallIdle",
BC.Return: "Return",
BC.ReturnVal: "ReturnVal",
BC.ReturnValN: "ReturnValN",
@ -563,6 +559,7 @@ char[][] bcToString =
BC.Store: "Store",
BC.Store8: "Store8",
BC.StoreMult: "StoreMult",
BC.RefFunc: "RefFunc",
BC.FetchElem: "FetchElem",
BC.GetArrLen: "GetArrLen",
BC.IMul: "IMul",
@ -634,6 +631,7 @@ char[][] bcToString =
BC.CastD2U: "CastD2U",
BC.CastD2L: "CastD2L",
BC.CastD2UL: "CastD2UL",
BC.CastS2C: "CastS2C",
BC.CastT2S: "CastT2S",
BC.DownCast: "DownCast",
BC.PopToArray: "PopToArray",

@ -390,6 +390,14 @@ abstract class Expression : Block
final void evalPop()
{
eval();
// Internal types never come from expressions that have any
// effect. Leaving them dangling without a reciever can cause
// stack bugs in some cases (since the owner and other internal
// data is never popped), so we forbid them.
if(!type.isLegal)
fail("Expression " ~ toString() ~ " has no effect", loc);
setLine();
if(!type.isVoid)
tasm.pop(type.getSize());
@ -621,7 +629,10 @@ class NewExpression : Expression
if(type.isObject)
{
// Then push the variable pointers and values on the stack
// Total stack size of parameters
int imprint = 0;
// Push the variable pointers and values on the stack
foreach(i, v; params)
{
auto lvar = varlist[i];
@ -630,14 +641,19 @@ class NewExpression : Expression
assert(lvar !is null);
assert(lvar.sc !is null);
// Push the expression and some meta info
v.value.eval();
tasm.push(v.value.type.getSize); // Type size
tasm.push(lvar.number); // Var number
tasm.push(lvar.sc.getClass().getTreeIndex()); // Class index
// Tally up the casualties
imprint += v.value.type.getSize() + 3;
}
// Create a new object. This is done through a special byte code
// instruction.
tasm.newObj(clsInd, params.length);
// Create a new object. This is done through a special byte
// code instruction.
tasm.newObj(clsInd, params.length, imprint);
}
else if(type.isArray)
{
@ -780,9 +796,9 @@ class ArrayLiteralExpr : Expression
}
// Expression representing a literal or other special single-token
// values. Supported tokens are StringLiteral, Int/FloatLiteral,
// CharLiteral, True, False, Null, Dollar and This. Array literals are
// handled by ArrayLiteralExpr.
// values. Supported tokens are StringLiteral, Int/FloatLiteral, True,
// False, Null, Dollar and This. Array literals are handled by
// ArrayLiteralExpr.
class LiteralExpr : Expression
{
Token value;
@ -792,22 +808,11 @@ class LiteralExpr : Expression
dchar dval; // Characters are handled internally as dchars
float fval;
// TODO/FIXME: When evalutationg the array length symbol $, we
// evaluate the array expression again, and the find its
// length. This is a TEMPORARY solution - if the array expression is
// a complicated expression or if it has side effects, then
// evaluating it more than once is obviously not a good idea. Example:
// myfunc()[$-4..$]; // myfunc() is called three times!
// A better solution is to store the array index on the stack for
// the entire duration of the array expression and remember the
// position, just like we do for foreach. Since the index has
// already been pushed, this is pretty trivial to do through the
// scope system.
Expression arrayExp;
// The array operator expression for $ symbols (ie. a[b] or a[b..c])
ArrayOperator arrOp;
// TODO: Does not support double, long or unsigned types yet. A much
// more complete implementation will come later.
// This is set if the array that $ refers to is ctime.
bool arrayCTime;
// String, with decoded escape characters etc and converted to
// utf32. We need the full dchar string here, since we might have to
@ -825,7 +830,6 @@ class LiteralExpr : Expression
isNext(toks, TT.StringLiteral) ||
isNext(toks, TT.IntLiteral) ||
isNext(toks, TT.FloatLiteral) ||
isNext(toks, TT.CharLiteral) ||
isNext(toks, TT.True) ||
isNext(toks, TT.False) ||
isNext(toks, TT.Null) ||
@ -893,7 +897,13 @@ class LiteralExpr : Expression
fail("Array length $ not allowed here", loc);
type = BasicType.getInt;
arrayExp = sc.getArray();
arrOp = sc.getArray();
// If the array itself is compile time, then so is the
// length.
assert(arrOp.name !is null);
assert(arrOp.name.type.isArray());
arrayCTime = arrOp.name.isCTime();
return;
}
@ -916,32 +926,12 @@ class LiteralExpr : Expression
return;
}
// Single character
if(value.type == TT.CharLiteral)
{
type = BasicType.getChar;
// Decode the unicode character. TODO: Error checking?
// Unicode sanity checks should be handled in the tokenizer.
size_t idx = 1;
dval = decode(value.str, idx);
return;
}
// Strings
if(value.type == TT.StringLiteral)
{
type = ArrayType.getString;
// Check that we do indeed have '"'s at the ends of the
// string. Special cases which we allow later (like wysiwig
// strings, @"c:\") will have their special characters
// removed in the tokenizer.
assert(value.str.length >=2 && value.str[0] == '"' && value.str[$-1] == '"',
"Encountered invalid string literal token: " ~ value.str);
strVal = toUTF32(value.str[1..$-1]);
//cls.reserveStatic(strVal.length);
strVal = value.str32;
return;
}
@ -952,7 +942,9 @@ class LiteralExpr : Expression
// We currently support a few kinds of constants
bool isCTime()
{
if(value.type == TT.Dollar) return false;
// If the array value itself is ctime, then $ is too.
if(value.type == TT.Dollar)
return arrayCTime;
return
type.isInt() || type.isBool() || type.isFloat || type.isChar ||
@ -961,6 +953,19 @@ class LiteralExpr : Expression
int[] evalCTime()
{
if(value.type == TT.Dollar)
{
// Get the array
assert(arrOp.name.isCTime());
int res[] = arrOp.name.evalCTime().dup;
assert(res.length == 1);
auto arf = arrays.getRef(cast(AIndex)res[0]);
// Return the length
res[0] = arf.length();
return res;
}
// Return a slice of the value
if(type.isInt || type.isBool) return (&ival)[0..1];
if(type.isChar) return (cast(int*)&dval)[0..1];
@ -988,14 +993,11 @@ class LiteralExpr : Expression
if(value.type == TT.Dollar)
{
// Get the array. TODO/FIXME: This is a very bad solution,
// the entire array expression is recomputed whenever we use
// the $ symbol. If the expression has side effects (like a
// function call), this can give unexpected results. This is
// a known bug that will be fixed later. The simplest
// solution is to let ArrayExpression create a new scope,
// which stores the stack position of the array index.
arrayExp.eval();
// The array index should already be on the stack. Get it.
auto sMark = arrOp.stackMark;
assert(sMark !is null);
tasm.pushMark(sMark);
// Convert it to the length
setLine();
tasm.getArrayLength();
@ -1053,7 +1055,7 @@ class CastExpression : Expression
orig.eval();
// The type does the low-level stuff
orig.type.evalCastTo(type);
orig.type.evalCastTo(type, orig.loc);
}
char[] toString()
@ -1075,6 +1077,10 @@ abstract class ImportHolder : Expression
// Get a short name (for error messages)
abstract char[] getName();
// Override these to avoid duplicate imports
bool isClass(MonsterClass mc) { return false; }
bool isPack(PackageScope sc) { return false; }
override:
// Override these
@ -1112,6 +1118,8 @@ class PackageImpHolder : ImportHolder
char[] getName() { return sc.toString(); }
char[] toString() { return "imported package " ~ sc.toString(); }
bool isPack(PackageScope s) { return s is sc; }
}
// Import holder for classes
@ -1160,6 +1168,8 @@ class ClassImpHolder : ImportHolder
tasm.pushSingleton(mc.getIndex());
}
}
bool isClass(MonsterClass m) { return m is mc; }
}
// An expression that works as a statement. This also handles all

@ -54,6 +54,8 @@ import monster.vm.thread;
import monster.vm.stack;
import monster.vm.vm;
import monster.util.growarray;
import std.stdio;
import std.stream;
import std.string;
@ -100,12 +102,38 @@ struct Function
int[][] defaults; // Default parameter values (if specified, null otherwise)
int index; // Unique function identifier within its class
int paramSize;
// Register this function in the global function list. This can only
// be done once, but it's required before functions can be
// referenced by function pointers in script code. Don't call this
// if you don't know what you're doing.
void register()
{
assert(!hasGIndex(), "Don't call register() more than once.");
gIndex = functionList.length();
functionList ~= this;
}
int getGIndex()
{
assert(hasGIndex(), "This function doesn't have a global index");
return gIndex;
}
/*
int imprint; // Stack imprint of this function. Equals
// (type.getSize() - paramSize) (not implemented yet)
*/
bool hasGIndex() { return gIndex != -1; }
static Function *fromIndex(int index)
{
if(index < 0)
fail("Null function reference encountered");
if(index > functionList.length)
fail("Invalid function index encountered");
return functionList[index];
}
int paramSize;
// Is this function final? (can not be overridden in child classes)
bool isFinal;
@ -116,6 +144,21 @@ struct Function
// What function we override (if any)
Function *overrides;
// Find the virtual replacement for this function in the context of
// the object mo.
Function *findVirtual(MonsterObject *mo)
{
assert(mo !is null);
return findVirtual(mo.cls);
}
// Find virtual replacement in the context of class cls.
Function *findVirtual(MonsterClass cls)
{
assert(cls.childOf(owner));
return cls.findVirtualFunc(owner.getTreeIndex(), index);
}
bool isNormal() { return ftype == FuncType.Normal; }
bool isNative()
{
@ -416,6 +459,9 @@ struct Function
private:
// Global unique function index
int gIndex = -1;
// Empty class / object used internally
static const char[] int_class = "class _ScriptFile_;";
static MonsterClass int_mc;
@ -475,6 +521,9 @@ class Constructor : FuncDeclaration
}
}
// Global list of functions.
GrowArray!(Function*) functionList;
// Responsible for parsing, analysing and compiling functions.
class FuncDeclaration : Statement
{
@ -604,6 +653,10 @@ class FuncDeclaration : Statement
// Create a Function struct.
fn = new Function;
// Register a global index for all class functions
fn.register();
assert(fn.getGIndex() != -1);
// Default function type is normal
fn.ftype = FuncType.Normal;
@ -626,20 +679,6 @@ class FuncDeclaration : Statement
if(fn.isAbstract || fn.isNative || fn.isIdle)
{
reqSep(toks);
// Check that the function declaration ends with a ; rather
// than a code block.
/*
if(!isNext(toks, TT.Semicolon))
{
if(fn.isAbstract)
fail("Abstract function declaration expected ;", toks);
else if(fn.isNative)
fail("Native function declaration expected ;", toks);
else if(fn.isIdle)
fail("Idle function declaration expected ;", toks);
else assert(0);
}
*/
}
else
{
@ -657,7 +696,6 @@ class FuncDeclaration : Statement
fail("Token '" ~ fn.name.str ~ "' cannot be used as a function name",
loc);
if(!isNext(toks, TT.LeftParen))
fail("Function expected parameter list", toks);
@ -907,11 +945,11 @@ class FuncDeclaration : Statement
if(fn.type.isVoid)
// Remove parameters from the stack at the end of the function
tasm.exit(fn.paramSize);
tasm.exit(fn.paramSize,0,0);
else
// Functions with return types must have a return statement
// and should never reach the end of the function. Fail if we
// do.
// Functions with return types must have a return statement,
// so we should never reach the end of the function. Fail if
// we do.
tasm.error(Err.NoReturn);
// Assemble the finished function
@ -936,7 +974,12 @@ class FunctionCallExpr : Expression
// function parameter list. Null expressions
// means we must use the default value. Never
// used for vararg functions.
// These are used to get information about the function and
// parameters. If this is a function reference call, fd is null and
// only frt is set.
Function* fd;
FuncRefType frt;
bool isCast; // If true, this is an explicit typecast, not a
// function call.
@ -1049,11 +1092,6 @@ class FunctionCallExpr : Expression
fResolved = console;
}
/* Might be used for D-like implicit function calling, eg. someFunc;
or (obj.prop)
this(Expression func) {}
*/
void parse(ref TokenArray toks) { assert(0); }
char[] toString()
@ -1099,41 +1137,75 @@ class FunctionCallExpr : Expression
// TODO: Do typecasting here. That will take care of polysemous
// types later as well.
if(!fname.type.isFunc)
if(!fname.type.isIntFunc && !fname.type.isFuncRef)
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;
if(fname.type.isFuncRef)
{
// Set frt to the reference type
frt = cast(FuncRefType)fname.type;
assert(frt !is null);
fd = null;
}
else if(fname.type.isIntFunc)
{
// Get the function from the type
auto ft = cast(IntFuncType)fname.type;
assert(ft !is null);
fd = ft.func;
// Create a temporary reference type
frt = new FuncRefType(ft);
}
isVararg = fd.isVararg;
type = fd.type;
isVararg = frt.isVararg;
type = frt.retType;
assert(type !is null);
if(fd is null && named.length != 0)
fail("Cannot use named parameters when calling function references",
loc);
// Get the best parameter name possible from the available
// information
char[] getParName(int i)
{
char[] dst = "parameter ";
if(fd !is null)
dst ~= fd.params[i].name.str;
else
dst ~= .toString(i);
return dst;
}
if(isVararg)
{
if(named.length)
fail("Cannot give named parameters to vararg functions", loc);
// The vararg parameter can match a variable number of
// arguments, including zero.
if(params.length < fd.params.length-1)
if(params.length < frt.params.length-1)
fail(format("%s() expected at least %s parameters, got %s",
name, fd.params.length-1, params.length),
name, frt.params.length-1, params.length),
loc);
// Check parameter types except for the vararg parameter
foreach(int i, par; fd.params[0..$-1])
foreach(int i, par; frt.params[0..$-1])
{
params[i].resolve(sc);
par.type.typeCast(params[i], "parameter " ~ par.name.str);
// The info about each parameter depends on whether this
// is a direct function call or a func reference call.
par.typeCast(params[i], getParName(i));
}
// Loop through remaining arguments
int start = fd.params.length-1;
int start = frt.params.length-1;
assert(fd.params[start].type.isArray);
Type base = fd.params[start].type.getBase();
assert(frt.params[start].isArray);
Type base = frt.params[start].getBase();
foreach(int i, ref par; params[start..$])
{
@ -1143,7 +1215,7 @@ class FunctionCallExpr : Expression
// array type itself, then we are sending an actual
// array. Treat it like a normal parameter.
if(i == 0 && start == params.length-1 &&
par.type == fd.params[start].type)
par.type == frt.params[start])
{
isVararg = false;
coverage = params;
@ -1151,7 +1223,7 @@ class FunctionCallExpr : Expression
}
// Otherwise, cast the type to the array base type.
base.typeCast(par, "array base type");
base.typeCast(par, "vararg array base type");
}
return;
}
@ -1159,10 +1231,17 @@ class FunctionCallExpr : Expression
// Non-vararg case. Non-vararg functions must cover at least all
// the non-optional function parameters.
int parNum = frt.params.length;
int parNum = fd.params.length;
// When calling a function reference, we can't use named
// parameters, and we don't know the function's default
// values. Because of this, all parameters must be present.
if(fd is null && params.length != parNum)
fail(format("Function reference %s (of type '%s') expected %s arguments, got %s",
name, frt, parNum, params.length), fname.loc);
// Sanity check on the parameter number
// Sanity check on the parameter number for normal function
// calls
if(params.length > parNum)
fail(format("Too many parameters to function %s(): expected %s, got %s",
name, parNum, params.length), fname.loc);
@ -1170,7 +1249,8 @@ class FunctionCallExpr : Expression
// Make the coverage list of all the parameters.
coverage = new Expression[parNum];
// Mark all the parameters which are present
// Mark all the parameters which are present. Start with the
// sequential (non-named) paramteres.
foreach(i,p; params)
{
assert(coverage[i] is null);
@ -1178,59 +1258,71 @@ class FunctionCallExpr : Expression
coverage[i] = p;
}
// Add named parameters to the list
foreach(p; named)
if(fd !is null)
{
// Look up the named parameter
int index = -1;
foreach(i, fp; fd.params)
if(fp.name.str == p.name.str)
{
index = i;
break;
}
if(index == -1)
fail(format("Function %s() has no paramter named %s",
name, p.name.str),
p.name.loc);
assert(index<parNum);
// Check that the parameter isn't already set
if(coverage[index] !is null)
fail("Parameter " ~ p.name.str ~ " set multiple times ",
p.name.loc);
// Finally, set the parameter
coverage[index] = p.value;
assert(coverage[index] !is null);
}
// This part (named and optional parameters) does not apply
// when calling function references
// Check that all non-optional parameters are present
assert(fd.defaults.length == coverage.length);
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),
loc);
assert(fd !is null);
// Add named parameters to the list
foreach(p; named)
{
// Look up the named parameter
int index = -1;
foreach(i, fp; fd.params)
if(fp.name.str == p.name.str)
{
index = i;
break;
}
if(index == -1)
fail(format("Function %s() has no parameter named %s",
name, p.name.str),
p.name.loc);
assert(index<parNum);
// Check that the parameter isn't already set
if(coverage[index] !is null)
fail("Parameter " ~ p.name.str ~ " set multiple times ",
p.name.loc);
// Finally, set the parameter
coverage[index] = p.value;
assert(coverage[index] !is null);
}
// Check that all non-optional parameters are present
assert(fd.defaults.length == coverage.length);
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),
loc);
}
// Check parameter types
foreach(int i, ref cov; coverage)
{
auto par = fd.params[i];
// Skip missing parameters
if(cov is null) continue;
if(cov is null)
{
assert(fd !is null);
continue;
}
auto par = frt.params[i];
cov.resolve(sc);
par.type.typeCast(cov, "parameter " ~ par.name.str);
par.typeCast(cov, getParName(i));
}
}
// Evaluate the parameters
private void evalParams()
{
// Again, let's handle the vararg case separately
// Handle the vararg case separately
if(isVararg)
{
assert(coverage is null);
@ -1241,19 +1333,19 @@ class FunctionCallExpr : Expression
ex.eval();
// The rest only applies to non-vararg parameters
if(i >= fd.params.length-1)
if(i >= frt.params.length-1)
continue;
// Convert 'const' parameters to actual constant references
if(fd.params[i].isConst)
if(frt.isConst[i])
{
assert(fd.params[i].type.isArray);
assert(frt.params[i].isArray);
tasm.makeArrayConst();
}
}
// Compute the length of the vararg array.
int len = params.length - fd.params.length + 1;
int len = params.length - frt.params.length + 1;
// If it contains no elements, push a null array reference
// (0 is always null).
@ -1264,9 +1356,9 @@ class FunctionCallExpr : Expression
tasm.popToArray(len, params[$-1].type.getSize());
// Convert the vararg array to 'const' if needed
if(fd.params[$-1].isConst)
if(frt.isConst[$-1])
{
assert(fd.params[$-1].type.isArray);
assert(frt.params[$-1].isArray);
tasm.makeArrayConst();
}
}
@ -1275,16 +1367,23 @@ class FunctionCallExpr : Expression
// Non-vararg case
assert(!isVararg);
assert(coverage.length == fd.params.length);
assert(coverage.length == frt.params.length);
foreach(i, ex; coverage)
{
if(ex !is null)
{
assert(ex.type == fd.params[i].type);
assert(ex.type !is null);
assert(frt.params[i] !is null);
assert(ex.type == frt.params[i]);
ex.eval();
}
else
{
// resolve() should already have caught this
assert(fd !is null,
"cannot use default parameters with reference calls");
// No param specified, use default value
assert(fd.defaults[i].length ==
fd.params[i].type.getSize);
@ -1293,9 +1392,9 @@ class FunctionCallExpr : Expression
}
// Convert 'const' parameters to actual constant references
if(fd.params[i].isConst)
if(frt.isConst[i])
{
assert(fd.params[i].type.isArray);
assert(frt.params[i].isArray);
tasm.makeArrayConst();
}
}
@ -1315,25 +1414,51 @@ class FunctionCallExpr : Expression
return;
}
assert(frt !is null);
// 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);
// Then push the function expression or reference.
fname.eval();
setLine();
auto ft = cast(FunctionType)fname.type;
assert(ft !is null);
bool isMember = ft.isMember;
bool isFar;
setLine();
assert(fd.owner !is null);
// Total stack imprint of the function call
int imprint;
if(fd !is null)
{
// Direct function call
assert(fname.type.isIntFunc);
auto ft = cast(IntFuncType)fname.type;
assert(ft !is null);
assert(fd.owner !is null);
if(fd.isIdle)
tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember);
// Calculate the stack imprint
imprint = fd.type.getSize() - fd.paramSize;
// Push the function index. For far function calls, we have
// in effect pushed a function reference (object+function)
tasm.push(fd.getGIndex());
isFar = ft.isMember;
}
else
tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember);
{
// Function reference call
assert(fname.type.isFuncRef);
isFar = true;
// Let the type calculate the stack imprint
auto frt = cast(FuncRefType)fname.type;
assert(frt !is null);
imprint = frt.getImprint();
}
tasm.call(isFar, imprint);
}
}

@ -39,13 +39,16 @@ import std.stdio;
import std.string;
import std.utf;
// Handles - ! ++ --
// Handles - ! ++ -- @
class UnaryOperator : Expression
{
TT opType;
Expression exp;
bool postfix;
// Original function type (for @)
IntFuncType ift;
this()
{
postfix = false;
@ -65,6 +68,7 @@ class UnaryOperator : Expression
return
isNext(toks, TT.Minus) ||
isNext(toks, TT.Not) ||
isNext(toks, TT.Alpha) ||
isNext(toks, TT.PlusPlus) ||
isNext(toks, TT.MinusMinus);
}
@ -81,14 +85,39 @@ class UnaryOperator : Expression
return tokenList[opType] ~ "(" ~ exp.toString ~ ")";
}
// Copy everything from the sub expression
void resolve(Scope sc)
{
exp.resolve(sc);
// Copy everything from the sub expression
type = exp.type;
loc = exp.getLoc;
if(opType == TT.Alpha)
{
if(!type.isIntFunc)
fail("Operator @ cannot be used on non-function " ~
exp.toString() ~ " (of type " ~ exp.typeString() ~
")", loc);
ift = cast(IntFuncType)type;
assert(ift !is null);
// Make sure this function has an index
if(!ift.func.hasGIndex())
fail("Cannot create reference to function " ~ ift.func.toString(),
loc);
// Replace the internal function type with a function
// reference.
type = new FuncRefType(ift);
// If the function is not a member, make sure it's a class
// function in our current class (or one of our parents.)
assert(ift.isMember || (sc.getClass().childOf(ift.func.owner)),
"oops, found the wrong owner class");
}
if(opType == TT.PlusPlus || opType == TT.MinusMinus)
{
if(!type.isIntegral)
@ -120,7 +149,8 @@ class UnaryOperator : Expression
bool isCTime()
{
if(opType == TT.PlusPlus || opType == TT.MinusMinus)
if(opType == TT.PlusPlus || opType == TT.MinusMinus ||
opType == TT.Alpha)
return false;
return exp.isCTime();
@ -168,10 +198,22 @@ class UnaryOperator : Expression
}
exp.eval();
setLine();
if(opType == TT.Minus)
if(opType == TT.Alpha)
{
assert(ift !is null);
// If the function is a member, the object will already have
// been pushed on the stack. If not, push this.
if(!ift.isMember)
tasm.pushThis();
// Push the function index
tasm.push(ift.func.getGIndex());
}
else if(opType == TT.Minus)
{
if(type.isInt || type.isLong || type.isFloat || type.isDouble)
tasm.neg(type);
@ -203,7 +245,10 @@ class ArrayOperator : OperatorExpr
bool isFill; // Set during assignment if we're filling the array
// with one single value
int isEnum; // Used for enum types
int isEnum; // Used for enum types
// A stack mark giving the position of the array index on the stack.
CodeElement *stackMark;
// name[], name[index], name[index..index2]
Expression name, index, index2;
@ -287,7 +332,7 @@ class ArrayOperator : OperatorExpr
// either name[index] or name[index..index2]
// Create an inner scope where $ is a valid character.
ArrayScope isc = new ArrayScope(sc, name);
ArrayScope isc = new ArrayScope(sc, this);
index.resolve(isc);
@ -317,7 +362,7 @@ class ArrayOperator : OperatorExpr
if(isDest) return false;
// a[b] and a[b..c]; is compile time if a, b and c is.
// a[b] and a[b..c]; are compile time if a, b and c is.
if(index !is null && !index.isCTime) return false;
if(index2 !is null && !index2.isCTime) return false;
return name.isCTime();
@ -413,7 +458,11 @@ class ArrayOperator : OperatorExpr
return;
}
// Push the array index first
// Get a stack mark. This may be used by $ to get the array
// index later.
stackMark = tasm.markStack();
// Push the array index
name.eval();
setLine();
@ -435,7 +484,7 @@ class ArrayOperator : OperatorExpr
setLine();
if(isDest) tasm.elementAddr();
else tasm.fetchElement();
else tasm.fetchElement(type.getSize());
assert(!isSlice);
}

@ -26,6 +26,7 @@ module monster.compiler.properties;
import monster.compiler.scopes;
import monster.compiler.types;
import monster.compiler.assembler;
import monster.vm.mclass;
import std.stdio;
@ -123,6 +124,29 @@ class FloatProperties : FloatingProperties!(float)
class DoubleProperties : FloatingProperties!(double)
{ static DoubleProperties singleton; }
// Function pointers / references
class FuncRefProperties : SimplePropertyScope
{
static FuncRefProperties singleton;
this()
{
super("FuncRefProperties", GenericProperties.singleton);
// A function ref is object + function index. Just pop away the
// function and the object is left on the stack. Since we cannot
// possibly know the type of the object at compile time, we
// default to 'Object' as the class type.
auto ot = MonsterClass.getObject().objType;
assert(ot !is null);
insert("obj", ot, { tasm.pop(); });
// TODO: Replace this with something else later (like a Function
// class or similar)
insert("func", ArrayType.getString(), { tasm.refFunc(); });
}
}
// Handles .length, .dup, etc for arrays
class ArrayProperties: SimplePropertyScope
{
@ -212,6 +236,10 @@ class GenericProperties : SimplePropertyScope
property, the left hand side (eg an int value) is never
evaluated. If the type is "" or "owner", then the property type
will be the same as the owner type.
It's likely that parts of this will be simplified or rewritten
later. One thing that's missing is compile time properties, for
example.
*/
abstract class SimplePropertyScope : PropertyScope
@ -345,4 +373,5 @@ void initProperties()
FloatProperties.singleton = new FloatProperties;
DoubleProperties.singleton = new DoubleProperties;
ClassProperties.singleton = new ClassProperties;
FuncRefProperties.singleton = new FuncRefProperties;
}

@ -27,6 +27,7 @@ import std.stdio;
import std.string;
import monster.util.aa;
import monster.options;
import monster.compiler.statement;
import monster.compiler.expression;
@ -38,14 +39,13 @@ import monster.compiler.functions;
import monster.compiler.states;
import monster.compiler.structs;
import monster.compiler.enums;
import monster.compiler.operators;
import monster.compiler.variables;
import monster.vm.mclass;
import monster.vm.error;
import monster.vm.vm;
//debug=lookupTrace;
// The global scope
RootScope global;
@ -199,7 +199,7 @@ abstract class Scope
// Copy the import list from our parent
if(!isRoot)
importList = parent.importList;
importList = parent.importList.dup;
}
// Verify that an identifier is not declared in this scope. If the
@ -207,7 +207,7 @@ abstract class Scope
// error. Recurses through parent scopes.
final void clearId(Token name)
{
debug(lookupTrace)
static if(traceLookups)
writefln("ClearId %s in %s (line %s)", name, this, __LINE__);
// Lookup checks all parent scopes so we only have to call it
@ -275,18 +275,12 @@ abstract class Scope
return parent.getState();
}
Expression getArray()
ArrayOperator getArray()
{
assert(!isRoot(), "getArray called on wrong scope type");
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");
@ -307,7 +301,7 @@ abstract class Scope
// automatically if a file exists.
ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
if(isRoot()) return ScopeLookup(name, LType.None, null, null);
else return parent.lookup(name, autoLoad);
@ -323,12 +317,10 @@ abstract class Scope
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.
// Look up an identifier, and check imported scopes as well.
ScopeLookup lookupImport(Token name, bool second=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("LookupImport %s in %s (line %s)", name, this, __LINE__);
auto l = lookup(name, second);
if(!l.isNone) return l;
@ -336,8 +328,13 @@ abstract class Scope
// Nuttin' was found, try the imports
bool found = false;
auto old = l;
foreach(imp; importList)
foreach(ind, imp; importList)
{
static if(traceLookups)
{
writefln(" LI: Import is: %s, index %s of %s",
imp, ind, importList.length-1);
}
l = imp.lookup(name, second);
// Only accept types, classes, variables and functions
@ -376,20 +373,38 @@ abstract class Scope
return old;
}
// Add an import to this scope
void registerImport(ImportHolder s)
// More user-friendly version for API-defined import of classes.
// Eg. global.registerImport(myclass) -> makes myclass available in
// ALL classes.
void registerImport(MonsterClass mc)
{
//writefln("Registering import %s in scope %s", s, this);
importList ~= s;
// Check if this class is already implemented
foreach(imp; importList)
if(imp.isClass(mc)) return;
static if(traceLookups)
writefln("Importing class %s into %s", mc, this);
importList ~= new ClassImpHolder(mc);
}
// More user-friendly version for API-defined
// imports. Eg. global.registerImport(myclass) -> makes myclass
// available in ALL classes.
void registerImport(MonsterClass mc)
{ registerImport(new ClassImpHolder(mc)); }
// Ditto for packages
void registerImport(PackageScope psc)
{
// Never import the global scope - it's already implied as a
// parent scope of most other scopes
//if(psc is global) return;
foreach(imp; importList)
if(imp.isPack(psc)) return;
static if(traceLookups)
writefln("Importing package %s into %s", psc, this);
importList ~= new PackageImpHolder(psc);
}
// Even more user-friendly version. Takes a list of class names.
// Even more user-friendly version for classes. Takes a list of
// class names.
void registerImport(char[][] cls ...)
{
foreach(c; cls)
@ -473,7 +488,7 @@ final class StateScope : Scope
override:
ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
assert(name.str != "");
@ -546,7 +561,7 @@ final class RootScope : PackageScope
override ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
// Basic types are looked up here
@ -601,11 +616,6 @@ class PackageScope : Scope
bool isPackage() { return true; }
PackageScope getPackage()
{
return this;
}
char[] getPath(char[] file)
{
if(path == "") return file;
@ -636,7 +646,7 @@ class PackageScope : Scope
// Used internally from lookup()
private PackageScope makeSubPackage(char[] name)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
if(!isValidIdent(name))
@ -729,7 +739,7 @@ class PackageScope : Scope
override ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
// Look up packages
@ -796,7 +806,7 @@ abstract class VarScope : Scope
ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
assert(name.str != "");
@ -847,7 +857,7 @@ class FVScope : VarScope
override:
ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
assert(name.str != "");
@ -894,7 +904,7 @@ class TFVScope : FVScope
override:
ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
assert(name.str != "");
@ -947,7 +957,7 @@ final class EnumScope : SimplePropertyScope
{ tasm.push(cast(uint)et.entries.length); }
void enumVal()
{ tasm.getEnumValue(et.tIndex); }
{ tasm.getEnumValue(et); }
EnumType et;
@ -991,7 +1001,7 @@ final class EnumScope : SimplePropertyScope
// en.errorString. The enum index is on the stack, and
// getEnumValue supplies the enum type and the field
// index. The VM can find the correct field value from that.
tasm.getEnumValue(et.tIndex, fe);
tasm.getEnumValue(et, fe);
return;
}
@ -1081,6 +1091,12 @@ final class ClassScope : TFVScope
{
cls = cl;
super(last, cls.name.str);
// Make sure the class has a valid package
assert(cl.pack !is null);
// Import the package into this scope
registerImport(cl.pack);
}
bool isClass() { return true; }
@ -1098,7 +1114,7 @@ final class ClassScope : TFVScope
override ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
assert(name.str != "");
@ -1246,20 +1262,20 @@ class CodeScope : StackScope
LabelStatement getContinue(char[] name = "") { return parent.getContinue(name); }
}
// Experimental! Used to recompute the array expression for $. NOT a
// permanent solution.
// A special scope used inside arrays that allow the $ token to be
// used as a shorthand for the array length.
class ArrayScope : StackScope
{
private Expression expArray;
private ArrayOperator arrOp;
this(Scope last, Expression arr)
this(Scope last, ArrayOperator op)
{
super(last, "arrayscope");
expArray = arr;
arrOp = op;
}
bool isArray() { return true; }
Expression getArray() { return expArray; }
ArrayOperator getArray() { return arrOp; }
}
// Base class for scopes that have properties. The instances of this
@ -1286,7 +1302,7 @@ abstract class PropertyScope : Scope
ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
// Does this scope contain the property?
@ -1365,7 +1381,7 @@ class LoopScope : CodeScope
override ScopeLookup lookup(Token name, bool autoLoad=false)
{
debug(lookupTrace)
static if(traceLookups)
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
assert(name.str != "");

@ -42,6 +42,8 @@ import monster.vm.error;
import monster.vm.mclass;
import monster.vm.vm;
import monster.options;
alias Statement[] StateArray;
abstract class Statement : Block
@ -131,7 +133,7 @@ class ImportStatement : Statement
assert(t !is null);
mc = t.getClass(type.loc);
sc.registerImport(new ClassImpHolder(mc));
sc.registerImport(mc);
}
else if(type.isPackage)
{
@ -139,7 +141,7 @@ class ImportStatement : Statement
assert(t !is null);
auto psc = t.sc;
sc.registerImport(new PackageImpHolder(psc));
sc.registerImport(psc);
}
else
fail("Can only import from classes and packages", type.loc);
@ -1328,9 +1330,11 @@ class ReturnStatement : Statement
Expression exp;
Function *fn;
// Number of local variables to unwind from the stack. Calculated
// both in resolve and in compile.
// Number of local variables to unwind from the stack, and the
// number of parameters. (Counts number of ints / stack values, not
// actual number of variables.)
int locals;
int params;
static bool canParse(TokenArray toks)
{
@ -1380,7 +1384,7 @@ class ReturnStatement : Statement
assert(fn !is null, "return called outside a function scope");
// Get the size of all parameters
locals += fn.paramSize;
params = fn.paramSize;
// Next, we must check that the returned expression, if any, is
// of the right type.
@ -1408,11 +1412,11 @@ class ReturnStatement : Statement
assert(!fn.type.isVoid);
exp.eval();
// Return an expression
tasm.exit(locals, exp.type.getSize);
tasm.exit(params, locals, exp.type.getSize);
}
else
// Return without an expression
tasm.exit(locals);
tasm.exit(params, locals, 0);
}
}
@ -1560,7 +1564,11 @@ class CodeBlock : Statement
assert(!isState || sc.isStateCode());
foreach(int i, stats; contents)
stats.resolve(sc);
{
static if(traceResolve)
writefln("Resolving %s at %s", stats, stats.loc);
stats.resolve(sc);
}
// TODO: Check that state code contains at least one idle
// function call. We could do that through the scope.
@ -1576,7 +1584,7 @@ class CodeBlock : Statement
// If this is the main block at the state level, we must finish
// it with an exit instruction.
if(isState)
tasm.exit();
tasm.exit(0,0,0);
// Local variables are forbidden in any state block, no matter
// at what level they are

@ -27,6 +27,7 @@ module monster.compiler.tokenizer;
import std.string;
import std.stream;
import std.stdio;
import std.utf;
import monster.util.string : begins;
@ -84,6 +85,9 @@ enum TT
// Array length symbol
Dollar,
// 'at' sign, @
Alpha,
// Conditional expressions
IsEqual, NotEqual,
IsCaseEqual, IsCaseEqual2,
@ -124,23 +128,24 @@ enum TT
Last, // Tokens after this do not have a specific string
// associated with them.
StringLiteral, // "something"
StringLiteral, // "something" or 'something'
IntLiteral, // Anything that starts with a number, except
// floats
FloatLiteral, // Any number which contains a period symbol
CharLiteral, // 'a'
Identifier, // user-named identifier
EOF, // end of file
EMPTY // empty line (not stored)
}
struct Token
{
TT type;
char[] str;
Floc loc;
// Translated string literal (with resolved escape codes.)
dchar[] str32;
// True if this token was the first on its line.
bool newline;
@ -196,6 +201,8 @@ const char[][] tokenList =
TT.Dollar : "$",
TT.Alpha : "@",
TT.IsEqual : "==",
TT.NotEqual : "!=",
@ -280,7 +287,6 @@ const char[][] tokenList =
TT.StringLiteral : "string literal",
TT.IntLiteral : "integer literal",
TT.FloatLiteral : "floating point literal",
TT.CharLiteral : "character literal",
TT.Identifier : "identifier",
TT.EOF : "end of file",
TT.EMPTY : "empty line - you should never see this"
@ -534,31 +540,229 @@ class Tokenizer
if(line.begins("+/")) fail("Unexpected end of nested comment");
// String literals (multi-line literals not implemented yet)
if(line.begins("\""))
if(line.begins("\"") || // Standard string: "abc"
line.begins("r\"") || // Wysiwig string: r"c:\dir"
line.begins("\\\"") || // ditto: \"c:\dir"
line.begins("'") ||
line.begins("r'") || // Equivalent ' versions
line.begins("\\'"))
{
int len = 1;
bool found = false;
foreach(char ch; line[1..$])
{
len++;
// No support for escape sequences as of now
if(ch == '"')
{
found = true;
break;
}
}
if(!found) fail("Unterminated string literal '" ~line~"'");
return retToken(TT.StringLiteral, line[0..len].dup);
}
bool wysiwig = false;
// Character literals (not parsed yet, so escape sequences like
// '\n', '\'', or unicode stuff won't work.)
if(line[0] == '\'')
{
if(line.length < 3 || line[2] != '\'')
fail("Malformed character literal " ~line);
return retToken(TT.CharLiteral, line[0..3].dup);
// Quote character that terminates this string.
char quote;
char[] slice = line;
// Removes the first num chars from the line
void skip(int num)
{
assert(num <= line.length);
slice = slice[num..$];
}
// Parse the first quotation
if(slice[0] == '"' || slice[0] == '\'')
{
quote = slice[0];
skip(1);
}
else
{
// Check for wysiwig strings
if(slice[0] == '\\' || slice[0] == 'r')
wysiwig = true;
else assert(0);
quote = slice[1];
skip(2);
}
assert(quote == '"' || quote == '\'');
// This will store the result
dchar[] result;
// Stores a single character in the result string, and
// removes a given number of input characters.
void store(dchar ch, int slen)
{
result ~= ch;
skip(slen);
}
// Convert a given code into 'ch', if it is found.
void convert(char[] code, dchar ch)
{
if(slice.begins(code))
store(ch, code.length);
}
// Convert given escape character to 'res'
void escape(char ch, dchar res)
{
if(slice.length >= 2 &&
slice[0] == '\\' &&
slice[1] == ch)
store(res, 2);
}
// Interpret string
while(slice.length)
{
int startLen = slice.length;
// Convert "" to " (or '' to ' in single-quote strings)
convert(""~quote~quote, quote);
// Interpret backslash escape codes if we're not in
// wysiwig mode
if(!wysiwig)
{
escape('"', '"'); // \" == literal "
escape('\'', '\''); // \' == literal '
escape('\\', '\\'); // \\ == literal \
escape('a', 7); // \a == bell
escape('b', 8); // \b == backspace
escape('f', 12); // \f == feed form
escape('n', '\n'); // \n == newline
escape('r', '\r'); // \r == carriage return
escape('t', '\t'); // \t == tab
escape('v', '\v'); // \v == vertical tab
escape('e', 27); // \e == ANSI escape
// Check for numerical escapes
// If either of these aren't met, this isn't a valid
// escape code.
if(slice.length < 2 ||
slice[0] != '\\')
goto nocode;
// Checks and converts the digits in slice[] into a
// character.
void convertNumber(int skp, int maxLen, int base,
char[] pattern, char[] name)
{
assert(base <= 16);
// Skip backslash and other leading characters
skip(skp);
int len; // Number of digits found
uint result = 0;
for(len=0; len<maxLen; len++)
{
if(slice.length <= len) break;
char digit = slice[len];
// Does the digit qualify?
if(!inPattern(digit, pattern))
break;
// Multiply up the existing number to
// make room for the digit.
result *= base;
// Convert single digit to a number
if(digit >= '0' && digit <= '9')
digit -= '0';
else if(digit >= 'a' && digit <= 'z')
digit -= 'a' - 10;
else if(digit >= 'A' && digit <= 'Z')
digit -= 'A' - 10;
assert(digit >= 0 && digit < base);
// Add inn the digit
result += digit;
}
if(len > 0)
{
// We got something. Convert it and store
// it.
store(result, len);
}
else
fail("Invalid " ~ name ~ " escape code");
}
const Dec = "0-9";
const Oct = "0-7";
const Hex = "0-9a-fA-F";
// Octal escapes: \0N, \0NN or \0NNN where N are
// octal digits (0-7). Also accepts \o instead of
// \0.
if(slice[1] == '0' || slice[1] == 'o')
convertNumber(2, 3, 8, Oct, "octal");
// Decimal escapes: \N \NN and \NNN, where N are
// digits and the first digit is not zero.
else if(inPattern(slice[1], Dec))
convertNumber(1, 3, 10, Dec, "decimal");
// Hex escape codes: \xXX where X are hex digits
else if(slice[1] == 'x')
convertNumber(2, 2, 16, Hex, "hex");
// Unicode escape codes:
// \uXXXX
else if(slice[1] == 'u')
convertNumber(2, 4, 16, Hex, "Unicode hex");
// \UXXXXXXXX
else if(slice[1] == 'U')
convertNumber(2, 8, 16, Hex, "Unicode hex");
}
nocode:
// If something was converted this round, start again
// from the top.
if(startLen != slice.length)
continue;
assert(slice.length > 0);
// Nothing was done. Are we at the end of the string?
if(slice[0] == quote)
{
skip(1);
found = true;
break;
}
// Unhandled escape code?
if(slice[0] == '\\' && !wysiwig)
{
if(slice.length == 0)
// Just a single \ at the end of the line
fail("Multiline string literals not implemented");
else
fail("Unhandled escape code: \\" ~ slice[1]);
}
// Nope. It's just a normal character. Decode it from
// UTF8.
size_t clen = 0;
dchar cres;
cres = decode(slice,clen);
store(cres, clen);
}
if(!found) fail("Unterminated string literal '" ~line~ "'");
// Set 'slice' to contain the original string
slice = line[0..(line.length-slice.length)];
// Set up the token
auto t = retToken(TT.StringLiteral, slice.dup);
t.str32 = result;
return t;
}
// Numerical literals - if it starts with a number, we accept

@ -45,28 +45,6 @@ import std.stdio;
import std.utf;
import std.string;
/*
List of all type classes:
Type (abstract)
InternalType (abstract)
ReplacerType (abstract)
NullType (null expression)
BasicType (covers int, char, bool, float, void)
ObjectType
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)
GenericType (var)
MetaType (type of type expressions, like writeln(int);)
*/
// A class that represents a type. The Type class is abstract, and the
// different types are actually handled by various subclasses of Type
// (see below.) The static function identify() is used to figure out
@ -81,18 +59,23 @@ abstract class Type : Block
// tokens.)
static bool canParseRem(ref TokenArray toks)
{
// This function is a bit messy unfortunately. We should improve
// the process so we can clean it up. Preferably we should
// rewrite the entire parser.
// We allow typeof(expression) as a type
if(isNext(toks, TT.Typeof))
{
reqNext(toks, TT.LeftParen);
Expression.identify(toks); // Possibly a bit wasteful...
reqNext(toks, TT.RightParen);
skipParens(toks);
return true;
}
// The 'var' keyword can be used as a type
if(isNext(toks, TT.Var)) return true;
if(FuncRefType.canParseRem(toks))
return true;
// Allow typename
if(!isNext(toks, TT.Identifier)) return false;
@ -133,6 +116,7 @@ abstract class Type : Block
else if(UserType.canParse(toks)) t = new UserType();
else if(GenericType.canParse(toks)) t = new GenericType();
else if(TypeofType.canParse(toks)) t = new TypeofType();
else if(FuncRefType.canParse(toks)) t = new FuncRefType();
else fail("Cannot parse " ~ toks[0].str ~ " as a type", toks[0].loc);
// Parse the actual tokens with our new and shiny object.
@ -178,6 +162,7 @@ abstract class Type : Block
{
tIndex = typeList.length;
typeList[tIndex] = this;
name = "UNNAMED TYPE";
}
// Used for easy checking
@ -200,7 +185,8 @@ abstract class Type : Block
bool isPackage() { return false; }
bool isStruct() { return false; }
bool isEnum() { return false; }
bool isFunc() { return false; }
bool isIntFunc() { return false; }
bool isFuncRef() { return false; }
bool isReplacer() { return false; }
@ -218,9 +204,7 @@ abstract class Type : Block
// Is this a legal type for variables? If this is false, you cannot
// create variables of this type, neither directly or indirectly
// (through automatic type inference.) This isn't currently used
// anywhere, but we will need it later when we implement automatic
// types.
// (through automatic type inference.)
bool isLegal() { return true; }
// Get base type (used for arrays and meta-types)
@ -239,11 +223,11 @@ abstract class Type : Block
// returns the class scope, where func is resolved.
// array.length -> getMemberScope called for ArrayType, returns a
// special scope that contains the 'length' property
Scope getMemberScope()
{
// Returning null means this type does not have any members.
return null;
}
// Returning null means that this type does not have any members.
Scope getMemberScope()
// By default we return a minimal property scope (containing
// .sizeof etc)
{ return GenericProperties.singleton; }
// Validate that this type actually exists. This is used to make
// sure that all forward references are resolved.
@ -365,7 +349,7 @@ abstract class Type : Block
assert(res.length == orig.type.getSize);
if(orig.type.canCastCTime(this))
res = orig.type.doCastCTime(res, this);
res = orig.type.doCastCTime(res, this, orig.loc);
else
fail(format("Cannot cast %s of type %s to %s of type %s (at compile time)",
orig.toString, orig.typeString,
@ -383,8 +367,10 @@ 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.
// Can the type be explicitly cast? This function does not have to
// handle cases where canCastTo is true. You only need to use it in
// cases where explicit casting is allowed but implicit is not, such
// as casting from float to int.
bool canCastToExplicit(Type to)
{
return false;
@ -403,14 +389,14 @@ abstract class Type : Block
// Do the cast in the assembler. Must handle both implicit and
// explicit casts.
void evalCastTo(Type to)
void evalCastTo(Type to, Floc lc)
{
assert(0, "evalCastTo not implemented for type " ~ toString);
}
// Do the cast in the compiler. Must handle both implicit and
// explicit casts.
int[] doCastCTime(int[] data, Type to)
int[] doCastCTime(int[] data, Type to, Floc lc)
{
assert(0, "doCastCTime not implemented for type " ~ toString);
}
@ -496,6 +482,8 @@ abstract class Type : Block
// for variables or values, neither directly nor indirectly.
abstract class InternalType : Type
{
override:
Scope getMemberScope() { return null; }
final:
bool isLegal() { return false; }
int[] defaultInit() {assert(0, name);}
@ -532,23 +520,22 @@ class NullType : InternalType
bool canCastTo(Type to)
{
return to.isArray || to.isObject || to.isEnum;
return to.isArray || to.isObject || to.isEnum || to.isFuncRef;
}
void evalCastTo(Type to)
void evalCastTo(Type to, Floc lc)
{
assert(to.getSize == 1);
// The value of null is always zero. There is no value on the
// stack to convert, so just push it.
tasm.push(0);
// Always evaluable at compile time - there is no runtime value
// to convert.
tasm.pushArray(doCastCTime(null, to, lc));
}
int[] doCastCTime(int[] data, Type to)
int[] doCastCTime(int[] data, Type to, Floc lc)
{
assert(to.getSize == 1);
// For all the types we currently support, 'null' is always
// equal to the type's default initializer.
assert(data.length == 0);
return [0];
return to.defaultInit();
}
}
@ -729,7 +716,7 @@ class BasicType : Type
return false;
}
void evalCastTo(Type to)
void evalCastTo(Type to, Floc lc)
{
assert(this != to);
assert(!isVoid);
@ -766,11 +753,11 @@ class BasicType : Type
// Create an array from one element on the stack
if(isChar) tasm.popToArray(1, 1);
else
tasm.castToString(tIndex);
tasm.castToString(this);
}
else
fail("Conversion " ~ toString ~ " to " ~ to.toString ~
" not implemented.");
" not implemented.", lc);
}
char[] valToString(int[] data)
@ -781,7 +768,7 @@ class BasicType : Type
return getHolder().getString(data);
}
int[] doCastCTime(int[] data, Type to)
int[] doCastCTime(int[] data, Type to, Floc lc)
{
assert(this != to);
assert(!isVoid);
@ -871,7 +858,7 @@ class BasicType : Type
}
else
fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~
" not implemented.");
" not implemented.", lc);
assert(toData.length == toSize);
assert(toData.ptr !is data.ptr);
@ -886,6 +873,9 @@ class BasicType : Type
if( isLong || isUlong || isDouble )
return 2;
if( isVoid )
return 0;
assert(0, "getSize() does not handle type '" ~ name ~ "'");
}
@ -905,7 +895,7 @@ class BasicType : Type
else if(isLong || isUlong) data = makeData!(long)(0);
// Chars default to an illegal utf-32 value
else if(isChar) data = makeData!(int)(0x0000FFFF);
// Floats default to not a number
// Floats default to NaN
else if(isFloat) data = makeData!(float)(float.nan);
else if(isDouble) data = makeData!(double)(double.nan);
else
@ -1033,7 +1023,7 @@ class ObjectType : Type
}
}
void evalCastTo(Type to)
void evalCastTo(Type to, Floc lc)
{
assert(clsIndex != 0);
assert(canCastTo(to) || canCastToExplicit(to));
@ -1059,7 +1049,7 @@ class ObjectType : Type
}
assert(to.isString);
tasm.castToString(tIndex);
tasm.castToString(this);
}
// Members of objects are resolved in the class scope.
@ -1113,6 +1103,12 @@ class ArrayType : Type
loc = base.loc;
}
ArrayRef *getArray(int[] data)
{
assert(data.length == 1);
return monster.vm.arrays.arrays.getRef(cast(AIndex)data[0]);
}
override:
void validate(Floc loc) { assert(base !is null); base.validate(loc); }
int arrays() { return base.arrays() + 1; }
@ -1127,26 +1123,125 @@ class ArrayType : Type
// All arrays can be cast to string
bool canCastTo(Type to)
{ return to.isString; }
{
// Strings can be cast to char, but the conversion is only valid
// if the string is one char long.
if(isString) return to.isChar;
void evalCastTo(Type to)
// Conversion to string is always allowed, and overrides other
// array conversions. TODO: This will change later when we get
// the generic 'var' type. Then implicit casting to string will
// be unnecessary.
if(to.isString)
return true;
if(to.isArray)
return getBase().canCastTo(to.getBase());
return false;
}
void evalCastTo(Type to, Floc lc)
{
assert(to.isString);
tasm.castToString(tIndex);
if(isString)
{
assert(to.isChar);
tasm.castStrToChar();
return;
}
if(to.isString)
{
tasm.castToString(this);
return;
}
if(to.isArray && getBase().canCastTo(to.getBase()))
fail("Casting arrays at runtime not implemented yet", lc);
assert(0);
}
int[] doCastCTime(int[] data, Type to)
int[] doCastCTime(int[] data, Type to, Floc lc)
{
assert(to.isString);
return [valToStringIndex(data)];
if(isString)
{
assert(to.isChar);
// When casting from string to char, we pick out the first
// (and only) character from the string.
auto arf = getArray(data);
// FIXME: These error messages don't produce a file/line
// number. Using our own loc doesn't work. I think we need
// to pass it on to doCastCTime.
if(arf.carr.length == 0)
fail("Cannot cast empty string to 'char'", lc);
assert(arf.elemSize == 1);
if(arf.carr.length > 1)
fail("Cannot cast string " ~ valToString(data) ~
" to 'char': too long", lc);
return [arf.iarr[0]];
}
if(to.isArray && getBase().canCastTo(to.getBase()))
{
auto arf = getArray(data);
assert(arf.iarr.length > 0, "shouldn't need to cast an empty array");
// The types
Type oldt = getBase();
Type newt = to.getBase();
assert(oldt.canCastCTime(newt),
"oops, cannot cast at compile time");
// Set up the source and destination array data
int len = arf.length();
int olds = oldt.getSize();
int news = newt.getSize();
int[] src = arf.iarr;
int[] dst = new int[len * news];
assert(src.length == len * olds);
for(int i=0; i<len; i++)
{
int[] slice = src[i*olds..(i+1)*olds];
slice = oldt.doCastCTime(slice, newt, lc);
assert(slice.length == news);
dst[i*news..(i+1)*news] = slice;
}
// Create the array index
bool wasConst = arf.isConst();
arf = monster.vm.arrays.arrays.create(dst, news);
assert(arf.length() == len);
// Copy the const setting from the original array
if(wasConst)
arf.flags.set(AFlags.Const);
// Return the index
return [arf.getIndex()];
}
if(to.isString)
return [valToStringIndex(data)];
assert(0);
}
char[] valToString(int[] data)
{
assert(data.length == 1);
// Get the array reference
ArrayRef *arf = monster.vm.arrays.arrays.getRef(cast(AIndex)data[0]);
ArrayRef *arf = getArray(data);
// Empty array?
if(arf.iarr.length == 0) return "[]";
@ -1184,9 +1279,137 @@ 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
// Type used for function references. Variables of this type consists
// of a object index followed by a global function index.
class FuncRefType : Type
{
// Function signature information
Type retType;
Type params[];
bool isConst[];
bool isVararg;
// For parsed types
this() {}
this(IntFuncType f)
{
auto fn = f.func;
retType = fn.type;
isVararg = fn.isVararg();
params.length = fn.params.length;
isConst.length = params.length;
foreach(i, v; fn.params)
{
params[i] = v.type;
isConst[i] = v.isConst;
}
createName();
}
void createName()
{
name = "function";
if(!retType.isVoid)
name ~= " " ~ retType.toString();
name ~= "(";
foreach(i, v; params)
{
if(i > 0)
name ~= ",";
name ~= v.toString();
if(isVararg && i == params.length-1)
name ~= "...";
}
name ~= ")";
}
static bool canParseRem(ref TokenArray toks)
{
if(!isNext(toks, TT.Function))
return false;
// Return type?
if(toks.length && toks[0].type != TT.LeftParen)
Type.canParseRem(toks);
skipParens(toks);
return true;
}
// Same as the above but it doesn't change the array (no 'ref'
// parameter)
static bool canParse(TokenArray toks)
{ return canParseRem(toks); }
// Calculate the stack imprint
int getImprint()
{
assert(retType !is null);
int res = retType.getSize();
foreach(p; params)
res -= p.getSize();
return res;
}
override:
void parse(ref TokenArray toks)
{
reqNext(toks, TT.Function);
if(!isNext(toks, TT.LeftParen))
{
retType = Type.identify(toks);
reqNext(toks, TT.LeftParen);
}
else
retType = BasicType.getVoid();
if(!isNext(toks, TT.RightParen))
{
do
{
params ~= Type.identify(toks);
}
while(isNext(toks, TT.Comma));
reqNext(toks, TT.RightParen);
}
}
void resolve(Scope sc)
{
retType.resolve(sc);
foreach(p; params)
p.resolve(sc);
createName();
}
void validate(Floc f)
{
retType.validate(f);
foreach(p; params)
p.validate(f);
}
int getSize() { return 2; }
bool isFuncRef() { return true; }
int[] defaultInit() { return [0, -1]; }
Scope getMemberScope() { return FuncRefProperties.singleton; }
}
// Type used for internal references to functions. This is NOT the
// same as the type used for function references ("pointers") - see
// FuncRefType for that.
class IntFuncType : InternalType
{
Function *func;
bool isMember;
@ -1201,7 +1424,7 @@ class FunctionType : InternalType
}
override:
bool isFunc() { return true; }
bool isIntFunc() { return true; }
}
class EnumType : Type
@ -1361,12 +1584,12 @@ class EnumType : Type
return false;
}
void evalCastTo(Type to)
void evalCastTo(Type to, Floc lc)
{
// Convert the enum name to a string
if(to.isString)
{
tasm.castToString(tIndex);
tasm.castToString(this);
return;
}
@ -1374,10 +1597,10 @@ class EnumType : Type
if(lng.canCastOrEqual(to))
{
// Get the value
tasm.getEnumValue(tIndex);
tasm.getEnumValue(this);
// Cast it if necessary
if(to != lng)
lng.evalCastTo(to);
lng.evalCastTo(to, lc);
return;
}
@ -1386,11 +1609,11 @@ class EnumType : Type
if(f.type.canCastOrEqual(to))
{
// Get the field value from the enum
tasm.getEnumValue(tIndex, i);
tasm.getEnumValue(this, i);
// If the type doesn't match exactly, convert it.
if(f.type != to)
f.type.evalCastTo(to);
f.type.evalCastTo(to, lc);
return;
}
@ -1398,7 +1621,7 @@ class EnumType : Type
assert(0);
}
int[] doCastCTime(int[] data, Type to)
int[] doCastCTime(int[] data, Type to, Floc lc)
{
if(to.isString)
return [valToStringIndex(data)];
@ -1406,7 +1629,7 @@ class EnumType : Type
// 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.
// later. TODO.
assert(0, "finished, but not tested");
// Get the enum index
@ -1415,7 +1638,7 @@ class EnumType : Type
// Check that we were not given a zero index
if(v-- == 0)
fail("Cannot get value of fields from an empty Enum variable.");
fail("Cannot get value of fields from an empty Enum variable.", lc);
// Get the entry
assert(v >= 0 && v < entries.length);
@ -1428,7 +1651,7 @@ class EnumType : Type
int[] val = (cast(int*)&ent.value)[0..2];
// Cast it if necessary
if(to != lng)
val = lng.doCastCTime(val, to);
val = lng.doCastCTime(val, to, lc);
return val;
}
@ -1442,7 +1665,7 @@ class EnumType : Type
// If the type doesn't match exactly, convert it.
if(f.type != to)
val = f.type.doCastCTime(val, to);
val = f.type.doCastCTime(val, to, lc);
return val;
}
@ -1538,8 +1761,6 @@ class StructType : Type
return defInit;
}
Scope getMemberScope()
{ return GenericProperties.singleton; }
// Scope getMemberScope() { return sc; }
}
@ -1650,7 +1871,11 @@ class UserType : ReplacerType
// Allow packages used as type names in some situations
else if(sl.isPackage)
realType = sl.sc.getPackage().type;
{
auto psc = cast(PackageScope)sl.sc;
assert(psc !is null);
realType = psc.type;
}
// Was anything found at all?
else if(!sl.isNone)
@ -1764,7 +1989,7 @@ class MetaType : InternalType
int[] cache;
int[] doCastCTime(int[] data, Type to)
int[] doCastCTime(int[] data, Type to, Floc lc)
{
assert(to.isString);
@ -1774,7 +1999,7 @@ class MetaType : InternalType
return cache;
}
void evalCastTo(Type to)
void evalCastTo(Type to, Floc lc)
{
assert(to.isString);

@ -41,6 +41,8 @@ import monster.vm.mclass;
import monster.vm.error;
import monster.vm.vm;
import monster.options;
enum VarType
{
Class,
@ -325,7 +327,15 @@ class VarDeclaration : Block
// Illegal types are illegal
if(!var.type.isLegal)
fail("Cannot create variables of type '" ~ var.type.toString ~ "'", loc);
{
char[] msg = "Cannot create variables of type '" ~
var.type.toString ~ "'";
if(var.type.isIntFunc)
msg ~= "\nPerhaps you meant @func ? Use @ to create function references.";
fail(msg, loc);
}
if(!allowConst && var.isConst)
fail("'const' is not allowed here", loc);
@ -704,7 +714,7 @@ class MemberExpr : Expression
else if(look.isFunc)
{
// The Function* is stored in the lookup variable
type = new FunctionType(look.func, isMember);
type = new IntFuncType(look.func, isMember);
vtype = VType.Function;
// TODO: Make the scope set the type for us. In fact, the
@ -793,6 +803,9 @@ class MemberExpr : Expression
}
body
{
static if(traceResolve)
writefln("Resolving member expression %s", this);
// Look for reserved names first.
if(name.str == "__STACK__")
{
@ -872,7 +885,9 @@ class MemberExpr : Expression
if(isType) return;
if(type.isFunc) return;
// If we're a function name, don't push anything. Everything is
// handled through the type system.
if(type.isIntFunc) return;
setLine();
@ -1058,17 +1073,12 @@ class VarDeclStatement : Statement
vars ~= varDec;
loc = varDec.var.name.loc;
int arr = varDec.arrays();
// Are there more?
while(isNext(toks, TT.Comma))
{
// Read a variable, but with the same type as the last
varDec = new VarDeclaration(varDec.var.type);
varDec.parse(toks);
if(varDec.arrays() != arr)
fail("Multiple declarations must have same type",
varDec.var.name.loc);
vars ~= varDec;
}
@ -1076,10 +1086,6 @@ class VarDeclStatement : Statement
reqNext(toks, TT.Semicolon);
else
reqSep(toks);
/*
if(!isNext(toks, TT.Semicolon))
fail("Declaration statement expected ;", toks);
*/
}
char[] toString()
@ -1097,6 +1103,15 @@ class VarDeclStatement : Statement
// Add variables to the scope.
foreach(vd; vars)
vd.resolve(sc);
// Check that all the vars are in fact the same type
assert(vars.length >= 1);
Type t = vars[0].var.type;
foreach(vd; vars[1..$])
{
if(t != vd.var.type)
fail("Multiple declarations must have the same type", loc);
}
}
// Validate types

@ -134,7 +134,6 @@ class Console
void putln(char[] str) { put(str, true); }
private:
Statement[] parse(TokenArray toks, Scope sc)
{
Statement b;
@ -307,32 +306,52 @@ class Console
// Phase IV, compile
tasm.newFunc();
ExprStatement es;
// Expression to print, if any
Expression printExp = null;
// Is it an special console statement?
// Is it a special console statement?
auto cs = cast(ConsoleStatement)st;
if(cs !is null)
{
// Yes. Is the client an expression?
es = cast(ExprStatement)cs.client;
// Get the client expression, if any.
ExprStatement es = cast(ExprStatement)cs.client;
if(es !is null)
{
// Ok. 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;
// It's a normal expression
if(es.right is null)
printExp = es.left;
}
// Not an expression, maybe a function?
else if(cs.func !is null)
{
// Yup, store it
printExp = cs.func;
}
}
// Ok, we got an expression. But is the type usable?
if(printExp !is null)
{
auto tp = printExp.type;
auto strt = ArrayType.getString();
assert(tp !is null);
assert(strt !is null);
if(tp == strt || tp.canCastTo(strt))
{
// Yup, it is! Cast the expression to string.
strt.typeCast(printExp, "console output");
printExp.eval();
}
else
// Type isn't usable, so set printExp to null to flag
// this.
printExp = null;
}
// No expression is being used, so compile the statement
// normally.
if(es is null)
if(printExp is null)
// No expression is being used, so compile the statement
// normally.
st.compile();
// Gather all the statements into one function and get the
@ -344,7 +363,7 @@ class Console
fn.call(obj);
// Finally, get the expression result, if any, and print it.
if(es !is null)
if(printExp !is null)
putln(stack.popString8());
// In the case of new a variable declaration, we have to
@ -467,7 +486,7 @@ class Console
// Statement that handles variable declarations, function calls and
// expression statements in consoles. Since these are gramatically
// similar, we need the type to determine which
// similar, we need the type to determine which it is.
class ConsoleStatement : Statement
{
// Used for variables and expression statements
@ -497,7 +516,7 @@ class ConsoleStatement : Statement
}
// Function?
else if(type.isFunc)
else if(type.isIntFunc)
func = new FunctionCallExpr(first, toks, true);
// It's an expression statement

@ -1,3 +1,8 @@
/*
NOTE: This file is not used - it is here just for reference. The
real module is defined internally in io.d.
*/
singleton thread;
// Used to kill or pause our own or other threads.
@ -12,31 +17,23 @@ native bool isDead();
bool isAlive() { return !isDead(); }
// Create a new (paused) thread for a given function
native thread create(char[] name);
native thread create(function() f);
// Schedule a (paused) thread to run the next frame
native restart();
// Call a (paused) thread directly - returns when the thread exits or
// calls an idle function.
idle resume();
idle call();
// Wait for a thread to finish. Will not return until the thread is
// dead.
idle wait();
// Call a function as a thread
thread call(char[] name)
{
var t = create(name);
t.resume();
return t;
}
// Start a function as a thread in the background
thread start(char[] name)
thread start(function() f)
{
var t = create(name);
var t = create(f);
t.restart();
return t;
}

@ -32,6 +32,7 @@ import monster.vm.mobject;
import monster.vm.idlefunction;
import monster.vm.thread;
import monster.vm.mclass;
import monster.compiler.functions;
import std.stdio;
const char[] moduleDef =
@ -49,7 +50,7 @@ native bool isDead();
bool isAlive() { return !isDead(); }
// Create a new (paused) thread for a given function
native thread create(char[] name);
native thread create(function() f);
// Schedule a (paused) thread to run the next frame
native restart();
@ -63,20 +64,19 @@ idle call();
idle wait();
// Start a function as a thread in the background
thread start(char[] name)
thread start(function() f)
{
var t = create(name);
var t = create(f);
t.restart();
return t;
}
"; //"
}"; //"
/*
The char[] name stuff above will of course be replaced with real
function pointers once those are done. When closures are done we
will also add:
function() wrap(function f())
function() wrap(function() f)
{
var t = create(f);
return { t.call(); }
@ -154,20 +154,8 @@ void create()
if(params.obj !is trdSing)
fail("Can only use create() on the global thread object.");
char[] name = stack.popString8();
assert(cthread !is null);
// This is a dirty hack that's only needed because we haven't
// implemented function pointers yet. Gets the caller object.
assert(cthread.fstack.list.length >= 2);
auto nd = cthread.fstack.cur;
nd = cthread.fstack.list.getNext(nd);
if(nd.getCls() is _threadClass)
nd = cthread.fstack.list.getNext(nd);
auto mo = nd.obj;
auto trd = mo.thread(name);
auto fn = stack.popFuncRef();
auto trd = fn.getObject().thread(fn.getFunction());
stack.pushObject(createObj(trd));
}

@ -123,6 +123,38 @@ bool logFStack = true;
// If true, log when threads are put into the background/forground.
bool logThreads = true;
// The following options control more low-level debugging. Most of
// these will enable tracing of various internal function calls and
// parameters directly to stdout.
// Trace the thread system
bool traceThreads = false;
// Trace scope lookups
bool traceLookups = false;
// Trace resolve() calls
bool traceResolve = false;
// Print completed output from the assembler
bool printAsmOutput = false;
// Trace VM opcode execution
bool traceVMOps = false;
/*********************************************************
Optimization options
*********************************************************/
// Enable assembler optimizations
bool optimizeAsm = true;
/*********************************************************

@ -123,6 +123,38 @@ bool logFStack = true;
// If true, log when threads are put into the background/forground.
bool logThreads = true;
// The following options control more low-level debugging. Most of
// these will enable tracing of various internal function calls and
// parameters directly to stdout.
// Trace the thread system
bool traceThreads = false;
// Trace scope lookups
bool traceLookups = false;
// Trace resolve() calls
bool traceResolve = false;
// Print completed output from the assembler
bool printAsmOutput = false;
// Trace VM opcode execution
bool traceVMOps = false;
/*********************************************************
Optimization options
*********************************************************/
// Enable assembler optimizations
bool optimizeAsm = true;
/*********************************************************

@ -1,6 +1,6 @@
#!/bin/bash
svn export ../../../monster/trunk/monster/ . --force
svn export https://monster-script.svn.sourceforge.net/svnroot/monster-script/trunk/monster/ . --force
rm -r minibos vm/c_api.d
for a in $(find -iname \*.d); do
@ -13,3 +13,4 @@ svn st
diff options.openmw options.d || $EDITOR options.d
mv options.openmw options.openmw_last
cp options.d options.openmw

@ -333,7 +333,7 @@ struct HashTable(Key, Value, Alloc = GCAlloc, Hash = DefHash,
// Get a pointer to value of given key, or insert a new. Useful for
// large value types when you want to avoid copying the value around
// needlessly. Also useful if you want to do in-place
// editing. Returns true if a new value was inserted.
// editing. Returns true if a value was inserted.
bool insertEdit(Key k, out Value *ptr)
{
Node *p;

@ -19,6 +19,7 @@
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module monster.util.freelist;
@ -182,6 +183,16 @@ struct Buffers
BufferList!(256) b256;
BufferList!(768) b768;
/*
static this()
{
writefln("64: ints=%s bytes=%s", b64.ints, b64.bytes);
writefln("128: ints=%s bytes=%s", b128.ints, b128.bytes);
writefln("256: ints=%s bytes=%s", b256.ints, b256.bytes);
writefln("768: ints=%s bytes=%s", b768.ints, b768.bytes);
}
*/
int[] getInt(uint size)
{
if(size <= b64.ints) return b64.getInt(size);

@ -24,7 +24,10 @@
module monster.util.growarray;
// Array that grows without reallocations.
// Array that grows without reallocations. It does this by
// block-allocation and by stringing several blocks together to work
// like one single array. Elements are not guaranteed to be stored
// continuously in memory.
struct GrowArray(T)
{
const defSize = 128;

@ -28,7 +28,6 @@ module monster.util.list;
// through the entire list on every insert and remove, so they are
// very slow for large lists. But they are very handy bug catchers
// when doing a little dirty list hacking.
// debug=slowcheck;
private import std.c.stdlib;
@ -222,8 +221,7 @@ struct LinkedList(Value, alias Alloc = GCAlloc)
Iterator insertNode(Node *p)
in
{
//debug(slowcheck)
assert(!hasIterator(p), "inserNode: Node is already in the list");
assert(!hasIterator(p), "inserNode: Node is already in the list");
}
body
{

@ -68,16 +68,21 @@ struct Dbg
void init()
{
static if(defaultLogToStdout)
dbgOut = dout;
enableStdout();
}
void log(char[] msg)
void enableStdout()
{
dbgOut = dout;
}
void log(char[] msg, Thread *tr = null)
{
if(dbgOut !is null)
{
int logLevel = getFStackLevel();
int extLevel = getExtLevel();
int trdIndex = getThreadIndex();
int trdIndex = getThreadIndex(tr);
char[] str = format("(trd=%s,ext=%s,lev=%s)",
trdIndex,
@ -134,9 +139,11 @@ struct Dbg
return 0;
}
int getThreadIndex()
int getThreadIndex(Thread *tr = null)
{
if(cthread !is null)
if(tr !is null)
return tr.getIndex();
else if(cthread !is null)
return cthread.getIndex();
else return 0;
}

@ -65,11 +65,6 @@ struct StackPoint
MonsterObject *obj; // "this"-pointer for the function
// Could have an afterStack to check that the function has the
// correct imprint (corresponding to an imprint-var in Function.)
int *frame; // Stack frame for this function
// Get the class owning the function
MonsterClass getCls()
{
@ -185,15 +180,22 @@ struct FunctionStack
else assert(list.length == 0);
}
// Set the global stack frame pointer to correspond to the current
// entry in the fstack. Must be called when putting a thread in the
// foreground.
void restoreFrame()
// Get the thread associated with this function stack. Depends on
// the fact that we are the first member of the Thread, so our
// pointers are the same. If this changes, you MUST change this
// function as well.
Thread *getThread()
{
if(cur !is null)
stack.setFrame(cur.frame);
else
stack.setFrame(null);
return cast(Thread*)this;
}
// Used for debug logging
void log(char[] msg)
{
static if(logFStack)
{
dbg.log(msg, getThread());
}
}
void killAll()
@ -236,7 +238,6 @@ struct FunctionStack
// Puts a new node at the beginning of the list
cur = list.getNew();
cur.obj = obj;
cur.frame = stack.setFrame();
}
// Set the stack point up as a function. Allows obj to be null.
@ -261,7 +262,7 @@ struct FunctionStack
static if(logFStack)
{
dbg.log("+++ " ~ cur.toString());
log("+++ " ~ cur.toString());
}
}
@ -285,7 +286,7 @@ struct FunctionStack
static if(logFStack)
{
dbg.log("+++ " ~ cur.toString());
log("+++ " ~ cur.toString());
}
}
@ -300,7 +301,7 @@ struct FunctionStack
static if(logFStack)
{
dbg.log("+++ " ~ cur.toString());
log("+++ " ~ cur.toString());
}
}
@ -317,7 +318,7 @@ struct FunctionStack
static if(logFStack)
{
dbg.log("+++ " ~ cur.toString());
log("+++ " ~ cur.toString());
}
}
@ -336,7 +337,7 @@ struct FunctionStack
static if(logFStack)
{
dbg.log(" -- " ~ cur.toString());
log(" -- " ~ cur.toString());
}
// Remove the topmost node from the list, and set cur.
@ -344,13 +345,11 @@ struct FunctionStack
list.remove(cur);
cur = list.getHead();
restoreFrame();
assert(list.length != 0 || cur is null);
static if(logFStack)
{
dbg.log("");
log("");
}
}

@ -30,6 +30,7 @@ import monster.compiler.properties;
import monster.compiler.scopes;
import monster.vm.thread;
import monster.vm.stack;
import monster.vm.mclass;
import monster.vm.arrays;
import monster.vm.vm;
import monster.vm.dbg;
@ -125,9 +126,8 @@ void doMonsterInit()
// Initialize the debugger structure
dbg.init();
// Initialize compiler constructs
// Initialize tokenizer
initTokenizer();
initProperties();
// initScope depends on doVMInit setting vm.vfs
vm.doVMInit();
@ -138,6 +138,12 @@ void doMonsterInit()
stack.init();
arrays.initialize();
// Compiles the 'Object' class
MonsterClass.initialize();
// Depends on 'Object'
initProperties();
// Load modules
static if(loadModules)
initAllModules();

@ -85,11 +85,37 @@ final class MonsterClass
return
Block.isNext(tokens, TT.Class) ||
Block.isNext(tokens, TT.Singleton) ||
Block.isNext(tokens, TT.Abstract) ||
Block.isNext(tokens, TT.Module);
}
static uint getTotalObjects() { return allObjects.length; }
// Sets up the Object class, which is the parent of all other
// classes.
static void initialize()
{
assert(baseObject is null,
"MonsterClass.initialize() run more than once");
assert(global !is null);
// The Object class is empty
baseObject = vm.loadString("abstract class Object;", "Object");
assert(baseObject !is null);
// Set up the class. createScope() etc will make a special case
// for us when it detects that it is running on baseObject.
baseObject.requireCompile();
}
private static MonsterClass baseObject;
// Returns the class called 'Object'
static MonsterClass getObject()
{
return baseObject;
}
final:
/*******************************************************
@ -110,8 +136,7 @@ final class MonsterClass
PackageScope pack;
ObjectType objType; // Type for objects of this class
Type classType; // Type for class references to this class (not
// implemented yet)
Type classType; // Type for class references to this class
// Pointer to the C++ wrapper class, if any. Could be used for other
// wrapper languages at well, but only one at a time.
@ -126,34 +151,6 @@ final class MonsterClass
public:
bool isParsed() { return flags.has(CFlags.Parsed); }
bool isScoped() { return flags.has(CFlags.Scoped); }
bool isResolved() { return flags.has(CFlags.Resolved); }
bool isCompiled() { return flags.has(CFlags.Compiled); }
bool isSingleton() { return flags.has(CFlags.Singleton); }
bool isModule() { return flags.has(CFlags.Module); }
bool isAbstract() { return flags.has(CFlags.Abstract); }
// Call whenever you require this function to have its scope in
// order. If the scope is missing, this will call createScope if
// possible, or fail if the class has not been loaded.
void requireScope()
{
if(isScoped) return;
if(!isParsed)
fail("Cannot use class '" ~ name.str ~
"': not found or forward reference",
name.loc);
createScope();
}
// Called whenever we need a completely compiled class, for example
// when creating an object. Compiles the class if it isn't done
// already.
void requireCompile() { if(!isCompiled) compileBody(); }
// Create a class belonging to the given package scope. Do not call
// this yourself, use vm.load* to load classes.
this(PackageScope psc = null)
@ -224,7 +221,8 @@ final class MonsterClass
return virtuals[ctree][findex];
}
// Find a given callable function, virtually.
// Find a given callable function, looking up parent classes if
// necessary.
Function *findFunction(char[] name)
{
requireScope();
@ -617,6 +615,34 @@ final class MonsterClass
* *
*******************************************************/
bool isParsed() { return flags.has(CFlags.Parsed); }
bool isScoped() { return flags.has(CFlags.Scoped); }
bool isResolved() { return flags.has(CFlags.Resolved); }
bool isCompiled() { return flags.has(CFlags.Compiled); }
bool isSingleton() { return flags.has(CFlags.Singleton); }
bool isModule() { return flags.has(CFlags.Module); }
bool isAbstract() { return flags.has(CFlags.Abstract); }
// Call whenever you require this function to have its scope in
// order. If the scope is missing, this will call createScope if
// possible, or fail if the class has not been loaded.
void requireScope()
{
if(isScoped) return;
if(!isParsed)
fail("Cannot use class '" ~ name.str ~
"': not found or forward reference",
name.loc);
createScope();
}
// Called whenever we need a completely compiled class, for example
// when creating an object. Compiles the class if it isn't done
// already.
void requireCompile() { if(!isCompiled) compileBody(); }
// Check if this class is a child of cls.
bool childOf(MonsterClass cls)
{
@ -707,35 +733,87 @@ final class MonsterClass
natNew.name.str = "native 'new' callback";
natNew.owner = this;
// TODO: Check for a list of keywords here. class, module,
// abstract, final. They can come in any order, but only certain
// combinations are legal. For example, class and module cannot
// both be present, and most other keywords only apply to
// classes. 'function' is not allowed at all, but should be
// checked for to make sure we're loading the right kind of
// file. If neither class nor module are found, that is also
// illegal in class files.
if(isNext(tokens, TT.Module))
{
flags.set(CFlags.Module);
flags.set(CFlags.Singleton);
}
else if(isNext(tokens, TT.Singleton))
flags.set(CFlags.Singleton);
else if(!isNext(tokens, TT.Class))
fail("File must begin with a class or module statement", tokens);
// Parse keywords. Uses a few local variables, so let's start a
// block.
{
assert(tokens.length > 0);
Floc loc = tokens[0].loc;
// Check for / set a given keyword flag. Returns true if it
// was NOT found.
bool check(TT type, ref bool flag)
{
Token tok;
if(isNext(tokens, type, tok))
{
if(flag)
fail("Keyword '" ~ tok.str ~
"' was specified twice", tok.loc);
flag = true;
return false;
}
return true;
}
// Flags for the various keywords
bool absSet;
bool classSet;
bool moduleSet;
bool singleSet;
bool something;
while(true)
{
if(check(TT.Class, classSet) &&
check(TT.Abstract, absSet) &&
check(TT.Module, moduleSet) &&
check(TT.Singleton, singleSet))
// Abort if nothing was found this round
break;
// If we get here at least once, then one of the keywords
// were present.
something = true;
}
// Was anything found?
if(!something)
fail("File must begin with a class or module statement", tokens);
// Module and singleton imply class as well
if(moduleSet || singleSet)
classSet = true;
// Do error checking
if(moduleSet && singleSet)
fail("Cannot specify both 'module' and 'singleton' (module implies singleton)",
loc);
if(!classSet)
fail("Class must start with one of 'class', 'singleton' or 'module'",
loc);
// Module implies singleton
if(moduleSet)
singleSet = true;
// But singletons cannot be abstract
if(singleSet && absSet)
fail("Modules and singletons cannot be abstract", loc);
// Process flags
if(singleSet) flags.set(CFlags.Singleton);
if(moduleSet) flags.set(CFlags.Module);
if(absSet) flags.set(CFlags.Abstract);
}
if(!isNext(tokens, TT.Identifier, name))
fail("Class statement expected identifier", tokens);
// Module implies singleton
assert(isSingleton || !isModule);
assert(!isSingleton || !isAbstract);
if(isSingleton && isAbstract)
fail("Modules and singletons cannot be abstract", name.loc);
// Insert ourselves into the global scope. This will also
// Insert ourselves into the package scope. This will also
// resolve forward references to this class, if any.
pack.insertClass(this);
@ -757,10 +835,6 @@ final class MonsterClass
}
isNext(tokens, TT.Semicolon);
/*
if(!isNext(tokens, TT.Semicolon))
fail("Missing semicolon after class statement", name.loc);
*/
if(parents.length > 1)
fail("Multiple inheritance is currently not supported", name.loc);
@ -866,6 +940,13 @@ final class MonsterClass
fail("Cannot bind to non-native function '" ~ name ~ "'");
}
// Check that the function really belongs to this class. We cannot
// bind functions belonging to parent classes.
assert(fn.owner !is null);
if(fn.owner !is this)
fail("Cannot bind to function " ~ fn.toString() ~
" - it is not a direct member of class " ~ toString());
fn.ftype = ft;
return fn;
@ -978,7 +1059,6 @@ final class MonsterClass
pName.loc);
auto mc = sl.mc;
assert(mc !is null);
//MonsterClass mc = vm.load(pName.str);
mc.requireScope();
assert(mc !is null);
@ -1006,9 +1086,21 @@ final class MonsterClass
// For now we only support one parent class.
assert(parents.length <= 1);
// Since there's only one parent, we can copy its tree and add
// ourselv to the list. TODO: At some point we need to
// automatically add Object to this list.
// initialize() must already have been called before we get here
assert(baseObject !is null);
// If we don't have a parent, and we aren't Object, then set
// Object as our parent.
if(parents.length != 1 && this !is baseObject)
parents = [baseObject];
// So at this point we either have a parent, or we are Object.
assert(parents.length == 1 ||
this is baseObject);
// Since there are only linear (single) inheritance graphs at
// the moment, we can just copy our parent's tree list and add
// ourself to it.
if(parents.length == 1)
tree = parents[0].tree;
else
@ -1024,8 +1116,14 @@ final class MonsterClass
// 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 = pack;
else
{
// For Object, use the package scope (which should be the
// global scope)
assert(this is baseObject);
assert(pack is global);
parSc = pack;
}
assert(parSc !is null);
@ -1140,8 +1238,7 @@ final class MonsterClass
// This calls resolve on the interior of functions and states.
void resolveBody()
{
if(!isScoped)
createScope();
requireScope();
assert(!isResolved, getName() ~ " is already resolved");

@ -298,6 +298,8 @@ struct MonsterObject
if(fn.paramSize > 0)
fail("thread(): function " ~ fn.name.str ~ " cannot have parameters");
fn = fn.findVirtual(this);
Thread *trd = Thread.getNew();
// Schedule the function to run the next frame
@ -305,11 +307,6 @@ struct MonsterObject
assert(trd.isPaused);
assert(trd.fstack.cur !is null);
// pushFunc will mess with the stack frame though, so fix it.
trd.fstack.cur.frame = stack.getStart();
if(cthread !is null)
cthread.fstack.restoreFrame();
return trd;
}

@ -29,6 +29,7 @@ import std.stdio;
import std.utf;
import monster.compiler.scopes;
import monster.compiler.functions;
import monster.options;
import monster.vm.mobject;
@ -40,7 +41,35 @@ import monster.vm.error;
// copies when they need it.
CodeStack stack;
// A simple stack frame. All data are in chunks of 4 bytes
struct FunctionRef
{
MIndex obj;
int fIndex;
MonsterObject *getObject()
{ return getMObject(obj); }
Function *getFunctionNonVirtual()
{ return functionList[fIndex]; }
Function *getFunction()
{
auto f = getFunctionNonVirtual();
return f.findVirtual(getObject());
}
void set(Function* fn, MonsterObject *mo)
{
assert(fn !is null);
assert(mo !is null);
assert(mo.cls.childOf(fn.owner));
fIndex = fn.getGIndex();
obj = mo.getIndex();
}
}
static assert(FunctionRef.sizeof == 8);
// A simple stack. All data are in chunks of 4 bytes
struct CodeStack
{
private:
@ -49,11 +78,6 @@ struct CodeStack
int left, total;
int *pos; // Current position
// Frame pointer, used for accessing local variables and parameters
int *frame;
int fleft; // A security measure to make sure we don't access
// variables outside the stack
public:
void init()
{
@ -61,7 +85,6 @@ struct CodeStack
left = maxStack;
total = maxStack;
pos = data.ptr;
frame = null;
}
// Get the current position index.
@ -70,37 +93,11 @@ struct CodeStack
return total-left;
}
// Sets the current position as the 'frame pointer', and return it.
int *setFrame()
{
frame = pos;
fleft = left;
//writefln("setFrame(): new=%s, old=%s", frame, old);
return frame;
}
// Sets the given frame pointer
void setFrame(int *frm)
{
//writefln("restoring frame: %s", frm);
frame = frm;
if(frm is null)
{
fleft = 0;
return;
}
fleft = left + (pos-frm);
assert(fleft >= 0 && fleft <= total);
}
// Reset the stack level to zero.
void reset()
{
left = total;
pos = data.ptr;
fleft = 0;
frame = null;
}
void pushInt(int i)
@ -135,12 +132,6 @@ struct CodeStack
return *(cast(long*)pos);
}
// Get the pointer from the start of the stack
int *getStart()
{
return cast(int*)data.ptr;
}
// Get the pointer to an int at the given position backwards from
// the current stack pointer. 0 means the first int, ie. the one we
// would get if we called popInt. 1 is the next, etc
@ -182,18 +173,6 @@ struct CodeStack
pos+=arr.length;
}
// Get an int a given position from frame pointer. Can be negative
// or positive. 0 means the int at the pointer, -1 is the one before
// and 1 the one after, etc.
int *getFrameInt(int ptr)
{
assert(frame !is null);
assert(frame <= pos);
if(ptr < (fleft-total) || ptr >= fleft)
fail("CodeStack.getFrameInt() pointer out of range");
return frame+ptr;
}
// Pushing and poping objects of the stack - will actually push/pop
// their index.
void pushObject(MonsterObject *mo)
@ -202,6 +181,9 @@ struct CodeStack
MonsterObject *popObject()
{ return getMObject(cast(MIndex)popInt()); }
MonsterObject *peekObject()
{ return getMObject(cast(MIndex)peekInt()); }
// Push arrays of objects. TODO: These do memory allocation, and I'm
// not sure that belongs here. I will look into it later.
void pushObjects(MonsterObject *objs[])
@ -234,6 +216,8 @@ struct CodeStack
{ return arrays.getRef(cast(AIndex)popInt()); }
ArrayRef *getArray(int i)
{ return arrays.getRef(cast(AIndex)*getInt(i)); }
ArrayRef *peekArray()
{ return getArray(0); }
// More easy versions. Note that pushArray() will create a new array
// reference each time it is called! Only use it if this is what you
@ -264,6 +248,8 @@ struct CodeStack
{ pushArray(toUTF32(str)); }
char[] popString8()
{ return toUTF8(popString()); }
char[] peekString8()
{ return toUTF8(peekArray().carr); }
// For multibyte arrays
void pushArray(int[] str, int size)
@ -271,9 +257,7 @@ struct CodeStack
// Various convenient conversion templates. These will be inlined,
// so don't worry :) The *4() functions are for types that are 4
// bytes long. These are mostly intended for use in native
// functions, so there is no equivalent of getFrameInt and similar
// functions.
// bytes long.
void push4(T)(T var)
{
static assert(T.sizeof == 4);
@ -285,8 +269,15 @@ struct CodeStack
int i = popInt();
return *(cast(T*)&i);
}
// Gets a pointer to a given stack value. Counts from the head - 0
// is the first int, 1 is the second, etc. Note that it counts in
// ints (four bytes) no matter what the type T is - this is by
// design.
T* get4(T)(int ptr) { return cast(T*)getInt(ptr); }
// Returns the first value on the stack without poping it
T peek4(T)() { return *(cast(T*)getInt(0)); }
// 64 bit version
void push8(T)(T var)
{
@ -314,32 +305,54 @@ struct CodeStack
alias push4!(MIndex) pushMIndex;
alias pop4!(MIndex) popMIndex;
alias get4!(MIndex) getMIndex;
alias peek4!(MIndex) peekMIndex;
alias push4!(AIndex) pushAIndex;
alias pop4!(AIndex) popAIndex;
alias get4!(AIndex) getAIndex;
alias peek4!(AIndex) peekAIndex;
alias peek4!(int) peekInt;
alias push4!(uint) pushUint;
alias pop4!(uint) popUint;
alias get4!(uint) getUint;
alias peek4!(uint) peekUint;
alias get4!(long) getLong;
alias peek4!(long) peekLong;
alias push8!(ulong) pushUlong;
alias pop8!(ulong) popUlong;
alias get4!(ulong) getUlong;
alias peek4!(ulong) peekUlong;
alias push4!(float) pushFloat;
alias pop4!(float) popFloat;
alias get4!(float) getFloat;
alias peek4!(float) peekFloat;
alias push8!(double) pushDouble;
alias pop8!(double) popDouble;
alias get4!(double) getDouble;
alias peek4!(double) peekDouble;
alias push4!(dchar) pushChar;
alias pop4!(dchar) popChar;
alias get4!(dchar) getChar;
alias peek4!(dchar) peekDchar;
alias push8!(FunctionRef) pushFuncRef;
alias pop8!(FunctionRef) popFuncRef;
alias get4!(FunctionRef) getFuncRef;
alias peek4!(FunctionRef) peekFuncRef;
void pushFuncRef(Function *fn, MonsterObject *obj)
{
FunctionRef f;
f.set(fn,obj);
pushFuncRef(f);
}
void pushFail(T)(T t)
{
@ -389,7 +402,10 @@ struct CodeStack
pos -= num;
}
// Pop off and ignore given values, but remember the top values
// Pop off and ignore given values, but remember the top
// values. Equivalent to popping of (and storing) 'keep' ints, then
// poping away 'num' ints, and finally pushing the kept ints
// back. The final stack imprint is -num.
void pop(uint num, uint keep)
{
assert(keep>0);

@ -56,7 +56,6 @@ import std.math : floor;
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;
alias _lstNode!(Thread) _tmp1;
@ -77,8 +76,9 @@ struct Thread
* *
*******************************************************/
// This has been copied from ScheduleStruct, which is now merged
// with Thread. We'll sort it out later.
// Function stack for this thread. This MUST be the first member of
// Thread, to make sure fstack.getThread() works properly.
FunctionStack fstack;
// Some generic variables that idle functions can use to store
// temporary data off the stack.
@ -92,9 +92,6 @@ struct Thread
// state is changed from within a function in active code.
bool shouldExit;
// Function stack for this thread
FunctionStack fstack;
/*******************************************************
* *
* Private variables *
@ -175,6 +172,11 @@ struct Thread
// Stop the execution of a thread and cancel any scheduling.
void stop()
{
static if(traceThreads)
{
dbg.log("Thread.stop()");
}
assert(!isDead);
// TODO: We also have to handle (forbid) cases where we are
@ -190,7 +192,7 @@ struct Thread
if(fstack.hasNatives)
fail("Cannot stop thread, there are native functions on the stack.");
// Kill the stack tell execute() to stop running
// Kill the stack and tell execute() to stop running
stack.reset();
shouldExit = true;
}
@ -225,6 +227,9 @@ struct Thread
// Schedule this thread to run state code the next frame
void scheduleState(MonsterObject *obj, int offs)
{
static if(logThreads)
dbg.log(format("------ scheduling state in thread %s ------", getIndex));
assert(!isDead);
assert(!isScheduled,
"cannot schedule an already scheduled thread");
@ -293,6 +298,11 @@ struct Thread
// Reenter this thread to the point where it was previously stopped.
void reenter()
{
static if(traceThreads)
{
dbg.log("Thread.reenter()");
}
assert(!isDead);
assert(cthread is null,
"cannot reenter when another thread is running");
@ -407,8 +417,6 @@ struct Thread
assert(!isTransient,
"cannot restore a transent thread with stack");
// Push the values back, and free the buffer
stack.pushInts(sstack);
Buffers.free(sstack);
@ -416,9 +424,6 @@ struct Thread
sstack = null;
}
// Restore the stack frame pointer
fstack.restoreFrame();
// Set ourselves as the running thread
cthread = this;
}
@ -458,10 +463,15 @@ struct Thread
.fail(msg, fl);
}
// Parse the BC.CallIdle instruction parameters and schedule the
// given idle function. Return true if we should exit execute()
bool callIdle(MonsterObject *iObj)
// Handle the call and scheduling of an given idle function. Return
// true if we should exit execute().
bool callIdle(MonsterObject *iObj, Function *idle)
{
static if(traceThreads)
{
dbg.log("Thread.callIdle()");
}
assert(isRunning);
assert(!isScheduled, "Thread is already scheduled");
assert(iObj !is null);
@ -469,16 +479,8 @@ struct Thread
if(fstack.hasNatives)
fail("Cannot run idle function: there are native functions on the stack");
CodeStream *code = &fstack.cur.code;
// Get the class from the index
auto cls = iObj.cls.upcast(code.getInt());
// Get the function
Function *idle = cls.findFunction(code.getInt());
assert(idle !is null && idle.isIdle);
assert(cls is idle.owner);
assert(iObj.cls.childOf(cls));
assert(idle !is null);
assert(idle.isIdle);
// The IdleFunction object bound to this function is stored in
// idle.idleFunc
@ -517,8 +519,11 @@ struct Thread
assert(res == IS.Manual || res == IS.Kill);
// The only difference between Manual and Kill is what list the
// thread ends in. If the thread is in the transient list, it will
// be killed automatically when it's no longer running.
// thread ends in. The idle function itself is responsible for
// putting the thread in the correct list when returning
// IS.Manual. If the thread is left in the transient list (ie. if
// the idle doesn't move the thread), it will be killed
// automatically when it is no longer running.
assert( (res == IS.Kill) == isTransient,
res == IS.Manual ? "Manually scheduled threads must be moved to another list." : "Killed threads cannot be moved to another list.");
@ -561,30 +566,46 @@ struct Thread
// exception.
int *popPtr()
{
// TODO: A better optimization here would be to pop the entire
// structure all at once. Once we work more on references,
// create a structure that takes care of all the conversions
// for us.
PT type;
int index;
decodePtr(stack.popInt(), type, index);
int *res;
// Null pointer?
if(type == PT.Null)
fail("Cannot access value, null pointer");
// Stack variable?
if(type == PT.Stack)
return stack.getFrameInt(index);
{
res = stack.getInt(index);
stack.pop(2);
}
// Variable in this object
if(type == PT.DataOffs)
return obj.getDataInt(cls.treeIndex, index);
else if(type == PT.DataOffs)
{
res = obj.getDataInt(cls.treeIndex, index);
stack.pop(2);
}
// This object, but another (parent) class
if(type == PT.DataOffsCls)
// We have to pop the class index of the stack as well
return obj.getDataInt(stack.popInt, index);
else if(type == PT.DataOffsCls)
{
// We have to pop the class index of the stack as well
res = obj.getDataInt(stack.popInt, index);
stack.popInt();
}
// Far pointer, with offset. Both the class index and the object
// reference is on the stack.
if(type == PT.FarDataOffs)
else if(type == PT.FarDataOffs)
{
int clsIndex = stack.popInt();
@ -592,11 +613,11 @@ struct Thread
MonsterObject *tmp = stack.popObject();
// Return the correct pointer
return tmp.getDataInt(clsIndex, index);
res = tmp.getDataInt(clsIndex, index);
}
// Array pointer
if(type == PT.ArrayIndex)
else if(type == PT.ArrayIndex)
{
assert(index==0);
// Array indices are on the stack
@ -611,10 +632,13 @@ struct Thread
if(index < 0 || index >= arf.iarr.length)
fail("Array index " ~ .toString(index/arf.elemSize) ~
" out of bounds (array length " ~ .toString(arf.length) ~ ")");
return &arf.iarr[index];
res = &arf.iarr[index];
}
else
fail("Unable to handle pointer type " ~ toString(cast(int)type));
fail("Unable to handle pointer type " ~ toString(cast(int)type));
assert(res !is null);
return res;
}
// Various temporary stuff
@ -643,7 +667,7 @@ struct Thread
ubyte opCode = code.get();
debug(traceOps)
static if(traceVMOps)
{
writefln("exec: %s (at stack %s)",
bcToString[opCode], stack.getPos);
@ -675,18 +699,20 @@ struct Thread
MonsterObject *mo;
Function *fn;
// Call function in this object
case BC.Call:
fn = Function.fromIndex(stack.popInt());
mo = obj;
goto CallCommon;
goto FindFunc;
// Call function in another object
case BC.CallFar:
fn = Function.fromIndex(stack.popInt());
mo = stack.popObject();
CallCommon:
FindFunc:
// Get the correct function from the virtual table
val = code.getInt(); // Class index
fn = mo.cls.findVirtualFunc(val, code.getInt());
fn = fn.findVirtual(mo);
if(fn.isNormal)
{
@ -699,6 +725,12 @@ struct Thread
obj = mo;
assert(obj is fstack.cur.obj);
}
else if(fn.isIdle)
{
if(callIdle(mo, fn)) return;
}
else if(fn.isAbstract)
fail("Cannot call abstract function " ~ fn.toString());
else
{
// Native function. Let Function handle it.
@ -709,16 +741,6 @@ struct Thread
break;
}
case BC.CallIdle:
if(callIdle(obj))
return;
break;
case BC.CallIdleFar:
if(callIdle(stack.popObject()))
return;
break;
case BC.Return:
// Remove the given number of bytes from the stack, and
// exit the function.
@ -730,8 +752,7 @@ struct Thread
goto case BC.Exit;
case BC.ReturnValN:
val = code.getInt(); // Get the value first, since order
// of evaluation is important.
val = code.getInt(); // Get the param number
stack.pop(val, code.getInt());
goto case BC.Exit;
@ -739,6 +760,7 @@ struct Thread
val = code.getInt(); // State index
val2 = code.getInt(); // Label index
// Get the class index and let setState handle everything
obj.setState(val, val2, code.getInt());
if(shouldExit) return;
break;
@ -858,19 +880,19 @@ struct Thread
case BC.PushData:
stack.pushInt(code.getInt());
debug(traceOps) writefln(" Data: %s", *stack.getInt(0));
static if(traceVMOps) writefln(" Data: %s", *stack.getInt(0));
break;
case BC.PushLocal:
debug(traceOps)
static if(traceVMOps)
{
auto p = code.getInt();
auto v = *stack.getFrameInt(p);
auto v = *stack.getInt(p);
stack.pushInt(v);
writefln(" Pushed %s from position %s",v,p);
}
else
stack.pushInt(*stack.getFrameInt(code.getInt()));
stack.pushInt(*stack.getInt(code.getInt()));
break;
case BC.PushClassVar:
@ -1258,7 +1280,6 @@ struct Thread
case BC.CastI2L:
if(*stack.getInt(0) < 0) stack.pushInt(-1);
else stack.pushInt(0);
//stack.pushLong(stack.popInt());
break;
// Cast int to float
@ -1344,6 +1365,17 @@ struct Thread
stack.pushUlong(cast(ulong)stack.popDouble);
break;
case BC.CastS2C:
arf = stack.popArray(); // Get array
if(arf.carr.length == 0)
fail("Cannot cast empty string to 'char'");
assert(arf.elemSize == 1);
if(arf.carr.length > 1)
fail("Cannot cast string of non-unit length " ~
.toString(arf.carr.length) ~ " to char");
stack.pushChar(arf.carr[0]);
break;
case BC.CastT2S:
{
// Get the type to cast from
@ -1370,6 +1402,13 @@ struct Thread
}
break;
case BC.RefFunc:
// Convert function reference into name
val = stack.popInt(); // Function index
stack.popInt(); // Ignore the object index
stack.pushString(functionList[val].toString());
break;
case BC.FetchElem:
// This is not very optimized
val = stack.popInt(); // Index
@ -1489,7 +1528,7 @@ struct Thread
arf = stack.popArray(); // Right array
if(arf.isNull)
{
// right is empty, just copy the left
// Right is empty, just copy the left
arf = stack.popArray();
if(arf.isNull) stack.pushArray(arf);
else stack.pushArray(arf.iarr.dup, arf.elemSize);
@ -1570,7 +1609,7 @@ struct Thread
case BC.IterUpdate:
val = code.getInt(); // Get stack index of iterator reference
if(val < 0) fail("Invalid argument to IterUpdate");
val = *stack.getFrameInt(val); // Iterator index
val = *stack.getInt(val); // Iterator index
iterators.update(cast(IIndex)val); // Do the update
break;

@ -40,9 +40,9 @@ void initMonsterScripts()
// Add the script directories
vm.addPath("mscripts/");
// Import some modules into the global scope, so we won't have to
// import them manually in each script.
global.registerImport("io", "random", "timer");
// Import some modules into scope of Object, so we won't have to
// import them manually into each script.
MonsterClass.getObject().sc.registerImport("random", "timer");
// Get the Config singleton object
config.mo = vm.load("Config").getSing();

Loading…
Cancel
Save