- updated to latest Monster svn

- some work on the scripts
- fps counter moved to Monster
- minor updates


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

@ -44,7 +44,8 @@ src := $(src) $(wildcard mscripts/*.d)
src := $(src) monster/monster.d \
$(wildcard monster/vm/*.d) \
$(wildcard monster/compiler/*.d) \
$(wildcard monster/util/*.d)
$(wildcard monster/util/*.d) \
$(wildcard monster/modules/*.d)
obj := $(src:%.d=objs/%.o)
# The NIF object files for niftool and bsatool are put in a separate

@ -8,4 +8,4 @@ g++ -c ogre\cpp_ogre.cpp -I.\includes\ogre\
g++ -c bullet\cpp_bullet.cpp -I.\includes\bullet\
echo Compiling main program (openmw.exe)
gdc -g openmw.d bsa\*.d core\*.d esm\*.d input\*.d nif\*.d ogre\*.d scene\*.d sound\*.d util\*.d bullet\*.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o libbulletdynamics.a libbulletcollision.a libbulletmath.a mscripts\object.d monster\monster.d monster\compiler\*.d monster\vm\*.d monster\util\*.d avcodec-51.dll avformat-52.dll avdevice-52.dll avutil-49.dll openal32.dll ogremain_d.dll OIS_d.dll -lstdc++ -o openmw.exe
gdc -g openmw.d bsa\*.d core\*.d esm\*.d input\*.d nif\*.d ogre\*.d scene\*.d sound\*.d util\*.d bullet\*.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o libbulletdynamics.a libbulletcollision.a libbulletmath.a mscripts\object.d monster\monster.d monster\compiler\*.d monster\vm\*.d monster\util\*.d monster\modules\*.d avcodec-51.dll avformat-52.dll avdevice-52.dll avutil-49.dll openal32.dll ogremain_d.dll OIS_d.dll -lstdc++ -o openmw.exe

@ -224,10 +224,15 @@ void initializeInput()
// put another import in core.config. I should probably check the
// bug list and report it.
updateMouseSensitivity();
// Set up the FPS ticker
auto mo = (new MonsterClass("FPSTicker")).getSing();
frameCount = mo.getIntPtr("frameCount");
mo.setState("tick");
}
float tmpTime = 0;
int cnt;
// Points directly to FPSTicker.frameCounter in Monster
int *frameCount;
extern(C) int ois_isPressed(int keysym);
@ -242,19 +247,12 @@ bool isPressed(Keys key)
extern(C) int d_frameStarted(float time)
{
tmpTime += time;
cnt++;
if(tmpTime >= 1.5)
{
writefln("fps: ", cnt/tmpTime);
cnt = 0;
tmpTime = 0;
}
(*frameCount)++;
if(doExit) return 0;
// Run the Monster scheduler
scheduler.doFrame();
vm.frame(time);
musCumTime += time;
if(musCumTime > musRefresh)

@ -32,6 +32,7 @@ import monster.util.list;
import monster.compiler.bytecode;
import monster.compiler.linespec;
import monster.compiler.types;
import monster.compiler.tokenizer;
import monster.vm.error;
@ -364,23 +365,18 @@ struct Assembler
assert(code != 0);
}
void callFunc(int func, int cls)
void callFunc(int func, int cls, bool isFar)
{
cmd(BC.Call);
if(isFar) cmd(BC.CallFar);
else cmd(BC.Call);
addi(cls);
addi(func);
}
void callIdle(int func, int cls)
void callIdle(int func, int cls, bool isFar)
{
cmd(BC.CallIdle);
addi(cls);
addi(func);
}
void callFarFunc(int func, int cls)
{
cmd(BC.CallFar);
if(isFar) cmd(BC.CallIdleFar);
else cmd(BC.CallIdle);
addi(cls);
addi(func);
}
@ -670,17 +666,6 @@ struct Assembler
// already on the stack.
void elementAddr() { pushPtr(PT.ArrayIndex,0); }
// Create an instruction that reads data from the data segment and
// creates an array from it at runtime. TODO: This might be
// decommissioned soon.
void makeArrayLit(int offset, int len, int elemSize)
{
cmd(BC.MakeArray);
addi(offset);
addi(elemSize);
addi(len*elemSize);
}
// Convert an array index to a const reference
void makeArrayConst() { cmd(BC.MakeConstArray); }
@ -864,10 +849,17 @@ struct Assembler
else assert(0);
}
void preInc(int s) { cmd2(BC.PreInc, BC.PreInc8, s); }
void preDec(int s) { cmd2(BC.PreDec, BC.PreDec8, s); }
void postInc(int s) { cmd2(BC.PostInc, BC.PostInc8, s); }
void postDec(int s) { cmd2(BC.PostDec, BC.PostDec8, s); }
void incDec(TT op, bool postfix, int s)
{
if(op == TT.PlusPlus)
if(postfix) cmd2(BC.PostInc, BC.PostInc8, s);
else cmd2(BC.PreInc, BC.PreInc8, s);
else if(op == TT.MinusMinus)
if(postfix) cmd2(BC.PostDec, BC.PostDec8, s);
else cmd2(BC.PreDec, BC.PreDec8, s);
else
assert(0, "Illegal op type");
}
// Type casting
void castIntToLong(bool fromSign)
@ -911,36 +903,13 @@ struct Assembler
else assert(0);
}
// Hmm, could as well merge the int and float versions. Later on
// we'll convert to a generic type instead of directly to string
// though.
void castIntToString(Type t)
// Cast a generic type to string
void castToString(int tindex)
{
cmd(BC.CastI2S);
if(t.isInt) addb(1);
else if(t.isUint) addb(2);
else if(t.isLong) addb(3);
else if(t.isUlong) addb(4);
else assert(0);
cmd(BC.CastT2S);
addi(tindex);
}
void castFloatToString(Type t)
{
assert(t.isFloating);
cmd(BC.CastF2S);
addb(t.getSize);
}
void castBoolToString() { cmd(BC.CastB2S); }
void castObjToString() { cmd(BC.CastO2S); }
// Cast an object to a parent class
void upcast(int cindex)
{
cmd(BC.Upcast);
addi(cindex);
}
// Boolean operators
void isEqual(int s) { cmdmult(BC.IsEqual, BC.IsEqualMulti, s); }

@ -79,7 +79,7 @@ abstract class Block
static void reqNext(ref TokenArray toks, TT type, out Token tok)
{
if(!isNext(toks, type, tok))
fail("Expected " ~ tokenList[type], toks);
fail("Expected " ~ tokenList[type], toks);
}
static void reqNext(ref TokenArray toks, TT type, out Floc loc)

@ -30,14 +30,19 @@ enum BC
Exit = 1, // Exit function.
Call, // Call function in this object. Takes a class
// index and a function index, both ints
// tree index and a function index, both ints.
CallFar, // Call function in another object. Takes a
// class index and a function index. The
// 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 index and a function index.
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.
Return, // Takes a parameter nr (int). Equivalent to:
// POPN nr (remove nr values of the stack)
@ -95,11 +100,11 @@ enum BC
// object). Parameter is an int offset.
PushParentVar, // Push value in data segment of parent
// object. int class index, int offset
// object. int class tree index, int offset
PushFarClassVar, // Push value from the data segment in another
// object. The object reference is already on
// the stack. int class index, int offset
// the stack. int class tree index, int offset
PushFarClassMulti, // Pushes multiple ints from the data
// segment. Takes the variable size (int) as
@ -243,27 +248,8 @@ enum BC
CastUL2D, // ulong to double
CastF2D, // float to double
CastI2S, // int to char[]. Takes one byte giving the
// int type: 1=int,2=uint,3=long,4=ulong
CastF2S, // float to char[]. Takes one byte giving the
// float size (1 or 2).
CastB2S, // bool to char[]
CastO2S, // an object to char[]
// The "Cast Type Array to String" instructions below are used to
// convert arrays of the form [1,2,3] and [[1,2,3],[4,5]] to a
// string. They all take a byte as parameter, specifying the array
// depth. They are NOT IMPLEMENTED YET!
CastIA2S, // Cast int arrays to char[]
CastFA2S, // Cast float arrays to char[]
CastBA2S, // Cast bool arrays to char[]
CastCA2S, // Cast char arrays to char[]
CastOA2S, // Cast object arrays to char[]
Upcast, // Implicitly cast an object to a parent class
// type. Takes an int class index.
CastT2S, // cast any type to string. Takes the type
// index (int) as a parameter
FetchElem, // Get an element from an array. Pops the
// index, then the array reference, then
@ -273,13 +259,6 @@ enum BC
GetArrLen, // Get the length of an array. Pops the array,
// pushes the length.
MakeArray, // Takes an offset (int), the element size
// (int) and the total raw length (number of
// elements * elem size). Reads data from the
// data segment at offset and creates an array
// of the given length (times the element
// size). Pushes the array index.
PopToArray, // Takes a raw length n (int) and an element
// size (int). Creates an array from the last
// n values on the stack and pops them off.
@ -433,8 +412,8 @@ enum PT
DataOffsCls = 4, // Variable is in this object, but in another
// class. The class MUST be a parent class of the
// current object. A class index follows this
// pointer on the stack.
// current object. A class tree index follows
// this pointer on the stack.
FarDataOffs = 5, // Another class, another object. The index is a
// data offset. Pop the class index off the
@ -616,17 +595,7 @@ char[][] bcToString =
BC.CastL2D: "CastL2D",
BC.CastUL2D: "CastUL2D",
BC.CastF2D: "CastF2D",
BC.CastI2S: "CastI2S",
BC.CastF2S: "CastF2S",
BC.CastB2S: "CastB2S",
BC.CastO2S: "CastO2S",
BC.CastIA2S: "CastIA2S",
BC.CastFA2S: "CastFA2S",
BC.CastBA2S: "CastBA2S",
BC.CastCA2S: "CastCA2S",
BC.CastOA2S: "CastOA2S",
BC.Upcast: "Upcast",
BC.MakeArray: "MakeArray",
BC.CastT2S: "CastT2S",
BC.PopToArray: "PopToArray",
BC.NewArray: "NewArray",
BC.CopyArray: "CopyArray",

@ -64,7 +64,7 @@ abstract class Expression : Block
Expression b;
Floc ln;
// These are allowed for members (eg. a.hello().you())
// These are allowed for members (eg. hello().to.you())
if(FunctionCallExpr.canParse(toks)) b = new FunctionCallExpr;
else if(VariableExpr.canParse(toks)) b = new VariableExpr;
else if(isMember) fail(toks[0].str ~ " can not be a member", toks[0].loc);
@ -381,26 +381,12 @@ abstract class Expression : Block
// messages.
Floc getLoc() { return loc; }
// Special version of resolve that is called for members,
// ie. objname.member;. Most expressions cannot be members,
// but those that can must override this.
void resolveMember(Scope sc, Type ownerType)
{ fail(toString() ~ " cannot be a member of " ~ ownerType.toString, loc); }
// Used for error messages
char[] typeString() { return type.toString(); }
// Can this expression be assigned to? (most types can not)
bool isLValue() { return false; }
// If true, assignments to this expression (for lvalues) are handled
// through writeProperty rather than with evalDest.
bool isProperty() { return false; }
// Static expressions. These can be evaluated as members without
// needing the owner pushed onto the stack.
bool isStatic() { return false; }
// Evaluate this using run-time instructions. This is only used when
// evalCTime can not be used (ie. when isCTime is false.)
void evalAsm() { fail(format("expression ", this, " not implemented")); }
@ -431,18 +417,24 @@ abstract class Expression : Block
}
// Evaluate this expression as a destination (ie. push a pointer
// instead of a value). Only valid for LValues. TODO: This
// completely replaces the need for a separate isLValue member, but
// I think I will keep it. It belongs in the semantic level, while
// this is in the code generation level.
// instead of a value). Only valid for LValues.
void evalDest() { assert(0, "evalDest() called for non-lvalue " ~ this.toString); }
// Called on all lvalue expressions after they have been
// modified, and must be stack-neutral.
void postWrite() { assert(isLValue()); }
// Pop values of the stack and store it in this expression. Only
// valid for lvalues. This is now used in place of evalDest in most
// places (although it may use evalDest internally.)
void store()
{ assert(0, "store not implemented for " ~ toString()); }
// Assign to this property. Only called if isProperty returns true.
void writeProperty() { assert(0); }
// Handles ++ and --
void incDec(TT op, bool post)
{
assert(isLValue);
assert(type.isInt || type.isUint ||
type.isLong || type.isUlong);
evalDest();
tasm.incDec(op, post, type.getSize());
}
// Can this expression be evaluated at compile time?
bool isCTime() { return false; }
@ -702,15 +694,7 @@ class ArrayLiteralExpr : Expression
int elem = type.getBase().getSize();
int num = params.length;
// Create a temporary array containing the data
const int LEN = 32;
int buf[LEN];
int data[];
if(num*elem <= LEN)
data = buf[0..num*elem];
else
data = new int[num*elem];
int data[] = new int[num*elem];
// Set up the data
foreach(i, par; params)
@ -719,10 +703,6 @@ class ArrayLiteralExpr : Expression
// Create the array and get the index
arrind = arrays.createConst(data, elem).getIndex;
// Delete the array, if necessary
if(data.length > LEN)
delete data;
// Create an array from the index and return it as the compile
// time value
return (cast(int*)&arrind)[0..1];
@ -1001,6 +981,10 @@ abstract class MemberExpression : Expression
resolve(sc);
}
// Static members. These can be evaluated without needing the owner
// pushed onto the stack.
bool isStatic() { return false; }
}
// Expression that handles conversion from one type to another. This
@ -1057,3 +1041,50 @@ class CastExpression : Expression
return "cast(" ~ type.toString ~ ")(" ~ orig.toString ~ ")";
}
}
// Used as the surrogate owner expression for imported
// members. Example:
// import x;
// y = 3; // refers to x.y
// y is resolved as x.y, where the owner (x) is an import holder.
class ImportHolder : Expression
{
MonsterClass mc;
this(MonsterClass pmc)
{
mc = pmc;
// Importing singletons and modules is like importing
// the object itself
if(mc.isSingleton)
type = mc.objType;
else
type = mc.classType;
}
// All lookups in this import is done through this function. Can be
// used to filter lookups later on.
ScopeLookup lookup(Token name)
{
assert(mc !is null);
mc.requireScope();
return mc.sc.lookup(name);
}
override:
void parse(ref TokenArray) { assert(0); }
void resolve(Scope sc) {}
void evalDest() { assert(0); }
char[] toString() { return "imported class " ~ mc.name.str ~ ""; }
void evalAsm()
{
if(mc.isSingleton)
{
assert(type.isObject);
tasm.pushSingleton(mc.getIndex());
}
}
}

@ -36,6 +36,7 @@ enum FuncType
}
import monster.compiler.types;
import monster.compiler.operators;
import monster.compiler.assembler;
import monster.compiler.bytecode;
import monster.compiler.scopes;
@ -50,6 +51,7 @@ import monster.vm.idlefunction;
import monster.vm.mclass;
import monster.vm.error;
import monster.vm.fstack;
import monster.vm.thread;
import monster.vm.vm;
import std.stdio;
@ -90,6 +92,9 @@ struct Function
// Is this function final? (can not be overridden in child classes)
bool isFinal;
// If true, this function can be executed without an object
bool isStatic;
// What function we override (if any)
Function *overrides;
@ -135,8 +140,13 @@ struct Function
{
assert(obj !is null);
// Cast the object to the correct type for this function.
obj = obj.Cast(owner);
// Make sure there's a thread to use
bool wasNew;
if(cthread is null)
{
wasNew = true;
cthread = Thread.getNew();
}
// Push the function on the stack
fstack.push(this, obj);
@ -153,7 +163,7 @@ struct Function
natFunc_c();
break;
case FuncType.Normal:
obj.thread.execute();
cthread.execute();
break;
case FuncType.Native:
fail("Called unimplemented native function " ~ toString);
@ -167,6 +177,19 @@ struct Function
// Remove ourselves from the function stack
fstack.pop();
assert(cthread !is null);
// Reset cthread, if we created it.
if(wasNew)
{
// If the thread is still in the unused list, we can safely
// kill it.
if(cthread.isUnused)
cthread.kill();
cthread = null;
}
}
// Call without an object. TODO: Only allowed for functions compiled
@ -180,6 +203,8 @@ struct Function
call(int_mo);
}
// This allows you to compile a function file by writing fn =
// Function("filename").
static Function opCall(char[] file, MonsterClass mc = null)
{
Function fn;
@ -191,13 +216,35 @@ struct Function
// 'mc'. If no class is given, use an empty internal class.
void compile(char[] file, MonsterClass mc = null)
{
assert(name.str == "",
"Function " ~ name.str ~ " has already been set up");
// Check if the file exists
if(!vm.findFile(file))
fail("File not found: " ~ file);
// Create the stream and pass it on
auto bf = new BufferedFile(file);
compile(file, bf, mc);
delete bf;
}
void compile(char[] file, Stream str, MonsterClass mc = null)
{
// Get the BOM and tokenize the stream
auto ef = new EndianStream(str);
int bom = ef.readBOM();
TokenArray tokens = tokenizeStream(file, ef, bom);
compile(file, tokens, mc);
//delete ef;
}
void compile(char[] file, ref TokenArray tokens, MonsterClass mc = null)
{
assert(name.str == "",
"Function " ~ name.str ~ " has already been set up");
// Check if this is a class or a module file first
if(MonsterClass.canParse(tokens))
fail("Cannot run " ~ file ~ " - it is a class or module.");
// Set mc to an empty class if no class is given
if(mc is null)
{
@ -213,21 +260,6 @@ struct Function
mc = int_mc;
}
// TODO: Maybe we should centralize this stuff somewhere.
// Especially since we have to reinvent loading from string or
// other custom streams, and so on. It's easier to put the tools
// for this here than to have them in MonsterClass. Fix this later
// when you see how things turn out.
auto bf = new BufferedFile(file);
auto ef = new EndianStream(bf);
int bom = ef.readBOM();
TokenArray tokens = tokenizeStream(file, ef, bom);
delete bf;
// Check if this is a class or a module file first
if(MonsterClass.canParse(tokens))
fail("Cannot run " ~ file ~ " - it is a class or module.");
auto fd = new FuncDeclaration;
// Parse and comile the function
fd.parseFile(tokens, this);
@ -679,6 +711,10 @@ class FunctionCallExpr : MemberExpression
bool isVararg;
// Used to simulate a member for imported variables
DotOperator dotImport;
bool recurse = true;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
@ -728,28 +764,36 @@ class FunctionCallExpr : MemberExpression
{
if(isMember) // Are we called as a member?
{
assert(leftScope !is null);
fd = leftScope.findFunc(name.str);
if(fd is null) fail(name.str ~ " is not a member function of "
~ leftScope.toString, loc);
assert(leftScope !is null);
auto l = leftScope.lookup(name);
fd = l.func;
if(!l.isFunc)
fail(name.str ~ " is not a member function of "
~ leftScope.toString, loc);
}
else
{
assert(leftScope is null);
fd = sc.findFunc(name.str);
if(fd is null) fail("Undefined function "~name.str, name.loc);
auto l = sc.lookupImport(name);
// For imported functions, we have to do some funky magic
if(l.isImport)
{
assert(l.imphold !is null);
dotImport = new DotOperator(l.imphold, this, loc);
dotImport.resolve(sc);
return;
}
fd = l.func;
if(!l.isFunc)
fail("Undefined function "~name.str, name.loc);
}
// Is this an idle function?
if(fd.isIdle)
{
if(!sc.isStateCode)
fail("Idle functions can only be called from state code",
name.loc);
if(isMember)
fail("Idle functions cannot be called as members", name.loc);
}
if(fd.isIdle && !sc.isStateCode)
fail("Idle functions can only be called from state code",
name.loc);
type = fd.type;
assert(type !is null);
@ -870,15 +914,20 @@ class FunctionCallExpr : MemberExpression
void evalAsm()
{
if(dotImport !is null && recurse)
{
recurse = false;
dotImport.evalAsm();
return;
}
if(!pdone) evalParams();
setLine();
assert(fd.owner !is null);
if(isMember)
tasm.callFarFunc(fd.index, fd.owner.getTreeIndex());
else if(fd.isIdle)
tasm.callIdle(fd.index, fd.owner.getIndex());
if(fd.isIdle)
tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember);
else
tasm.callFunc(fd.index, fd.owner.getTreeIndex());
tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember);
}
}

@ -87,10 +87,6 @@ class UnaryOperator : Expression
if(opType == TT.PlusPlus || opType == TT.MinusMinus)
{
if(exp.isProperty)
fail("Operators ++ and -- not implemented for properties yet",
loc);
if(!type.isIntegral)
fail("Operator " ~ tokenList[opType] ~ " not allowed for " ~
exp.toString() ~ " of type " ~ exp.typeString(), loc);
@ -162,26 +158,8 @@ class UnaryOperator : Expression
{
if(opType == TT.PlusPlus || opType == TT.MinusMinus)
{
exp.evalDest();
assert(exp.type.isInt || exp.type.isUint ||
exp.type.isLong || exp.type.isUlong);
setLine();
if(postfix)
{
if(opType == TT.PlusPlus) tasm.postInc(exp.type.getSize());
else tasm.postDec(exp.type.getSize());
}
else
{
if(opType == TT.PlusPlus) tasm.preInc(exp.type.getSize());
else tasm.preDec(exp.type.getSize());
}
// The expression has been modified.
exp.postWrite();
exp.incDec(opType, postfix);
return;
}
@ -217,7 +195,9 @@ abstract class OperatorExpr : Expression
// getList()[14], and array[2..$];
class ArrayOperator : OperatorExpr
{
bool isSlice;
bool isSlice; // Set if this is a slice
bool isFill; // Set during assignment if we're filling the array
// with one single value
// name[], name[index], name[index..index2]
Expression name, index, index2;
@ -242,11 +222,8 @@ class ArrayOperator : OperatorExpr
return res ~= "])";
}
// We can ALWAYS assign to elements of an array. There is no such
// thing as a constant array in Monster, only constant array
// references. Exceptions (such as strings in the static data area)
// must be dealt with at runtime. We might moderate this rule later
// though.
// We can ALWAYS assign to elements of an array. Const arrays are
// handled at runtime only at the moment.
bool isLValue() { return true; }
void resolve(Scope sc)
@ -376,7 +353,7 @@ class ArrayOperator : OperatorExpr
setLine();
if(isDest) tasm.elementAddr();
else tasm.fetchElement(/*type.getSize*/);
else tasm.fetchElement();
assert(!isSlice);
}
@ -390,30 +367,53 @@ class ArrayOperator : OperatorExpr
setLine();
tasm.makeSlice();
assert(isSlice);
}
}
void evalDest()
{
assert(isLValue);
isDest = true;
eval();
}
void store()
{
evalDest();
if(isSlice)
{
// Special case for left hand slices, of the type a[] or
// a[i..j]. In this case we use special commands to fill or
// copy the entire array data.
if(isFill)
tasm.fillArray(type.getBase().getSize());
else tasm.copyArray();
}
else
tasm.mov(type.getSize());
}
}
// Handles the DOT operator, eg. someObj.someFunc();
class DotOperator : OperatorExpr
{
// owner.member
Expression owner, member;
Expression owner;
MemberExpression member;
this(Expression own, Expression memb, Floc loc)
{
this.owner = own;
owner = own;
assert(own !is null, "owner cannot be null");
assert(memb !is null);
this.member = memb;
member = cast(MemberExpression)memb;
assert(member !is null);
this.loc = loc;
}
@ -431,10 +431,6 @@ class DotOperator : OperatorExpr
return member.isLValue;
}
// We are a property if the member is a property
bool isProperty() { return member.isProperty; }
void writeProperty() { member.writeProperty(); }
void resolve(Scope sc)
{
// In order to find the correct scope for the right side, we
@ -451,6 +447,12 @@ class DotOperator : OperatorExpr
member.resolveMember(sc, ot);
type = member.type;
// Make sure we only call static members when the owner is a
// type.
if(ot.isMeta && !member.isStatic)
fail("Can only access static members for " ~ owner.toString,
loc);
}
// TODO: An evalMemberCTime() function could be used here
@ -467,7 +469,17 @@ class DotOperator : OperatorExpr
member.evalDest();
}
void postWrite() { member.postWrite(); }
void store()
{
evalCommon();
member.store();
}
void incDec(TT o, bool b)
{
evalCommon();
member.incDec(o,b);
}
void evalCommon()
{
@ -491,13 +503,6 @@ class DotOperator : OperatorExpr
// Assignment operators, =, +=, *=, /=, %=, ~=
class AssignOperator : BinaryOperator
{
// Set to true if we are assigning to an array slice, eg. a[1..3] =
// b[] or a[] = 3; This changes some rules, eg array data must be
// copied instead or just their reference, and we are allowed to
// assign a single value to a slice and fill the array that way.
bool isSlice;
bool isFill; // Used for filling elements with one value.
bool catElem; // For concatinations (~=), true when the right hand
// side is a single element rather than an array.
@ -527,7 +532,6 @@ class AssignOperator : BinaryOperator
if(arr !is null && arr.isSlice)
{
assert(type.isArray);
isSlice = true;
if(opType == TT.CatEq)
fail("Cannot use ~= on array slice " ~ left.toString, loc);
@ -555,8 +559,8 @@ class AssignOperator : BinaryOperator
~ " to slice " ~ left.toString ~ " of type "
~ left.typeString, loc);
// Inform eval() what to do
isFill = true;
// Inform arr.store() that we're filling in a single element
arr.isFill = true;
return;
}
@ -644,43 +648,18 @@ class AssignOperator : BinaryOperator
}
else right.eval();
// Special case for properties and other cases where the
// assignment is actually a function call. We don't call
// evalDest or mov, just let writeProperty handle everything.
if(left.isProperty)
{
left.writeProperty();
return;
}
// Store the value on the stack into the left expression.
setLine();
left.store();
/*
left.evalDest();
setLine();
// Special case for left hand slices, of the type a[] or
// a[i..j].
if(isSlice)
{
assert(type.isArray);
if(isFill)
{
// Fill the array with the result of the right-hand
// expression
tasm.fillArray(s);
}
else
// Copy array contents from the right array to the left
// array
tasm.copyArray();
return;
}
tasm.mov(right.type.getSize()); // Move the data
// Left hand value has been modified, notify it.
left.postWrite();
*/
}
}

@ -48,12 +48,8 @@ class NumericProperties(T) : SimplePropertyScope
{
this()
{
super(T.stringof ~ "Properties");
// Argh, this fails of course because we're trying to push a
// long value as an int. We can use a static if here to check
// the size, and use different instructions. Eg push8 that takes
// long and double.
super(T.stringof ~ "Properties",
GenericProperties.singleton);
// Static properties of all numeric types
static if(T.sizeof == 4)
@ -67,8 +63,6 @@ class NumericProperties(T) : SimplePropertyScope
inserts("max", T.stringof, { tasm.push8(T.max); });
}
else static assert(0);
nextProp = GenericProperties.singleton;
}
}
@ -136,7 +130,7 @@ class ArrayProperties: SimplePropertyScope
this()
{
super("ArrayProperties");
super("ArrayProperties", GenericProperties.singleton);
insert("length", "int",
{ tasm.getArrayLength(); },
@ -146,8 +140,6 @@ class ArrayProperties: SimplePropertyScope
insert("sort", "owner", { assert(0, "sort not implemented"); });
insert("const", "owner", { tasm.makeArrayConst(); });
insert("isConst", "bool", { tasm.isArrayConst(); });
nextProp = GenericProperties.singleton;
}
}
@ -158,23 +150,11 @@ class ClassProperties : SimplePropertyScope
this()
{
super("ClassProperties");
super("ClassProperties",
GenericProperties.singleton);
insert("clone", "owner", { tasm.cloneObj(); });
// For testing purposes. Makes 'singleton' an alias for the
// first variable in the data segment. This might actually not
// be far from how the end result would work - the singleton
// would just be a hidden variable, but in the variable list
// belonging to the class. We don't have to handle it using the
// property system at all, really, we only need to allow the
// special name 'singleton' as a variable.
/*
insert("singleton", "int",
{ tasm.pushClass(0, 1); },
{ tasm.pushClassAddr(0); tasm.movRet(); });
*/
// We should move handling of states here. This will mean
// removing StateStatement and making states a propert type. We
// can't leave statestatement in as a special syntax for setting
@ -191,8 +171,6 @@ class ClassProperties : SimplePropertyScope
// differentiate between near and far properties too. Think more
// about it.
//insert("state", "int", { tasm.push(6); });
nextProp = GenericProperties.singleton;
}
}
@ -238,7 +216,7 @@ class GenericProperties : SimplePropertyScope
abstract class SimplePropertyScope : PropertyScope
{
this(char[] n) { super(n); }
this(char[] n, PropertyScope ps = null) { super(n, ps); }
private SP[char[]] propList;
@ -277,7 +255,7 @@ abstract class SimplePropertyScope : PropertyScope
// Return the stored type. If it is null, return the owner type
// instead.
Type getPropType(char[] name, Type oType)
Type getType(char[] name, Type oType)
{
assert(hasProperty(name));
Type tp = propList[name].type;

@ -52,6 +52,111 @@ void initScope()
global = new PackageScope(null, "global");
}
// List all identifier types
enum LType
{
None,
Class,
Type,
Variable,
Function,
State,
StateLabel,
LoopLabel,
Property,
Import,
}
const char[][] LTypeName =
[
LType.None: "",
LType.Class: "class",
LType.Type: "type",
LType.Variable: "variable",
LType.Function: "function",
LType.State: "state",
LType.StateLabel: "state label",
LType.LoopLabel: "loop label",
LType.Property: "property",
];
struct ScopeLookup
{
Token name;
LType ltype;
Scope sc;
Type type;
union
{
MonsterClass mc;
Variable* var;
Function* func;
State* state;
StateLabel *slabel;
ImportHolder imphold;
void *ptr;
Object ob;
}
bool isClass() { return ltype == LType.Class; }
bool isType() { return ltype == LType.Type; }
bool isVar() { return ltype == LType.Variable; }
bool isFunc() { return ltype == LType.Function; }
bool isState() { return ltype == LType.State; }
bool isNone() { return ltype == LType.None; }
bool isImport() { return ltype == LType.Import; }
bool isProperty()
{
bool ret = (ltype == LType.Property);
assert( ret == (cast(PropertyScope)sc !is null) );
return ret;
}
// For properties only
Type getPropType(Type owner)
{
assert(type is null);
assert(owner !is null);
assert(name.str != "");
type = owner;
return ps().getType(name.str, owner);
}
private PropertyScope ps()
{
assert(isProperty);
assert(type !is null);
return cast(PropertyScope)sc;
}
bool isPropLValue() { return ps().isLValue(name.str, type); }
bool isPropStatic() { return ps().isStatic(name.str, type); }
void getPropValue() { ps().getValue(name.str, type); }
void setPropValue() { ps().setValue(name.str, type); }
static ScopeLookup opCall(Token nm, LType lt, Type tp, Scope sc, Object ob)
{
auto sl = ScopeLookup(nm, lt, tp, sc);
sl.ob = ob;
return sl;
}
static ScopeLookup opCall(Token nm, LType lt, Type tp, Scope sc, void*p = null)
{
assert(nm.str != "");
ScopeLookup sl;
sl.name = nm;
sl.ltype = lt;
sl.type = tp;
sl.sc = sc;
sl.ptr = p;
return sl;
}
}
// TODO: Write here which of these should be kept around at runtime,
// and which of them can be discarded after compilation.
@ -65,16 +170,26 @@ abstract class Scope
// global scope.
Scope parent;
// Properties assigned to this scope (if any).
PropertyScope nextProp;
// Verify that an identifier is not declared in this scope. If the
// identifier is found, give a duplicate identifier compiler
// error. Recurses through parent scopes.
void clearId(Token name)
final void clearId(Token name)
{
assert(!isRoot());
parent.clearId(name);
// Lookup checks all parent scopes so we only have to call it
// once.
auto sl = lookup(name);
assert(sl.name.str == name.str);
if(!sl.isNone)
{
if(sl.isProperty)
fail(name.str ~ " is a property and cannot be redeclared",
name.loc);
fail(format("%s is already declared (at %s) as a %s",
name.str, name.loc, LTypeName[sl.ltype]),
name.loc);
}
}
// Made protected since it is so easy to confuse with isStateCode(),
@ -87,6 +202,8 @@ abstract class Scope
// mostly used for debugging.
char[] scopeName;
ImportHolder importList[];
public:
this(Scope last, char[] name)
@ -95,8 +212,11 @@ abstract class Scope
parent = last;
assert(last !is this, "scope cannot be it's own parent");
assert(name != "");
// Copy the import list from our parent
if(!isRoot)
importList = parent.importList;
}
// Is this the root scope?
@ -120,12 +240,6 @@ abstract class Scope
// Is this scope allowed to be a root scope (without parent?)
bool allowRoot() { return false; }
// Get a property from this scope
void getProperty(Token name, Type ownerType, ref Property p)
{
assert(0);
}
// Get the function definition belonging to this scope.
Function *getFunction()
{
@ -164,76 +278,73 @@ abstract class Scope
LabelStatement getBreak(char[] name = "") { return null; }
LabelStatement getContinue(char[] name = "") { return null; }
// Does this scope have a property with the given name?
bool hasProperty(Token name)
{
assert(!isProperty);
return false;
}
final ScopeLookup lookupName(char[] name)
{ return lookup(Token(name, Floc.init)); }
final bool findProperty(Token name, Type ownerType, ref Property result)
ScopeLookup lookup(Token name)
{
if(hasProperty(name))
{
getProperty(name, ownerType, result);
return true;
}
if(nextProp !is null)
return nextProp.findProperty(name, ownerType, result);
// No property in this scope. Check the parent.
if(!isRoot) return parent.findProperty(name, ownerType, result);
// No parent, property not found.
return false;
if(isRoot()) return ScopeLookup(name, LType.None, null, null);
else return parent.lookup(name);
}
Variable* findVar(char[] name)
// Look up an identifier, and check imported scopes as well.
ScopeLookup lookupImport(Token name)
{
if(isRoot()) return null;
return parent.findVar(name);
}
auto l = lookup(name);
if(!l.isNone) return l;
State* findState(char[] name)
{
if(isRoot()) return null;
return parent.findState(name);
}
// Nuttin' was found, try the imports
bool found = false;
auto old = l;
foreach(imp; importList)
{
l = imp.lookup(name);
Function* findFunc(char[] name)
{
if(isRoot()) return null;
return parent.findFunc(name);
}
// Only accept types, classes, variables and functions
if(l.isType || l.isClass || l.isVar || l.isFunc)
{
// Duplicate matches aren't allowed
if(found && l.sc !is old.sc)
fail(format(
"%s matches both %s.%s (at %s) and %s.%s (at %s)", name.str,
old.imphold.mc.name.str, old.name.str, old.name.loc,
imp.mc.name.str, l.name.str, l.name.loc),
name.loc);
// First match
found = true;
old = l;
old.imphold = imp;
}
}
StructType findStruct(char[] name)
{
if(isRoot()) return null;
return parent.findStruct(name);
}
if(!found)
return ScopeLookup(name, LType.None, null, null);
EnumType findEnum(char[] name)
{
if(isRoot()) return null;
return parent.findEnum(name);
// Tell the caller that this is an import. We override the
// lookup struct, but it doesn't matter since the lookup will
// have to be performed again anyway.
old.ltype = LType.Import;
assert(old.imphold !is null);
return old;
}
void insertLabel(StateLabel *lb)
{
assert(!isRoot);
parent.insertLabel(lb);
}
// Add an import to this scope
void registerImport(ImportHolder s) { importList ~= s; }
void insertVar(Variable* dec) { assert(0); }
// More user-friendly version for API-defined
// imports. Eg. global.registerImport(myclass) -> makes myclass
// available in ALL classes.
void registerImport(MonsterClass mc)
{ registerImport(new ImportHolder(mc)); }
// Used for summing up stack level. Redeclared in StackScope.
int getTotLocals() { return 0; }
int getLocals() { assert(0); }
// These must be overridden in their respective scopes
int addNewDataVar(int varSize) { assert(0); }
int addNewLocalVar(int varSize) { assert(0); }
// This must be overridden in all scopes that allow variable
// declarations.
int addNewVar(int varSize) { assert(0); }
final:
@ -292,21 +403,6 @@ final class StateScope : Scope
super(last, st.name.str);
}
override:
void clearId(Token name)
{
assert(name.str != "");
// Check against labels. We are not allowed to shadow labels at
// any point.
StateLabel *lb;
if(st.labels.inList(name.str, lb))
fail(format("Identifier '%s' conflicts with label on line %s", name.str,
lb.name.loc), name.loc);
super.clearId(name);
}
// Insert a label, check for name collisions.
void insertLabel(StateLabel *lb)
{
@ -316,6 +412,19 @@ final class StateScope : Scope
st.labels[lb.name.str] = lb;
}
override:
ScopeLookup lookup(Token name)
{
assert(name.str != "");
// Check against state labels
StateLabel *lb;
if(st.labels.inList(name.str, lb))
return ScopeLookup(lb.name, LType.StateLabel, null, this, lb);
return super.lookup(name);
}
State* getState() { return st; }
bool isState() { return true; }
@ -414,13 +523,6 @@ final class PackageScope : Scope
bool csInList(char[] name, ref MonsterClass cb)
{
return ciInList(name, cb) && cb.name.str == name;
//fail(format("Class name mismatch: wanted %s but found %s",
// name, cb.name.str));
}
bool csInList(char[] name)
{
MonsterClass mc;
return csInList(name, mc);
}
// Get the class. It must exist and the case must match. getClass
@ -448,18 +550,21 @@ final class PackageScope : Scope
return mc;
}
override void clearId(Token name)
override ScopeLookup lookup(Token name)
{
assert(name.str != "");
// Type names can never be overwritten, so we check findClass
// and the built-in types. We might move the builtin type check
// to a "global" scope at some point.
if(BasicType.isBasic(name.str) || csInList(name.str))
fail("Identifier '"~ name.str~ "' is a type and cannot be redeclared",
name.loc);
if(BasicType.isBasic(name.str))
return ScopeLookup(name, LType.Type, BasicType.get(name.str), this);
MonsterClass mc;
if(csInList(name.str, mc))
return ScopeLookup(mc.name, LType.Class, null, this, mc);
// No parents to check
assert(isRoot());
return super.lookup(name);
}
// Find a parsed class of the given name. Looks in the list of
@ -549,42 +654,29 @@ abstract class VarScope : Scope
this(Scope last, char[] name)
{ super(last, name); }
override:
// Find a variable, returns the declaration.
Variable* findVar(char[] name)
// Insert a variable, checks if it already exists.
void insertVar(Variable* dec)
{
Variable* vd;
assert(!isStateCode, "called insertVar in state code");
if(variables.inList(name, vd))
return vd;
// Check for name collisions.
clearId(dec.name);
return super.findVar(name);
variables[dec.name.str] = dec;
}
void clearId(Token name)
override:
ScopeLookup lookup(Token name)
{
assert(name.str != "");
Variable *vd;
if(variables.inList(name.str, vd))
fail(format("Identifier '%s' already declared on line %s (as a variable)",
name.str, vd.name.loc),
name.loc);
return ScopeLookup(vd.name, LType.Variable, vd.type, this, vd);
super.clearId(name);
return super.lookup(name);
}
// Insert a variable, checks if it already exists.
void insertVar(Variable* dec)
{
assert(!isStateCode, "called insertVar in state code");
// Check for name collisions.
clearId(dec.name);
variables[dec.name.str] = dec;
}
}
// A scope that can contain functions and variables
@ -603,15 +695,14 @@ class FVScope : VarScope
if(isClass)
{
// Are we overriding a function?
auto old = findFunc(fd.name.str);
// If there is no existing function, call clearId
if(old is null)
clearId(fd.name);
else
auto look = lookup(fd.name);
if(look.isFunc)
// We're overriding. Let fd know, and let it handle the
// details when it resolves.
fd.overrides = old;
fd.overrides = look.func;
else
// No matching function. Check that the name is available.
clearId(fd.name);
}
else
// Non-class functions can never override anything
@ -624,33 +715,18 @@ class FVScope : VarScope
}
override:
void clearId(Token name)
ScopeLookup lookup(Token name)
{
assert(name.str != "");
Function* fd;
if(functions.inList(name.str, fd))
{
fail(format("Identifier '%s' already declared on line %s (as a function)",
name.str, fd.name.loc),
name.loc);
}
return ScopeLookup(fd.name, LType.Function, fd.type, this, fd);
// Let VarScope handle variables
super.clearId(name);
return super.lookup(name);
}
Function* findFunc(char[] name)
{
Function* fd;
if(functions.inList(name, fd))
return fd;
assert(!isRoot());
return parent.findFunc(name);
}
}
// Can contain types, functions and variables. 'Types' means structs,
@ -683,62 +759,33 @@ class TFVScope : FVScope
}
override:
void clearId(Token name)
ScopeLookup lookup(Token name)
{
assert(name.str != "");
StructType fd;
StructType sd;
EnumType ed;
Type tp;
if(structs.inList(name.str, fd))
{
fail(format("Identifier '%s' already declared on line %s (as a struct)",
name.str, fd.loc),
name.loc);
}
if(structs.inList(name.str, sd)) tp = sd;
else if(enums.inList(name.str, ed)) tp = ed;
if(enums.inList(name.str, ed))
{
fail(format("Identifier '%s' already declared on line %s (as an enum)",
name.str, ed.loc),
name.loc);
}
if(tp !is null)
return ScopeLookup(Token(tp.name, tp.loc), LType.Type, tp, this);
// Let VarScope handle variables
super.clearId(name);
// Pass it on to the parent
return super.lookup(name);
}
StructType findStruct(char[] name)
{
StructType fd;
if(structs.inList(name, fd))
return fd;
assert(!isRoot());
return parent.findStruct(name);
}
EnumType findEnum(char[] name)
{
EnumType ed;
if(enums.inList(name, ed))
return ed;
assert(!isRoot());
return parent.findEnum(name);
}
}
// Lookup scope for enums. For simplicity we use the property system
// to handle enum members.
final class EnumScope : SimplePropertyScope
{
this() { super("EnumScope"); }
this() { super("EnumScope", GenericProperties.singleton); }
int index; // Index in a global enum index list. Do whatever you do
// with global class indices here.
int index; // Index in a global enum index list. Make a static list
// here or something.
void setup()
{
@ -772,7 +819,7 @@ final class StructScope : VarScope
bool isStruct() { return true; }
int addNewLocalVar(int varSize)
int addNewVar(int varSize)
{ return (offset+=varSize) - varSize; }
// Define it only here since we don't need it anywhere else.
@ -788,7 +835,6 @@ final class ClassScope : TFVScope
MonsterClass cls;
HashTable!(char[], State*) states;
int dataSize; // Data segment size for this class
public:
@ -797,16 +843,13 @@ final class ClassScope : TFVScope
{
cls = cl;
super(last, cls.name.str);
// Connect a class property scope with this scope.
nextProp = ClassProperties.singleton;
}
bool isClass() { return true; }
MonsterClass getClass() { return cls; }
// Add a variable to the data segment, returns the offset.
int addNewDataVar(int varSize)
int addNewVar(int varSize)
{
int tmp = dataSize;
@ -815,19 +858,22 @@ final class ClassScope : TFVScope
return tmp;
}
override void clearId(Token name)
override ScopeLookup lookup(Token name)
{
assert(name.str != "");
State* sd;
if(states.inList(name.str, sd))
fail(format("Identifier '%s' already declared on line %s (as a state)",
name.str, sd.name.loc),
name.loc);
return ScopeLookup(sd.name, LType.State, null, this, sd);
// Check the property list
auto sl = ClassProperties.singleton.lookup(name);
if(sl.isProperty)
return sl;
// Let the parent handle everything else
super.clearId(name);
return super.lookup(name);
}
// Get total data segment size
@ -841,17 +887,6 @@ final class ClassScope : TFVScope
st.index = states.length;
states[st.name.str] = st;
}
State* findState(char[] name)
{
State* st;
if(states.inList(name, st))
return st;
assert(!isRoot());
return parent.findState(name);
}
}
// A scope that keeps track of the stack
@ -885,7 +920,7 @@ abstract class StackScope : VarScope
// Allocate a local variable on the stack, and return the offset.
// The parameter gives the size of the requested variable in ints (4
// bytes.)
int addNewLocalVar(int varSize)
int addNewVar(int varSize)
{
assert(expStack == 0);
@ -935,8 +970,6 @@ class FuncScope : StackScope
}
override:
void insertLabel(StateLabel *lb)
{ assert(0, "cannot insert labels in function scopes"); }
bool isFunc() { return true; }
Function *getFunction() { return fnc; }
@ -947,7 +980,10 @@ class CodeScope : StackScope
this(Scope last, CodeBlock cb)
{
char[] name = "codeblock";
if(cb.isState) name = "stateblock";
assert(cb !is null);
if(cb.isState)
name = "stateblock";
super(last, name);
}
@ -980,11 +1016,11 @@ class ArrayScope : StackScope
// scope are defined in properties.d
abstract class PropertyScope : Scope
{
this(char[] n) { super(null, n); }
this(char[] n, PropertyScope last = null) { super(last, n); }
// Override these in base classes.
Type getPropType(char[] name, Type oType);
Type getType(char[] name, Type oType);
void getValue(char[] name, Type oType);
bool hasProperty(char[] name);
bool isStatic(char[] name, Type oType);
@ -996,51 +1032,17 @@ abstract class PropertyScope : Scope
override:
final:
bool isProperty() { return true; }
// No need for collision checks
void clearId(Token name)
{ assert(isRoot()); }
bool allowRoot() { return true; }
void getProperty(Token name, Type ownerType, ref Property p)
ScopeLookup lookup(Token name)
{
assert(hasProperty(name));
// Does this scope contain the property?
if(hasProperty(name.str))
return ScopeLookup(name, LType.Property, null, this);
p.scp = this;
p.name = name.str;
p.oType = ownerType;
// Check the parent scope
return super.lookup(name);
}
// Check if we have a given property.
bool hasProperty(Token name) { return hasProperty(name.str); }
}
// A reference to a property, used in VariableExpr.
struct Property
{
PropertyScope scp;
char[] name; // Name of the property
Type oType; // Type of the owner
// Get the type of this property
Type getType() { return scp.getPropType(name, oType); }
// Push the value (of type getType) onto the stack. Assumes the
// owner (of type oType) has already been pushed onto the stack,
// unless the property is static.
void getValue() { scp.getValue(name, oType); }
// Pops a value (of type getType) off the stack and sets the
// property. Can only be called for lvalues.
void setValue() { assert(isLValue); scp.setValue(name, oType); }
// Can we write to this property?
bool isLValue() { return scp.isLValue(name, oType); }
// Is this property static? If it is, we do not have to push the
// owner onto the stack before using the property.
bool isStatic() { return scp.isStatic(name, oType); }
}
// Scope inside of loops. Handles break and continue, loop labels, and
@ -1108,21 +1110,21 @@ class LoopScope : CodeScope
continueLabel = new LabelStatement(getTotLocals());
}
override void clearId(Token name)
override ScopeLookup lookup(Token name)
{
assert(name.str != "");
// Check for loop labels as well
// Check for loop labels
if(loopName.str == name.str)
fail(format("Identifier %s is a loop label on line %s and cannot be redefined.",
name.str, loopName.loc),
name.loc);
return ScopeLookup(loopName, LType.LoopLabel, null, this);
super.clearId(name);
return super.lookup(name);
}
// Get the break or continue label for the given named loop, or the
// innermost loop if name is empty or omitted.
// innermost loop if name is empty or omitted. TODO: Might fold
// these into lookup as well. For non-named labels we could use
// __closest_loop__ or something like that internally.
LabelStatement getBreak(char[] name = "")
{
if(name == "" || name == loopName.str)

@ -66,6 +66,103 @@ class ExprStatement : Statement
void compile() { exp.evalPop(); }
}
/* Handles:
import type1, type2, ...; // module scope
import exp1, exp2, ...; // local scope (not implemented yet)
Also handles:
with(exp1, etc) statement;
which is equivalent to:
{
import exp1, etc;
statement;
}
*/
class ImportStatement : Statement
{
Type typeList[];
MonsterClass mc;
bool isLocal;
Statement stm;
this(bool local = false) { isLocal = local; }
// TODO: Things like this should be completely unnecessary - we'll
// fix a simpler and more concise parser-system later.
static bool canParse(TokenArray toks)
{ return
isNext(toks, TT.Import) ||
isNext(toks, TT.With);
}
void parse(ref TokenArray toks)
{
void getList()
{
typeList = [Type.identify(toks)];
while(isNext(toks, TT.Comma))
typeList ~= Type.identify(toks);
}
if(isNext(toks, TT.Import, loc))
{
// form: import ImportList;
getList();
reqNext(toks, TT.Semicolon);
}
else if(isNext(toks, TT.With, loc))
{
// form: with(ImportList) statement
if(!isLocal)
fail("with(...) not allowed in class scopes", loc);
reqNext(toks, TT.LeftParen);
getList();
reqNext(toks, TT.RightParen);
stm = CodeBlock.identify(toks, false);
}
else assert(0);
}
void resolve(Scope sc)
{
// If a statement is present (ie. if this is a with-statement),
// wrap the imports in a code scope so they become temporary.
if(stm !is null)
sc = new CodeScope(sc, "with-scope");
// Add the imports to the scope
foreach(type; typeList)
{
type.resolve(sc);
if(type.isReplacer)
type = type.getBase();
if(!type.isObject)
fail("Can only import from classes", type.loc);
auto t = cast(ObjectType)type;
assert(t !is null);
mc = t.getClass(type.loc);
sc.registerImport(new ImportHolder(mc));
}
// Resolve the statement if present
if(stm !is null)
stm.resolve(sc);
}
void compile()
{
if(stm !is null)
stm.compile();
}
}
// Destination for a goto statement, on the form 'label:'. This
// statement is actually a bit complicated, since it must handle jumps
// occuring both above and below the label. Seeing as the assembler
@ -157,7 +254,9 @@ class LabelStatement : Statement
// Do the magic. Just tell the scope that we exist and let it
// handle the rest.
sc.insertLabel(lb);
auto scp = sc.getState().sc;
assert(scp !is null);
scp.insertLabel(lb);
stacklevel = sc.getTotLocals();
assert(stacklevel == 0);
@ -750,7 +849,7 @@ class ForeachStatement : Statement
index.var.type.toString, index.var.name.loc);
}
// If not, allocate a stack value for it anyway.
else sc.addNewLocalVar(1);
else sc.addNewVar(1);
// Check that the array is in fact an array
arrayExp.resolve(sc);
@ -773,7 +872,7 @@ class ForeachStatement : Statement
// a local variable. This is not the same as the index variable
// above - the iterator index is an internal variable used by
// the system for storing the iterator state.
sc.addNewLocalVar(1);
sc.addNewVar(1);
// This defines the stack level of the loop internals. Break and
// continue labels are set up so that they return to this level
@ -1126,10 +1225,12 @@ class StateStatement : Statement, LabelUser
// Check the state name.
if(stateName.type == TT.Identifier)
{
stt = sc.findState(stateName.str);
if(stt is null)
auto sl = sc.lookup(stateName);
if(!sl.isState)
fail("Undefined state " ~ stateName.str, stateName.loc);
stt = sl.state;
// If a label is specified, tell the state that we are
// asking for a label. The function will set label for us,
// either now or when the label is resolved. The label will
@ -1159,7 +1260,7 @@ class StateStatement : Statement, LabelUser
return;
}
int cindex = stt.sc.getClass().getIndex();
int cindex = stt.owner.getTreeIndex();
if(label is null)
tasm.setState(stt.index, -1, cindex);
@ -1387,6 +1488,7 @@ class CodeBlock : Statement
else if(GotoStatement.canParse(toks)) b = new GotoStatement;
else if(ContinueBreakStatement.canParse(toks)) b = new ContinueBreakStatement;
else if(ForeachStatement.canParse(toks)) b = new ForeachStatement;
else if(ImportStatement.canParse(toks)) b = new ImportStatement(true);
// switch / select
// case
// assert ?

@ -46,6 +46,7 @@ struct State
StateLabel* labelList[];
StateScope sc; // Scope for this state
MonsterClass owner; // Class where this state was defined
// State declaration - used to resolve forward references. Should
// not be kept around when compilation is finished.
@ -159,6 +160,7 @@ class StateDeclaration : Statement
// rules, such as allowing idle functions and disallowing
// variable declarations.
st.sc = new StateScope(last, st);
st.owner = st.sc.getClass();
// Resolve the interior of the code block
assert(code !is null);

@ -91,24 +91,17 @@ enum TT
// Keywords. Note that we use Class as a separator below, so it
// must be first in this list. All operator tokens must occur
// before Class, and all keywords must come after Class.
Class, Module,
For,
If,
Else,
Foreach,
ForeachRev,
Do,
While,
Until,
Class, Module, Singleton,
If, Else,
For, Foreach, ForeachRev,
Do, While, Until,
Continue, Break,
Typeof,
Return,
Continue,
Break,
Switch,
Select,
Switch, Select,
State,
Struct, Enum, Thread,
Singleton, Clone, Override, Final, Function,
Import, Clone, Override, Final, Function, With,
This, New, Static, Const, Out, Ref, Abstract, Idle,
Public, Private, Protected, True, False, Native, Null,
Goto, Halt, Var, In,
@ -131,6 +124,17 @@ struct Token
Floc loc;
char[] toString() { return str; }
static Token opCall(char[] name, Floc loc)
{ return Token(TT.Identifier, name, loc); }
static Token opCall(TT tt, char[] name, Floc loc)
{
Token t;
t.type = tt;
t.str = name;
t.loc = loc;
return t;
}
}
// Used to look up keywords.
@ -221,6 +225,7 @@ const char[][] tokenList =
TT.Struct : "struct",
TT.Enum : "enum",
TT.Thread : "thread",
TT.Import : "import",
TT.Typeof : "typeof",
TT.Singleton : "singleton",
TT.Clone : "clone",
@ -230,6 +235,7 @@ const char[][] tokenList =
TT.Override : "override",
TT.Final : "final",
TT.Function : "function",
TT.With : "with",
TT.Idle : "idle",
TT.Out : "out",
TT.Ref : "ref",
@ -244,6 +250,13 @@ const char[][] tokenList =
TT.Halt : "halt",
TT.Var : "var",
TT.In : "in",
// These are only used in error messages
TT.StringLiteral : "string literal",
TT.NumberLiteral : "number literal",
TT.CharLiteral : "character literal",
TT.Identifier : "identifier",
TT.EOF : "end of file"
];
class StreamTokenizer

@ -39,6 +39,7 @@ import monster.vm.mclass;
import monster.vm.error;
import std.stdio;
import std.utf;
import std.string;
/*
@ -125,7 +126,8 @@ abstract class Type : Block
Type t = null;
// Find what kind of type this is and create an instance of the
// corresponding class.
// corresponding class. TODO: Lots of redundant objects created
// here.
if(BasicType.canParse(toks)) t = new BasicType();
else if(UserType.canParse(toks)) t = new UserType();
else if(GenericType.canParse(toks)) t = new GenericType();
@ -156,6 +158,10 @@ abstract class Type : Block
return identify(toks, false, exp);
}
// Lookup table of all types
static Type[int] typeList;
int tIndex; // Index of this type
// The complete type name including specifiers, eg. "int[]".
char[] name;
@ -167,6 +173,12 @@ abstract class Type : Block
return meta;
}
this()
{
tIndex = typeList.length;
typeList[tIndex] = this;
}
// Used for easy checking
bool isInt() { return false; }
bool isUint() { return false; }
@ -371,6 +383,18 @@ abstract class Type : Block
assert(0, "doCastCTime not implemented for type " ~ toString);
}
// Cast variable of this type to string
char[] valToString(int[] data)
{
assert(0, "valToString not implemented for " ~ toString);
}
final AIndex valToStringIndex(int[] data)
{
assert(data.length == getSize);
return monster.vm.arrays.arrays.create(valToString(data)).getIndex;
}
void parse(ref TokenArray toks) {assert(0, name);}
void resolve(Scope sc) {assert(0, name);}
@ -445,7 +469,7 @@ abstract class InternalType : Type
final:
bool isLegal() { return false; }
int[] defaultInit() {assert(0, name);}
int getSize() {assert(0, name);}
int getSize() { return 0; }
}
// Handles the 'null' literal. This type is only used for
@ -476,6 +500,29 @@ class NullType : InternalType
}
}
// Helper template for BasicType
abstract class TypeHolderBase
{
char[] getString(int[] data);
}
class TypeHolder(T) : TypeHolderBase
{
T toValue(int[] data)
{
static if(!is(T==bool))
assert(data.length*4 == T.sizeof);
return *(cast(T*)data.ptr);
}
char[] getString(int [] data)
{
static if(is(T == dchar))
return toUTF8([toValue(data)]);
else
return .toString(toValue(data));
}
}
// Handles all the built-in types. These are: int, uint, long, ulong,
// float, double, bool, char and the "void" type, which is represented
// by an empty string. The void type is only allowed in special cases
@ -500,6 +547,25 @@ class BasicType : Type
store[tn] = this;
}
TypeHolderBase tph;
TypeHolderBase getHolder()
{
if(tph is null)
{
if(isInt) tph = new TypeHolder!(int);
if(isUint) tph = new TypeHolder!(uint);
if(isLong) tph = new TypeHolder!(long);
if(isUlong) tph = new TypeHolder!(ulong);
if(isFloat) tph = new TypeHolder!(float);
if(isDouble) tph = new TypeHolder!(double);
if(isChar) tph = new TypeHolder!(dchar);
if(isBool) tph = new TypeHolder!(bool);
}
assert(!isVoid);
return tph;
}
private static BasicType[char[]] store;
// Get a basic type of the given name. This will not allocate a new
@ -508,6 +574,7 @@ class BasicType : Type
{
if(tn in store) return store[tn];
// Automatically adds itself to the store
return new BasicType(tn);
}
@ -591,13 +658,6 @@ class BasicType : Type
return false;
}
bool canCastCTime(Type to)
{
// We haven't implemented compile time string casting yet
if(to.isString) return false;
return canCastTo(to);
}
void evalCastTo(Type to)
{
assert(this != to);
@ -630,31 +690,38 @@ class BasicType : Type
{
assert(!isVoid);
if(isIntegral)
tasm.castIntToString(this);
else if(isFloating)
tasm.castFloatToString(this);
else if(isBool) tasm.castBoolToString();
// Create an array from one element on the stack
else if(isChar) tasm.popToArray(1, 1);
else assert(0, name ~ " not done yet");
if(isChar) tasm.popToArray(1, 1);
else
tasm.castToString(tIndex);
}
else
fail("Conversion " ~ toString ~ " to " ~ to.toString ~
" not implemented.");
}
char[] valToString(int[] data)
{
assert(data.length == getSize);
assert(!isVoid);
return getHolder().getString(data);
}
int[] doCastCTime(int[] data, Type to)
{
assert(this != to);
assert(!isVoid);
assert(!to.isVoid);
if(to.isString)
return [valToStringIndex(data)];
int fromSize = getSize();
int toSize = to.getSize();
bool fromSign = isInt || isLong || isFloat || isBool;
assert(data.length == fromSize);
bool fromSign = isInt || isLong || isFloat || isBool;
if(to.isInt || to.isUint)
{
@ -799,10 +866,18 @@ class ObjectType : Type
fail("Cannot use module " ~ name ~ " as a type", loc);
}
char[] valToString(int[] data)
{
assert(data.length == 1);
return format("%s#%s", name, data[0]);
}
int getSize() { return 1; }
bool isObject() { return true; }
int[] defaultInit() { return makeData!(int)(0); }
bool canCastCTime(Type to) { return false; }
bool canCastTo(Type type)
{
assert(clsIndex != 0);
@ -836,14 +911,11 @@ class ObjectType : Type
auto tt = cast(ObjectType)to;
assert(tt !is null);
assert(clsIndex !is tt.clsIndex);
int cnum = tt.clsIndex;
tasm.upcast(cnum);
return;
}
assert(to.isString);
tasm.castObjToString();
tasm.castToString(tIndex);
}
// Members of objects are resolved in the class scope.
@ -902,6 +974,50 @@ class ArrayType : Type
return ArrayProperties.singleton;
}
// All arrays can be cast to string
bool canCastTo(Type to)
{ return to.isString; }
void evalCastTo(Type to)
{
assert(to.isString);
tasm.castToString(tIndex);
}
int[] doCastCTime(int[] data, Type to)
{
assert(to.isString);
return [valToStringIndex(data)];
}
char[] valToString(int[] data)
{
assert(data.length == 1);
// Get the array reference
ArrayRef *arf = monster.vm.arrays.arrays.getRef(cast(AIndex)data[0]);
// Empty array?
if(arf.iarr.length == 0) return "[]";
assert(arf.elemSize == base.getSize);
assert(arf.iarr.length == arf.length * arf.elemSize);
if(isString) return format("\"%s\"", arf.carr);
char[] res = "[";
// For element of the array, run valToString on the appropriate
// data
for(int i; i<arf.iarr.length; i+=arf.elemSize)
{
if(i != 0) res ~= ',';
res ~= base.valToString(arf.iarr[i..i+arf.elemSize]);
}
return res ~ "]";
}
// We are a string (char[]) if the base type is char.
bool isString()
{
@ -914,6 +1030,7 @@ class ArrayType : Type
if(base.isReplacer())
base = base.getBase();
name = base.name ~ "[]";
}
}
@ -1012,29 +1129,8 @@ class StructType : Type
// Scope getMemberScope() { return sc; }
}
/*
// A type that mimics another after it is resolved. Used in cases
// where we can't know the real type until the resolve phase.
abstract class Doppelganger : Type
{
private:
bool resolved;
Type realType;
public:
override:
int getSize()
{
assert(resolved);
return realType.getSize();
}
}
*/
// A 'delayed lookup' type - can replace itself after resolve is
// called.
// OK - SCREW THIS. Make a doppelganger instead.
abstract class ReplacerType : InternalType
{
protected:
@ -1072,19 +1168,34 @@ class UserType : ReplacerType
void resolve(Scope sc)
{
realType = sc.findStruct(id.str);
auto sl = sc.lookupImport(id);
if(realType is null)
// Not a struct. Maybe an enum?
realType = sc.findEnum(id.str);
if(sl.isImport)
sl = sl.imphold.mc.sc.lookup(id);
if(realType is null)
// Default to class name if nothing else is found. We can't
// really check this here since it might be a forward
// reference. These are handled later on.
realType = new ObjectType(id);
// Is it a type?
if(sl.isType)
// Great!
realType = sl.type;
// If not, maybe a class?
else if(sl.isClass)
// Splendid
realType = sl.mc.objType;
realType.resolve(sc);
// Was anything found at all?
else if(!sl.isNone)
// Ouch, something was found that's not a type or class.
fail("Cannot use " ~ sl.name.str ~ " (which is a " ~
LTypeName[sl.ltype] ~ ") as a type!", id.loc);
if(realType is null)
{
// Nothing was found. Assume it's a forward reference to a
// class. These are handled later on.
realType = new ObjectType(id);
realType.resolve(sc);
}
assert(realType !is this);
assert(!realType.isReplacer);
@ -1165,6 +1276,24 @@ class MetaType : InternalType
return type.isString;
}
char[] valToString(int[] data)
{
assert(data.length == 0);
return base.name;
}
int[] cache;
int[] doCastCTime(int[] data, Type to)
{
assert(to.isString);
if(cache.length == 0)
cache = [valToStringIndex(data)];
return cache;
}
void evalCastTo(Type to)
{
assert(to.isString);

@ -30,6 +30,7 @@ import monster.compiler.expression;
import monster.compiler.scopes;
import monster.compiler.statement;
import monster.compiler.block;
import monster.compiler.operators;
import monster.compiler.assembler;
import std.string;
@ -266,15 +267,8 @@ class VarDeclaration : Block
{
var.type.resolve(sc);
//writefln("Type is: %s", var.type);
if(var.type.isReplacer)
{
//writefln(" (we're here!)");
var.type = var.type.getBase();
}
//writefln(" now it is: %s", var.type);
var.type = var.type.getBase();
// Allow 'const' for function array parameters
if(isParam && var.type.isArray())
@ -310,7 +304,7 @@ class VarDeclaration : Block
// We are inside ourselves
assert(sc.isStruct);
fail("Struct variables cannot be used inside the struct itself!",
fail("Struct variables cannot be declared inside the struct itself!",
loc);
}
}
@ -331,21 +325,14 @@ class VarDeclaration : Block
assert(var.sc !is null, "variables can only be declared in VarScopes");
if(!isParam)
{
// If we are not a function parameter, we must get
// var.number from the scope.
if(sc.isClass())
// Class variable. Get a position in the data segment.
var.number = sc.addNewDataVar(var.type.getSize());
else
// We're a local variable. Ask the scope what number we
// should have.
var.number = sc.addNewLocalVar(var.type.getSize());
}
else assert(sc.isFunc());
// If we are not a function parameter, we must get
// var.number from the scope.
var.number = sc.addNewVar(var.type.getSize());
else
assert(sc.isFunc());
// Insert ourselves into the scope.
sc.insertVar(var);
var.sc.insertVar(var);
}
int[] getCTimeValue()
@ -397,8 +384,12 @@ class VarDeclaration : Block
class VariableExpr : MemberExpression
{
Token name;
Variable *var;
Property prop;
ScopeLookup look;
// Used to simulate a member for imported variables
DotOperator dotImport;
bool recurse = true;
enum VType
{
@ -413,7 +404,6 @@ class VariableExpr : MemberExpression
}
VType vtype;
int classIndex = -1; // Index of the class that owns this variable.
CIndex singCls = -1; // Singleton class index
@ -428,29 +418,23 @@ class VariableExpr : MemberExpression
}
// Does this variable name refer to a type name rather than an
// actual variable?
bool isType()
{
return type.isMeta();
}
// actual variable? TODO: Could be swapped for "requireStatic" or
// similar in expression instead.
bool isType() { return type.isMeta(); }
bool isProperty()
out(res)
{
if(res)
{
assert(prop.name != "");
assert(var is null);
assert(vtype == VType.Property);
assert(!isSpecial);
}
else
{
assert(prop.name == "");
}
else assert(vtype != VType.Property);
}
body
{
return vtype == VType.Property;
return look.isProperty();
}
bool isSpecial() { return vtype == VType.Special; }
@ -467,7 +451,7 @@ class VariableExpr : MemberExpression
// Properties may or may not be changable
if(isProperty)
return prop.isLValue;
return look.isPropLValue;
// Normal variables are always lvalues.
return true;
@ -477,18 +461,17 @@ class VariableExpr : MemberExpression
{
// Properties can be static
if(isProperty)
return prop.isStatic;
return look.isPropStatic;
// Type names are always static. However, isType will return
// false for type names of eg. singletons, since these will not
// resolve to a meta type.
// Type names are always static.
if(isType)
return true;
// Currently no other static variables
return false;
}
// TODO: isCTime - should be usable for static properties and members
bool isCTime() { return isType; }
void parse(ref TokenArray toks)
{
@ -496,20 +479,13 @@ class VariableExpr : MemberExpression
loc = name.loc;
}
void writeProperty()
{
assert(isProperty);
prop.setValue();
}
void resolve(Scope sc)
out
{
// Some sanity checks on the result
if(isProperty) assert(var is null);
if(var !is null)
if(look.isVar)
{
assert(var.sc !is null);
assert(look.var.sc !is null);
assert(!isProperty);
}
assert(type !is null);
@ -521,36 +497,40 @@ class VariableExpr : MemberExpression
{
// Look up the name in the scope belonging to the owner
assert(leftScope !is null);
look = leftScope.lookup(name);
type = look.type;
// Check first if this is a variable
var = leftScope.findVar(name.str);
if(var !is null)
if(look.isVar)
{
// We are a member variable
type = var.type;
// The object pointer is pushed on the stack. We must
// also provide the class index, so the variable is
// changed in the correct class (it could be a parent
// class of the given object.)
// We are a class member variable.
vtype = VType.FarOtherVar;
assert(var.sc.isClass);
classIndex = var.sc.getClass().getIndex();
assert(look.sc.isClass);
return;
}
// Check for properties last
if(leftScope.findProperty(name, ownerType, prop))
// Check for properties
if(look.isProperty)
{
// We are a property
// TODO: Need to account for ownerType here somehow -
// rewrite the property system
vtype = VType.Property;
type = prop.getType;
type = look.getPropType(ownerType);
return;
}
// Check types too
if(look.isType)
{
vtype = VType.Type;
type = look.type.getMeta();
return;
}
// No match
fail(name.str ~ " is not a member of " ~ ownerType.toString,
fail(name.str ~ " is not a variable member of " ~ ownerType.toString,
loc);
}
@ -564,54 +544,61 @@ class VariableExpr : MemberExpression
return;
}
if(name.type == TT.Const)
fail("Cannot use const as a variable", name.loc);
if(name.type == TT.Const || name.type == TT.Clone)
fail("Cannot use " ~ name.str ~ " as a variable", name.loc);
// Not a member or a special name. Look ourselves up in the
// local scope, and include imported scopes.
look = sc.lookupImport(name);
if(name.type == TT.Clone)
fail("Cannot use clone as a variable", name.loc);
if(look.isImport)
{
// We're imported from another scope. This means we're
// essentially a member variable. Let DotOperator handle
// this.
dotImport = new DotOperator(look.imphold, this, loc);
dotImport.resolve(sc);
return;
}
// These are special cases that work both as properties
// (object.state) and as non-member variables (state=...) inside
// class functions / state code. Since we already handle them
// nicely as properties, treat them as properties.
// nicely as properties, treat them as properties even if
// they're not members.
if(name.type == TT.Singleton || name.type == TT.State)
{
if(!sc.isInClass)
fail(name.str ~ " can only be used in classes", name.loc);
if(!sc.findProperty(name, sc.getClass().objType, prop))
assert(0, "should have found property " ~ name.str ~
" in scope " ~ sc.toString);
assert(look.isProperty, name.str ~ " expression not implemented yet");
vtype = VType.Property;
type = prop.getType;
type = look.getPropType(ownerType);
return;
}
// Not a member, property or a special name. Look ourselves up
// in the local variable scope.
var = sc.findVar(name.str);
type = look.type;
if(var !is null)
if(look.isVar)
{
type = var.type;
assert(var.sc !is null);
assert(look.sc !is null);
assert(look.sc is look.var.sc);
// Class variable?
if(var.sc.isClass)
if(look.sc.isClass)
{
// Check if it's in THIS class, which is a common
// case. If so, we can use a simplified instruction that
// doesn't have to look up the class.
if(var.sc.getClass is sc.getClass)
if(look.sc.getClass is sc.getClass)
vtype = VType.ThisVar;
else
{
// It's another class. For non-members this can only
// mean a parent class.
vtype = VType.ParentVar;
classIndex = var.sc.getClass().getIndex();
}
}
else
@ -620,46 +607,64 @@ class VariableExpr : MemberExpression
return;
}
// We are not a variable. Our last chance is a type name.
vtype = VType.Type;
if(BasicType.isBasic(name.str))
// We are not a variable. Last chance is a type name / class.
if(!look.isType && !look.isClass)
{
// Yes! Basic type.
type = MetaType.getBasic(name.str);
// Still no match. Might be an unloaded class however,
// lookup() doesn't load classes. Try loading it.
if(global.findParsed(name.str) is null)
// No match at all.
fail("Undefined identifier "~name.str, name.loc);
// We found a class! Check that we can look it up now
look = sc.lookup(name);
assert(look.isClass);
}
vtype = VType.Type;
// Class name?
else if(auto mc = global.findParsed(name.str))
if(look.isClass)
{
// This doesn't allow forward references.
mc.requireScope();
type = mc.classType;
assert(look.mc !is null);
look.mc.requireScope();
type = look.mc.classType;
// Singletons are treated differently - the class name can
// be used to access the singleton object
if(mc.isSingleton)
if(look.mc.isSingleton)
{
type = mc.objType;
singCls = mc.getIndex();
type = look.mc.objType;
singCls = look.mc.getIndex();
}
}
// Struct?
else if(auto tp = sc.findStruct(name.str))
{
type = tp.getMeta();
}
// Err, enum?
else if(auto tp = sc.findEnum(name.str))
else
{
type = tp.getMeta();
assert(look.type !is null);
type = look.type.getMeta();
}
else
// No match at all
fail("Undefined identifier "~name.str, name.loc);
}
int[] evalCTime()
{
assert(isCTime);
assert(isType);
assert(type.getSize == 0);
return null;
}
void evalAsm()
{
assert(!isType);
// Hairy. But does the trick for now.
if(dotImport !is null && recurse)
{
recurse = false;
dotImport.evalAsm();
return;
}
if(isType) return;
setLine();
@ -675,7 +680,7 @@ class VariableExpr : MemberExpression
// Property
if(isProperty)
{
prop.getValue();
look.getPropValue();
return;
}
@ -692,6 +697,7 @@ class VariableExpr : MemberExpression
// Normal variable
int s = type.getSize;
auto var = look.var;
if(vtype == VType.LocalVar)
// This is a variable local to this function. The number gives
@ -705,13 +711,13 @@ class VariableExpr : MemberExpression
else if(vtype == VType.ParentVar)
// Variable in a parent but this object
tasm.pushParentVar(var.number, classIndex, s);
tasm.pushParentVar(var.number, var.sc.getClass().getTreeIndex(), s);
else if(vtype == VType.FarOtherVar)
// Push the value from a "FAR pointer". The class index should
// already have been pushed on the stack by DotOperator, we
// only push the index.
tasm.pushFarClass(var.number, classIndex, s);
tasm.pushFarClass(var.number, var.sc.getClass().getTreeIndex(), s);
else assert(0);
}
@ -719,6 +725,13 @@ class VariableExpr : MemberExpression
// Push the address of the variable rather than its value
void evalDest()
{
if(dotImport !is null && recurse)
{
recurse = false;
dotImport.evalDest();
return;
}
assert(!isType, "types can never be written to");
assert(isLValue());
assert(!isProperty);
@ -726,28 +739,71 @@ class VariableExpr : MemberExpression
setLine();
// No size information is needed for addresses.
auto var = look.var;
if(vtype == VType.LocalVar)
tasm.pushLocalAddr(var.number);
else if(vtype == VType.ThisVar)
tasm.pushClassAddr(var.number);
else if(vtype == VType.ParentVar)
tasm.pushParentVarAddr(var.number, classIndex);
tasm.pushParentVarAddr(var.number, var.sc.getClass().getTreeIndex());
else if(vtype == VType.FarOtherVar)
tasm.pushFarClassAddr(var.number, classIndex);
tasm.pushFarClassAddr(var.number, var.sc.getClass().getTreeIndex());
else assert(0);
}
void postWrite()
void store()
{
assert(!isProperty);
assert(isLValue());
assert(var.sc !is null);
if(var.isRef)
// TODO: This assumes all ref variables are foreach values,
// which will probably not be true in the future.
tasm.iterateUpdate(var.sc.getLoopStack());
if(dotImport !is null && recurse)
{
recurse = false;
dotImport.store();
return;
}
assert(isLValue);
if(isProperty)
look.setPropValue();
else
{
assert(look.isVar);
auto var = look.var;
// Get the destination and move the data
evalDest();
tasm.mov(type.getSize());
assert(var.sc !is null);
if(var.isRef)
// TODO: This assumes all ref variables are foreach values,
// which will probably not be true in the future.
tasm.iterateUpdate(var.sc.getLoopStack());
}
}
void incDec(TT op, bool post)
{
if(dotImport !is null && recurse)
{
recurse = false;
dotImport.incDec(op, post);
return;
}
if(!isProperty)
{
assert(look.isVar);
auto var = look.var;
super.incDec(op, post);
assert(var.sc !is null);
if(var.isRef)
tasm.iterateUpdate(var.sc.getLoopStack());
}
else fail("Cannot use ++ and -- on properties yet", loc);
}
}

@ -0,0 +1,10 @@
module monster.modules.all;
import monster.modules.io;
import monster.modules.timer;
void initAllModules()
{
initIOModule();
initTimerModule();
}

@ -0,0 +1,46 @@
// This module provides simple output functions for Monster. The 'i'
// part (input) isn't really there yet.
module monster.modules.io;
import std.stdio;
import monster.monster;
const char[] moduleDef =
"module io;
native write(char[][] args...);
native writeln(char[][] args...);
native writes(char[][] args...);
native writelns(char[][] args...);
native print(char[][] args...);
"; //"
void doWrite(bool space)
{
AIndex[] args = stack.popAArray();
char[] form = "%s";
if(space) form = "%s ";
foreach(AIndex ind; args)
writef(form, arrays.getRef(ind).carr);
fflush(stdout);
}
void initIOModule()
{
static MonsterClass mc;
if(mc !is null) return;
mc = new MonsterClass(MC.String, moduleDef, "io");
mc.bind("write", { doWrite(false); });
mc.bind("writeln", { doWrite(false); writefln(); });
mc.bind("writes", { doWrite(true); });
mc.bind("writelns", { doWrite(true); writefln(); });
// Print is just another name for writelns
mc.bind("print", { doWrite(true); writefln(); });
}

@ -0,0 +1,12 @@
/*
NOTE: This file is not used - it is here just for reference. The
real module is defined internally in io.d.
*/
module io;
native write(char[][] args...);
native writeln(char[][] args...);
native writes(char[][] args...);
native writelns(char[][] args...);
native print(char[][] args...);

@ -0,0 +1,171 @@
// This module contains (or will contain) various routines for
// timing. It is also home of the ubiquitous "sleep" idle function.
module monster.modules.timer;
import std.stdio;
import std.date;
// For some utterly idiotic reason, DMD's public imports will suddenly
// stop working from time to time.
import monster.vm.mclass;
import monster.vm.mobject;
import monster.vm.stack;
import monster.vm.thread;
import monster.vm.idlefunction;
import monster.monster;
const char[] moduleDef =
"singleton timer;
idle sleep(float secs);
"; //"
// Sleep a given amount of time. This implementation uses the system
// clock and is the default.
class IdleSleep_SystemClock : IdleFunction
{
override:
bool initiate(Thread* cn)
{
// Get the parameter
float secs = stack.popFloat;
// Get current time
cn.idleData.l = getUTCtime();
// Calculate when we should return
cn.idleData.l += secs*TicksPerSecond;
// Schedule us
return true;
}
bool hasFinished(Thread* cn)
{
// Is it time?
return getUTCtime() >= cn.idleData.l;
}
}
// This implementation uses a user-driven timer instead of the system
// clock. It's more efficient, but requires the user to update the
// given timer manuall each frame. The default sleep (timer.sleep) is
// bound to the default timer, but it's possible to create multiple
// independent timers.
class IdleSleep_Timer : IdleFunction
{
override:
bool initiate(Thread* cn)
{
// The timer is stored in the object's 'extra' pointer
auto t = cast(SleepManager)cn.extraData.obj;
assert(t !is null);
// Calculate the return time
cn.idleData.l = t.current + cast(long)(t.tickSize*stack.popFloat);
// Schedule us
return true;
}
bool hasFinished(Thread* cn)
{
// Get the timer
auto t = cast(SleepManager)cn.extraData.obj;
assert(t !is null);
// Is it time?
return t.current >= cn.idleData.l;
}
}
// A manually updated timer. This can be improved quite a lot: Most
// sleep operations (depending on application of course) will skip
// many frames before they return. For example, for sleep(0.5) at 100
// fps, hasFinished will return false approximately 50 times before
// returning true. For bigger sleep values and a large number of
// objects, the impact of this is significant. A good solution would
// be to pool scheduled objects together and only perform one check on
// the entire pool. If the pool is due, all the nodes within it are
// inserted into the scheduler for detailed checking. We could have a
// series of such pools, ordered by expiration time, so that we only
// ever need to check the first pool in the list. The optimal pool
// interval, number of pools etc depends on the application and the
// fps - but it should be possible to find some reasonable defaults. A
// more generalized priority queue implementation is also possible.
class SleepManager
{
private:
// Instance of the timer class that is associated with this timer
MonsterObject *tobj;
// Current tick count
long current;
public:
// Specify a Monster object to associate with this timer. Use 'null'
// if you don't need an object.
this(MonsterObject *obj)
{
if(obj is null) return;
tobj = obj;
tobj.getExtra(_timerClass).obj = this;
}
// By default, create a new object
this()
{ this(_timerClass.createObject); }
// Number of 'ticks' per second
static const long tickSize = 1000;
// Reset the timer to zero
void reset() { current = 0; }
// Return the total number of elapsed seconds since start (or last
// reset)
double read() { return current/cast(double)tickSize; }
// Add time to the timer.
void add(double d) { current += cast(long)(tickSize*d); }
void addl(long l) { current += l; }
MonsterObject *getObj() { return tobj; }
}
SleepManager idleTime;
MonsterClass _timerClass;
void initTimerModule()
{
if(_timerClass !is null)
{
assert(idleTime !is null);
return;
}
_timerClass = new MonsterClass(MC.String, moduleDef, "timer");
assert(idleTime is null);
idleTime = new SleepManager(_timerClass.getSing());
setSleepMethod(SleepMethod.Clock);
}
enum SleepMethod
{ Clock, Timer }
void setSleepMethod(SleepMethod meth)
{
initTimerModule();
if(meth == SleepMethod.Clock)
_timerClass.bind("sleep", new IdleSleep_SystemClock);
else if(meth == SleepMethod.Timer)
_timerClass.bind("sleep", new IdleSleep_Timer);
else assert(0, "unknown timer method");
}

@ -0,0 +1,7 @@
/*
NOTE: This file is not used - it is here just for reference. The
real module is defined internally in timer.d.
*/
singleton timer;
idle sleep(float secs);

@ -27,15 +27,17 @@ module monster.monster;
public
{
// These should contain all you need for normal usage.
import monster.vm.mclass;
import monster.vm.mobject;
import monster.vm.mclass;
import monster.vm.stack;
import monster.vm.vm;
import monster.vm.scheduler;
import monster.vm.thread;
import monster.vm.idlefunction;
import monster.vm.arrays;
import monster.vm.params;
import monster.vm.error;
import monster.modules.all;
}
private import monster.compiler.tokenizer;
@ -54,6 +56,6 @@ static this()
// Initialize VM
scheduler.init();
stack.init();
initStack();
arrays.initialize();
}

@ -24,10 +24,269 @@
module monster.util.freelist;
// TODO: Create unittests
import monster.util.list;
import monster.util.growarray;
import std.c.stdlib : malloc, free;
// A freelist for buffers of a given size. The 'size' template
// parameter gives the total requested size of the entire struct,
// including overhead.
struct BufferList(int size)
{
// Calculate the 'overhead' size of the structs
alias void* vp;
static const junk = 2*vp.sizeof + int.sizeof;
static union BuffData(int size)
{
static assert(size >= 2, "size must be at least 2");
int[size/int.sizeof] ints;
ubyte[size] bytes;
}
// Get the data sizes
static const bytes = size - junk;
static const ints = bytes / int.sizeof;
static assert(bytes > 0, "size is too small");
alias BuffData!(bytes) Value;
alias Value* ValuePtr;
static assert(Value.sizeof == bytes);
alias LinkedList!(Value, NoAlloc) List;
alias List.Node LNode;
static struct BufferNode(int size)
{
LNode data;
int index;
}
alias BufferNode!(bytes) Node;
alias Node* NodePtr;
static assert(Node.sizeof == size);
private:
// This is the array that does all the actual allocations. It is
// used for quickly looking up indices. GrowArrays are designed to
// grow dynamically without reallocation, while still being easily
// indexed like a normal array.
static GrowArray!(Node) array;
// The freelist. This is shared between all template instances of
// the same size.
static List freeList;
// The nodes belonging to THIS list instance
List nodes;
// Get a new node (move from freelist to node list)
ValuePtr getNew()
{
// Is the freelist empty?
if(freeList.length == 0)
{
// Create a bunch of nodes and shove them into the freelist.
const makeSize = 50;
// Grow the growarray
uint len = array.length;
array.length = len + makeSize;
// Loop through the new nodes, number them, and insert them
// into freeList
for(int i=0; i < makeSize; i++)
{
NodePtr fn = array.getPtr(i+len);
fn.index = i + len;
freeList.insertNode(&fn.data);
}
}
// Move the first element from the freelist into the node list.
auto node = freeList.getHead;
freeList.removeNode(node);
nodes.insertNodeFirst(node);
// Return the value pointer. Since the value is always at the
// begining of the Node struct, this is the same
// pointer.
return &node.value;
}
// Move a node back to the freelist ("delete" it)
void remove(ValuePtr node)
{
nodes.removeNode(node);
freeList.insertNodeFirst(node);
}
public:
// Get the node corresponding to an index
static ValuePtr getNode(int index)
{
return &array.getPtr(index).data.value;
}
// Get the index from a node
static int getIndex(ValuePtr node)
{
return ( cast(Node*)node ).index;
}
uint length() { return nodes.length; }
static uint totLength() { return array.length; }
// Move the given node to another list
ValuePtr moveTo(ref BufferList fl, ValuePtr node)
{
nodes.removeNode(node);
fl.nodes.insertNodeFirst(node);
return node;
}
// Get the first element in the list
ValuePtr getHead() { return &nodes.getHead().value; }
// Loop through the structs in this list
int opApply(int delegate(ref Value) dg)
{
return nodes.opApply(dg);
}
int[] getInt(int isize)
{
assert(isize <= ints);
return getNew().ints[0..isize];
}
void freeInt(int[] buf)
{
assert(buf.length <= ints);
remove(cast(ValuePtr)buf.ptr);
}
void* get() { return getNew(); }
void free(void* p) { remove(cast(ValuePtr)p); }
}
import std.stdio;
struct Buffers
{
static:
BufferList!(64) b64;
BufferList!(128) b128;
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);
else if(size <= b128.ints) return b128.getInt(size);
else if(size <= b256.ints) return b256.getInt(size);
else if(size <= b768.ints) return b768.getInt(size);
// Too large for our lists - just use malloc
else
{
writefln("WARNING: using malloc for %s ints (%s bytes)",
size, size*int.sizeof);
return ( cast(int*)malloc(size*int.sizeof) )[0..size];
}
}
void free(int[] buf)
{
uint size = buf.length;
if(size <= b64.ints) b64.freeInt(buf);
else if(size <= b128.ints) b128.freeInt(buf);
else if(size <= b256.ints) b256.freeInt(buf);
else if(size <= b768.ints) b768.freeInt(buf);
else .free(buf.ptr);
}
}
/* THIS DOESN'T WORK - because DMD is still stubborn with those
template forwarding issues. Instead we'll just reuse the old
freelist implementation below.
// A list that uses a freelist for allocation. It is built on top of
// BufferList.
struct FreeList(T)
{
private:
// For small sizes, pool together with existing lists.
static if(T.sizeof <= 64) static const size = 64;
else static if(T.sizeof <= 128) static const size = 128;
else static if(T.sizeof <= 256) static const size = 256;
// Just use the actual size, rounded up to the nearest 16
else static if(T.sizeof % 16 == 0)
const size = T.sizeof;
else
const size = T.sizeof + 16 - (T.sizeof%16);
alias BufferList!(size) BuffList;
BuffList buffer;
alias BuffList.Value Value;
alias BuffList.ValuePtr ValuePtr;
static assert(T.sizeof <= BuffList.bytes);
public:
// Get a new node (move from freelist to node list)
T* getNew()
{ return cast(T*) buffer.get(); }
// Move a node back to the freelist ("delete" it)
void remove(T* node)
{ buffer.free(node); }
// Get the node corresponding to an index
static T* getNode(int index)
{ return cast(T*) buffer.getNode(index); }
// Get the index from a node
static int getIndex(T *node)
{ return buffer.getIndex(cast(ValuePtr)node); }
uint length() { return buffer.length(); }
static uint totLength() { return buffer.totLength(); }
// Move the given node to another list
T* moveTo(ref FreeList fl, T* node)
{
auto vp = cast(ValuePtr) node;
return cast(T*) buffer.moveTo(fl.buffer, vp);
}
// Get the first element in the list
T* getHead() { return cast(T*) buffer.getHead(); }
// Loop through the structs in this list
int opApply(int delegate(ref T) dg)
{
auto dgc = cast(int delegate(ref Value)) dg;
return nodes.opApply(dgc);
}
}
*/
// This had to be moved outside FreeList to work around some
// irritating DMD template problems. (Can you say Aaargh!)
@ -47,13 +306,6 @@ struct FreeList(T)
private:
/*
static struct _FreeNode
{
TNode data;
int index;
}
*/
alias __FreeNode!(T) _FreeNode;
// This is the array that does all the actual allocations. It is

@ -27,8 +27,7 @@ module monster.util.list;
// Set this to enable some more extensive list checks. These will loop
// 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, and they have saved me in
// the past.
// when doing a little dirty list hacking.
// debug=slowcheck;
@ -583,6 +582,10 @@ struct LinkedList(Value, alias Alloc = GCAlloc)
}
}
alias LinkedList!(void*, NoAlloc) PointerList;
alias PointerList.Node vpNode;
alias PointerList.Iterator vpIter;
/* This test is NOT very complete */
unittest
{

@ -39,7 +39,8 @@ enum SPType
NConst, // Native constructor
// The idle function callbacks are split because they handle the
// stack differently.
// stack differently. We probably don't need to have one type for
// each though.
Idle_Initiate, // IdleFunction.initiate()
Idle_Reentry, // IdleFunction.reentry()
Idle_Abort, // IdleFunction.abort()
@ -65,6 +66,11 @@ struct StackPoint
int afterStack; // Where the stack should be when this function
// returns
int *frame; // Stack frame, stored when entering the function
bool isStatic()
{
return (ftype == SPType.Function) && func.isStatic;
}
}
FunctionStack fstack;
@ -129,12 +135,15 @@ struct FunctionStack
// Set the stack point up as a state
void push(State *st, MonsterObject *obj)
{
assert(st !is null);
push(obj);
cur.ftype = SPType.State;
cur.state = st;
assert(obj !is null);
cur.cls = obj.cls;
assert(st.owner.parentOf(obj.cls));
cur.cls = st.owner;
// Set up the byte code
cur.code.setData(st.bcode, st.lines);
@ -156,11 +165,13 @@ struct FunctionStack
push(obj);
cur.func = fn;
cur.cls = fn.owner;
assert(fn.isIdle, fn.name.str ~ "() is not an idle function");
cur.ftype = tp;
}
// These are used for the various idle callbacks
// These are used for the various idle callbacks. TODO: Probably
// overkill to have one for each, but leave it until you're sure.
void pushIdleInit(Function *fn, MonsterObject *obj)
{ pushIdleCommon(fn, obj, SPType.Idle_Initiate); }

@ -0,0 +1,26 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007, 2008 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (gc.d) is part of the Monster script language
package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
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/ .
*/
// This file will eventually contain the garbage collector.
module monster.vm.gc;

@ -24,15 +24,15 @@
module monster.vm.idlefunction;
import monster.vm.mobject;
import monster.vm.thread;
// A callback class for idle functions. A child object of this class
// is what you "bind" to idle functions (rather than just a delegate,
// like for native functions.) Note that instances are not bound to
// specific script objects; one idle function instance may be called
// for many objects simultaneously. Any data specific to the monster
// object (such as parameters) must be stored elsewhere, usually
// through the 'extra' pointer in MonsterObject.
// specific script objects or threads; one idle function instance may
// be called for many objects / threads simultaneously. Any data
// specific to this call (such as parameters) must be stored
// elsewhere, usually within the Thread.
abstract class IdleFunction
{
// This is called immediately after the idle function is "called"
@ -40,21 +40,21 @@ abstract class IdleFunction
// from the stack), but otherwise does not have to do
// anything. Return true if the scheduler should put this idle
// function into the condition list, which is usually a good
// idea. For functions which never "return", or event driven idle
// functions (which handle their own scheduling), we should return
// false.
bool initiate(MonsterObject*) { return true; }
// idea. For functions which never "return", and for event driven
// idle functions (which handle their own scheduling), you should
// return false.
bool initiate(Thread*) { return true; }
// This is called whenever the idle function is about to "return" to
// state code. It has to push the return value, if any, but
// otherwise it can be empty. Note that if the idle function is
// aborted (eg. state is changed), this function is never called,
// and abort() is called instead.
void reentry(MonsterObject*) {}
// aborted (eg. the state is changed), this function is never
// called, and abort() is called instead.
void reentry(Thread*) {}
// Called whenever an idle function is aborted, for example by a
// state change. No action is usually required.
void abort(MonsterObject*) {}
void abort(Thread*) {}
// The condition that determines if this function has finished. This
// is the main method by which the scheduler determines when to
@ -65,5 +65,5 @@ abstract class IdleFunction
// should return false in initiate and instead reschedule the object
// manually when the event occurs. (A nice interface for this has
// not been created yet, though.)
abstract bool hasFinished(MonsterObject*);
abstract bool hasFinished(Thread*);
}

@ -58,6 +58,7 @@ struct IteratorRef
bool isClass;
MonsterObject *mo;
MonsterClass mc;
// Array iterators
bool firstArray(bool irev, bool iref, int *stk)
@ -112,6 +113,7 @@ struct IteratorRef
stk[1] = cast(int) getIndex();
mo = mc.getFirst();
this.mc = mc;
// Are there any objects?
if(mo == null) return false;
@ -136,7 +138,7 @@ struct IteratorRef
// Handle class iterations seperately
if(isClass)
{
mo = mo.getNext();
mo = mc.getNext(mo);
if(mo == null) return false;
*sindex = cast(int)mo.getIndex();

@ -34,9 +34,7 @@ import monster.compiler.structs;
import monster.compiler.block;
import monster.compiler.enums;
import monster.vm.thread;
import monster.vm.codestream;
import monster.vm.scheduler;
import monster.vm.idlefunction;
import monster.vm.fstack;
import monster.vm.arrays;
@ -45,38 +43,27 @@ import monster.vm.vm;
import monster.vm.mobject;
import monster.util.flags;
import monster.util.freelist;
import monster.util.string;
import monster.util.list;
import monster.util.freelist;
import std.string;
import std.stdio;
import std.file;
import std.stream;
// TODO: Needed to fix DMD/GDC template problems. Remove if this bug
// is fixed.
import monster.util.list;
alias _lstNode!(CodeThread) _tmp1;
alias __FreeNode!(CodeThread) _tmp2;
alias _lstNode!(MonsterObject) _tmp3;
alias __FreeNode!(MonsterObject) _tmp4;
typedef void *MClass; // Pointer to C++ equivalent of MonsterClass.
typedef int CIndex;
alias FreeList!(CodeThread) ThreadList;
// Parameter to the constructor. Decides how the class is created.
// Parameter to the constructor. Decides how the class is
// created. TODO: This system will be removed again, because we'll
// stop using constructors.
enum MC
{
None = 0, // Initial value
File = 1, // Load class from file (default)
NoCase = 2, // Load class from file, case insensitive name match
String = 3, // Load class from string
Stream = 4, // Load class from stream
Manual = 5, // Manually create class
File, // Load class from file (default)
NoCase, // Load class from file, case insensitive name match
String, // Load class from string
}
enum CFlags
@ -116,9 +103,12 @@ final class MonsterClass
{
return
Block.isNext(tokens, TT.Class) ||
Block.isNext(tokens, TT.Singleton) ||
Block.isNext(tokens, TT.Module);
}
static uint getTotalObjects() { return allObjects.length; }
final:
/*******************************************************
@ -127,11 +117,6 @@ final class MonsterClass
* *
*******************************************************/
alias FreeList!(MonsterObject) ObjectList;
// TODO: Put as many of these as possible in the private
// section. Ie. move all of them and see what errors you get.
// Index within the parent tree. This might become a list at some
// point.
int treeIndex;
@ -146,8 +131,15 @@ final class MonsterClass
Type classType; // Type for class references to this class (not
// implemented yet)
private:
// List of objects of this class. Includes objects of all subclasses
// as well.
PointerList objects;
Flags!(CFlags) flags;
public:
bool isParsed() { return flags.has(CFlags.Parsed); }
bool isScoped() { return flags.has(CFlags.Scoped); }
bool isResolved() { return flags.has(CFlags.Resolved); }
@ -183,19 +175,12 @@ final class MonsterClass
* *
*******************************************************/
// By default we leave the loadType at None. This leaves us open to
// define the class later. Calling eg. setName will define the class
// as a manual class. This isn't supported yet though.
this() {}
this(MC type, char[] name1, char[] name2 = "", bool usePath = true)
{
loadType = type;
if(type == MC.File || type == MC.NoCase)
{
loadType = MC.File;
if(type == MC.NoCase)
loadCI(name1, name2, usePath);
else
@ -211,25 +196,18 @@ final class MonsterClass
return;
}
if(type == MC.Manual)
{
assert(name2 == "", "MC.Manual only takes one parameter");
setName(name1);
return;
}
assert(0, "encountered unknown MC type");
}
this(MC type, Stream str, char[] nam = "")
{
assert(type == MC.Stream);
loadType = type;
loadStream(str, nam);
}
this(ref TokenArray toks, char[] nam="")
{ loadTokens(toks, nam); }
this(Stream str, char[] nam="")
{ this(MC.Stream, str, nam); }
{ loadStream(str, nam); }
this(char[] nam1, char[] nam2 = "", bool usePath=true)
{ this(MC.File, nam1, nam2, usePath); }
@ -265,6 +243,9 @@ final class MonsterClass
void load(Stream str, char[] fname="(stream)")
{ loadStream(str, fname); }
void loadTokens(ref TokenArray toks, char[] name)
{ parse(toks, name); }
void loadStream(Stream str, char[] fname="(stream)", int bom = -1)
{
assert(str !is null);
@ -326,11 +307,13 @@ final class MonsterClass
requireScope();
// Get the function from the scope
auto fn = sc.findFunc(name);
auto ln = sc.lookupName(name);
if(fn is null)
if(!ln.isFunc)
fail("Function '" ~ name ~ "' not found.");
auto fn = ln.func;
if(!fn.isNormal && !fn.isNative)
{
// Being here is always bad. Now we just need to find
@ -388,11 +371,13 @@ final class MonsterClass
{
requireScope();
Variable *vb = sc.findVar(name);
auto ln = sc.lookupName(name);
if(vb is null)
if(!ln.isVar)
fail("Variable " ~ name ~ " not found");
Variable *vb = ln.var;
assert(vb.vtype == VarType.Class);
return vb;
@ -409,11 +394,12 @@ final class MonsterClass
{
requireScope();
State *st = sc.findState(name);
if(st is null)
auto ln = sc.lookupName(name);
if(!ln.isState)
fail("State " ~ name ~ " not found");
State *st = ln.state;
return st;
}
@ -467,13 +453,25 @@ final class MonsterClass
// Loop through all objects of this type
int opApply(int delegate(ref MonsterObject v) del)
{ return objects.opApply(del); }
{
int dg(ref void *vp)
{
auto mop = cast(MonsterObject*)vp;
return del(*mop);
}
return objects.opApply(&dg);
}
// Get the first object in the 'objects' list. Used for
// iterator-like looping through objects, together with getNext in
// MonsterObject. Returns null if no objects exist.
// Get the first object in the list for this class
MonsterObject* getFirst()
{ return objects.getHead(); }
{ return cast(MonsterObject*)objects.getHead().value; }
MonsterObject* getNext(MonsterObject *ob)
{
auto iter = (*getListPtr(ob, treeIndex)).getNext();
if(iter is null) return null;
return cast(MonsterObject*)iter.value;
}
// Get the singleton object
MonsterObject* getSing()
@ -485,106 +483,119 @@ final class MonsterClass
return singObj;
}
// Create a new object, and assign a thread to it.
MonsterObject* createObject()
{ return createClone(null); }
// Get the whole allocated buffer belonging to this object
private int[] getDataBlock(MonsterObject *obj)
{
requireCompile();
assert(obj !is null);
assert(obj.cls is this);
return (cast(int*)obj.data.ptr)[0..totalData.length];
}
if(isAbstract)
fail("Cannot create objects from abstract class " ~ name.str);
private vpIter getListPtr(MonsterObject *obj, int i)
{
auto ep = cast(ExtraData*) &obj.data[i][$-MonsterObject.exSize];
return &ep.node;
}
// Create a new object based on an existing object
MonsterObject* createClone(MonsterObject *source)
{
requireCompile();
if(isModule && singObj !is null)
fail("Cannot create instances of module " ~ name.str);
// Create the thread
CodeThread *trd = threads.getNew();
MonsterObject *obj = allObjects.getNew();
// Create an object tree equivalent of the class tree
MonsterObject* otree[];
otree.length = tree.length;
assert(otree.length > 0);
obj.state = null;
obj.cls = this;
// Create one buffer big enough for all the data segments here,
// and let getObject slice it. TODO: This can be optimized even
// further, by using a freelist or other preallocation, and by
// precalculating the result and the slicing. Not important at
// the moment.
int[] totalData = new int[totalDataSize];
// Allocate the object data segment from a freelist
int[] odata = Buffers.getInt(totalData.length);
// Fill the list with objects, and assign the thread.
foreach(i, ref obj; otree)
// Copy the data, either from the class (in case of new objects)
// or from the source (when cloning.)
if(source !is null)
{
obj = tree[i].getObject(totalData);
obj.thread = trd;
}
// Make sure we used the entire buffer
assert(totalData.length == 0);
// Pick out the top object
MonsterObject* top = otree[$-1];
assert(tree[$-1] is this);
assert(top !is null);
assert(!isAbstract);
assert(source.cls is this);
// Initialize the thread
trd.initialize(top);
assert(source.data.length == tree.length);
// For each object we assign a slice of the object list. TODO:
// In the future it's likely that these lists might have
// different contents from each other (eg. in the case of
// multiple inheritance), and simple slices will not be good
// enough. This is the main reason why we give each object its
// own tree, instead of using one shared list in the thread.
foreach(i, ref obj; otree)
obj.tree = otree[0..i+1];
assert(top.tree == otree);
return top;
}
// Create a new object based on an existing object
MonsterObject* createClone(MonsterObject *source)
{
requireCompile();
// Copy data from the object
odata[] = getDataBlock(source);
}
else
{
if(isAbstract)
fail("Cannot create objects from abstract class " ~ name.str);
assert(source.tree.length == tree.length);
assert(source.thread.topObj == source,
"createClone can only clone the topmost object");
// Copy init values from the class
odata[] = totalData[];
}
// Create a new thread
CodeThread *trd = threads.getNew();
// Use this to get subslices of the data segment
int[] slice = odata;
int[] get(int ints)
{
assert(ints <= slice.length);
int[] res = slice[0..ints];
slice = slice[ints..$];
return res;
}
// Create one buffer big enough for all the data segments here,
// and let getClone slice it.
int[] totalData = new int[totalDataSize];
// The beginning of the block is used for the int data[][]
// array.
obj.data = cast(int[][]) get(iasize*tree.length);
// Loop through the objects in the source tree, and clone each
// of them
MonsterObject* otree[] = source.tree.dup;
foreach(i, ref obj; otree)
// Set up the a slice for the data segment of each class
foreach(i, c; tree)
{
obj = obj.cls.getClone(obj, totalData);
obj.tree = otree[0..i+1];
obj.thread = trd;
// Just get the slice - the actual data is already set up.
obj.data[i] = get(c.data.length + MonsterObject.exSize);
// Insert ourselves into the per-class list. We've already
// allocated size for a node, we just have to add it to the
// list.
auto node = getListPtr(obj, i);
node.value = obj; // Store the object pointer
c.objects.insertNode(node);
}
// Make sure we used the entire buffer
assert(totalData.length == 0);
// At this point we should have used up the entire slice
assert(slice.length == 0);
// Call constructors
foreach(c; tree)
{
// Custom native constructor
if(c.constType != FuncType.Native)
{
fstack.pushNConst(obj);
if(c.constType == FuncType.NativeDDel)
c.dg_const();
else if(c.constType == FuncType.NativeDFunc)
c.fn_const();
else if(c.constType == FuncType.NativeCFunc)
c.c_const();
fstack.pop();
}
// Pick out the top object
MonsterObject* top = otree[$-1];
assert(top !is null);
// TODO: Call script-constructor here
}
// Initialize the thread
trd.initialize(top);
// Set the same state as the source
if(source !is null)
obj.setState(source.state, null);
// Set the same state
trd.setState(source.thread.getState(), null);
// Make sure that getDataBlock works
assert(getDataBlock(obj).ptr == odata.ptr &&
getDataBlock(obj).length == odata.length);
return top;
return obj;
}
// Free an object and its thread
@ -595,18 +606,28 @@ final class MonsterClass
if(isModule)
fail("Cannot delete instances of module " ~ name.str);
// Get the head object
obj = obj.thread.topObj;
// Shut down any active code in the thread
obj.thread.setState(null, null);
obj.clearState();
// clearState should also clear the thread
assert(obj.sthread is null);
// Destruct the objects in reverse order
foreach_reverse(ob; obj.thread.topObj.tree)
ob.cls.returnObject(ob);
// This effectively marks the object as dead
obj.cls = null;
// Put the thread back into the free list
threads.remove(obj.thread);
foreach_reverse(i, c; tree)
{
// TODO: Call destructors here
// Remove from class list
c.objects.removeNode(getListPtr(obj,i));
}
// Return it to the freelist
allObjects.remove(obj);
// Return the data segment
Buffers.free(getDataBlock(obj));
}
@ -616,23 +637,6 @@ final class MonsterClass
* *
*******************************************************/
/* For Manual classes. These are just ideas, not implemented yet
void addNative(char[] name, dg_callback dg) {}
void addNative(char[] name, fn_callback fn) {}
// Not for manual classes, but intended for reloading a changed
// file. It will replace the current class in the scope with a new
// one - and all new objects created will be of the new type
// (requires some work on vm.d and scope.d to get this to work). Old
// objects keep the old class. An alternative is to convert the old
// objects to the new class in some way, if possible.
void reload() {}
*/
// Will set the name of the class. Can only be called on manual
// classes, and only once. Not implemented yet.
void setName(char[] name) {assert(0);}
// Check if this class is a child of cls.
bool childOf(MonsterClass cls)
{
@ -651,16 +655,37 @@ final class MonsterClass
bool parentOf(MonsterObject *obj)
{ return obj.cls.childOf(this); }
// Get the tree-index of a given parent class
int upcast(MonsterClass mc)
{
requireScope();
int ind = mc.treeIndex;
if(ind < tree.length && tree[ind] is mc)
return ind;
fail("Cannot upcast " ~ toString ~ " to " ~ mc.toString);
}
// Get the given class from a tree index
MonsterClass upcast(int ind)
{
requireScope();
if(ind < tree.length) return tree[ind];
fail("Cannot upcast " ~toString ~ " to index " ~ .toString(ind));
}
// Get the global index of this class
CIndex getIndex() { requireScope(); return gIndex; }
int getTreeIndex() { requireScope(); return treeIndex; }
char[] getName() { assert(name.str != ""); return name.str; }
char[] toString() { return getName(); }
uint numObjects() { return objects.length; }
private:
/*******************************************************
* *
* Private variables *
@ -687,18 +712,13 @@ final class MonsterClass
// overrided functions have been replaced by their successors.
Function*[][] virtuals;
// The freelists used for allocation of objects and threads.
ObjectList objects;
ThreadList threads;
int[] data; // Contains the initial object data segment
int[] sdata; // Static data segment
// Size of the data segment
uint dataSize;
// Total for this class + all base classes.
uint totalDataSize;
// The total data segment that's assigned to each object. It
// includes the data segment of all parent objects and some
// additional internal data.
int[] totalData;
// Direct parents of this class
MonsterClass parents[];
@ -710,9 +730,7 @@ final class MonsterClass
StateDeclaration[] statedecs;
StructDeclaration[] structdecs;
EnumDeclaration[] enumdecs;
// Current stage of the loading process
MC loadType = MC.None;
ImportStatement[] imports;
// Native constructor type. Changed when the actual constructor is
// set.
@ -740,7 +758,7 @@ final class MonsterClass
int[] getDataSegment()
{
assert(sc !is null && sc.isClass(), "Class does not have a class scope");
assert(dataSize == sc.getDataSize);
uint dataSize = sc.getDataSize;
int[] data = new int[dataSize];
int totSize = 0;
@ -757,109 +775,11 @@ final class MonsterClass
}
// Make sure the total size of the variables match the total size
// requested by variables through addNewDataVar.
assert(totSize == sc.getDataSize, "Data size mismatch in scope");
assert(totSize == dataSize, "Data size mismatch in scope");
return data;
}
// Get an object from this class (but do not assign a thread to it)
MonsterObject *getObject(ref int[] dataBuf)
{
requireCompile();
MonsterObject *obj = objects.getNew();
// Set the class
obj.cls = this;
// TODO: Better memory management here. I have been thinking
// about a general freelist manager, that works with object
// sizes rather than with templates. That would work with
// objects of any size, and could also be used directly from C /
// C++. If we have one for every size we might end up with a
// whole lot freelists though. Maybe we can pool the sizes, for
// example use one for 16 bytes, one for 64, 128, 256, 1k, 4k,
// etc. We will have to make the system and do some statistics
// to see what sizes are actually used. The entire structure can
// reside inside it's own region.
// Copy the data segment into the buffer
assert(data.length == dataSize);
assert(dataBuf.length >= dataSize);
obj.data = dataBuf[0..dataSize];
obj.data[] = data[];
dataBuf = dataBuf[dataSize..$];
// Point to the static data segment
obj.sdata = sdata;
obj.extra = null;
// Call the custom native constructor
if(constType != FuncType.Native)
{
fstack.pushNConst(obj);
if(constType == FuncType.NativeDDel)
dg_const();
else if(constType == FuncType.NativeDFunc)
fn_const();
else if(constType == FuncType.NativeCFunc)
c_const();
fstack.pop();
}
return obj;
}
// Clone an existing object
MonsterObject *getClone(MonsterObject *source, ref int[] dataBuf)
{
assert(source !is null);
assert(source.cls is this);
assert(source.data.length == data.length);
assert(source.sdata.ptr is sdata.ptr);
requireCompile();
MonsterObject *obj = objects.getNew();
// Set the class
obj.cls = this;
// Copy the data segment from the source
assert(data.length == dataSize);
assert(dataBuf.length >= dataSize);
assert(dataSize == source.data.length);
obj.data = dataBuf[0..dataSize];
obj.data[] = source.data[];
dataBuf = dataBuf[dataSize..$];
// Point to the static data segment
obj.sdata = sdata;
obj.extra = null;
// Call the custom native constructor
if(constType != FuncType.Native)
{
fstack.pushNConst(obj);
if(constType == FuncType.NativeDDel)
dg_const();
else if(constType == FuncType.NativeDFunc)
fn_const();
else if(constType == FuncType.NativeCFunc)
c_const();
fstack.pop();
}
return obj;
}
// Delete an object belonging to this class
void returnObject(MonsterObject *obj)
{
// Put it back into the freelist
objects.remove(obj);
}
// Load file based on file name, class name, or both. The order of
// the strings doesn't matter, and name2 can be empty. useCase
// determines if we require a case sensitive match between the given
@ -956,9 +876,10 @@ final class MonsterClass
requireScope();
// Look the function up in the scope
auto fn = sc.findFunc(name);
auto ln = sc.lookupName(name);
auto fn = ln.func;
if(fn is null)
if(!ln.isFunc)
fail("Cannot bind to '" ~ name ~ "': no such function");
if(ft == FuncType.Idle)
@ -988,8 +909,6 @@ final class MonsterClass
// parse them, and store it in the appropriate list;
void store(ref TokenArray toks)
{
// canParse() is not ment as a complete syntax test, only to be
// enough to identify which Block parser to apply.
if(FuncDeclaration.canParse(toks))
{
auto fd = new FuncDeclaration;
@ -1020,17 +939,28 @@ final class MonsterClass
sd.parse(toks);
enumdecs ~= sd;
}
else if(ImportStatement.canParse(toks))
{
auto sd = new ImportStatement;
sd.parse(toks);
imports ~= sd;
}
else
fail("Illegal type or declaration", toks);
}
// Converts a stream to tokens and parses it.
void parse(Stream str, char[] fname, int bom)
{
assert(!isParsed(), "parse() called on a parsed class " ~ name.str);
{
assert(str !is null);
TokenArray tokens = tokenizeStream(fname, str, bom);
parse(tokens, fname);
}
// Parses a list of tokens
void parse(ref TokenArray tokens, char[] fname)
{
assert(!isParsed(), "parse() called on a parsed class " ~ name.str);
alias Block.isNext isNext;
@ -1178,13 +1108,19 @@ final class MonsterClass
objType = new ObjectType(this);
classType = objType.getMeta();
// Insert custom types first
// Insert custom types first. This will never refer to other
// identifiers.
foreach(dec; structdecs)
dec.insertType(sc);
foreach(dec; enumdecs)
dec.insertType(sc);
// Then resolve the headers.
// Resolve imports next. May refer to custom types, but no other
// ids.
foreach(dec; imports)
dec.resolve(sc);
// Then resolve the type headers.
foreach(dec; structdecs)
dec.resolve(sc);
foreach(dec; enumdecs)
@ -1203,9 +1139,7 @@ final class MonsterClass
foreach(dec; statedecs)
sc.insertState(dec.st);
// Resolve function headers. Here too, the init values will have
// to be moved to the body. We still need the parameter and
// return types though.
// Resolve function headers.
foreach(func; funcdecs)
func.resolve(sc);
@ -1272,16 +1206,6 @@ final class MonsterClass
}
}
// Set the data segment size and the total data size for all
// base classes.
dataSize = sc.getDataSize();
totalDataSize = 0;
foreach(t; tree)
totalDataSize += t.dataSize;
assert(totalDataSize >= dataSize);
flags.unset(CFlags.InScope);
}
@ -1314,6 +1238,10 @@ final class MonsterClass
flags.set(CFlags.Resolved);
}
alias int[] ia;
// These are platform dependent:
static const iasize = ia.sizeof / int.sizeof;
void compileBody()
{
assert(!isCompiled, getName() ~ " is already compiled");
@ -1321,15 +1249,58 @@ final class MonsterClass
// Resolve the class body if it's not already done
if(!isResolved) resolveBody();
// Require that all parent classes are compiled before us
foreach(mc; tree[0..$-1])
mc.requireCompile();
// Generate data segment and byte code for functions and
// states. The result is stored in the respective objects.
foreach(f; funcdecs) f.compile();
foreach(s; statedecs) s.compile();
// Set the data segment. TODO: Separate static data from
// variables.
// Set the data segment for this class.
data = getDataSegment();
// Calculate the total data size we need to allocate for each
// object
uint tsize = 0;
foreach(c; tree)
{
tsize += c.data.length; // Data segment size
tsize += MonsterObject.exSize; // Extra data per object
tsize += iasize; // The size of our entry in the data[]
// table
}
// Allocate the buffer
totalData = new int[tsize];
// Use this to get subslices of the data segment
int[] slice = totalData;
int[] get(int ints)
{
assert(ints <= slice.length);
int[] res = slice[0..ints];
slice = slice[ints..$];
return res;
}
// Skip the data[] list
get(iasize*tree.length);
// Assign the data segment values
foreach(c; tree)
{
int[] d = get(c.data.length);
d[] = c.data[];
// Skip the extra data
get(MonsterObject.exSize);
}
// At this point we should have used up the entire slice
assert(slice.length == 0);
flags.set(CFlags.Compiled);
// If it's a singleton, set up the object.

@ -28,6 +28,9 @@ import monster.vm.error;
import monster.vm.mclass;
import monster.vm.arrays;
import monster.util.freelist;
import monster.util.list;
import monster.compiler.states;
import monster.compiler.variables;
import monster.compiler.scopes;
@ -39,6 +42,25 @@ import std.utf;
// An index to a monster object.
typedef int MIndex;
union SharedType
{
int i;
uint ui;
long l;
ulong ul;
float f;
double d;
void *vptr;
Object obj;
}
struct ExtraData
{
SharedType extra;
vpNode node;
}
struct MonsterObject
{
/*******************************************************
@ -49,26 +71,28 @@ struct MonsterObject
MonsterClass cls;
// Extra data. This allows you to assign additional data to an
// object. We might refine this concept a little later.
void *extra;
// Thread used for running state code. May be null if no code is
// running or scheduled.
Thread *sthread;
// The thread. Each object has its own thread, but not every
// MonsterObject has its own unique thread. For derived classes, we
// allocate a MonsterObject for each parent class, but only one
// thread for the object entire object.
CodeThread *thread;
// The following variables are "tree-indexed". This means that
// they're arrays, with one element for each class in the
// inheritance hierarchy. The corresponding class tree can be found
// in cls.tree.
// Object data segment
int[] data;
// Object data segment.
int[][] data;
// Static data segment. Do not write to this.
int[] sdata;
/*******************************************************
* *
* Private variables *
* *
*******************************************************/
// Parent object tree. This reflects the equivalent 'tree' table in
// the MonsterClass.
MonsterObject* tree[];
//private:
State *state; // Current state, null is the empty state.
public:
/*******************************************************
* *
@ -76,20 +100,10 @@ struct MonsterObject
* *
*******************************************************/
// Get the next object in the objects list - used to iterate through
// objects of one class
MonsterObject *getNext()
{
// TODO: This syntax is rather hackish, and bug-prone if we
// suddenly change the list structure.
return cast(MonsterObject*)
( cast(MonsterClass.ObjectList.TList.Iterator)this ).getNext();
}
// Get the index of this object
MIndex getIndex()
{
return cast(MIndex)( MonsterClass.ObjectList.getIndex(this)+1 );
return cast(MIndex)( ObjectList.getIndex(this)+1 );
}
// Delete this object. Do not use the object after calling this
@ -99,61 +113,27 @@ struct MonsterObject
cls.deleteObject(this);
}
// Create a clone of this object. Note that this will always clone
// and return the top object (thread.topObj), regardless of which
// object in the list it is called on. In other words, the class
// mo.cls is not always the same as mo.clone().cls.
// Create a clone of this object.
MonsterObject *clone()
{
auto t = thread.topObj;
return t.cls.createClone(t);
}
{ return cls.createClone(this); }
/*******************************************************
* *
* Casting / polymorphism functions *
* Member variable getters / setters *
* *
*******************************************************/
// Cast this object to the given class, if possible. Both upcasts
// and downcasts are allowed.
MonsterObject *Cast(MonsterClass toClass)
{ return doCast(toClass, thread.topObj.tree); }
// Upcast this object to the given class. Upcasting means that
// toClass must be the class of this object, or one of its parent
// classes.
MonsterObject *upcast(MonsterClass toClass)
{
assert(toClass !is null);
return doCast(toClass, tree);
}
// Special version used from bytecode. The index is the global class
// index.
MonsterObject *upcastIndex(int index)
// The last two ints of the data segment can be used to store extra
// data associated with the object. A typical example is the pointer
// to a D/C++ struct or class counterpart to the Monster class.
static const exSize = ExtraData.sizeof / int.sizeof;
static assert(exSize*4 == ExtraData.sizeof);
SharedType *getExtra(int index)
{
// Convert the global class index to the tree index. TODO: Later
// on we should pass this index directly, but that is just
// optimization.
index = global.getClass(cast(CIndex)index).treeIndex;
assert(index < tree.length, "cannot upcast class " ~ cls.getName ~
" to index " ~ format(index));
return tree[index];
return & (cast(ExtraData*)&data[index][$-exSize]).extra;
}
// Is this object part of a linked inheritance chain?
bool isBaseObject() {return !isTopObject(); }
// Is this object the topmost object in the inheritance chain?
bool isTopObject() { return thread.topObj is this; }
/*******************************************************
* *
* Member variable getters / setters *
* *
*******************************************************/
SharedType *getExtra(MonsterClass mc)
{ return getExtra(cls.upcast(mc)); }
// This is the work horse for all the set/get functions.
T* getPtr(T)(char[] name)
@ -179,10 +159,9 @@ struct MonsterObject
assert(vb.sc.isClass(), "variable must be a class variable");
MonsterClass mc = vb.sc.getClass();
assert(mc !is null);
MonsterObject *obj = upcast(mc);
// Return the pointer
return cast(T*) obj.getDataInt(vb.number);
return cast(T*) getDataInt(mc.treeIndex, vb.number);
}
T getType(T)(char[] name)
{ return *getPtr!(T)(name); }
@ -246,13 +225,16 @@ struct MonsterObject
*******************************************************/
// Get an int from the data segment
int *getDataInt(int pos)
int *getDataInt(int treeIndex, int pos)
{
if(pos < 0 || pos>=data.length)
fail("MonsterObject: data pointer out of range: " ~ toString(pos));
return &data[pos];
assert(treeIndex >= 0 && treeIndex < data.length,
"tree index out of range: " ~ toString(treeIndex));
assert(pos >= 0 && pos<data[treeIndex].length,
"data pointer out of range: " ~ toString(pos));
return &data[treeIndex][pos];
}
/* KILLME
// Get a long (two ints) from the data segment
long *getDataLong(int pos)
{
@ -260,14 +242,18 @@ struct MonsterObject
fail("MonsterObject: data pointer out of range: " ~ toString(pos));
return cast(long*)&data[pos];
}
*/
// Get an array from the data segment
int[] getDataArray(int pos, int len)
int[] getDataArray(int treeIndex, int pos, int len)
{
if(pos < 0 || len < 0 || (pos+len) > data.length)
fail("MonsterObject: data array out of range: pos=" ~ toString(pos) ~
assert(len > 0);
assert(treeIndex >= 0 && treeIndex < data.length,
"tree index out of range: " ~ toString(treeIndex));
assert(pos >= 0 && (pos+len)<=data[treeIndex].length,
"data pointer out of range: pos=" ~ toString(pos) ~
", len=" ~toString(len));
return data[pos..pos+len];
return data[treeIndex][pos..pos+len];
}
@ -283,7 +269,7 @@ struct MonsterObject
// will take precedence.
void call(char[] name)
{
thread.topObj.cls.findFunction(name).call(this);
cls.findFunction(name).call(this);
}
// Call a function non-virtually. In other words, ignore
@ -293,17 +279,120 @@ struct MonsterObject
assert(0, "not implemented");
}
// Set the current state of the object. If called from within state
// code, we have to return all the way back to the state code level
// before the new state is scheduled. If called when the object is
// idle (not actively running state code), the state is scheduled
// now, and the idle function is aborted. New state code does not
// start running until the next frame.
void setState(State *st, StateLabel *lb = null)
/* Set state. Invoked by the statement "state = statename;". This
function can be called in several situations, with various
results:
+ setState called with current state, no label
-> no action is performed
+ setState called with another state
+ setState called with current state + a label
-> state is changed normally
If a state change takes place directly in state code, the code is
aborted immediately. If it takes place in a function called from
state code, then code flow is allowed to return normally back to
the state code level, but is aborted immediately once it reaches
state code.
State changes outside state code will always unschedule any
previously scheduled code (such as idle functions, or previous
calls to setState.)
*/
void setState(State *st, StateLabel *label)
{
assert(st !is null || lb is null,
"If state is null, label must also be null");
thread.setState(st, lb);
// Does the state actually change?
if(st !is state)
{
// Set the state
state = st;
// We must handle state functions and other magic here.
}
// If no label is specified and we are already in this state, then
// don't do anything.
else if(label is null) return;
// TODO: We can reorganize the entire function to deal with one
// sthread !is null test. Just do the label-checking first, and
// store the label offset
// Do we already have a thread?
if(sthread !is null)
{
// If we are already scheduled (if an idle function has
// scheduled us, or if setState has been called multiple
// times), unschedule. This will automatically cancel any
// scheduled idle functions and call their abort() functions.
if(sthread.isScheduled)
sthread.cancel();
assert(sthread.isActive || !sthread.stateChange,
"stateChange was set outside active code");
// If we are running from state code, signal it that we must
// now abort execution when we reach the state level.
sthread.stateChange = sthread.isActive;
}
// If we are jumping to anything but the empty state, we will have
// to schedule some code.
if(st !is null)
{
// Check that this state is valid
assert(st.owner.parentOf(cls), "state '" ~ st.name.str ~
"' is not part of class " ~ cls.getName());
if(label is null)
// findLabel will return null if the label is not found.
// TODO: The begin label should probably be cached within
// State.
label = st.findLabel("begin");
if(label !is null)
{
// Make sure there's a thread to run in
if(sthread is null)
sthread = Thread.getNew(this);
// Schedule the thread to start at the given label
sthread.schedule(label.offs);
assert(sthread.isScheduled);
}
}
// Don't leave an unused thread dangling - kill it instead.
if(sthread !is null && !sthread.isScheduled)
{
sthread.kill();
sthread = null;
}
assert(sthread is null || sthread.isScheduled);
}
void clearState() { setState(cast(State*)null, null); }
// Index version of setState - called from bytecode
void setState(int st, int label, int clsInd)
{
if(st == -1)
{
assert(label == -1);
clearState();
return;
}
auto cls = cls.upcast(clsInd);
// TODO: This does not support virtual states yet
auto pair = cls.findState(st, label);
assert(pair.state.index == st);
assert(pair.state.owner is cls);
setState(pair.state, pair.label);
}
// Named version of the above function. An empty string sets the
@ -314,8 +403,8 @@ struct MonsterObject
{
if(label == "")
{
if(name == "") thread.setState(null,null);
else setState(cls.findState(name));
if(name == "") clearState();
else setState(cls.findState(name), null);
return;
}
@ -324,43 +413,13 @@ struct MonsterObject
auto stl = cls.findState(name, label);
setState(stl.state, stl.label);
}
}
/*******************************************************
* *
* Private functions *
* *
*******************************************************/
private:
MonsterObject *doCast(MonsterClass toClass, MonsterObject* ptree[])
{
assert(toClass !is null);
if(toClass is cls) return this;
// TODO: At some point, a class will have several possible tree
// indices. We will loop through the list and try them all.
int index = toClass.treeIndex;
MonsterObject *mo = null;
if(index < ptree.length)
{
mo = ptree[index];
assert(mo !is this);
// It's only a match if the classes match
if(mo.cls !is toClass) mo = null;
}
alias FreeList!(MonsterObject) ObjectList;
// If no match was found, then the cast failed.
if(mo is null)
fail("object of class " ~ cls.name.str ~
" cannot be cast to " ~ toClass.name.str);
return mo;
}
}
// The freelist used for allocation of objects. This contains all
// allocated and in-use objects.
ObjectList allObjects;
// Convert an index to an object pointer
MonsterObject *getMObject(MIndex index)
@ -368,22 +427,15 @@ MonsterObject *getMObject(MIndex index)
if(index == 0)
fail("Null object reference encountered");
if(index < 0 || index > getTotalObjects())
if(index < 0 || index > ObjectList.totLength())
fail("Invalid object reference");
MonsterObject *obj = MonsterClass.ObjectList.getNode(index-1);
MonsterObject *obj = ObjectList.getNode(index-1);
if(obj.thread == null)
if(obj.cls is null)
fail("Dead object reference (index " ~ toString(cast(int)index) ~ ")");
assert(obj.getIndex() == index);
return obj;
}
// Get the total number of MonsterObjects ever allocated for the free
// list. Does NOT correspond to the number of objects in use.
int getTotalObjects()
{
return MonsterClass.ObjectList.totLength();
}

@ -1,332 +0,0 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007, 2008 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (scheduler.d) is part of the Monster script language
package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
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.vm.scheduler;
import monster.compiler.functions;
import monster.vm.mobject;
import monster.vm.idlefunction;
import monster.vm.error;
import monster.vm.fstack;
import monster.util.freelist;
import std.string;
// Enable minor safety checks - can be removed from release code.
debug=safecheck;
// Are we currently looping through the wait list?
debug(safecheck) bool waitLoop;
// The various types of code a scheduled node will call
enum CallType
{
None, // Not used and should never be set
Idle, // The return of an idle function (starts state code)
State // The beginning of a state (also starts state code)
}
// The scheduler singleton
Scheduler scheduler;
// Represents a code point for the scheduler to jump back to. Points
// to an object (which owns a coe thread object) and a position. TODO:
// Function calls must refer to some index, if the state changes we
// must call the correct function.
struct ScheduleStruct
{
CallType type;
Function *idle;
MonsterObject *obj;
ListManager *list;
int retPos; // Return position for idle functions.
// Unschedule this node from the runlist or waitlist it belongs
// to. Any idle function connected to this node is aborted.
void cancel()
{
debug(safecheck) auto node = obj.thread.scheduleNode;
if(idle !is null)
{
fstack.pushIdleAbort(idle, obj);
idle.idleFunc.abort(obj);
fstack.pop();
}
// Make sure the scheduleNode is the same before and after calling
// abort().
debug(safecheck)
assert(node == obj.thread.scheduleNode,
"abort() can not reschedule object or change state");
remove();
}
// Remove this node from the list it belongs to.
void remove()
{
debug(safecheck) assert(!waitLoop, "remove() called from hasFinished()");
type = CallType.None;
list.remove(obj);
}
}
alias FreeList!(ScheduleStruct) ScheduleFreeList;
alias ScheduleStruct* CallNode;
// Get the next node in a freelist
static CallNode getNext(CallNode cn)
{
// Simple hack. The ScheduleStruct (pointed at by the CallNode) is
// the first part of, and therefore in the same location as, the
// iterator struct for the FreeList. It's therefore ok to cast the
// pointer, as long as we never change the iterator struct layout.
return cast(CallNode)
( cast(ScheduleFreeList.TList.Iterator)cn ).getNext();
}
// A wrapper around a freelist. This struct takes care of some
// additional pointers in CodeThread and in ScheduleStruct.
struct ListManager
{
ScheduleFreeList list;
// Create a new node in this list. This means scheduling the given
// object in one of the lists.
CallNode newNode(MonsterObject *obj, CallType type,
Function *idle, int retPos)
{
assert(obj.thread.scheduleNode == null,
"CodeThread cannot have two schedule nodes.");
CallNode cn = list.getNew();
cn.obj = obj;
cn.type = type;
cn.idle = idle;
cn.list = this;
cn.retPos = retPos;
obj.thread.scheduleNode = cn;
return cn;
}
// Remove a node from this run list (put it back into the freelist.)
void remove(MonsterObject *obj)
{
CallNode node = obj.thread.scheduleNode;
node.list = null;
obj.thread.scheduleNode = null;
list.remove(node);
}
CallNode moveTo(ListManager *to, CallNode node)
{
node.list = to;
return list.moveTo(to.list, node);
}
}
struct Scheduler
{
// The acutal lists. We use pointers to access the run lists, since
// we want to swap them easily.
ListManager run1, run2, wait;
// The run lists for this and the next round.
ListManager* runNext, run;
void init()
{
// Assign the run list pointers
run = &run1;
runNext = &run2;
}
// Statistics:
// Number of elements in the waiting list
int numWait() { return wait.list.length; }
// Number of elements scheduled to run the next frame
int numRun() { return runNext.list.length; }
// Total number of objects scheduled or waiting
int numTotal()
{
assert(run.list.length == 0); // The 'run' list is always empty
// between frames.
return numRun() + numWait();
}
// "Call" an idle function. We must notify the idle function that it
// has been called. We also have the responsibility of rescheduling
// the given code thread at the right moment.
void callIdle(MonsterObject *obj, Function *idle,
int pos)
{
//writefln("Idle function '%s' called", lf.name);
debug(safecheck) assert(!waitLoop, "callIdle() called from hasFinished()");
assert(obj.thread.scheduleNode == null);
// Make sure the object and the function are set up correctly
assert(obj.cls is idle.owner);
assert(idle.isIdle);
assert(idle.idleFunc !is null);
// Notify the idle function
fstack.pushIdleInit(idle, obj);
if(idle.idleFunc.initiate(obj))
{
// The idle function wants to be scheduled
// Make sure initiate() didn't change anything it shouldn't
// have.
assert(obj.thread.scheduleNode == null,
"initiate() cannot reschedule object or change state");
// Schedule it to be checked next round.
wait.newNode(obj, CallType.Idle, idle, pos);
}
fstack.pop();
}
// Schedule the given state code to run the next frame
void scheduleState(MonsterObject *obj, int offs)
{
runNext.newNode(obj, CallType.State, null, offs);
}
// Do a complete frame. TODO: Make a distinction between a round and
// a frame later. We could for example do several rounds per frame,
// measured by some criterion of how much time we want to spend on
// script code or whether there are any pending items in the run
// list. We could do several runs of the run-list (to handle state
// changes etc) but only one run on the condition list (actually
// that is a good idea.) We also do not have to execute everything in
// the run list if it is long (otoh, allowing a build-up is not
// good.) But all this falls in the "optimization" category.
void doFrame()
{
//writefln("Beginning of frame");
// Turn on some safety features
debug(safecheck) waitLoop = true;
// Go through the condition list for this round.
CallNode cn = wait.list.getHead();
CallNode next;
while(cn != null)
{
// Get the next node here, since the current node might move
// somewhere else during this iteration, and then getNext will
// point to another list.
next = getNext(cn);
assert(cn.obj.thread.scheduleNode == cn);
// This is an idle function and it is finished. Note that
// hasFinished() is NOT allowed to change the wait list in any
// way, ie to change object states or interact with the
// scheduler. In fact, hasFinished() should do as little as
// possible.
if(cn.type == CallType.Idle)
{
fstack.pushIdleCheck(cn.idle, cn.obj);
if(cn.idle.idleFunc.hasFinished(cn.obj))
// Schedule the code to start running again this round. We
// move it from the wait list to the run list.
wait.moveTo(runNext,cn);
fstack.pop();
}
// Set the next item
cn = next;
}
debug(safecheck) waitLoop = false;
/*
if(wait.list.length)
writefln(" There are idle functions lurking in the shadows");
*/
//writefln("Condition phase complete, beginning execution phase.");
// Swap the runlist for the next frame with the current one. All
// code that is scheduled after this point is executed the next
// frame.
auto tmp = runNext;
runNext = run;
run = tmp;
// Now execute the run list for this frame. Note that items might
// be removed from the run list as we go (eg. if a scheduled
// object has it's state changed) but this is handled. New nodes
// might also be scheduled, but these are added to the runNext
// list.
// First element
cn = run.list.getHead();
while(cn != null)
{
// Remove the current item from the list before starting.
MonsterObject *obj = cn.obj;
auto code = obj.thread;
assert(code.scheduleNode == cn);
run.remove(obj);
// Now execute the item
if(cn.type == CallType.Idle)
{
// Tell the idle function that we we are reentering
fstack.pushIdleReentry(cn.idle, obj);
cn.idle.idleFunc.reentry(obj);
fstack.pop();
assert(code.scheduleNode == null,
"reentry() cannot reschedule object or change state");
// Return to the code point
code.callState(cn.retPos);
}
else if(cn.type == CallType.State)
// Code is scheduled after a state change. We must jump to
// the right offset.
code.callState(cn.retPos);
else assert(0, "Unhandled return type");
// The function stack should now be at zero
assert(fstack.isEmpty());
// Get the next item.
cn = run.list.getHead();
}
// Check that we cleared the run list
assert(run.list.length == 0);
//writefln("End of frame\n");
}
}

@ -35,14 +35,14 @@ import monster.vm.mclass;
import monster.vm.arrays;
import monster.vm.error;
// The stack. One nice attribue of our cooperative multithreading
// scheme is that we only need one single stack frame. All functions
// and code is required to "finish up" before returning or giving
// control to other code. When we switch to "real" threads later, we
// will need one stack per system thread, but many virtual threads
// will still share each stack.
// Stack
CodeStack stack;
void initStack()
{
stack.init();
}
// A simple stack frame. All data are in chunks of 4 bytes
struct CodeStack
{
@ -205,13 +205,6 @@ struct CodeStack
MonsterObject *popObject()
{ return getMObject(cast(MIndex)popInt()); }
// Push an object, and make sure it is cast to the right type
void pushCast(MonsterObject *obj, MonsterClass cls)
{ pushObject(obj.Cast(cls)); }
void pushCast(MonsterObject *obj, char[] name)
{ pushCast(obj, global.getClass(name)); }
// 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[])

@ -28,17 +28,19 @@ import std.stdio;
import std.uni;
import std.c.string;
import monster.util.freelist;
import monster.compiler.bytecode;
import monster.compiler.linespec;
import monster.compiler.states;
import monster.compiler.functions;
import monster.compiler.scopes;
import monster.compiler.types;
import monster.vm.mclass;
import monster.vm.mobject;
import monster.vm.codestream;
import monster.vm.stack;
import monster.vm.scheduler;
import monster.vm.idlefunction;
import monster.vm.arrays;
import monster.vm.iterators;
@ -50,10 +52,18 @@ extern(C) void* memmove(void *dest, void *src, size_t n);
extern(C) double floor(double d);
import monster.util.list;
alias _lstNode!(Thread) _tmp1;
alias __FreeNode!(Thread) _tmp2;
alias FreeList!(Thread) NodeList;
// Current thread
Thread *cthread;
// This represents an execution 'thread' in the system. Each object
// has its own thread. The thread contains a link to the object and
// the class, along with some other data.
struct CodeThread
struct Thread
{
/*******************************************************
* *
@ -61,15 +71,114 @@ struct CodeThread
* *
*******************************************************/
// The object that "owns" this thread. This can only point to the
// top-most object in the linked parent object chain.
MonsterObject* topObj;
// This has been copied from ScheduleStruct, which is now merged
// with Thread. We'll sort it out later.
// Some generic variables that idle functions can use to store
// temporary data off the stack.
SharedType idleData;
// The contents of idleObj's extra data for the idle's owner class.
SharedType extraData;
// Temporarily needed since we need a state and an object to push on
// the stack to return to state code. This'll change soon (we won't
// need to push anything to reenter, since the function stack will
// already be set up for us.)
MonsterObject * theObj;
// Pointer to our current scheduling point. If null, we are not
// currently sceduled. Only applies to state code, not scheduled
// function calls.
CallNode scheduleNode;
Function *idle;
MonsterObject *idleObj; // Object owning the idle function
NodeList * list; // List owning this thread
int retPos; // Return position in byte code.
bool isActive; // Set to true whenever we are running from state
// code. If we are inside the state itself, this will
// be true and 'next' will be 1.
bool stateChange; // Set to true when a state change is in
// progress. Only used when state is changed from
// within a function in active code.
// Unschedule this node from the runlist or waitlist it belongs to,
// but don't kill it. Any idle function connected to this node is
// aborted.
void cancel()
{
if(idle !is null)
{
fstack.pushIdleAbort(idle, idleObj);
idle.idleFunc.abort(this);
fstack.pop();
idle = null;
}
retPos = -1;
moveTo(&scheduler.unused);
assert(!isScheduled);
}
static Thread* getNew(MonsterObject *obj = null)
{
auto cn = scheduler.unused.getNew();
cn.list = &scheduler.unused;
cn.initialize(obj);
return cn;
}
// Remove the thread comletely
void kill()
{
cancel();
list.remove(this);
list = null;
}
// Schedule this thread to run next frame
void schedule(uint offs)
{
assert(!isScheduled,
"cannot schedule an already scheduled thread");
retPos = offs;
moveTo(scheduler.runNext);
}
// Move this node to another list.
void moveTo(NodeList *to)
{
assert(list !is null);
list.moveTo(*to, this);
list = to;
}
// Are we currently scheduled?
bool isScheduled()
{
// The node is per definition scheduled if it is in one of these
// lists
return
list is &scheduler.wait ||
list is scheduler.run ||
list is scheduler.runNext;
}
bool isUnused()
{
return list is &scheduler.unused;
}
bool isIdle() { return idle !is null; }
// Get the next node in the freelist
Thread* getNext()
{
// Simple hack. The Thread (pointed at by the Thread*) is the
// first part of, and therefore in the same location as, the
// iterator struct for the FreeList. This is per design, so it's
// ok to cast the pointer.
return cast(Thread*)
( cast(NodeList.TList.Iterator)this ).getNext();
}
/*******************************************************
* *
@ -77,44 +186,71 @@ struct CodeThread
* *
*******************************************************/
void initialize(MonsterObject* top)
void initialize(MonsterObject *obj)
{
topObj = top;
theObj = obj;
// Initialize other variables
state = null; // Start in the empty state
scheduleNode = null;
idle = null;
idleObj = null;
isActive = false;
stateChange = false;
retPos = -1;
}
State* getState() { return state; }
// Call state code for this object. 'pos' gives the byte position
// within the bytecode. It is called when a new state is entered, or
// when an idle funtion returns. The state must already be set with
// setState
void callState(int pos)
// Reenter this thread to the point where it was previously stopped.
void reenter()
{
assert(state !is null, "attempted to call the empty state");
assert(theObj !is null,
"cannot reenter a non-state thread yet");
// Most if not all of these checks will have to be removed in the
// future
assert(theObj.state !is null, "attempted to call the empty state");
assert(!isActive,
"callState cannot be called when object is already active");
"reenter cannot be called when object is already active");
assert(fstack.isEmpty,
"ctate code can only run at the bottom of the function stack");
"state code can only run at the bottom of the function stack");
assert(isScheduled);
if(isIdle)
{
assert(idle !is null);
assert(idleObj !is null || idle.isStatic);
// Set a bool to indicate that we are now actively running state
// code.
// Tell the idle function that we we are reentering
fstack.pushIdleReentry(idle, idleObj);
idle.idleFunc.reentry(this);
fstack.pop();
// We're no longer idle
idle = null;
}
// Remove the current node from the run list
moveTo(&scheduler.unused);
// Set the active flat to indicate that we are now actively
// running. (Might not be needed in the future)
isActive = true;
// Set up the code stack
fstack.push(state, topObj.upcast(state.sc.getClass()));
// Set the thread
assert(cthread is null);
cthread = this;
// Set up the code stack for state code.
fstack.push(theObj.state, theObj);
// Set the position
fstack.cur.code.jump(pos);
assert(retPos >= 0);
fstack.cur.code.jump(retPos);
// Run the code
execute();
// Reset the thread
cthread = null;
// We are no longer active
isActive = false;
@ -123,98 +259,10 @@ struct CodeThread
stack.getPos));
fstack.pop();
}
/* Set state. Invoked by the statement "state = statename;". This
function can be called in several situations, with various
results:
+ setState called with current state, no label
-> no action is performed
+ setState called with another state
+ setState called with current state + a label
-> state is changed normally
If a state change takes place directly in state code, the code is
aborted immediately. If it takes place in a function called from
state code, then code flow is allowed to return normally back to
the state code level, but is aborted immediately once it reaches
state code.
State changes outside state code will always unschedule any
previously scheduled code (such as idle functions, or previous
calls to setState.)
*/
void setState(State *st, StateLabel *label)
{
// If no label is specified and we are already in this state, then
// do nothing.
if(st is state && label is null)
return;
// Does the state actually change?
if(st !is state)
{
// If so, we must handle state functions and other magic here.
}
// Set the state
state = st;
// If we are already scheduled (if an idle function has scheduled
// us, or if setState has been called multiple times),
// unschedule. This will automatically cancel any scheduled idle
// functions and call their abort() functions.
if(scheduleNode)
scheduleNode.cancel();
// If we are jumping to anything but the empty state, we might
// have to schedule some code.
if(st !is null)
{
// Check that this state is valid
assert(st.sc.getClass().parentOf(topObj), "state '" ~ st.name.str ~
"' is not part of class " ~ topObj.cls.getName());
if(label is null)
// findLabel will return null if the label is not found.
// TODO: The begin label should probably be cached within
// State.
label = st.findLabel("begin");
// Reschedule the new state for the next frame, if a label is
// specified. We have to cast to find the right object first
// though.
auto mo = topObj.upcast(st.sc.getClass());
if(label !is null)
scheduler.scheduleState(mo, label.offs);
}
assert(isActive || !stateChange,
"stateChange was set outside active code");
// If we are running from state code, signal it that we must now
// abort execution when we reach the state level.
stateChange = isActive;
}
/*******************************************************
* *
* Private variables *
* *
*******************************************************/
private:
bool isActive; // Set to true whenever we are running from state
// code. If we are inside the state itself, this will
// be true and 'next' will be 1.
bool stateChange; // Set to true when a state change is in
// progress. Only used when state is changed from
// within a function in active code.
State *state; // Current state, null is the empty state.
/*******************************************************
* *
* Private helper functions *
@ -234,113 +282,47 @@ struct CodeThread
.fail(msg, file, line);
}
// Index version of setState - called from bytecode
void setState(int st, int label, int cls)
{
if(st == -1)
{
assert(label == -1);
setState(null, null);
return;
}
auto mo = topObj.upcastIndex(cls);
auto pair = mo.cls.findState(st, label);
setState(pair.state, pair.label);
assert(pair.state.index == st);
assert(pair.state.sc.getClass().getIndex == cls);
}
void callIdle()
// Parse the BC.CallIdle instruction parameters and call schedule
// the given idle function.
void callIdle(MonsterObject *iObj)
{
assert(isActive && fstack.isStateCode,
"Byte code attempted to call an idle function outside of state code.");
"Byte code attempted to call an idle function outside of state code."); assert(!isScheduled, "Thread is already scheduled");
CodeStream *code = &fstack.cur.code;
// Get the correct object
MonsterObject *mo = topObj.upcastIndex(code.getInt());
// Store the object
idleObj = iObj;
assert(idleObj !is null);
// And the function
Function *fn = mo.cls.findFunction(code.getInt());
assert(fn !is null && fn.isIdle);
// Get the class from the index
auto cls = iObj.cls.upcast(code.getInt());
// The IdleFunction object bound to this function is stored in
// fn.idleFunc
if(fn.idleFunc is null)
fail("Called unimplemented idle function '" ~ fn.name.str ~ "'");
// Get the function
idle = cls.findFunction(code.getInt());
assert(idle !is null && idle.isIdle);
assert(cls is idle.owner);
assert(idleObj.cls.childOf(cls));
// Tell the scheduler that an idle function was called. It
// will reschedule us as needed.
scheduler.callIdle(mo, fn, fstack.cur.code.getPos);
}
// Pops a pointer off the stack. Null pointers will throw an
// exception.
int *popPtr(MonsterObject *obj)
{
PT type;
int index;
decodePtr(stack.popInt(), type, index);
// Null pointer?
if(type == PT.Null)
fail("Cannot access value, null pointer");
// Local variable?
if(type == PT.Stack)
return stack.getFrameInt(index);
// Variable in this object
if(type == PT.DataOffs)
return obj.getDataInt(index);
// This object, but another (parent) class
if(type == PT.DataOffsCls)
{
// We have to pop the class index of the stack as well
return obj.upcastIndex(stack.popInt()).getDataInt(index);
}
// Far pointer, with offset. Both the class index and the object
// reference is on the stack.
if(type == PT.FarDataOffs)
{
int clsIndex = stack.popInt();
// Get the object reference from the stack
MonsterObject *tmp = stack.popObject();
// Cast the object to the correct class
tmp = tmp.upcastIndex(clsIndex);
// The IdleFunction object bound to this function is stored in
// idle.idleFunc
if(idle.idleFunc is null)
fail("Called unimplemented idle function '" ~ idle.name.str ~ "'");
// Return the correct pointer
return tmp.getDataInt(index);
}
// Set the return position
retPos = fstack.cur.code.getPos();
// Array pointer
if(type == PT.ArrayIndex)
{
assert(index==0);
// Array indices are on the stack, not in the opcode.
index = stack.popInt();
ArrayRef *arf = stack.popArray();
assert(!arf.isNull);
if(arf.isConst)
fail("Cannot assign to constant array");
index *= arf.elemSize;
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];
}
// Set up extraData
extraData = *idleObj.getExtra(idle.owner);
fail("Unable to handle pointer type " ~ toString(cast(int)type));
// Notify the idle function
fstack.pushIdleInit(idle, idleObj);
if(idle.idleFunc.initiate(this))
moveTo(&scheduler.wait);
fstack.pop();
}
bool shouldExitState()
bool shouldExit()
{
if(fstack.isStateCode && stateChange)
{
@ -372,21 +354,87 @@ struct CodeThread
{
// The maximum amount of instructions we execute before assuming
// an infinite loop.
const long limit = 10000000;
static const long limit = 10000000;
assert(fstack.cur !is null,
"CodeThread.execute called but there is no code on the function stack.");
"Thread.execute called but there is no code on the function stack.");
assert(cthread == this,
"can only run the current thread");
// Get some values from the function stack
CodeStream *code = &fstack.cur.code;
MonsterObject *obj = fstack.cur.obj;
MonsterClass cls = fstack.cur.cls;
int clsInd = cls.getTreeIndex();
// Only an object belonging to this thread can be passed to
// execute() on the function stack.
assert(obj is null || cls.parentOf(topObj));
assert(obj is null || cls.parentOf(obj));
assert(obj is null || obj.cls.upcast(cls) == clsInd);
assert(obj !is null || fstack.cur.isStatic);
// Pops a pointer off the stack. Null pointers will throw an
// exception.
int *popPtr()
{
PT type;
int index;
decodePtr(stack.popInt(), type, index);
// Null pointer?
if(type == PT.Null)
fail("Cannot access value, null pointer");
// Stack variable?
if(type == PT.Stack)
return stack.getFrameInt(index);
// Variable in this object
if(type == PT.DataOffs)
return obj.getDataInt(clsInd, index);
// 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);
// Far pointer, with offset. Both the class index and the object
// reference is on the stack.
if(type == PT.FarDataOffs)
{
int clsIndex = stack.popInt();
// Get the object reference from the stack
MonsterObject *tmp = stack.popObject();
// Return the correct pointer
return tmp.getDataInt(clsIndex, index);
}
// Array pointer
if(type == PT.ArrayIndex)
{
assert(index==0);
// Array indices are on the stack
index = stack.popInt();
ArrayRef *arf = stack.popArray();
assert(!arf.isNull);
if(arf.isConst)
fail("Cannot assign to constant array");
// Reduce or remove as many of these as possible
index *= arf.elemSize;
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];
}
fail("Unable to handle pointer type " ~ toString(cast(int)type));
}
// Various temporary stuff
int *ptr;
long *lptr;
float *fptr;
@ -396,7 +444,7 @@ struct CodeThread
int val, val2;
long lval;
// Disable this for now. It should be a per-function option, perhaps,
// Disable this for now.
// or at least a compile time option.
//for(long i=0;i<limit;i++)
for(;;)
@ -416,25 +464,22 @@ struct CodeThread
case BC.Call:
{
// Lots of potential for optimization here. But DON'T do
// that yet.
// Get the correct function from the virtual table
val = code.getInt(); // Class index
auto fn = topObj.cls.findVirtualFunc(val, code.getInt());
auto fn = obj.cls.findVirtualFunc(val, code.getInt());
// Finally, call
fn.call(topObj);
fn.call(obj);
if(shouldExitState()) return;
if(shouldExit()) return;
break;
}
case BC.CallFar:
{
// Get the correct function from the virtual table
auto mo = stack.popObject().thread.topObj;
auto mo = stack.popObject();
// Get the correct function from the virtual table
val = code.getInt(); // Class index
auto fn = mo.cls.findVirtualFunc(val, code.getInt());
@ -442,15 +487,16 @@ struct CodeThread
fn.call(mo);
// Exit state code if the state was changed
if(shouldExitState()) return;
if(shouldExit()) return;
break;
}
case BC.CallIdle:
// Initiate the idle function.
assert(isActive && fstack.isStateCode,
"idle call encountered outside state code.");
callIdle();
callIdle(obj);
return;
case BC.CallIdleFar:
callIdle(stack.popObject());
return;
case BC.Return:
@ -473,8 +519,8 @@ struct CodeThread
val = code.getInt(); // State index
val2 = code.getInt(); // Label index
// Get the class index and let setState handle everything
setState(val, val2, code.getInt());
if(shouldExitState()) return;
obj.setState(val, val2, code.getInt());
if(shouldExit()) return;
break;
case BC.Halt:
@ -520,33 +566,34 @@ struct CodeThread
break;
case BC.PushClassVar:
stack.pushInt(*obj.getDataInt(code.getInt()));
stack.pushInt(*obj.getDataInt(clsInd, code.getInt()));
break;
case BC.PushParentVar:
{
// Get object to work on.
MonsterObject *mo = obj.upcastIndex(code.getInt());
assert(mo !is obj, "should use PushClassVar");
stack.pushInt(*mo.getDataInt(code.getInt()));
}
// Get the tree index
val = code.getInt();
stack.pushInt(*obj.getDataInt(val, code.getInt()));
break;
case BC.PushFarClassVar:
{
// Get object to work on
MonsterObject *mo = stack.popObject().upcastIndex(code.getInt());
stack.pushInt(*mo.getDataInt(code.getInt()));
MonsterObject *mo = stack.popObject();
// And the tree index
val = code.getInt();
stack.pushInt(*mo.getDataInt(val, code.getInt()));
}
break;
case BC.PushFarClassMulti:
{
val = code.getInt(); // Variable size
int siz = code.getInt(); // Variable size
// Get object to work on
MonsterObject *mo = stack.popObject().upcastIndex(code.getInt());
MonsterObject *mo = stack.popObject();
// And the tree index
val = code.getInt(); // Class tree index
val2 = code.getInt(); // Data segment offset
stack.pushInts(mo.getDataArray(val2,val));
stack.pushInts(mo.getDataArray(val,val2,siz));
}
break;
@ -556,7 +603,6 @@ struct CodeThread
break;
case BC.PushSingleton:
// Push the index of this object.
stack.pushObject(global.getClass(cast(CIndex)code.getInt).getSing);
break;
@ -569,19 +615,19 @@ struct CodeThread
case BC.StoreRet:
// Get the pointer off the stack, and convert it to a real
// pointer.
ptr = popPtr(obj);
ptr = popPtr();
// Read the value and store it, but leave it in the stack
*ptr = *stack.getInt(0);
break;
case BC.StoreRet8:
ptr = popPtr(obj);
ptr = popPtr();
*(cast(long*)ptr) = *stack.getLong(1);
break;
case BC.StoreRetMult:
val = code.getInt(); // Size
ptr = popPtr(obj);
ptr = popPtr();
ptr[0..val] = stack.getInts(val-1, val);
break;
@ -816,42 +862,42 @@ struct CodeThread
break;
case BC.PreInc:
ptr = popPtr(obj);
ptr = popPtr();
stack.pushInt(++(*ptr));
break;
case BC.PreDec:
ptr = popPtr(obj);
ptr = popPtr();
stack.pushInt(--(*ptr));
break;
case BC.PostInc:
ptr = popPtr(obj);
ptr = popPtr();
stack.pushInt((*ptr)++);
break;
case BC.PostDec:
ptr = popPtr(obj);
ptr = popPtr();
stack.pushInt((*ptr)--);
break;
case BC.PreInc8:
lptr = cast(long*)popPtr(obj);
lptr = cast(long*)popPtr();
stack.pushLong(++(*lptr));
break;
case BC.PreDec8:
lptr = cast(long*)popPtr(obj);
lptr = cast(long*)popPtr();
stack.pushLong(--(*lptr));
break;
case BC.PostInc8:
lptr = cast(long*)popPtr(obj);
lptr = cast(long*)popPtr();
stack.pushLong((*lptr)++);
break;
case BC.PostDec8:
lptr = cast(long*)popPtr(obj);
lptr = cast(long*)popPtr();
stack.pushLong((*lptr)--);
break;
@ -953,51 +999,20 @@ struct CodeThread
stack.pushDouble(stack.popFloat);
break;
case BC.CastI2S:
{
val = code.get();
char[] res;
if(val == 1) res = .toString(stack.popInt);
else if(val == 2) res = .toString(stack.popUint);
else if(val == 3) res = .toString(stack.popLong);
else if(val == 4) res = .toString(stack.popUlong);
else assert(0);
stack.pushArray(res);
}
break;
case BC.CastF2S:
{
val = code.get();
char[] res;
if(val == 1) res = .toString(stack.popFloat);
else if(val == 2) res = .toString(stack.popDouble);
else assert(0);
stack.pushArray(res);
}
break;
case BC.CastB2S:
stack.pushArray(.toString(stack.popBool));
break;
case BC.CastO2S:
case BC.CastT2S:
{
MIndex idx = stack.popMIndex();
if(idx != 0)
stack.pushArray(format("%s#%s", getMObject(idx).cls.getName,
cast(int)idx));
else
stack.pushCArray("(null object)");
// Get the type to cast from
val = code.getInt();
Type t = Type.typeList[val];
// Get the data
iarr = stack.popInts(t.getSize());
// Let the type convert to string
char[] str = t.valToString(iarr);
// And push it back
stack.pushArray(str);
}
break;
case BC.Upcast:
// TODO: If classes ever get more than one index, it might
// be more sensible to use the global class index here.
stack.pushObject(stack.popObject().upcastIndex(code.getInt()));
break;
case BC.FetchElem:
// This is not very optimized
val = stack.popInt(); // Index
@ -1014,14 +1029,6 @@ struct CodeThread
stack.pushInt(stack.popArray().length);
break;
case BC.MakeArray:
val = code.getInt(); // The data segment offset
val2 = code.getInt(); // Element size
// Get the raw length (array length * elem size) and
// create the new array from data.
stack.pushArray(obj.getDataArray(val, code.getInt()), val2);
break;
case BC.PopToArray:
val = code.getInt; // Raw length
val2 = code.getInt; // Element size
@ -1267,3 +1274,127 @@ void swap(int[] a, int[] b)
b[] = buf[0..len];
}
}
// The scheduler singleton
Scheduler scheduler;
struct Scheduler
{
// Run lists - threads that run this or the next round.
NodeList run1, run2;
// Waiting list - idle threads that are actively checked each frame.
NodeList wait;
// List of unused nodes. Any thread in this list (that is not
// actively running) can and will be deleted eventually.
NodeList unused;
// The run lists for this and the next round. We use pointers to the
// actual lists, since we want to swap them easily.
NodeList* runNext, run;
void init()
{
// Assign the run list pointers
run = &run1;
runNext = &run2;
}
// Statistics:
// Number of elements in the waiting list
int numWait() { return wait.length; }
// Number of elements scheduled to run the next frame
int numRun() { return runNext.length; }
// Number of remaining elements this frame
int numLeft() { return run.length; }
// Total number of objects scheduled or waiting
int numTotal()
{ return numRun() + numWait() + numLeft(); }
// Do a complete frame. TODO: Make a distinction between a round and
// a frame later. We could for example do several rounds per frame,
// measured by some criterion of how much time we want to spend on
// script code or whether there are any pending items in the run
// list. We could do several runs of the run-list (to handle state
// changes etc) but only one run on the condition list (actually
// that is a good idea.) We also do not have to execute everything in
// the run list if it is long (otoh, allowing a build-up is not
// good.) But all this falls in the "optimization" category.
void doFrame()
{
checkConditions();
dispatch();
}
void checkConditions()
{
// Go through the condition list for this round.
Thread* cn = wait.getHead();
Thread* next;
while(cn != null)
{
// Get the next node here, since the current node might move
// somewhere else during this iteration, and then getNext will
// point to another list.
next = cn.getNext();
assert(cn.isScheduled);
// This is an idle function and it is finished. Note that
// hasFinished() is NOT allowed to change the wait list in any
// way, ie to change object states or interact with the
// scheduler. In fact, hasFinished() should do as little as
// possible.
if(cn.isIdle)
{
fstack.pushIdleCheck(cn.idle, cn.idleObj);
if(cn.idle.idleFunc.hasFinished(cn))
// Schedule the code to start running again this round. We
// move it from the wait list to the run list.
cn.moveTo(runNext);
fstack.pop();
}
// Set the next item
cn = next;
}
}
void dispatch()
{
// Swap the runlist for the next frame with the current one. All
// code that is scheduled after this point is executed the next
// frame.
auto tmp = runNext;
runNext = run;
run = tmp;
// Now execute the run list for this frame. Note that items might
// be removed from the run list as we go (eg. if a scheduled
// object has it's state changed) but this is handled. New nodes
// might also be scheduled, but these are added to the runNext
// list.
// First element
Thread* cn = run.getHead();
while(cn != null)
{
// Execute
cn.reenter();
// The function stack should now be at zero
assert(fstack.isEmpty());
// Get the next item.
cn = run.getHead();
}
// Check that we cleared the run list
assert(run.length == 0);
}
}

@ -35,6 +35,8 @@ import monster.compiler.functions;
import monster.compiler.assembler;
import monster.compiler.scopes;
import monster.modules.timer;
import std.file;
import monster.util.string;
@ -58,6 +60,14 @@ struct VM
}
}
void frame(float time = 0)
{
if(time != 0)
idleTime.add(time);
scheduler.doFrame();
}
// Path to search for script files. Extremely simple at the moment.
private char[][] includes = [""];
@ -90,5 +100,4 @@ struct VM
return false;
}
}

@ -0,0 +1,18 @@
// Small script that prints the FPS to screen with regular intervals.
singleton FPSTicker;
import io, timer;
// This is updated automatically by input/events.d
int frameCount;
float delay = 1.5;
state tick
{
begin:
sleep(delay);
print("fps: ", frameCount / delay);
frameCount = 0;
goto begin;
}

@ -24,15 +24,21 @@
module mscripts.object;
import monster.monster;
import monster.modules.timer;
import std.stdio;
import std.date;
import core.resource : rnd;
import core.config;
import sound.music;
// Set up the base Monster classes we need in OpenMW
void initMonsterScripts()
{
initAllModules();
setSleepMethod(SleepMethod.Timer);
// Add the script directories
vm.addPath("mscripts/");
vm.addPath("mscripts/gameobjects/");
@ -45,60 +51,11 @@ void initMonsterScripts()
config.mo = (new MonsterClass("Config")).getSing();
// Bind various functions
mc.bind("print", { print(); });
mc.bind("randInt",
{ stack.pushInt(rnd.randInt
(stack.popInt,stack.popInt));});
mc.bind("sleep", new IdleSleep);
// Load and run the test script
mc = new MonsterClass("Test");
mc.createObject().call("test");
}
// Write a message to screen
void print()
{
AIndex[] args = stack.popAArray();
foreach(AIndex ind; args)
writef("%s ", arrays.getRef(ind).carr);
writefln();
}
// Sleep a given amount of time. Currently uses the system clock, but
// will later be optimized to use the already existing timing
// information from OGRE.
class IdleSleep : IdleFunction
{
long getLong(MonsterObject *mo)
{ return *(cast(long*)mo.extra); }
void setLong(MonsterObject *mo, long l)
{ *(cast(long*)mo.extra) = l; }
override:
bool initiate(MonsterObject *mo)
{
// Get the parameter
float secs = stack.popFloat;
// Get current time
long newTime = getUTCtime();
// Calculate when we should return
newTime += cast(long)secs*TicksPerSecond;
// Store it
if(mo.extra == null) mo.extra = new long;
setLong(mo, newTime);
// Schedule us
return true;
}
bool hasFinished(MonsterObject *mo)
{
// Is it time?
return getUTCtime() >= getLong(mo);
}
}

@ -24,11 +24,7 @@
// This is the base class of all OpenMW Monster classes.
class Object;
// Sleeps a given amount of time
idle sleep(float seconds);
// Print a message to screen
native print(char[][] msg...);
import io, timer;
// Get a random number between a and b (inclusive)
native int randInt(int a, int b);

@ -1,5 +1,7 @@
// A sample class
class Test : Object;
class Test;
import io, timer;
test()
{
@ -15,7 +17,6 @@ state printMessage
loop:
print("Howdy from the world of Monster scripts!");
print("This script is located in mscripts/test.mn. Check it out!");
print("GMST.sThief: ", GMST.sThief);
sleep(60);
goto loop;
}

@ -42,7 +42,7 @@ import core.memory;
import core.config;
import monster.util.string;
import monster.vm.mobject;
import monster.vm.mclass;
import mscripts.object;
import sound.audio;
@ -417,5 +417,5 @@ void main(char[][] args)
// Write some memory statistics
poolSize();
writefln(esmRegion);
writefln("Total objects: ", getTotalObjects);
writefln("Total objects: ", MonsterClass.getTotalObjects);
}

@ -32,7 +32,7 @@ void loadGameSettings()
assert(name != a);
}
if(mc.sc.findVar(name) is null)
if(!mc.sc.lookupName(name).isVar)
{
writefln("WARNING: GMST %s not supported!", name);
continue;

@ -38,11 +38,12 @@ import core.resource;
class Idle_waitUntilFinished : IdleFunction
{
override:
bool initiate(MonsterObject *mo) { return true; }
bool initiate(Thread*) { return true; }
bool hasFinished(MonsterObject *mo)
bool hasFinished(Thread* trd)
{
Jukebox mgr = cast(Jukebox)mo.extra;
Jukebox mgr = cast(Jukebox)trd.extraData.obj;
assert(mgr !is null);
// Return when the music is no longer playing
return !mgr.isPlaying();
@ -179,7 +180,7 @@ class Jukebox
this(MonsterObject *m)
{
mo = m;
m.extra = cast(void*)this;
m.getExtra(Music.jukeC).obj = this;
sID = 0;
bIDs[] = 0;
@ -187,7 +188,7 @@ class Jukebox
static Jukebox get(MonsterObject *m)
{
auto j = cast(Jukebox)m.extra;
auto j = cast(Jukebox)m.getExtra(Music.jukeC).obj;
assert(j !is null);
assert(j.mo == m);
return j;

Loading…
Cancel
Save