forked from mirror/openmw-tes3mp
- 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
This commit is contained in:
parent
af7f8c3d10
commit
a0a95927c4
41 changed files with 2571 additions and 1954 deletions
3
Makefile
3
Makefile
|
@ -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)
|
||||
final ScopeLookup lookupName(char[] name)
|
||||
{ return lookup(Token(name, Floc.init)); }
|
||||
|
||||
ScopeLookup lookup(Token name)
|
||||
{
|
||||
assert(!isProperty);
|
||||
return false;
|
||||
if(isRoot()) return ScopeLookup(name, LType.None, null, null);
|
||||
else return parent.lookup(name);
|
||||
}
|
||||
|
||||
final bool findProperty(Token name, Type ownerType, ref Property result)
|
||||
// Look up an identifier, and check imported scopes as well.
|
||||
ScopeLookup lookupImport(Token name)
|
||||
{
|
||||
if(hasProperty(name))
|
||||
auto l = lookup(name);
|
||||
if(!l.isNone) return l;
|
||||
|
||||
// Nuttin' was found, try the imports
|
||||
bool found = false;
|
||||
auto old = l;
|
||||
foreach(imp; importList)
|
||||
{
|
||||
getProperty(name, ownerType, result);
|
||||
return true;
|
||||
l = imp.lookup(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;
|
||||
}
|
||||
}
|
||||
|
||||
if(nextProp !is null)
|
||||
return nextProp.findProperty(name, ownerType, result);
|
||||
if(!found)
|
||||
return ScopeLookup(name, LType.None, null, null);
|
||||
|
||||
// No property in this scope. Check the parent.
|
||||
if(!isRoot) return parent.findProperty(name, ownerType, result);
|
||||
|
||||
// No parent, property not found.
|
||||
return false;
|
||||
// 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;
|
||||
}
|
||||
|
||||
Variable* findVar(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findVar(name);
|
||||
}
|
||||
// Add an import to this scope
|
||||
void registerImport(ImportHolder s) { importList ~= s; }
|
||||
|
||||
State* findState(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findState(name);
|
||||
}
|
||||
|
||||
Function* findFunc(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findFunc(name);
|
||||
}
|
||||
|
||||
StructType findStruct(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findStruct(name);
|
||||
}
|
||||
|
||||
EnumType findEnum(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findEnum(name);
|
||||
}
|
||||
|
||||
void insertLabel(StateLabel *lb)
|
||||
{
|
||||
assert(!isRoot);
|
||||
parent.insertLabel(lb);
|
||||
}
|
||||
|
||||
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,32 +654,6 @@ abstract class VarScope : Scope
|
|||
this(Scope last, char[] name)
|
||||
{ super(last, name); }
|
||||
|
||||
override:
|
||||
|
||||
// Find a variable, returns the declaration.
|
||||
Variable* findVar(char[] name)
|
||||
{
|
||||
Variable* vd;
|
||||
|
||||
if(variables.inList(name, vd))
|
||||
return vd;
|
||||
|
||||
return super.findVar(name);
|
||||
}
|
||||
|
||||
void clearId(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);
|
||||
|
||||
super.clearId(name);
|
||||
}
|
||||
|
||||
// Insert a variable, checks if it already exists.
|
||||
void insertVar(Variable* dec)
|
||||
{
|
||||
|
@ -585,6 +664,19 @@ abstract class VarScope : Scope
|
|||
|
||||
variables[dec.name.str] = dec;
|
||||
}
|
||||
|
||||
override:
|
||||
|
||||
ScopeLookup lookup(Token name)
|
||||
{
|
||||
assert(name.str != "");
|
||||
|
||||
Variable *vd;
|
||||
if(variables.inList(name.str, vd))
|
||||
return ScopeLookup(vd.name, LType.Variable, vd.type, this, vd);
|
||||
|
||||
return super.lookup(name);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(sl.isImport)
|
||||
sl = sl.imphold.mc.sc.lookup(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;
|
||||
|
||||
// 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)
|
||||
// Not a struct. Maybe an enum?
|
||||
realType = sc.findEnum(id.str);
|
||||
|
||||
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);
|
||||
|
||||
realType.resolve(sc);
|
||||
{
|
||||
// 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);
|
||||
|
||||
if(name.type == TT.Clone)
|
||||
fail("Cannot use clone 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(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))
|
||||
{
|
||||
type = tp.getMeta();
|
||||
}
|
||||
else
|
||||
// No match at all
|
||||
fail("Undefined identifier "~name.str, name.loc);
|
||||
{
|
||||
assert(look.type !is null);
|
||||
type = look.type.getMeta();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
10
monster/modules/all.d
Normal file
10
monster/modules/all.d
Normal file
|
@ -0,0 +1,10 @@
|
|||
module monster.modules.all;
|
||||
|
||||
import monster.modules.io;
|
||||
import monster.modules.timer;
|
||||
|
||||
void initAllModules()
|
||||
{
|
||||
initIOModule();
|
||||
initTimerModule();
|
||||
}
|
46
monster/modules/io.d
Normal file
46
monster/modules/io.d
Normal file
|
@ -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(); });
|
||||
}
|
12
monster/modules/io.mn
Normal file
12
monster/modules/io.mn
Normal file
|
@ -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...);
|
171
monster/modules/timer.d
Normal file
171
monster/modules/timer.d
Normal file
|
@ -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");
|
||||
}
|
7
monster/modules/timer.mn
Normal file
7
monster/modules/timer.mn
Normal file
|
@ -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); }
|
||||
|
||||
|
|
26
monster/vm/gc.d
Normal file
26
monster/vm/gc.d
Normal file
|
@ -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,64 +483,21 @@ 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);
|
||||
|
||||
if(isModule && singObj !is null)
|
||||
fail("Cannot create instances of module " ~ name.str);
|
||||
|
||||
// Create the thread
|
||||
CodeThread *trd = threads.getNew();
|
||||
|
||||
// Create an object tree equivalent of the class tree
|
||||
MonsterObject* otree[];
|
||||
otree.length = tree.length;
|
||||
|
||||
assert(otree.length > 0);
|
||||
|
||||
// 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];
|
||||
|
||||
// Fill the list with objects, and assign the thread.
|
||||
foreach(i, ref obj; otree)
|
||||
{
|
||||
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);
|
||||
|
||||
// Initialize the thread
|
||||
trd.initialize(top);
|
||||
|
||||
// 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;
|
||||
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
|
||||
|
@ -550,41 +505,97 @@ final class MonsterClass
|
|||
{
|
||||
requireCompile();
|
||||
|
||||
assert(source.tree.length == tree.length);
|
||||
assert(source.thread.topObj == source,
|
||||
"createClone can only clone the topmost object");
|
||||
if(isModule && singObj !is null)
|
||||
fail("Cannot create instances of module " ~ name.str);
|
||||
|
||||
// Create a new thread
|
||||
CodeThread *trd = threads.getNew();
|
||||
MonsterObject *obj = allObjects.getNew();
|
||||
|
||||
// Create one buffer big enough for all the data segments here,
|
||||
// and let getClone slice it.
|
||||
int[] totalData = new int[totalDataSize];
|
||||
obj.state = null;
|
||||
obj.cls = this;
|
||||
|
||||
// Loop through the objects in the source tree, and clone each
|
||||
// of them
|
||||
MonsterObject* otree[] = source.tree.dup;
|
||||
foreach(i, ref obj; otree)
|
||||
// Allocate the object data segment from a freelist
|
||||
int[] odata = Buffers.getInt(totalData.length);
|
||||
|
||||
// Copy the data, either from the class (in case of new objects)
|
||||
// or from the source (when cloning.)
|
||||
if(source !is null)
|
||||
{
|
||||
obj = obj.cls.getClone(obj, totalData);
|
||||
obj.tree = otree[0..i+1];
|
||||
obj.thread = trd;
|
||||
assert(!isAbstract);
|
||||
assert(source.cls is this);
|
||||
|
||||
assert(source.data.length == tree.length);
|
||||
|
||||
// Copy data from the object
|
||||
odata[] = getDataBlock(source);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(isAbstract)
|
||||
fail("Cannot create objects from abstract class " ~ name.str);
|
||||
|
||||
// Copy init values from the class
|
||||
odata[] = totalData[];
|
||||
}
|
||||
|
||||
// Make sure we used the entire buffer
|
||||
assert(totalData.length == 0);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Pick out the top object
|
||||
MonsterObject* top = otree[$-1];
|
||||
assert(top !is null);
|
||||
// The beginning of the block is used for the int data[][]
|
||||
// array.
|
||||
obj.data = cast(int[][]) get(iasize*tree.length);
|
||||
|
||||
// Initialize the thread
|
||||
trd.initialize(top);
|
||||
// Set up the a slice for the data segment of each class
|
||||
foreach(i, c; tree)
|
||||
{
|
||||
// Just get the slice - the actual data is already set up.
|
||||
obj.data[i] = get(c.data.length + MonsterObject.exSize);
|
||||
|
||||
// Set the same state
|
||||
trd.setState(source.thread.getState(), null);
|
||||
// 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);
|
||||
}
|
||||
|
||||
return top;
|
||||
// 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();
|
||||
}
|
||||
|
||||
// TODO: Call script-constructor here
|
||||
}
|
||||
|
||||
// Set the same state as the source
|
||||
if(source !is null)
|
||||
obj.setState(source.state, null);
|
||||
|
||||
// Make sure that getDataBlock works
|
||||
assert(getDataBlock(obj).ptr == odata.ptr &&
|
||||
getDataBlock(obj).length == odata.length);
|
||||
|
||||
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();
|
||||
|
||||
// Destruct the objects in reverse order
|
||||
foreach_reverse(ob; obj.thread.topObj.tree)
|
||||
ob.cls.returnObject(ob);
|
||||
// clearState should also clear the thread
|
||||
assert(obj.sthread is null);
|
||||
|
||||
// Put the thread back into the free list
|
||||
threads.remove(obj.thread);
|
||||
// This effectively marks the object as dead
|
||||
obj.cls = null;
|
||||
|
||||
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(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);
|
||||
assert(str !is null);
|
||||
|
||||
TokenArray tokens = tokenizeStream(fname, str, bom);
|
||||
|
||||
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,55 +113,9 @@ 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);
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Casting / polymorphism functions *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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];
|
||||
}
|
||||
|
||||
// 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; }
|
||||
|
||||
{ return cls.createClone(this); }
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
|
@ -155,6 +123,18 @@ struct MonsterObject
|
|||
* *
|
||||
*******************************************************/
|
||||
|
||||
// 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)
|
||||
{
|
||||
return & (cast(ExtraData*)&data[index][$-exSize]).extra;
|
||||
}
|
||||
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,66 +413,29 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
alias FreeList!(MonsterObject) ObjectList;
|
||||
|
||||
// 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)
|
||||
{
|
||||
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.
|
||||
|
||||
// Pointer to our current scheduling point. If null, we are not
|
||||
// currently sceduled. Only applies to state code, not scheduled
|
||||
// function calls.
|
||||
CallNode scheduleNode;
|
||||
// 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;
|
||||
|
||||
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(!isActive,
|
||||
"callState cannot be called when object is already active");
|
||||
assert(fstack.isEmpty,
|
||||
"ctate code can only run at the bottom of the function stack");
|
||||
assert(theObj !is null,
|
||||
"cannot reenter a non-state thread yet");
|
||||
|
||||
// Set a bool to indicate that we are now actively running state
|
||||
// code.
|
||||
// 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,
|
||||
"reenter cannot be called when object is already active");
|
||||
assert(fstack.isEmpty,
|
||||
"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);
|
||||
|
||||
// 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());
|
||||
|
||||
// Get the function
|
||||
idle = cls.findFunction(code.getInt());
|
||||
assert(idle !is null && idle.isIdle);
|
||||
assert(cls is idle.owner);
|
||||
assert(idleObj.cls.childOf(cls));
|
||||
|
||||
// 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 ~ "'");
|
||||
// idle.idleFunc
|
||||
if(idle.idleFunc is null)
|
||||
fail("Called unimplemented idle function '" ~ idle.name.str ~ "'");
|
||||
|
||||
// Tell the scheduler that an idle function was called. It
|
||||
// will reschedule us as needed.
|
||||
scheduler.callIdle(mo, fn, fstack.cur.code.getPos);
|
||||
// Set the return position
|
||||
retPos = fstack.cur.code.getPos();
|
||||
|
||||
// Set up extraData
|
||||
extraData = *idleObj.getExtra(idle.owner);
|
||||
|
||||
// Notify the idle function
|
||||
fstack.pushIdleInit(idle, idleObj);
|
||||
if(idle.idleFunc.initiate(this))
|
||||
moveTo(&scheduler.wait);
|
||||
fstack.pop();
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Return the correct pointer
|
||||
return tmp.getDataInt(index);
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
fail("Unable to handle pointer type " ~ toString(cast(int)type));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Reduce or remove as many of these as possible
|
||||
// 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");
|
||||
|
||||
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:
|
||||
case BC.CastT2S:
|
||||
{
|
||||
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);
|
||||
// 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.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:
|
||||
{
|
||||
MIndex idx = stack.popMIndex();
|
||||
if(idx != 0)
|
||||
stack.pushArray(format("%s#%s", getMObject(idx).cls.getName,
|
||||
cast(int)idx));
|
||||
else
|
||||
stack.pushCArray("(null object)");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
18
mscripts/fpsticker.mn
Normal file
18
mscripts/fpsticker.mn
Normal file
|
@ -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;
|
||||
}
|
||||
|
|
4
openmw.d
4
openmw.d
|
@ -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…
Reference in a new issue