Updated to Monster 0.14 and fixed console bugs

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save