mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-21 21:39:42 +00:00
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
This commit is contained in:
parent
af6503977a
commit
a8c594ac8b
30 changed files with 2299 additions and 771 deletions
File diff suppressed because it is too large
Load diff
|
@ -117,6 +117,35 @@ abstract class Block
|
|||
reqNext(toks, type, t);
|
||||
}
|
||||
|
||||
// Skip any matching set of parens (), [] or {}, and anything inside
|
||||
// it.
|
||||
static void skipParens(ref TokenArray toks, TT type = TT.LeftParen)
|
||||
{
|
||||
TT endType;
|
||||
if(type == TT.LeftParen)
|
||||
endType = TT.RightParen;
|
||||
else if(type == TT.LeftCurl)
|
||||
endType = TT.RightCurl;
|
||||
else if(type == TT.LeftSquare)
|
||||
endType = TT.RightSquare;
|
||||
else assert(0);
|
||||
|
||||
int count = 0;
|
||||
|
||||
while(toks.length != 0)
|
||||
{
|
||||
if(toks[0].type == type)
|
||||
count++;
|
||||
else if(toks[0].type == endType)
|
||||
count--;
|
||||
|
||||
toks = toks[1..$];
|
||||
|
||||
if(count <= 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the assembler debug line to the line belonging to this
|
||||
// block.
|
||||
final void setLine() { tasm.setLine(loc.line); }
|
||||
|
|
|
@ -29,20 +29,12 @@ enum BC
|
|||
{
|
||||
Exit = 1, // Exit function.
|
||||
|
||||
Call, // Call function in this object. Takes a class
|
||||
// tree index and a function index, both ints.
|
||||
Call, // Call function in this object. Takes a
|
||||
// global function index on the stack, int.
|
||||
|
||||
CallFar, // Call function in another object. Takes a
|
||||
// class index tree and a function index. The
|
||||
// object must be pushed on the stack.
|
||||
|
||||
CallIdle, // Calls an idle function in this object.
|
||||
// Takes a class tree index and a function
|
||||
// index.
|
||||
|
||||
CallIdleFar, // Calls an idle function in another
|
||||
// object. Also takes an object index from the
|
||||
// stack.
|
||||
CallFar, // Call function in another object. The object
|
||||
// must be pushed on the stack, followed by
|
||||
// the global function index.
|
||||
|
||||
Return, // Takes a parameter nr (int). Equivalent to:
|
||||
// POPN nr (remove nr values of the stack)
|
||||
|
@ -267,6 +259,8 @@ enum BC
|
|||
CastD2L, // double to long
|
||||
CastD2UL, // double to ulong
|
||||
|
||||
CastS2C, // string to char - string must have length 1
|
||||
|
||||
CastT2S, // cast any type to string. Takes the type
|
||||
// index (int) as a parameter
|
||||
|
||||
|
@ -274,6 +268,9 @@ enum BC
|
|||
// the object on the stack is an instance of
|
||||
// the given class.
|
||||
|
||||
RefFunc, // Pop an array reference (two ints) and push
|
||||
// the corresponding function name as a string
|
||||
|
||||
FetchElem, // Get an element from an array. Pops the
|
||||
// index, then the array reference, then
|
||||
// pushes the value. The element size is
|
||||
|
@ -397,7 +394,7 @@ enum BC
|
|||
// (byte) defined below. The library user will
|
||||
// later be able to choose whether this halts
|
||||
// execution entirely or just kills the
|
||||
// offending object.
|
||||
// offending vthread.
|
||||
|
||||
Last
|
||||
}
|
||||
|
@ -418,13 +415,13 @@ enum Err
|
|||
// Used for coded pointers. The first byte in a coded pointer gives
|
||||
// the pointer type, and the remaining 24 bits gives an index whose
|
||||
// meaning is determined by the type. The pointers can be used both
|
||||
// for variables and for functions.
|
||||
// for variables and for functions. All pointers are 3 ints in size on
|
||||
// the stack. The comments below defines what the indices mean - the
|
||||
// ones that are not mentioned are zero.
|
||||
enum PT
|
||||
{
|
||||
Null = 0, // Null pointer. The index must also be zero.
|
||||
|
||||
// Variable pointers
|
||||
|
||||
Stack = 1, // Index is relative to function stack
|
||||
// frame. Used for local variables.
|
||||
|
||||
|
@ -532,7 +529,6 @@ char[][] bcToString =
|
|||
BC.Exit: "Exit",
|
||||
BC.Call: "Call",
|
||||
BC.CallFar: "CallFar",
|
||||
BC.CallIdle: "CallIdle",
|
||||
BC.Return: "Return",
|
||||
BC.ReturnVal: "ReturnVal",
|
||||
BC.ReturnValN: "ReturnValN",
|
||||
|
@ -563,6 +559,7 @@ char[][] bcToString =
|
|||
BC.Store: "Store",
|
||||
BC.Store8: "Store8",
|
||||
BC.StoreMult: "StoreMult",
|
||||
BC.RefFunc: "RefFunc",
|
||||
BC.FetchElem: "FetchElem",
|
||||
BC.GetArrLen: "GetArrLen",
|
||||
BC.IMul: "IMul",
|
||||
|
@ -634,6 +631,7 @@ char[][] bcToString =
|
|||
BC.CastD2U: "CastD2U",
|
||||
BC.CastD2L: "CastD2L",
|
||||
BC.CastD2UL: "CastD2UL",
|
||||
BC.CastS2C: "CastS2C",
|
||||
BC.CastT2S: "CastT2S",
|
||||
BC.DownCast: "DownCast",
|
||||
BC.PopToArray: "PopToArray",
|
||||
|
|
|
@ -390,6 +390,14 @@ abstract class Expression : Block
|
|||
final void evalPop()
|
||||
{
|
||||
eval();
|
||||
|
||||
// Internal types never come from expressions that have any
|
||||
// effect. Leaving them dangling without a reciever can cause
|
||||
// stack bugs in some cases (since the owner and other internal
|
||||
// data is never popped), so we forbid them.
|
||||
if(!type.isLegal)
|
||||
fail("Expression " ~ toString() ~ " has no effect", loc);
|
||||
|
||||
setLine();
|
||||
if(!type.isVoid)
|
||||
tasm.pop(type.getSize());
|
||||
|
@ -621,7 +629,10 @@ class NewExpression : Expression
|
|||
|
||||
if(type.isObject)
|
||||
{
|
||||
// Then push the variable pointers and values on the stack
|
||||
// Total stack size of parameters
|
||||
int imprint = 0;
|
||||
|
||||
// Push the variable pointers and values on the stack
|
||||
foreach(i, v; params)
|
||||
{
|
||||
auto lvar = varlist[i];
|
||||
|
@ -630,14 +641,19 @@ class NewExpression : Expression
|
|||
assert(lvar !is null);
|
||||
assert(lvar.sc !is null);
|
||||
|
||||
// Push the expression and some meta info
|
||||
v.value.eval();
|
||||
tasm.push(v.value.type.getSize); // Type size
|
||||
tasm.push(lvar.number); // Var number
|
||||
tasm.push(lvar.sc.getClass().getTreeIndex()); // Class index
|
||||
|
||||
// Tally up the casualties
|
||||
imprint += v.value.type.getSize() + 3;
|
||||
}
|
||||
// Create a new object. This is done through a special byte code
|
||||
// instruction.
|
||||
tasm.newObj(clsInd, params.length);
|
||||
|
||||
// Create a new object. This is done through a special byte
|
||||
// code instruction.
|
||||
tasm.newObj(clsInd, params.length, imprint);
|
||||
}
|
||||
else if(type.isArray)
|
||||
{
|
||||
|
@ -780,9 +796,9 @@ class ArrayLiteralExpr : Expression
|
|||
}
|
||||
|
||||
// Expression representing a literal or other special single-token
|
||||
// values. Supported tokens are StringLiteral, Int/FloatLiteral,
|
||||
// CharLiteral, True, False, Null, Dollar and This. Array literals are
|
||||
// handled by ArrayLiteralExpr.
|
||||
// values. Supported tokens are StringLiteral, Int/FloatLiteral, True,
|
||||
// False, Null, Dollar and This. Array literals are handled by
|
||||
// ArrayLiteralExpr.
|
||||
class LiteralExpr : Expression
|
||||
{
|
||||
Token value;
|
||||
|
@ -792,22 +808,11 @@ class LiteralExpr : Expression
|
|||
dchar dval; // Characters are handled internally as dchars
|
||||
float fval;
|
||||
|
||||
// TODO/FIXME: When evalutationg the array length symbol $, we
|
||||
// evaluate the array expression again, and the find its
|
||||
// length. This is a TEMPORARY solution - if the array expression is
|
||||
// a complicated expression or if it has side effects, then
|
||||
// evaluating it more than once is obviously not a good idea. Example:
|
||||
// myfunc()[$-4..$]; // myfunc() is called three times!
|
||||
// The array operator expression for $ symbols (ie. a[b] or a[b..c])
|
||||
ArrayOperator arrOp;
|
||||
|
||||
// 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
|
||||
// more complete implementation will come later.
|
||||
// This is set if the array that $ refers to is ctime.
|
||||
bool arrayCTime;
|
||||
|
||||
// String, with decoded escape characters etc and converted to
|
||||
// utf32. We need the full dchar string here, since we might have to
|
||||
|
@ -825,7 +830,6 @@ class LiteralExpr : Expression
|
|||
isNext(toks, TT.StringLiteral) ||
|
||||
isNext(toks, TT.IntLiteral) ||
|
||||
isNext(toks, TT.FloatLiteral) ||
|
||||
isNext(toks, TT.CharLiteral) ||
|
||||
isNext(toks, TT.True) ||
|
||||
isNext(toks, TT.False) ||
|
||||
isNext(toks, TT.Null) ||
|
||||
|
@ -893,7 +897,13 @@ class LiteralExpr : Expression
|
|||
fail("Array length $ not allowed here", loc);
|
||||
|
||||
type = BasicType.getInt;
|
||||
arrayExp = sc.getArray();
|
||||
arrOp = sc.getArray();
|
||||
|
||||
// If the array itself is compile time, then so is the
|
||||
// length.
|
||||
assert(arrOp.name !is null);
|
||||
assert(arrOp.name.type.isArray());
|
||||
arrayCTime = arrOp.name.isCTime();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -916,32 +926,12 @@ class LiteralExpr : Expression
|
|||
return;
|
||||
}
|
||||
|
||||
// Single character
|
||||
if(value.type == TT.CharLiteral)
|
||||
{
|
||||
type = BasicType.getChar;
|
||||
|
||||
// Decode the unicode character. TODO: Error checking?
|
||||
// Unicode sanity checks should be handled in the tokenizer.
|
||||
size_t idx = 1;
|
||||
dval = decode(value.str, idx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Strings
|
||||
if(value.type == TT.StringLiteral)
|
||||
{
|
||||
type = ArrayType.getString;
|
||||
|
||||
// Check that we do indeed have '"'s at the ends of the
|
||||
// string. Special cases which we allow later (like wysiwig
|
||||
// strings, @"c:\") will have their special characters
|
||||
// removed in the tokenizer.
|
||||
assert(value.str.length >=2 && value.str[0] == '"' && value.str[$-1] == '"',
|
||||
"Encountered invalid string literal token: " ~ value.str);
|
||||
|
||||
strVal = toUTF32(value.str[1..$-1]);
|
||||
//cls.reserveStatic(strVal.length);
|
||||
strVal = value.str32;
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -952,7 +942,9 @@ class LiteralExpr : Expression
|
|||
// We currently support a few kinds of constants
|
||||
bool isCTime()
|
||||
{
|
||||
if(value.type == TT.Dollar) return false;
|
||||
// If the array value itself is ctime, then $ is too.
|
||||
if(value.type == TT.Dollar)
|
||||
return arrayCTime;
|
||||
|
||||
return
|
||||
type.isInt() || type.isBool() || type.isFloat || type.isChar ||
|
||||
|
@ -961,6 +953,19 @@ class LiteralExpr : Expression
|
|||
|
||||
int[] evalCTime()
|
||||
{
|
||||
if(value.type == TT.Dollar)
|
||||
{
|
||||
// Get the array
|
||||
assert(arrOp.name.isCTime());
|
||||
int res[] = arrOp.name.evalCTime().dup;
|
||||
assert(res.length == 1);
|
||||
auto arf = arrays.getRef(cast(AIndex)res[0]);
|
||||
|
||||
// Return the length
|
||||
res[0] = arf.length();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return a slice of the value
|
||||
if(type.isInt || type.isBool) return (&ival)[0..1];
|
||||
if(type.isChar) return (cast(int*)&dval)[0..1];
|
||||
|
@ -988,14 +993,11 @@ class LiteralExpr : Expression
|
|||
|
||||
if(value.type == TT.Dollar)
|
||||
{
|
||||
// Get the array. TODO/FIXME: This is a very bad solution,
|
||||
// the entire array expression is recomputed whenever we use
|
||||
// the $ symbol. If the expression has side effects (like a
|
||||
// function call), this can give unexpected results. This is
|
||||
// a known bug that will be fixed later. The simplest
|
||||
// solution is to let ArrayExpression create a new scope,
|
||||
// which stores the stack position of the array index.
|
||||
arrayExp.eval();
|
||||
// The array index should already be on the stack. Get it.
|
||||
auto sMark = arrOp.stackMark;
|
||||
assert(sMark !is null);
|
||||
tasm.pushMark(sMark);
|
||||
|
||||
// Convert it to the length
|
||||
setLine();
|
||||
tasm.getArrayLength();
|
||||
|
@ -1053,7 +1055,7 @@ class CastExpression : Expression
|
|||
orig.eval();
|
||||
|
||||
// The type does the low-level stuff
|
||||
orig.type.evalCastTo(type);
|
||||
orig.type.evalCastTo(type, orig.loc);
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
|
@ -1075,6 +1077,10 @@ abstract class ImportHolder : Expression
|
|||
// Get a short name (for error messages)
|
||||
abstract char[] getName();
|
||||
|
||||
// Override these to avoid duplicate imports
|
||||
bool isClass(MonsterClass mc) { return false; }
|
||||
bool isPack(PackageScope sc) { return false; }
|
||||
|
||||
override:
|
||||
|
||||
// Override these
|
||||
|
@ -1112,6 +1118,8 @@ class PackageImpHolder : ImportHolder
|
|||
char[] getName() { return sc.toString(); }
|
||||
|
||||
char[] toString() { return "imported package " ~ sc.toString(); }
|
||||
|
||||
bool isPack(PackageScope s) { return s is sc; }
|
||||
}
|
||||
|
||||
// Import holder for classes
|
||||
|
@ -1160,6 +1168,8 @@ class ClassImpHolder : ImportHolder
|
|||
tasm.pushSingleton(mc.getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
bool isClass(MonsterClass m) { return m is mc; }
|
||||
}
|
||||
|
||||
// An expression that works as a statement. This also handles all
|
||||
|
|
|
@ -54,6 +54,8 @@ import monster.vm.thread;
|
|||
import monster.vm.stack;
|
||||
import monster.vm.vm;
|
||||
|
||||
import monster.util.growarray;
|
||||
|
||||
import std.stdio;
|
||||
import std.stream;
|
||||
import std.string;
|
||||
|
@ -100,12 +102,38 @@ struct Function
|
|||
int[][] defaults; // Default parameter values (if specified, null otherwise)
|
||||
int index; // Unique function identifier within its class
|
||||
|
||||
int paramSize;
|
||||
// Register this function in the global function list. This can only
|
||||
// be done once, but it's required before functions can be
|
||||
// referenced by function pointers in script code. Don't call this
|
||||
// if you don't know what you're doing.
|
||||
void register()
|
||||
{
|
||||
assert(!hasGIndex(), "Don't call register() more than once.");
|
||||
|
||||
/*
|
||||
int imprint; // Stack imprint of this function. Equals
|
||||
// (type.getSize() - paramSize) (not implemented yet)
|
||||
*/
|
||||
gIndex = functionList.length();
|
||||
functionList ~= this;
|
||||
}
|
||||
|
||||
int getGIndex()
|
||||
{
|
||||
assert(hasGIndex(), "This function doesn't have a global index");
|
||||
return gIndex;
|
||||
}
|
||||
|
||||
bool hasGIndex() { return gIndex != -1; }
|
||||
|
||||
static Function *fromIndex(int index)
|
||||
{
|
||||
if(index < 0)
|
||||
fail("Null function reference encountered");
|
||||
|
||||
if(index > functionList.length)
|
||||
fail("Invalid function index encountered");
|
||||
|
||||
return functionList[index];
|
||||
}
|
||||
|
||||
int paramSize;
|
||||
|
||||
// Is this function final? (can not be overridden in child classes)
|
||||
bool isFinal;
|
||||
|
@ -116,6 +144,21 @@ struct Function
|
|||
// What function we override (if any)
|
||||
Function *overrides;
|
||||
|
||||
// Find the virtual replacement for this function in the context of
|
||||
// the object mo.
|
||||
Function *findVirtual(MonsterObject *mo)
|
||||
{
|
||||
assert(mo !is null);
|
||||
return findVirtual(mo.cls);
|
||||
}
|
||||
|
||||
// Find virtual replacement in the context of class cls.
|
||||
Function *findVirtual(MonsterClass cls)
|
||||
{
|
||||
assert(cls.childOf(owner));
|
||||
return cls.findVirtualFunc(owner.getTreeIndex(), index);
|
||||
}
|
||||
|
||||
bool isNormal() { return ftype == FuncType.Normal; }
|
||||
bool isNative()
|
||||
{
|
||||
|
@ -416,6 +459,9 @@ struct Function
|
|||
|
||||
private:
|
||||
|
||||
// Global unique function index
|
||||
int gIndex = -1;
|
||||
|
||||
// Empty class / object used internally
|
||||
static const char[] int_class = "class _ScriptFile_;";
|
||||
static MonsterClass int_mc;
|
||||
|
@ -475,6 +521,9 @@ class Constructor : FuncDeclaration
|
|||
}
|
||||
}
|
||||
|
||||
// Global list of functions.
|
||||
GrowArray!(Function*) functionList;
|
||||
|
||||
// Responsible for parsing, analysing and compiling functions.
|
||||
class FuncDeclaration : Statement
|
||||
{
|
||||
|
@ -604,6 +653,10 @@ class FuncDeclaration : Statement
|
|||
// Create a Function struct.
|
||||
fn = new Function;
|
||||
|
||||
// Register a global index for all class functions
|
||||
fn.register();
|
||||
assert(fn.getGIndex() != -1);
|
||||
|
||||
// Default function type is normal
|
||||
fn.ftype = FuncType.Normal;
|
||||
|
||||
|
@ -626,20 +679,6 @@ class FuncDeclaration : Statement
|
|||
if(fn.isAbstract || fn.isNative || fn.isIdle)
|
||||
{
|
||||
reqSep(toks);
|
||||
// Check that the function declaration ends with a ; rather
|
||||
// than a code block.
|
||||
/*
|
||||
if(!isNext(toks, TT.Semicolon))
|
||||
{
|
||||
if(fn.isAbstract)
|
||||
fail("Abstract function declaration expected ;", toks);
|
||||
else if(fn.isNative)
|
||||
fail("Native function declaration expected ;", toks);
|
||||
else if(fn.isIdle)
|
||||
fail("Idle function declaration expected ;", toks);
|
||||
else assert(0);
|
||||
}
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -657,7 +696,6 @@ class FuncDeclaration : Statement
|
|||
fail("Token '" ~ fn.name.str ~ "' cannot be used as a function name",
|
||||
loc);
|
||||
|
||||
|
||||
if(!isNext(toks, TT.LeftParen))
|
||||
fail("Function expected parameter list", toks);
|
||||
|
||||
|
@ -907,11 +945,11 @@ class FuncDeclaration : Statement
|
|||
|
||||
if(fn.type.isVoid)
|
||||
// Remove parameters from the stack at the end of the function
|
||||
tasm.exit(fn.paramSize);
|
||||
tasm.exit(fn.paramSize,0,0);
|
||||
else
|
||||
// Functions with return types must have a return statement
|
||||
// and should never reach the end of the function. Fail if we
|
||||
// do.
|
||||
// Functions with return types must have a return statement,
|
||||
// so we should never reach the end of the function. Fail if
|
||||
// we do.
|
||||
tasm.error(Err.NoReturn);
|
||||
|
||||
// Assemble the finished function
|
||||
|
@ -936,7 +974,12 @@ class FunctionCallExpr : Expression
|
|||
// function parameter list. Null expressions
|
||||
// means we must use the default value. Never
|
||||
// used for vararg functions.
|
||||
|
||||
// These are used to get information about the function and
|
||||
// parameters. If this is a function reference call, fd is null and
|
||||
// only frt is set.
|
||||
Function* fd;
|
||||
FuncRefType frt;
|
||||
|
||||
bool isCast; // If true, this is an explicit typecast, not a
|
||||
// function call.
|
||||
|
@ -1049,11 +1092,6 @@ class FunctionCallExpr : Expression
|
|||
fResolved = console;
|
||||
}
|
||||
|
||||
/* Might be used for D-like implicit function calling, eg. someFunc;
|
||||
or (obj.prop)
|
||||
this(Expression func) {}
|
||||
*/
|
||||
|
||||
void parse(ref TokenArray toks) { assert(0); }
|
||||
|
||||
char[] toString()
|
||||
|
@ -1099,41 +1137,75 @@ class FunctionCallExpr : Expression
|
|||
|
||||
// TODO: Do typecasting here. That will take care of polysemous
|
||||
// types later as well.
|
||||
if(!fname.type.isFunc)
|
||||
if(!fname.type.isIntFunc && !fname.type.isFuncRef)
|
||||
fail(format("Expression '%s' of type %s is not a function",
|
||||
fname, fname.typeString), loc);
|
||||
|
||||
// In the future, we will probably use a global function
|
||||
// index. Right now, just get the function from the type.
|
||||
auto ft = cast(FunctionType)fname.type;
|
||||
if(fname.type.isFuncRef)
|
||||
{
|
||||
// Set frt to the reference type
|
||||
frt = cast(FuncRefType)fname.type;
|
||||
assert(frt !is null);
|
||||
fd = null;
|
||||
}
|
||||
else if(fname.type.isIntFunc)
|
||||
{
|
||||
// Get the function from the type
|
||||
auto ft = cast(IntFuncType)fname.type;
|
||||
assert(ft !is null);
|
||||
fd = ft.func;
|
||||
|
||||
isVararg = fd.isVararg;
|
||||
type = fd.type;
|
||||
// Create a temporary reference type
|
||||
frt = new FuncRefType(ft);
|
||||
}
|
||||
|
||||
isVararg = frt.isVararg;
|
||||
type = frt.retType;
|
||||
assert(type !is null);
|
||||
|
||||
if(fd is null && named.length != 0)
|
||||
fail("Cannot use named parameters when calling function references",
|
||||
loc);
|
||||
|
||||
// Get the best parameter name possible from the available
|
||||
// information
|
||||
char[] getParName(int i)
|
||||
{
|
||||
char[] dst = "parameter ";
|
||||
if(fd !is null)
|
||||
dst ~= fd.params[i].name.str;
|
||||
else
|
||||
dst ~= .toString(i);
|
||||
return dst;
|
||||
}
|
||||
|
||||
if(isVararg)
|
||||
{
|
||||
if(named.length)
|
||||
fail("Cannot give named parameters to vararg functions", loc);
|
||||
|
||||
// The vararg parameter can match a variable number of
|
||||
// arguments, including zero.
|
||||
if(params.length < fd.params.length-1)
|
||||
if(params.length < frt.params.length-1)
|
||||
fail(format("%s() expected at least %s parameters, got %s",
|
||||
name, fd.params.length-1, params.length),
|
||||
name, frt.params.length-1, params.length),
|
||||
loc);
|
||||
|
||||
// Check parameter types except for the vararg parameter
|
||||
foreach(int i, par; fd.params[0..$-1])
|
||||
foreach(int i, par; frt.params[0..$-1])
|
||||
{
|
||||
params[i].resolve(sc);
|
||||
par.type.typeCast(params[i], "parameter " ~ par.name.str);
|
||||
|
||||
// The info about each parameter depends on whether this
|
||||
// is a direct function call or a func reference call.
|
||||
par.typeCast(params[i], getParName(i));
|
||||
}
|
||||
|
||||
// Loop through remaining arguments
|
||||
int start = fd.params.length-1;
|
||||
int start = frt.params.length-1;
|
||||
|
||||
assert(fd.params[start].type.isArray);
|
||||
Type base = fd.params[start].type.getBase();
|
||||
assert(frt.params[start].isArray);
|
||||
Type base = frt.params[start].getBase();
|
||||
|
||||
foreach(int i, ref par; params[start..$])
|
||||
{
|
||||
|
@ -1143,7 +1215,7 @@ class FunctionCallExpr : Expression
|
|||
// array type itself, then we are sending an actual
|
||||
// array. Treat it like a normal parameter.
|
||||
if(i == 0 && start == params.length-1 &&
|
||||
par.type == fd.params[start].type)
|
||||
par.type == frt.params[start])
|
||||
{
|
||||
isVararg = false;
|
||||
coverage = params;
|
||||
|
@ -1151,7 +1223,7 @@ class FunctionCallExpr : Expression
|
|||
}
|
||||
|
||||
// Otherwise, cast the type to the array base type.
|
||||
base.typeCast(par, "array base type");
|
||||
base.typeCast(par, "vararg array base type");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1159,10 +1231,17 @@ class FunctionCallExpr : Expression
|
|||
// Non-vararg case. Non-vararg functions must cover at least all
|
||||
// the non-optional function parameters.
|
||||
|
||||
int parNum = frt.params.length;
|
||||
|
||||
int parNum = fd.params.length;
|
||||
// When calling a function reference, we can't use named
|
||||
// parameters, and we don't know the function's default
|
||||
// values. Because of this, all parameters must be present.
|
||||
if(fd is null && params.length != parNum)
|
||||
fail(format("Function reference %s (of type '%s') expected %s arguments, got %s",
|
||||
name, frt, parNum, params.length), fname.loc);
|
||||
|
||||
// Sanity check on the parameter number
|
||||
// Sanity check on the parameter number for normal function
|
||||
// calls
|
||||
if(params.length > parNum)
|
||||
fail(format("Too many parameters to function %s(): expected %s, got %s",
|
||||
name, parNum, params.length), fname.loc);
|
||||
|
@ -1170,7 +1249,8 @@ class FunctionCallExpr : Expression
|
|||
// Make the coverage list of all the parameters.
|
||||
coverage = new Expression[parNum];
|
||||
|
||||
// Mark all the parameters which are present
|
||||
// Mark all the parameters which are present. Start with the
|
||||
// sequential (non-named) paramteres.
|
||||
foreach(i,p; params)
|
||||
{
|
||||
assert(coverage[i] is null);
|
||||
|
@ -1178,6 +1258,13 @@ class FunctionCallExpr : Expression
|
|||
coverage[i] = p;
|
||||
}
|
||||
|
||||
if(fd !is null)
|
||||
{
|
||||
// This part (named and optional parameters) does not apply
|
||||
// when calling function references
|
||||
|
||||
assert(fd !is null);
|
||||
|
||||
// Add named parameters to the list
|
||||
foreach(p; named)
|
||||
{
|
||||
|
@ -1190,7 +1277,7 @@ class FunctionCallExpr : Expression
|
|||
break;
|
||||
}
|
||||
if(index == -1)
|
||||
fail(format("Function %s() has no paramter named %s",
|
||||
fail(format("Function %s() has no parameter named %s",
|
||||
name, p.name.str),
|
||||
p.name.loc);
|
||||
|
||||
|
@ -1213,24 +1300,29 @@ class FunctionCallExpr : Expression
|
|||
fail(format("Non-optional parameter %s is missing in call to %s()",
|
||||
fd.params[i].name.str, name),
|
||||
loc);
|
||||
}
|
||||
|
||||
// Check parameter types
|
||||
foreach(int i, ref cov; coverage)
|
||||
{
|
||||
auto par = fd.params[i];
|
||||
|
||||
// Skip missing parameters
|
||||
if(cov is null) continue;
|
||||
if(cov is null)
|
||||
{
|
||||
assert(fd !is null);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto par = frt.params[i];
|
||||
|
||||
cov.resolve(sc);
|
||||
par.type.typeCast(cov, "parameter " ~ par.name.str);
|
||||
par.typeCast(cov, getParName(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the parameters
|
||||
private void evalParams()
|
||||
{
|
||||
// Again, let's handle the vararg case separately
|
||||
// Handle the vararg case separately
|
||||
if(isVararg)
|
||||
{
|
||||
assert(coverage is null);
|
||||
|
@ -1241,19 +1333,19 @@ class FunctionCallExpr : Expression
|
|||
ex.eval();
|
||||
|
||||
// The rest only applies to non-vararg parameters
|
||||
if(i >= fd.params.length-1)
|
||||
if(i >= frt.params.length-1)
|
||||
continue;
|
||||
|
||||
// Convert 'const' parameters to actual constant references
|
||||
if(fd.params[i].isConst)
|
||||
if(frt.isConst[i])
|
||||
{
|
||||
assert(fd.params[i].type.isArray);
|
||||
assert(frt.params[i].isArray);
|
||||
tasm.makeArrayConst();
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the length of the vararg array.
|
||||
int len = params.length - fd.params.length + 1;
|
||||
int len = params.length - frt.params.length + 1;
|
||||
|
||||
// If it contains no elements, push a null array reference
|
||||
// (0 is always null).
|
||||
|
@ -1264,9 +1356,9 @@ class FunctionCallExpr : Expression
|
|||
tasm.popToArray(len, params[$-1].type.getSize());
|
||||
|
||||
// Convert the vararg array to 'const' if needed
|
||||
if(fd.params[$-1].isConst)
|
||||
if(frt.isConst[$-1])
|
||||
{
|
||||
assert(fd.params[$-1].type.isArray);
|
||||
assert(frt.params[$-1].isArray);
|
||||
tasm.makeArrayConst();
|
||||
}
|
||||
}
|
||||
|
@ -1275,16 +1367,23 @@ class FunctionCallExpr : Expression
|
|||
|
||||
// Non-vararg case
|
||||
assert(!isVararg);
|
||||
assert(coverage.length == fd.params.length);
|
||||
assert(coverage.length == frt.params.length);
|
||||
foreach(i, ex; coverage)
|
||||
{
|
||||
if(ex !is null)
|
||||
{
|
||||
assert(ex.type == fd.params[i].type);
|
||||
assert(ex.type !is null);
|
||||
assert(frt.params[i] !is null);
|
||||
assert(ex.type == frt.params[i]);
|
||||
|
||||
ex.eval();
|
||||
}
|
||||
else
|
||||
{
|
||||
// resolve() should already have caught this
|
||||
assert(fd !is null,
|
||||
"cannot use default parameters with reference calls");
|
||||
|
||||
// No param specified, use default value
|
||||
assert(fd.defaults[i].length ==
|
||||
fd.params[i].type.getSize);
|
||||
|
@ -1293,9 +1392,9 @@ class FunctionCallExpr : Expression
|
|||
}
|
||||
|
||||
// Convert 'const' parameters to actual constant references
|
||||
if(fd.params[i].isConst)
|
||||
if(frt.isConst[i])
|
||||
{
|
||||
assert(fd.params[i].type.isArray);
|
||||
assert(frt.params[i].isArray);
|
||||
tasm.makeArrayConst();
|
||||
}
|
||||
}
|
||||
|
@ -1315,25 +1414,51 @@ class FunctionCallExpr : Expression
|
|||
|
||||
return;
|
||||
}
|
||||
assert(frt !is null);
|
||||
|
||||
// Push parameters first
|
||||
evalParams();
|
||||
|
||||
// Then push the function expression, to get the object (if any)
|
||||
// and later to get the function pointer value well.
|
||||
assert(fname.type.isFunc);
|
||||
// Then push the function expression or reference.
|
||||
fname.eval();
|
||||
|
||||
auto ft = cast(FunctionType)fname.type;
|
||||
assert(ft !is null);
|
||||
bool isMember = ft.isMember;
|
||||
|
||||
setLine();
|
||||
|
||||
bool isFar;
|
||||
|
||||
// Total stack imprint of the function call
|
||||
int imprint;
|
||||
|
||||
if(fd !is null)
|
||||
{
|
||||
// Direct function call
|
||||
assert(fname.type.isIntFunc);
|
||||
auto ft = cast(IntFuncType)fname.type;
|
||||
assert(ft !is null);
|
||||
|
||||
assert(fd.owner !is null);
|
||||
|
||||
if(fd.isIdle)
|
||||
tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember);
|
||||
// Calculate the stack imprint
|
||||
imprint = fd.type.getSize() - fd.paramSize;
|
||||
|
||||
// Push the function index. For far function calls, we have
|
||||
// in effect pushed a function reference (object+function)
|
||||
tasm.push(fd.getGIndex());
|
||||
|
||||
isFar = ft.isMember;
|
||||
}
|
||||
else
|
||||
tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember);
|
||||
{
|
||||
// Function reference call
|
||||
assert(fname.type.isFuncRef);
|
||||
isFar = true;
|
||||
|
||||
// Let the type calculate the stack imprint
|
||||
auto frt = cast(FuncRefType)fname.type;
|
||||
assert(frt !is null);
|
||||
|
||||
imprint = frt.getImprint();
|
||||
}
|
||||
|
||||
tasm.call(isFar, imprint);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,13 +39,16 @@ import std.stdio;
|
|||
import std.string;
|
||||
import std.utf;
|
||||
|
||||
// Handles - ! ++ --
|
||||
// Handles - ! ++ -- @
|
||||
class UnaryOperator : Expression
|
||||
{
|
||||
TT opType;
|
||||
Expression exp;
|
||||
bool postfix;
|
||||
|
||||
// Original function type (for @)
|
||||
IntFuncType ift;
|
||||
|
||||
this()
|
||||
{
|
||||
postfix = false;
|
||||
|
@ -65,6 +68,7 @@ class UnaryOperator : Expression
|
|||
return
|
||||
isNext(toks, TT.Minus) ||
|
||||
isNext(toks, TT.Not) ||
|
||||
isNext(toks, TT.Alpha) ||
|
||||
isNext(toks, TT.PlusPlus) ||
|
||||
isNext(toks, TT.MinusMinus);
|
||||
}
|
||||
|
@ -81,14 +85,39 @@ class UnaryOperator : Expression
|
|||
return tokenList[opType] ~ "(" ~ exp.toString ~ ")";
|
||||
}
|
||||
|
||||
// Copy everything from the sub expression
|
||||
void resolve(Scope sc)
|
||||
{
|
||||
exp.resolve(sc);
|
||||
|
||||
// Copy everything from the sub expression
|
||||
type = exp.type;
|
||||
loc = exp.getLoc;
|
||||
|
||||
if(opType == TT.Alpha)
|
||||
{
|
||||
if(!type.isIntFunc)
|
||||
fail("Operator @ cannot be used on non-function " ~
|
||||
exp.toString() ~ " (of type " ~ exp.typeString() ~
|
||||
")", loc);
|
||||
|
||||
ift = cast(IntFuncType)type;
|
||||
assert(ift !is null);
|
||||
|
||||
// Make sure this function has an index
|
||||
if(!ift.func.hasGIndex())
|
||||
fail("Cannot create reference to function " ~ ift.func.toString(),
|
||||
loc);
|
||||
|
||||
// Replace the internal function type with a function
|
||||
// reference.
|
||||
type = new FuncRefType(ift);
|
||||
|
||||
// If the function is not a member, make sure it's a class
|
||||
// function in our current class (or one of our parents.)
|
||||
assert(ift.isMember || (sc.getClass().childOf(ift.func.owner)),
|
||||
"oops, found the wrong owner class");
|
||||
}
|
||||
|
||||
if(opType == TT.PlusPlus || opType == TT.MinusMinus)
|
||||
{
|
||||
if(!type.isIntegral)
|
||||
|
@ -120,7 +149,8 @@ class UnaryOperator : Expression
|
|||
|
||||
bool isCTime()
|
||||
{
|
||||
if(opType == TT.PlusPlus || opType == TT.MinusMinus)
|
||||
if(opType == TT.PlusPlus || opType == TT.MinusMinus ||
|
||||
opType == TT.Alpha)
|
||||
return false;
|
||||
|
||||
return exp.isCTime();
|
||||
|
@ -168,10 +198,22 @@ class UnaryOperator : Expression
|
|||
}
|
||||
|
||||
exp.eval();
|
||||
|
||||
setLine();
|
||||
|
||||
if(opType == TT.Minus)
|
||||
if(opType == TT.Alpha)
|
||||
{
|
||||
assert(ift !is null);
|
||||
|
||||
// If the function is a member, the object will already have
|
||||
// been pushed on the stack. If not, push this.
|
||||
if(!ift.isMember)
|
||||
tasm.pushThis();
|
||||
|
||||
// Push the function index
|
||||
tasm.push(ift.func.getGIndex());
|
||||
}
|
||||
|
||||
else if(opType == TT.Minus)
|
||||
{
|
||||
if(type.isInt || type.isLong || type.isFloat || type.isDouble)
|
||||
tasm.neg(type);
|
||||
|
@ -205,6 +247,9 @@ class ArrayOperator : OperatorExpr
|
|||
|
||||
int isEnum; // Used for enum types
|
||||
|
||||
// A stack mark giving the position of the array index on the stack.
|
||||
CodeElement *stackMark;
|
||||
|
||||
// name[], name[index], name[index..index2]
|
||||
Expression name, index, index2;
|
||||
|
||||
|
@ -287,7 +332,7 @@ class ArrayOperator : OperatorExpr
|
|||
// either name[index] or name[index..index2]
|
||||
|
||||
// Create an inner scope where $ is a valid character.
|
||||
ArrayScope isc = new ArrayScope(sc, name);
|
||||
ArrayScope isc = new ArrayScope(sc, this);
|
||||
|
||||
index.resolve(isc);
|
||||
|
||||
|
@ -317,7 +362,7 @@ class ArrayOperator : OperatorExpr
|
|||
|
||||
if(isDest) return false;
|
||||
|
||||
// a[b] and a[b..c]; is compile time if a, b and c is.
|
||||
// a[b] and a[b..c]; are compile time if a, b and c is.
|
||||
if(index !is null && !index.isCTime) return false;
|
||||
if(index2 !is null && !index2.isCTime) return false;
|
||||
return name.isCTime();
|
||||
|
@ -413,7 +458,11 @@ class ArrayOperator : OperatorExpr
|
|||
return;
|
||||
}
|
||||
|
||||
// Push the array index first
|
||||
// Get a stack mark. This may be used by $ to get the array
|
||||
// index later.
|
||||
stackMark = tasm.markStack();
|
||||
|
||||
// Push the array index
|
||||
name.eval();
|
||||
|
||||
setLine();
|
||||
|
@ -435,7 +484,7 @@ class ArrayOperator : OperatorExpr
|
|||
|
||||
setLine();
|
||||
if(isDest) tasm.elementAddr();
|
||||
else tasm.fetchElement();
|
||||
else tasm.fetchElement(type.getSize());
|
||||
|
||||
assert(!isSlice);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ module monster.compiler.properties;
|
|||
import monster.compiler.scopes;
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.assembler;
|
||||
import monster.vm.mclass;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
|
@ -123,6 +124,29 @@ class FloatProperties : FloatingProperties!(float)
|
|||
class DoubleProperties : FloatingProperties!(double)
|
||||
{ static DoubleProperties singleton; }
|
||||
|
||||
// Function pointers / references
|
||||
class FuncRefProperties : SimplePropertyScope
|
||||
{
|
||||
static FuncRefProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("FuncRefProperties", GenericProperties.singleton);
|
||||
|
||||
// A function ref is object + function index. Just pop away the
|
||||
// function and the object is left on the stack. Since we cannot
|
||||
// possibly know the type of the object at compile time, we
|
||||
// default to 'Object' as the class type.
|
||||
auto ot = MonsterClass.getObject().objType;
|
||||
assert(ot !is null);
|
||||
insert("obj", ot, { tasm.pop(); });
|
||||
|
||||
// TODO: Replace this with something else later (like a Function
|
||||
// class or similar)
|
||||
insert("func", ArrayType.getString(), { tasm.refFunc(); });
|
||||
}
|
||||
}
|
||||
|
||||
// Handles .length, .dup, etc for arrays
|
||||
class ArrayProperties: SimplePropertyScope
|
||||
{
|
||||
|
@ -212,6 +236,10 @@ class GenericProperties : SimplePropertyScope
|
|||
property, the left hand side (eg an int value) is never
|
||||
evaluated. If the type is "" or "owner", then the property type
|
||||
will be the same as the owner type.
|
||||
|
||||
It's likely that parts of this will be simplified or rewritten
|
||||
later. One thing that's missing is compile time properties, for
|
||||
example.
|
||||
*/
|
||||
|
||||
abstract class SimplePropertyScope : PropertyScope
|
||||
|
@ -345,4 +373,5 @@ void initProperties()
|
|||
FloatProperties.singleton = new FloatProperties;
|
||||
DoubleProperties.singleton = new DoubleProperties;
|
||||
ClassProperties.singleton = new ClassProperties;
|
||||
FuncRefProperties.singleton = new FuncRefProperties;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import std.stdio;
|
|||
import std.string;
|
||||
|
||||
import monster.util.aa;
|
||||
import monster.options;
|
||||
|
||||
import monster.compiler.statement;
|
||||
import monster.compiler.expression;
|
||||
|
@ -38,14 +39,13 @@ import monster.compiler.functions;
|
|||
import monster.compiler.states;
|
||||
import monster.compiler.structs;
|
||||
import monster.compiler.enums;
|
||||
import monster.compiler.operators;
|
||||
import monster.compiler.variables;
|
||||
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.error;
|
||||
import monster.vm.vm;
|
||||
|
||||
//debug=lookupTrace;
|
||||
|
||||
// The global scope
|
||||
RootScope global;
|
||||
|
||||
|
@ -199,7 +199,7 @@ abstract class Scope
|
|||
|
||||
// Copy the import list from our parent
|
||||
if(!isRoot)
|
||||
importList = parent.importList;
|
||||
importList = parent.importList.dup;
|
||||
}
|
||||
|
||||
// Verify that an identifier is not declared in this scope. If the
|
||||
|
@ -207,7 +207,7 @@ abstract class Scope
|
|||
// error. Recurses through parent scopes.
|
||||
final void clearId(Token name)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("ClearId %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
// Lookup checks all parent scopes so we only have to call it
|
||||
|
@ -275,18 +275,12 @@ abstract class Scope
|
|||
return parent.getState();
|
||||
}
|
||||
|
||||
Expression getArray()
|
||||
ArrayOperator getArray()
|
||||
{
|
||||
assert(!isRoot(), "getArray called on wrong scope type");
|
||||
return parent.getArray();
|
||||
}
|
||||
|
||||
PackageScope getPackage()
|
||||
{
|
||||
assert(!isRoot(), "getPackage called on the wrong scope type");
|
||||
return parent.getPackage();
|
||||
}
|
||||
|
||||
int getLoopStack()
|
||||
{
|
||||
assert(!isRoot(), "getLoopStack called on wrong scope type");
|
||||
|
@ -307,7 +301,7 @@ abstract class Scope
|
|||
// automatically if a file exists.
|
||||
ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
if(isRoot()) return ScopeLookup(name, LType.None, null, null);
|
||||
else return parent.lookup(name, autoLoad);
|
||||
|
@ -323,12 +317,10 @@ abstract class Scope
|
|||
return sl;
|
||||
}
|
||||
|
||||
// Look up an identifier, and check imported scopes as well. If the
|
||||
// token is not found, do the lookup again but this time check the
|
||||
// file system for classes as well.
|
||||
// Look up an identifier, and check imported scopes as well.
|
||||
ScopeLookup lookupImport(Token name, bool second=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("LookupImport %s in %s (line %s)", name, this, __LINE__);
|
||||
auto l = lookup(name, second);
|
||||
if(!l.isNone) return l;
|
||||
|
@ -336,8 +328,13 @@ abstract class Scope
|
|||
// Nuttin' was found, try the imports
|
||||
bool found = false;
|
||||
auto old = l;
|
||||
foreach(imp; importList)
|
||||
foreach(ind, imp; importList)
|
||||
{
|
||||
static if(traceLookups)
|
||||
{
|
||||
writefln(" LI: Import is: %s, index %s of %s",
|
||||
imp, ind, importList.length-1);
|
||||
}
|
||||
l = imp.lookup(name, second);
|
||||
|
||||
// Only accept types, classes, variables and functions
|
||||
|
@ -376,20 +373,38 @@ abstract class Scope
|
|||
return old;
|
||||
}
|
||||
|
||||
// Add an import to this scope
|
||||
void registerImport(ImportHolder s)
|
||||
// More user-friendly version for API-defined import of classes.
|
||||
// Eg. global.registerImport(myclass) -> makes myclass available in
|
||||
// ALL classes.
|
||||
void registerImport(MonsterClass mc)
|
||||
{
|
||||
//writefln("Registering import %s in scope %s", s, this);
|
||||
importList ~= s;
|
||||
// Check if this class is already implemented
|
||||
foreach(imp; importList)
|
||||
if(imp.isClass(mc)) return;
|
||||
|
||||
static if(traceLookups)
|
||||
writefln("Importing class %s into %s", mc, this);
|
||||
importList ~= new ClassImpHolder(mc);
|
||||
}
|
||||
|
||||
// More user-friendly version for API-defined
|
||||
// imports. Eg. global.registerImport(myclass) -> makes myclass
|
||||
// available in ALL classes.
|
||||
void registerImport(MonsterClass mc)
|
||||
{ registerImport(new ClassImpHolder(mc)); }
|
||||
// Ditto for packages
|
||||
void registerImport(PackageScope psc)
|
||||
{
|
||||
// Never import the global scope - it's already implied as a
|
||||
// parent scope of most other scopes
|
||||
//if(psc is global) return;
|
||||
|
||||
// Even more user-friendly version. Takes a list of class names.
|
||||
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 for classes. Takes a list of
|
||||
// class names.
|
||||
void registerImport(char[][] cls ...)
|
||||
{
|
||||
foreach(c; cls)
|
||||
|
@ -473,7 +488,7 @@ final class StateScope : Scope
|
|||
override:
|
||||
ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
assert(name.str != "");
|
||||
|
@ -546,7 +561,7 @@ final class RootScope : PackageScope
|
|||
|
||||
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
// Basic types are looked up here
|
||||
|
@ -601,11 +616,6 @@ class PackageScope : Scope
|
|||
|
||||
bool isPackage() { return true; }
|
||||
|
||||
PackageScope getPackage()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
char[] getPath(char[] file)
|
||||
{
|
||||
if(path == "") return file;
|
||||
|
@ -636,7 +646,7 @@ class PackageScope : Scope
|
|||
// Used internally from lookup()
|
||||
private PackageScope makeSubPackage(char[] name)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
if(!isValidIdent(name))
|
||||
|
@ -729,7 +739,7 @@ class PackageScope : Scope
|
|||
|
||||
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
// Look up packages
|
||||
|
@ -796,7 +806,7 @@ abstract class VarScope : Scope
|
|||
|
||||
ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
assert(name.str != "");
|
||||
|
@ -847,7 +857,7 @@ class FVScope : VarScope
|
|||
override:
|
||||
ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
assert(name.str != "");
|
||||
|
@ -894,7 +904,7 @@ class TFVScope : FVScope
|
|||
override:
|
||||
ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
assert(name.str != "");
|
||||
|
@ -947,7 +957,7 @@ final class EnumScope : SimplePropertyScope
|
|||
{ tasm.push(cast(uint)et.entries.length); }
|
||||
|
||||
void enumVal()
|
||||
{ tasm.getEnumValue(et.tIndex); }
|
||||
{ tasm.getEnumValue(et); }
|
||||
|
||||
EnumType et;
|
||||
|
||||
|
@ -991,7 +1001,7 @@ final class EnumScope : SimplePropertyScope
|
|||
// en.errorString. The enum index is on the stack, and
|
||||
// getEnumValue supplies the enum type and the field
|
||||
// index. The VM can find the correct field value from that.
|
||||
tasm.getEnumValue(et.tIndex, fe);
|
||||
tasm.getEnumValue(et, fe);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1081,6 +1091,12 @@ final class ClassScope : TFVScope
|
|||
{
|
||||
cls = cl;
|
||||
super(last, cls.name.str);
|
||||
|
||||
// Make sure the class has a valid package
|
||||
assert(cl.pack !is null);
|
||||
|
||||
// Import the package into this scope
|
||||
registerImport(cl.pack);
|
||||
}
|
||||
|
||||
bool isClass() { return true; }
|
||||
|
@ -1098,7 +1114,7 @@ final class ClassScope : TFVScope
|
|||
|
||||
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
assert(name.str != "");
|
||||
|
@ -1246,20 +1262,20 @@ class CodeScope : StackScope
|
|||
LabelStatement getContinue(char[] name = "") { return parent.getContinue(name); }
|
||||
}
|
||||
|
||||
// Experimental! Used to recompute the array expression for $. NOT a
|
||||
// permanent solution.
|
||||
// A special scope used inside arrays that allow the $ token to be
|
||||
// used as a shorthand for the array length.
|
||||
class ArrayScope : StackScope
|
||||
{
|
||||
private Expression expArray;
|
||||
private ArrayOperator arrOp;
|
||||
|
||||
this(Scope last, Expression arr)
|
||||
this(Scope last, ArrayOperator op)
|
||||
{
|
||||
super(last, "arrayscope");
|
||||
expArray = arr;
|
||||
arrOp = op;
|
||||
}
|
||||
|
||||
bool isArray() { return true; }
|
||||
Expression getArray() { return expArray; }
|
||||
ArrayOperator getArray() { return arrOp; }
|
||||
}
|
||||
|
||||
// Base class for scopes that have properties. The instances of this
|
||||
|
@ -1286,7 +1302,7 @@ abstract class PropertyScope : Scope
|
|||
|
||||
ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
// Does this scope contain the property?
|
||||
|
@ -1365,7 +1381,7 @@ class LoopScope : CodeScope
|
|||
|
||||
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
||||
{
|
||||
debug(lookupTrace)
|
||||
static if(traceLookups)
|
||||
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
||||
|
||||
assert(name.str != "");
|
||||
|
|
|
@ -42,6 +42,8 @@ import monster.vm.error;
|
|||
import monster.vm.mclass;
|
||||
import monster.vm.vm;
|
||||
|
||||
import monster.options;
|
||||
|
||||
alias Statement[] StateArray;
|
||||
|
||||
abstract class Statement : Block
|
||||
|
@ -131,7 +133,7 @@ class ImportStatement : Statement
|
|||
assert(t !is null);
|
||||
mc = t.getClass(type.loc);
|
||||
|
||||
sc.registerImport(new ClassImpHolder(mc));
|
||||
sc.registerImport(mc);
|
||||
}
|
||||
else if(type.isPackage)
|
||||
{
|
||||
|
@ -139,7 +141,7 @@ class ImportStatement : Statement
|
|||
assert(t !is null);
|
||||
auto psc = t.sc;
|
||||
|
||||
sc.registerImport(new PackageImpHolder(psc));
|
||||
sc.registerImport(psc);
|
||||
}
|
||||
else
|
||||
fail("Can only import from classes and packages", type.loc);
|
||||
|
@ -1328,9 +1330,11 @@ class ReturnStatement : Statement
|
|||
Expression exp;
|
||||
Function *fn;
|
||||
|
||||
// Number of local variables to unwind from the stack. Calculated
|
||||
// both in resolve and in compile.
|
||||
// Number of local variables to unwind from the stack, and the
|
||||
// number of parameters. (Counts number of ints / stack values, not
|
||||
// actual number of variables.)
|
||||
int locals;
|
||||
int params;
|
||||
|
||||
static bool canParse(TokenArray toks)
|
||||
{
|
||||
|
@ -1380,7 +1384,7 @@ class ReturnStatement : Statement
|
|||
assert(fn !is null, "return called outside a function scope");
|
||||
|
||||
// Get the size of all parameters
|
||||
locals += fn.paramSize;
|
||||
params = fn.paramSize;
|
||||
|
||||
// Next, we must check that the returned expression, if any, is
|
||||
// of the right type.
|
||||
|
@ -1408,11 +1412,11 @@ class ReturnStatement : Statement
|
|||
assert(!fn.type.isVoid);
|
||||
exp.eval();
|
||||
// Return an expression
|
||||
tasm.exit(locals, exp.type.getSize);
|
||||
tasm.exit(params, locals, exp.type.getSize);
|
||||
}
|
||||
else
|
||||
// Return without an expression
|
||||
tasm.exit(locals);
|
||||
tasm.exit(params, locals, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1560,7 +1564,11 @@ class CodeBlock : Statement
|
|||
assert(!isState || sc.isStateCode());
|
||||
|
||||
foreach(int i, stats; contents)
|
||||
{
|
||||
static if(traceResolve)
|
||||
writefln("Resolving %s at %s", stats, stats.loc);
|
||||
stats.resolve(sc);
|
||||
}
|
||||
|
||||
// TODO: Check that state code contains at least one idle
|
||||
// function call. We could do that through the scope.
|
||||
|
@ -1576,7 +1584,7 @@ class CodeBlock : Statement
|
|||
// If this is the main block at the state level, we must finish
|
||||
// it with an exit instruction.
|
||||
if(isState)
|
||||
tasm.exit();
|
||||
tasm.exit(0,0,0);
|
||||
|
||||
// Local variables are forbidden in any state block, no matter
|
||||
// at what level they are
|
||||
|
|
|
@ -27,6 +27,7 @@ module monster.compiler.tokenizer;
|
|||
import std.string;
|
||||
import std.stream;
|
||||
import std.stdio;
|
||||
import std.utf;
|
||||
|
||||
import monster.util.string : begins;
|
||||
|
||||
|
@ -84,6 +85,9 @@ enum TT
|
|||
// Array length symbol
|
||||
Dollar,
|
||||
|
||||
// 'at' sign, @
|
||||
Alpha,
|
||||
|
||||
// Conditional expressions
|
||||
IsEqual, NotEqual,
|
||||
IsCaseEqual, IsCaseEqual2,
|
||||
|
@ -124,23 +128,24 @@ enum TT
|
|||
Last, // Tokens after this do not have a specific string
|
||||
// associated with them.
|
||||
|
||||
StringLiteral, // "something"
|
||||
StringLiteral, // "something" or 'something'
|
||||
IntLiteral, // Anything that starts with a number, except
|
||||
// floats
|
||||
FloatLiteral, // Any number which contains a period symbol
|
||||
CharLiteral, // 'a'
|
||||
Identifier, // user-named identifier
|
||||
EOF, // end of file
|
||||
EMPTY // empty line (not stored)
|
||||
}
|
||||
|
||||
|
||||
struct Token
|
||||
{
|
||||
TT type;
|
||||
char[] str;
|
||||
Floc loc;
|
||||
|
||||
// Translated string literal (with resolved escape codes.)
|
||||
dchar[] str32;
|
||||
|
||||
// True if this token was the first on its line.
|
||||
bool newline;
|
||||
|
||||
|
@ -196,6 +201,8 @@ const char[][] tokenList =
|
|||
|
||||
TT.Dollar : "$",
|
||||
|
||||
TT.Alpha : "@",
|
||||
|
||||
TT.IsEqual : "==",
|
||||
TT.NotEqual : "!=",
|
||||
|
||||
|
@ -280,7 +287,6 @@ const char[][] tokenList =
|
|||
TT.StringLiteral : "string literal",
|
||||
TT.IntLiteral : "integer literal",
|
||||
TT.FloatLiteral : "floating point literal",
|
||||
TT.CharLiteral : "character literal",
|
||||
TT.Identifier : "identifier",
|
||||
TT.EOF : "end of file",
|
||||
TT.EMPTY : "empty line - you should never see this"
|
||||
|
@ -534,31 +540,229 @@ class Tokenizer
|
|||
if(line.begins("+/")) fail("Unexpected end of nested comment");
|
||||
|
||||
// String literals (multi-line literals not implemented yet)
|
||||
if(line.begins("\""))
|
||||
if(line.begins("\"") || // Standard string: "abc"
|
||||
line.begins("r\"") || // Wysiwig string: r"c:\dir"
|
||||
line.begins("\\\"") || // ditto: \"c:\dir"
|
||||
line.begins("'") ||
|
||||
line.begins("r'") || // Equivalent ' versions
|
||||
line.begins("\\'"))
|
||||
{
|
||||
int len = 1;
|
||||
bool found = false;
|
||||
foreach(char ch; line[1..$])
|
||||
bool wysiwig = false;
|
||||
|
||||
// Quote character that terminates this string.
|
||||
char quote;
|
||||
|
||||
char[] slice = line;
|
||||
|
||||
// Removes the first num chars from the line
|
||||
void skip(int num)
|
||||
{
|
||||
len++;
|
||||
// No support for escape sequences as of now
|
||||
if(ch == '"')
|
||||
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;
|
||||
}
|
||||
}
|
||||
if(!found) fail("Unterminated string literal '" ~line~"'");
|
||||
return retToken(TT.StringLiteral, line[0..len].dup);
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
// Character literals (not parsed yet, so escape sequences like
|
||||
// '\n', '\'', or unicode stuff won't work.)
|
||||
if(line[0] == '\'')
|
||||
{
|
||||
if(line.length < 3 || line[2] != '\'')
|
||||
fail("Malformed character literal " ~line);
|
||||
return retToken(TT.CharLiteral, line[0..3].dup);
|
||||
// Nope. It's just a normal character. Decode it from
|
||||
// UTF8.
|
||||
size_t clen = 0;
|
||||
dchar cres;
|
||||
cres = decode(slice,clen);
|
||||
store(cres, clen);
|
||||
}
|
||||
if(!found) fail("Unterminated string literal '" ~line~ "'");
|
||||
|
||||
// Set 'slice' to contain the original string
|
||||
slice = line[0..(line.length-slice.length)];
|
||||
|
||||
// Set up the token
|
||||
auto t = retToken(TT.StringLiteral, slice.dup);
|
||||
t.str32 = result;
|
||||
return t;
|
||||
}
|
||||
|
||||
// Numerical literals - if it starts with a number, we accept
|
||||
|
|
|
@ -45,28 +45,6 @@ import std.stdio;
|
|||
import std.utf;
|
||||
import std.string;
|
||||
|
||||
/*
|
||||
List of all type classes:
|
||||
|
||||
Type (abstract)
|
||||
InternalType (abstract)
|
||||
ReplacerType (abstract)
|
||||
NullType (null expression)
|
||||
BasicType (covers int, char, bool, float, void)
|
||||
ObjectType
|
||||
ArrayType
|
||||
StructType
|
||||
EnumType
|
||||
FunctionType
|
||||
PackageType
|
||||
UserType (replacer for identifier-named types like structs and
|
||||
classes)
|
||||
TypeofType (replacer for typeof(x) when used as a type)
|
||||
GenericType (var)
|
||||
MetaType (type of type expressions, like writeln(int);)
|
||||
|
||||
*/
|
||||
|
||||
// A class that represents a type. The Type class is abstract, and the
|
||||
// different types are actually handled by various subclasses of Type
|
||||
// (see below.) The static function identify() is used to figure out
|
||||
|
@ -81,18 +59,23 @@ abstract class Type : Block
|
|||
// tokens.)
|
||||
static bool canParseRem(ref TokenArray toks)
|
||||
{
|
||||
// This function is a bit messy unfortunately. We should improve
|
||||
// the process so we can clean it up. Preferably we should
|
||||
// rewrite the entire parser.
|
||||
|
||||
// We allow typeof(expression) as a type
|
||||
if(isNext(toks, TT.Typeof))
|
||||
{
|
||||
reqNext(toks, TT.LeftParen);
|
||||
Expression.identify(toks); // Possibly a bit wasteful...
|
||||
reqNext(toks, TT.RightParen);
|
||||
skipParens(toks);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The 'var' keyword can be used as a type
|
||||
if(isNext(toks, TT.Var)) return true;
|
||||
|
||||
if(FuncRefType.canParseRem(toks))
|
||||
return true;
|
||||
|
||||
// Allow typename
|
||||
if(!isNext(toks, TT.Identifier)) return false;
|
||||
|
||||
|
@ -133,6 +116,7 @@ abstract class Type : Block
|
|||
else if(UserType.canParse(toks)) t = new UserType();
|
||||
else if(GenericType.canParse(toks)) t = new GenericType();
|
||||
else if(TypeofType.canParse(toks)) t = new TypeofType();
|
||||
else if(FuncRefType.canParse(toks)) t = new FuncRefType();
|
||||
else fail("Cannot parse " ~ toks[0].str ~ " as a type", toks[0].loc);
|
||||
|
||||
// Parse the actual tokens with our new and shiny object.
|
||||
|
@ -178,6 +162,7 @@ abstract class Type : Block
|
|||
{
|
||||
tIndex = typeList.length;
|
||||
typeList[tIndex] = this;
|
||||
name = "UNNAMED TYPE";
|
||||
}
|
||||
|
||||
// Used for easy checking
|
||||
|
@ -200,7 +185,8 @@ abstract class Type : Block
|
|||
bool isPackage() { return false; }
|
||||
bool isStruct() { return false; }
|
||||
bool isEnum() { return false; }
|
||||
bool isFunc() { return false; }
|
||||
bool isIntFunc() { return false; }
|
||||
bool isFuncRef() { return false; }
|
||||
|
||||
bool isReplacer() { return false; }
|
||||
|
||||
|
@ -218,9 +204,7 @@ abstract class Type : Block
|
|||
|
||||
// Is this a legal type for variables? If this is false, you cannot
|
||||
// create variables of this type, neither directly or indirectly
|
||||
// (through automatic type inference.) This isn't currently used
|
||||
// anywhere, but we will need it later when we implement automatic
|
||||
// types.
|
||||
// (through automatic type inference.)
|
||||
bool isLegal() { return true; }
|
||||
|
||||
// Get base type (used for arrays and meta-types)
|
||||
|
@ -239,11 +223,11 @@ abstract class Type : Block
|
|||
// returns the class scope, where func is resolved.
|
||||
// array.length -> getMemberScope called for ArrayType, returns a
|
||||
// special scope that contains the 'length' property
|
||||
// Returning null means that this type does not have any members.
|
||||
Scope getMemberScope()
|
||||
{
|
||||
// Returning null means this type does not have any members.
|
||||
return null;
|
||||
}
|
||||
// By default we return a minimal property scope (containing
|
||||
// .sizeof etc)
|
||||
{ return GenericProperties.singleton; }
|
||||
|
||||
// Validate that this type actually exists. This is used to make
|
||||
// sure that all forward references are resolved.
|
||||
|
@ -365,7 +349,7 @@ abstract class Type : Block
|
|||
assert(res.length == orig.type.getSize);
|
||||
|
||||
if(orig.type.canCastCTime(this))
|
||||
res = orig.type.doCastCTime(res, this);
|
||||
res = orig.type.doCastCTime(res, this, orig.loc);
|
||||
else
|
||||
fail(format("Cannot cast %s of type %s to %s of type %s (at compile time)",
|
||||
orig.toString, orig.typeString,
|
||||
|
@ -383,8 +367,10 @@ abstract class Type : Block
|
|||
return false; // By default we can't cast anything
|
||||
}
|
||||
|
||||
// Can the type be explicitly cast? Is not required to handle cases
|
||||
// where canCastTo is true.
|
||||
// Can the type be explicitly cast? This function does not have to
|
||||
// handle cases where canCastTo is true. You only need to use it in
|
||||
// cases where explicit casting is allowed but implicit is not, such
|
||||
// as casting from float to int.
|
||||
bool canCastToExplicit(Type to)
|
||||
{
|
||||
return false;
|
||||
|
@ -403,14 +389,14 @@ abstract class Type : Block
|
|||
|
||||
// Do the cast in the assembler. Must handle both implicit and
|
||||
// explicit casts.
|
||||
void evalCastTo(Type to)
|
||||
void evalCastTo(Type to, Floc lc)
|
||||
{
|
||||
assert(0, "evalCastTo not implemented for type " ~ toString);
|
||||
}
|
||||
|
||||
// Do the cast in the compiler. Must handle both implicit and
|
||||
// explicit casts.
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
int[] doCastCTime(int[] data, Type to, Floc lc)
|
||||
{
|
||||
assert(0, "doCastCTime not implemented for type " ~ toString);
|
||||
}
|
||||
|
@ -496,6 +482,8 @@ abstract class Type : Block
|
|||
// for variables or values, neither directly nor indirectly.
|
||||
abstract class InternalType : Type
|
||||
{
|
||||
override:
|
||||
Scope getMemberScope() { return null; }
|
||||
final:
|
||||
bool isLegal() { return false; }
|
||||
int[] defaultInit() {assert(0, name);}
|
||||
|
@ -532,23 +520,22 @@ class NullType : InternalType
|
|||
|
||||
bool canCastTo(Type to)
|
||||
{
|
||||
return to.isArray || to.isObject || to.isEnum;
|
||||
return to.isArray || to.isObject || to.isEnum || to.isFuncRef;
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
void evalCastTo(Type to, Floc lc)
|
||||
{
|
||||
assert(to.getSize == 1);
|
||||
|
||||
// The value of null is always zero. There is no value on the
|
||||
// stack to convert, so just push it.
|
||||
tasm.push(0);
|
||||
// Always evaluable at compile time - there is no runtime value
|
||||
// to convert.
|
||||
tasm.pushArray(doCastCTime(null, to, lc));
|
||||
}
|
||||
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
int[] doCastCTime(int[] data, Type to, Floc lc)
|
||||
{
|
||||
assert(to.getSize == 1);
|
||||
// For all the types we currently support, 'null' is always
|
||||
// equal to the type's default initializer.
|
||||
assert(data.length == 0);
|
||||
return [0];
|
||||
return to.defaultInit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -729,7 +716,7 @@ class BasicType : Type
|
|||
return false;
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
void evalCastTo(Type to, Floc lc)
|
||||
{
|
||||
assert(this != to);
|
||||
assert(!isVoid);
|
||||
|
@ -766,11 +753,11 @@ class BasicType : Type
|
|||
// Create an array from one element on the stack
|
||||
if(isChar) tasm.popToArray(1, 1);
|
||||
else
|
||||
tasm.castToString(tIndex);
|
||||
tasm.castToString(this);
|
||||
}
|
||||
else
|
||||
fail("Conversion " ~ toString ~ " to " ~ to.toString ~
|
||||
" not implemented.");
|
||||
" not implemented.", lc);
|
||||
}
|
||||
|
||||
char[] valToString(int[] data)
|
||||
|
@ -781,7 +768,7 @@ class BasicType : Type
|
|||
return getHolder().getString(data);
|
||||
}
|
||||
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
int[] doCastCTime(int[] data, Type to, Floc lc)
|
||||
{
|
||||
assert(this != to);
|
||||
assert(!isVoid);
|
||||
|
@ -871,7 +858,7 @@ class BasicType : Type
|
|||
}
|
||||
else
|
||||
fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~
|
||||
" not implemented.");
|
||||
" not implemented.", lc);
|
||||
|
||||
assert(toData.length == toSize);
|
||||
assert(toData.ptr !is data.ptr);
|
||||
|
@ -886,6 +873,9 @@ class BasicType : Type
|
|||
if( isLong || isUlong || isDouble )
|
||||
return 2;
|
||||
|
||||
if( isVoid )
|
||||
return 0;
|
||||
|
||||
assert(0, "getSize() does not handle type '" ~ name ~ "'");
|
||||
}
|
||||
|
||||
|
@ -905,7 +895,7 @@ class BasicType : Type
|
|||
else if(isLong || isUlong) data = makeData!(long)(0);
|
||||
// Chars default to an illegal utf-32 value
|
||||
else if(isChar) data = makeData!(int)(0x0000FFFF);
|
||||
// Floats default to not a number
|
||||
// Floats default to NaN
|
||||
else if(isFloat) data = makeData!(float)(float.nan);
|
||||
else if(isDouble) data = makeData!(double)(double.nan);
|
||||
else
|
||||
|
@ -1033,7 +1023,7 @@ class ObjectType : Type
|
|||
}
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
void evalCastTo(Type to, Floc lc)
|
||||
{
|
||||
assert(clsIndex != 0);
|
||||
assert(canCastTo(to) || canCastToExplicit(to));
|
||||
|
@ -1059,7 +1049,7 @@ class ObjectType : Type
|
|||
}
|
||||
|
||||
assert(to.isString);
|
||||
tasm.castToString(tIndex);
|
||||
tasm.castToString(this);
|
||||
}
|
||||
|
||||
// Members of objects are resolved in the class scope.
|
||||
|
@ -1113,6 +1103,12 @@ class ArrayType : Type
|
|||
loc = base.loc;
|
||||
}
|
||||
|
||||
ArrayRef *getArray(int[] data)
|
||||
{
|
||||
assert(data.length == 1);
|
||||
return monster.vm.arrays.arrays.getRef(cast(AIndex)data[0]);
|
||||
}
|
||||
|
||||
override:
|
||||
void validate(Floc loc) { assert(base !is null); base.validate(loc); }
|
||||
int arrays() { return base.arrays() + 1; }
|
||||
|
@ -1127,26 +1123,125 @@ class ArrayType : Type
|
|||
|
||||
// All arrays can be cast to string
|
||||
bool canCastTo(Type to)
|
||||
{ return to.isString; }
|
||||
|
||||
void evalCastTo(Type to)
|
||||
{
|
||||
assert(to.isString);
|
||||
tasm.castToString(tIndex);
|
||||
// Strings can be cast to char, but the conversion is only valid
|
||||
// if the string is one char long.
|
||||
if(isString) return to.isChar;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
void evalCastTo(Type to, Floc lc)
|
||||
{
|
||||
assert(to.isString);
|
||||
if(isString)
|
||||
{
|
||||
assert(to.isChar);
|
||||
tasm.castStrToChar();
|
||||
return;
|
||||
}
|
||||
|
||||
if(to.isString)
|
||||
{
|
||||
tasm.castToString(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if(to.isArray && getBase().canCastTo(to.getBase()))
|
||||
fail("Casting arrays at runtime not implemented yet", lc);
|
||||
|
||||
assert(0);
|
||||
}
|
||||
|
||||
int[] doCastCTime(int[] data, Type to, Floc lc)
|
||||
{
|
||||
if(isString)
|
||||
{
|
||||
assert(to.isChar);
|
||||
|
||||
// When casting from string to char, we pick out the first
|
||||
// (and only) character from the string.
|
||||
|
||||
auto arf = getArray(data);
|
||||
|
||||
// FIXME: These error messages don't produce a file/line
|
||||
// number. Using our own loc doesn't work. I think we need
|
||||
// to pass it on to doCastCTime.
|
||||
|
||||
if(arf.carr.length == 0)
|
||||
fail("Cannot cast empty string to 'char'", lc);
|
||||
|
||||
assert(arf.elemSize == 1);
|
||||
|
||||
if(arf.carr.length > 1)
|
||||
fail("Cannot cast string " ~ valToString(data) ~
|
||||
" to 'char': too long", lc);
|
||||
|
||||
return [arf.iarr[0]];
|
||||
}
|
||||
|
||||
if(to.isArray && getBase().canCastTo(to.getBase()))
|
||||
{
|
||||
auto arf = getArray(data);
|
||||
assert(arf.iarr.length > 0, "shouldn't need to cast an empty array");
|
||||
|
||||
// The types
|
||||
Type oldt = getBase();
|
||||
Type newt = to.getBase();
|
||||
|
||||
assert(oldt.canCastCTime(newt),
|
||||
"oops, cannot cast at compile time");
|
||||
|
||||
// Set up the source and destination array data
|
||||
int len = arf.length();
|
||||
int olds = oldt.getSize();
|
||||
int news = newt.getSize();
|
||||
|
||||
int[] src = arf.iarr;
|
||||
int[] dst = new int[len * news];
|
||||
|
||||
assert(src.length == len * olds);
|
||||
|
||||
for(int i=0; i<len; i++)
|
||||
{
|
||||
int[] slice = src[i*olds..(i+1)*olds];
|
||||
slice = oldt.doCastCTime(slice, newt, lc);
|
||||
assert(slice.length == news);
|
||||
dst[i*news..(i+1)*news] = slice;
|
||||
}
|
||||
|
||||
// Create the array index
|
||||
bool wasConst = arf.isConst();
|
||||
arf = monster.vm.arrays.arrays.create(dst, news);
|
||||
assert(arf.length() == len);
|
||||
|
||||
// Copy the const setting from the original array
|
||||
if(wasConst)
|
||||
arf.flags.set(AFlags.Const);
|
||||
|
||||
// Return the index
|
||||
return [arf.getIndex()];
|
||||
}
|
||||
|
||||
if(to.isString)
|
||||
return [valToStringIndex(data)];
|
||||
|
||||
assert(0);
|
||||
}
|
||||
|
||||
char[] valToString(int[] data)
|
||||
{
|
||||
assert(data.length == 1);
|
||||
|
||||
// Get the array reference
|
||||
ArrayRef *arf = monster.vm.arrays.arrays.getRef(cast(AIndex)data[0]);
|
||||
ArrayRef *arf = getArray(data);
|
||||
|
||||
// Empty array?
|
||||
if(arf.iarr.length == 0) return "[]";
|
||||
|
@ -1184,9 +1279,137 @@ class ArrayType : Type
|
|||
}
|
||||
}
|
||||
|
||||
// Type used for references to functions. Will later contain type
|
||||
// information, but right now it's just used as an internal flag.
|
||||
class FunctionType : InternalType
|
||||
// Type used for function references. Variables of this type consists
|
||||
// of a object index followed by a global function index.
|
||||
class FuncRefType : Type
|
||||
{
|
||||
// Function signature information
|
||||
Type retType;
|
||||
Type params[];
|
||||
bool isConst[];
|
||||
bool isVararg;
|
||||
|
||||
// For parsed types
|
||||
this() {}
|
||||
|
||||
this(IntFuncType f)
|
||||
{
|
||||
auto fn = f.func;
|
||||
|
||||
retType = fn.type;
|
||||
isVararg = fn.isVararg();
|
||||
params.length = fn.params.length;
|
||||
isConst.length = params.length;
|
||||
|
||||
foreach(i, v; fn.params)
|
||||
{
|
||||
params[i] = v.type;
|
||||
isConst[i] = v.isConst;
|
||||
}
|
||||
|
||||
createName();
|
||||
}
|
||||
|
||||
void createName()
|
||||
{
|
||||
name = "function";
|
||||
if(!retType.isVoid)
|
||||
name ~= " " ~ retType.toString();
|
||||
name ~= "(";
|
||||
|
||||
foreach(i, v; params)
|
||||
{
|
||||
if(i > 0)
|
||||
name ~= ",";
|
||||
name ~= v.toString();
|
||||
|
||||
if(isVararg && i == params.length-1)
|
||||
name ~= "...";
|
||||
}
|
||||
|
||||
name ~= ")";
|
||||
}
|
||||
|
||||
static bool canParseRem(ref TokenArray toks)
|
||||
{
|
||||
if(!isNext(toks, TT.Function))
|
||||
return false;
|
||||
|
||||
// Return type?
|
||||
if(toks.length && toks[0].type != TT.LeftParen)
|
||||
Type.canParseRem(toks);
|
||||
|
||||
skipParens(toks);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Same as the above but it doesn't change the array (no 'ref'
|
||||
// parameter)
|
||||
static bool canParse(TokenArray toks)
|
||||
{ return canParseRem(toks); }
|
||||
|
||||
// Calculate the stack imprint
|
||||
int getImprint()
|
||||
{
|
||||
assert(retType !is null);
|
||||
int res = retType.getSize();
|
||||
|
||||
foreach(p; params)
|
||||
res -= p.getSize();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
override:
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
reqNext(toks, TT.Function);
|
||||
|
||||
if(!isNext(toks, TT.LeftParen))
|
||||
{
|
||||
retType = Type.identify(toks);
|
||||
reqNext(toks, TT.LeftParen);
|
||||
}
|
||||
else
|
||||
retType = BasicType.getVoid();
|
||||
|
||||
if(!isNext(toks, TT.RightParen))
|
||||
{
|
||||
do
|
||||
{
|
||||
params ~= Type.identify(toks);
|
||||
}
|
||||
while(isNext(toks, TT.Comma));
|
||||
reqNext(toks, TT.RightParen);
|
||||
}
|
||||
}
|
||||
|
||||
void resolve(Scope sc)
|
||||
{
|
||||
retType.resolve(sc);
|
||||
foreach(p; params)
|
||||
p.resolve(sc);
|
||||
|
||||
createName();
|
||||
}
|
||||
|
||||
void validate(Floc f)
|
||||
{
|
||||
retType.validate(f);
|
||||
foreach(p; params)
|
||||
p.validate(f);
|
||||
}
|
||||
|
||||
int getSize() { return 2; }
|
||||
bool isFuncRef() { return true; }
|
||||
int[] defaultInit() { return [0, -1]; }
|
||||
Scope getMemberScope() { return FuncRefProperties.singleton; }
|
||||
}
|
||||
|
||||
// Type used for internal references to functions. This is NOT the
|
||||
// same as the type used for function references ("pointers") - see
|
||||
// FuncRefType for that.
|
||||
class IntFuncType : InternalType
|
||||
{
|
||||
Function *func;
|
||||
bool isMember;
|
||||
|
@ -1201,7 +1424,7 @@ class FunctionType : InternalType
|
|||
}
|
||||
|
||||
override:
|
||||
bool isFunc() { return true; }
|
||||
bool isIntFunc() { return true; }
|
||||
}
|
||||
|
||||
class EnumType : Type
|
||||
|
@ -1361,12 +1584,12 @@ class EnumType : Type
|
|||
return false;
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
void evalCastTo(Type to, Floc lc)
|
||||
{
|
||||
// Convert the enum name to a string
|
||||
if(to.isString)
|
||||
{
|
||||
tasm.castToString(tIndex);
|
||||
tasm.castToString(this);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1374,10 +1597,10 @@ class EnumType : Type
|
|||
if(lng.canCastOrEqual(to))
|
||||
{
|
||||
// Get the value
|
||||
tasm.getEnumValue(tIndex);
|
||||
tasm.getEnumValue(this);
|
||||
// Cast it if necessary
|
||||
if(to != lng)
|
||||
lng.evalCastTo(to);
|
||||
lng.evalCastTo(to, lc);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1386,11 +1609,11 @@ class EnumType : Type
|
|||
if(f.type.canCastOrEqual(to))
|
||||
{
|
||||
// Get the field value from the enum
|
||||
tasm.getEnumValue(tIndex, i);
|
||||
tasm.getEnumValue(this, i);
|
||||
|
||||
// If the type doesn't match exactly, convert it.
|
||||
if(f.type != to)
|
||||
f.type.evalCastTo(to);
|
||||
f.type.evalCastTo(to, lc);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -1398,7 +1621,7 @@ class EnumType : Type
|
|||
assert(0);
|
||||
}
|
||||
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
int[] doCastCTime(int[] data, Type to, Floc lc)
|
||||
{
|
||||
if(to.isString)
|
||||
return [valToStringIndex(data)];
|
||||
|
@ -1406,7 +1629,7 @@ class EnumType : Type
|
|||
// This code won't run yet, because the enum fields are
|
||||
// properties and we haven't implemented ctime property reading
|
||||
// yet. Leave this assert in here so that we remember to test it
|
||||
// later.
|
||||
// later. TODO.
|
||||
assert(0, "finished, but not tested");
|
||||
|
||||
// Get the enum index
|
||||
|
@ -1415,7 +1638,7 @@ class EnumType : Type
|
|||
|
||||
// Check that we were not given a zero index
|
||||
if(v-- == 0)
|
||||
fail("Cannot get value of fields from an empty Enum variable.");
|
||||
fail("Cannot get value of fields from an empty Enum variable.", lc);
|
||||
|
||||
// Get the entry
|
||||
assert(v >= 0 && v < entries.length);
|
||||
|
@ -1428,7 +1651,7 @@ class EnumType : Type
|
|||
int[] val = (cast(int*)&ent.value)[0..2];
|
||||
// Cast it if necessary
|
||||
if(to != lng)
|
||||
val = lng.doCastCTime(val, to);
|
||||
val = lng.doCastCTime(val, to, lc);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
@ -1442,7 +1665,7 @@ class EnumType : Type
|
|||
|
||||
// If the type doesn't match exactly, convert it.
|
||||
if(f.type != to)
|
||||
val = f.type.doCastCTime(val, to);
|
||||
val = f.type.doCastCTime(val, to, lc);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
@ -1538,8 +1761,6 @@ class StructType : Type
|
|||
return defInit;
|
||||
}
|
||||
|
||||
Scope getMemberScope()
|
||||
{ return GenericProperties.singleton; }
|
||||
// Scope getMemberScope() { return sc; }
|
||||
}
|
||||
|
||||
|
@ -1650,7 +1871,11 @@ class UserType : ReplacerType
|
|||
|
||||
// Allow packages used as type names in some situations
|
||||
else if(sl.isPackage)
|
||||
realType = sl.sc.getPackage().type;
|
||||
{
|
||||
auto psc = cast(PackageScope)sl.sc;
|
||||
assert(psc !is null);
|
||||
realType = psc.type;
|
||||
}
|
||||
|
||||
// Was anything found at all?
|
||||
else if(!sl.isNone)
|
||||
|
@ -1764,7 +1989,7 @@ class MetaType : InternalType
|
|||
|
||||
int[] cache;
|
||||
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
int[] doCastCTime(int[] data, Type to, Floc lc)
|
||||
{
|
||||
assert(to.isString);
|
||||
|
||||
|
@ -1774,7 +1999,7 @@ class MetaType : InternalType
|
|||
return cache;
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
void evalCastTo(Type to, Floc lc)
|
||||
{
|
||||
assert(to.isString);
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ import monster.vm.mclass;
|
|||
import monster.vm.error;
|
||||
import monster.vm.vm;
|
||||
|
||||
import monster.options;
|
||||
|
||||
enum VarType
|
||||
{
|
||||
Class,
|
||||
|
@ -325,7 +327,15 @@ class VarDeclaration : Block
|
|||
|
||||
// Illegal types are illegal
|
||||
if(!var.type.isLegal)
|
||||
fail("Cannot create variables of type '" ~ var.type.toString ~ "'", loc);
|
||||
{
|
||||
char[] msg = "Cannot create variables of type '" ~
|
||||
var.type.toString ~ "'";
|
||||
|
||||
if(var.type.isIntFunc)
|
||||
msg ~= "\nPerhaps you meant @func ? Use @ to create function references.";
|
||||
|
||||
fail(msg, loc);
|
||||
}
|
||||
|
||||
if(!allowConst && var.isConst)
|
||||
fail("'const' is not allowed here", loc);
|
||||
|
@ -704,7 +714,7 @@ class MemberExpr : Expression
|
|||
else if(look.isFunc)
|
||||
{
|
||||
// The Function* is stored in the lookup variable
|
||||
type = new FunctionType(look.func, isMember);
|
||||
type = new IntFuncType(look.func, isMember);
|
||||
vtype = VType.Function;
|
||||
|
||||
// TODO: Make the scope set the type for us. In fact, the
|
||||
|
@ -793,6 +803,9 @@ class MemberExpr : Expression
|
|||
}
|
||||
body
|
||||
{
|
||||
static if(traceResolve)
|
||||
writefln("Resolving member expression %s", this);
|
||||
|
||||
// Look for reserved names first.
|
||||
if(name.str == "__STACK__")
|
||||
{
|
||||
|
@ -872,7 +885,9 @@ class MemberExpr : Expression
|
|||
|
||||
if(isType) return;
|
||||
|
||||
if(type.isFunc) return;
|
||||
// If we're a function name, don't push anything. Everything is
|
||||
// handled through the type system.
|
||||
if(type.isIntFunc) return;
|
||||
|
||||
setLine();
|
||||
|
||||
|
@ -1058,17 +1073,12 @@ class VarDeclStatement : Statement
|
|||
vars ~= varDec;
|
||||
loc = varDec.var.name.loc;
|
||||
|
||||
int arr = varDec.arrays();
|
||||
|
||||
// Are there more?
|
||||
while(isNext(toks, TT.Comma))
|
||||
{
|
||||
// Read a variable, but with the same type as the last
|
||||
varDec = new VarDeclaration(varDec.var.type);
|
||||
varDec.parse(toks);
|
||||
if(varDec.arrays() != arr)
|
||||
fail("Multiple declarations must have same type",
|
||||
varDec.var.name.loc);
|
||||
vars ~= varDec;
|
||||
}
|
||||
|
||||
|
@ -1076,10 +1086,6 @@ class VarDeclStatement : Statement
|
|||
reqNext(toks, TT.Semicolon);
|
||||
else
|
||||
reqSep(toks);
|
||||
/*
|
||||
if(!isNext(toks, TT.Semicolon))
|
||||
fail("Declaration statement expected ;", toks);
|
||||
*/
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
|
@ -1097,6 +1103,15 @@ class VarDeclStatement : Statement
|
|||
// Add variables to the scope.
|
||||
foreach(vd; vars)
|
||||
vd.resolve(sc);
|
||||
|
||||
// Check that all the vars are in fact the same type
|
||||
assert(vars.length >= 1);
|
||||
Type t = vars[0].var.type;
|
||||
foreach(vd; vars[1..$])
|
||||
{
|
||||
if(t != vd.var.type)
|
||||
fail("Multiple declarations must have the same type", loc);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate types
|
||||
|
|
|
@ -134,7 +134,6 @@ class Console
|
|||
void putln(char[] str) { put(str, true); }
|
||||
|
||||
private:
|
||||
|
||||
Statement[] parse(TokenArray toks, Scope sc)
|
||||
{
|
||||
Statement b;
|
||||
|
@ -307,32 +306,52 @@ class Console
|
|||
// Phase IV, compile
|
||||
tasm.newFunc();
|
||||
|
||||
ExprStatement es;
|
||||
// Expression to print, if any
|
||||
Expression printExp = null;
|
||||
|
||||
// Is it an special console statement?
|
||||
// Is it a special console statement?
|
||||
auto cs = cast(ConsoleStatement)st;
|
||||
if(cs !is null)
|
||||
{
|
||||
// Yes. Is the client an expression?
|
||||
es = cast(ExprStatement)cs.client;
|
||||
// Get the client expression, if any.
|
||||
ExprStatement es = cast(ExprStatement)cs.client;
|
||||
if(es !is null)
|
||||
{
|
||||
// Ok. But is the type usable?
|
||||
if(es.left.type.canCastTo(ArrayType.getString())
|
||||
&& es.right is null)
|
||||
{
|
||||
// Yup. Get the type, and cast the expression to string.
|
||||
scope auto ce = new
|
||||
CastExpression(es.left, ArrayType.getString());
|
||||
ce.eval();
|
||||
// It's a normal expression
|
||||
if(es.right is null)
|
||||
printExp = es.left;
|
||||
}
|
||||
else es = null;
|
||||
// Not an expression, maybe a function?
|
||||
else if(cs.func !is null)
|
||||
{
|
||||
// Yup, store it
|
||||
printExp = cs.func;
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, we got an expression. But is the type usable?
|
||||
if(printExp !is null)
|
||||
{
|
||||
auto tp = printExp.type;
|
||||
auto strt = ArrayType.getString();
|
||||
assert(tp !is null);
|
||||
assert(strt !is null);
|
||||
|
||||
if(tp == strt || tp.canCastTo(strt))
|
||||
{
|
||||
// Yup, it is! Cast the expression to string.
|
||||
strt.typeCast(printExp, "console output");
|
||||
printExp.eval();
|
||||
}
|
||||
else
|
||||
// Type isn't usable, so set printExp to null to flag
|
||||
// this.
|
||||
printExp = null;
|
||||
}
|
||||
|
||||
if(printExp is null)
|
||||
// No expression is being used, so compile the statement
|
||||
// normally.
|
||||
if(es is null)
|
||||
st.compile();
|
||||
|
||||
// Gather all the statements into one function and get the
|
||||
|
@ -344,7 +363,7 @@ class Console
|
|||
fn.call(obj);
|
||||
|
||||
// Finally, get the expression result, if any, and print it.
|
||||
if(es !is null)
|
||||
if(printExp !is null)
|
||||
putln(stack.popString8());
|
||||
|
||||
// In the case of new a variable declaration, we have to
|
||||
|
@ -467,7 +486,7 @@ class Console
|
|||
|
||||
// Statement that handles variable declarations, function calls and
|
||||
// expression statements in consoles. Since these are gramatically
|
||||
// similar, we need the type to determine which
|
||||
// similar, we need the type to determine which it is.
|
||||
class ConsoleStatement : Statement
|
||||
{
|
||||
// Used for variables and expression statements
|
||||
|
@ -497,7 +516,7 @@ class ConsoleStatement : Statement
|
|||
}
|
||||
|
||||
// Function?
|
||||
else if(type.isFunc)
|
||||
else if(type.isIntFunc)
|
||||
func = new FunctionCallExpr(first, toks, true);
|
||||
|
||||
// It's an expression statement
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
NOTE: This file is not used - it is here just for reference. The
|
||||
real module is defined internally in io.d.
|
||||
*/
|
||||
|
||||
singleton thread;
|
||||
|
||||
// Used to kill or pause our own or other threads.
|
||||
|
@ -12,31 +17,23 @@ native bool isDead();
|
|||
bool isAlive() { return !isDead(); }
|
||||
|
||||
// Create a new (paused) thread for a given function
|
||||
native thread create(char[] name);
|
||||
native thread create(function() f);
|
||||
|
||||
// Schedule a (paused) thread to run the next frame
|
||||
native restart();
|
||||
|
||||
// Call a (paused) thread directly - returns when the thread exits or
|
||||
// calls an idle function.
|
||||
idle resume();
|
||||
idle call();
|
||||
|
||||
// Wait for a thread to finish. Will not return until the thread is
|
||||
// dead.
|
||||
idle wait();
|
||||
|
||||
// Call a function as a thread
|
||||
thread call(char[] name)
|
||||
{
|
||||
var t = create(name);
|
||||
t.resume();
|
||||
return t;
|
||||
}
|
||||
|
||||
// Start a function as a thread in the background
|
||||
thread start(char[] name)
|
||||
thread start(function() f)
|
||||
{
|
||||
var t = create(name);
|
||||
var t = create(f);
|
||||
t.restart();
|
||||
return t;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import monster.vm.mobject;
|
|||
import monster.vm.idlefunction;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.mclass;
|
||||
import monster.compiler.functions;
|
||||
import std.stdio;
|
||||
|
||||
const char[] moduleDef =
|
||||
|
@ -49,7 +50,7 @@ native bool isDead();
|
|||
bool isAlive() { return !isDead(); }
|
||||
|
||||
// Create a new (paused) thread for a given function
|
||||
native thread create(char[] name);
|
||||
native thread create(function() f);
|
||||
|
||||
// Schedule a (paused) thread to run the next frame
|
||||
native restart();
|
||||
|
@ -63,20 +64,19 @@ idle call();
|
|||
idle wait();
|
||||
|
||||
// Start a function as a thread in the background
|
||||
thread start(char[] name)
|
||||
thread start(function() f)
|
||||
{
|
||||
var t = create(name);
|
||||
var t = create(f);
|
||||
t.restart();
|
||||
return t;
|
||||
}
|
||||
"; //"
|
||||
}"; //"
|
||||
|
||||
/*
|
||||
The char[] name stuff above will of course be replaced with real
|
||||
function pointers once those are done. When closures are done we
|
||||
will also add:
|
||||
|
||||
function() wrap(function f())
|
||||
function() wrap(function() f)
|
||||
{
|
||||
var t = create(f);
|
||||
return { t.call(); }
|
||||
|
@ -154,20 +154,8 @@ void create()
|
|||
if(params.obj !is trdSing)
|
||||
fail("Can only use create() on the global thread object.");
|
||||
|
||||
char[] name = stack.popString8();
|
||||
|
||||
assert(cthread !is null);
|
||||
|
||||
// This is a dirty hack that's only needed because we haven't
|
||||
// implemented function pointers yet. Gets the caller object.
|
||||
assert(cthread.fstack.list.length >= 2);
|
||||
auto nd = cthread.fstack.cur;
|
||||
nd = cthread.fstack.list.getNext(nd);
|
||||
if(nd.getCls() is _threadClass)
|
||||
nd = cthread.fstack.list.getNext(nd);
|
||||
auto mo = nd.obj;
|
||||
|
||||
auto trd = mo.thread(name);
|
||||
auto fn = stack.popFuncRef();
|
||||
auto trd = fn.getObject().thread(fn.getFunction());
|
||||
|
||||
stack.pushObject(createObj(trd));
|
||||
}
|
||||
|
|
|
@ -123,6 +123,38 @@ bool logFStack = true;
|
|||
// If true, log when threads are put into the background/forground.
|
||||
bool logThreads = true;
|
||||
|
||||
// The following options control more low-level debugging. Most of
|
||||
// these will enable tracing of various internal function calls and
|
||||
// parameters directly to stdout.
|
||||
|
||||
// Trace the thread system
|
||||
bool traceThreads = false;
|
||||
|
||||
// Trace scope lookups
|
||||
bool traceLookups = false;
|
||||
|
||||
// Trace resolve() calls
|
||||
bool traceResolve = false;
|
||||
|
||||
// Print completed output from the assembler
|
||||
bool printAsmOutput = false;
|
||||
|
||||
// Trace VM opcode execution
|
||||
bool traceVMOps = false;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Optimization options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Enable assembler optimizations
|
||||
bool optimizeAsm = true;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
|
|
|
@ -123,6 +123,38 @@ bool logFStack = true;
|
|||
// If true, log when threads are put into the background/forground.
|
||||
bool logThreads = true;
|
||||
|
||||
// The following options control more low-level debugging. Most of
|
||||
// these will enable tracing of various internal function calls and
|
||||
// parameters directly to stdout.
|
||||
|
||||
// Trace the thread system
|
||||
bool traceThreads = false;
|
||||
|
||||
// Trace scope lookups
|
||||
bool traceLookups = false;
|
||||
|
||||
// Trace resolve() calls
|
||||
bool traceResolve = false;
|
||||
|
||||
// Print completed output from the assembler
|
||||
bool printAsmOutput = false;
|
||||
|
||||
// Trace VM opcode execution
|
||||
bool traceVMOps = false;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Optimization options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Enable assembler optimizations
|
||||
bool optimizeAsm = true;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
svn export ../../../monster/trunk/monster/ . --force
|
||||
svn export https://monster-script.svn.sourceforge.net/svnroot/monster-script/trunk/monster/ . --force
|
||||
rm -r minibos vm/c_api.d
|
||||
|
||||
for a in $(find -iname \*.d); do
|
||||
|
@ -13,3 +13,4 @@ svn st
|
|||
diff options.openmw options.d || $EDITOR options.d
|
||||
mv options.openmw options.openmw_last
|
||||
cp options.d options.openmw
|
||||
|
||||
|
|
|
@ -333,7 +333,7 @@ struct HashTable(Key, Value, Alloc = GCAlloc, Hash = DefHash,
|
|||
// Get a pointer to value of given key, or insert a new. Useful for
|
||||
// large value types when you want to avoid copying the value around
|
||||
// needlessly. Also useful if you want to do in-place
|
||||
// editing. Returns true if a new value was inserted.
|
||||
// editing. Returns true if a value was inserted.
|
||||
bool insertEdit(Key k, out Value *ptr)
|
||||
{
|
||||
Node *p;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.util.freelist;
|
||||
|
@ -182,6 +183,16 @@ struct Buffers
|
|||
BufferList!(256) b256;
|
||||
BufferList!(768) b768;
|
||||
|
||||
/*
|
||||
static this()
|
||||
{
|
||||
writefln("64: ints=%s bytes=%s", b64.ints, b64.bytes);
|
||||
writefln("128: ints=%s bytes=%s", b128.ints, b128.bytes);
|
||||
writefln("256: ints=%s bytes=%s", b256.ints, b256.bytes);
|
||||
writefln("768: ints=%s bytes=%s", b768.ints, b768.bytes);
|
||||
}
|
||||
*/
|
||||
|
||||
int[] getInt(uint size)
|
||||
{
|
||||
if(size <= b64.ints) return b64.getInt(size);
|
||||
|
|
|
@ -24,7 +24,10 @@
|
|||
|
||||
module monster.util.growarray;
|
||||
|
||||
// Array that grows without reallocations.
|
||||
// Array that grows without reallocations. It does this by
|
||||
// block-allocation and by stringing several blocks together to work
|
||||
// like one single array. Elements are not guaranteed to be stored
|
||||
// continuously in memory.
|
||||
struct GrowArray(T)
|
||||
{
|
||||
const defSize = 128;
|
||||
|
|
|
@ -28,7 +28,6 @@ module monster.util.list;
|
|||
// through the entire list on every insert and remove, so they are
|
||||
// very slow for large lists. But they are very handy bug catchers
|
||||
// when doing a little dirty list hacking.
|
||||
|
||||
// debug=slowcheck;
|
||||
|
||||
private import std.c.stdlib;
|
||||
|
@ -222,7 +221,6 @@ struct LinkedList(Value, alias Alloc = GCAlloc)
|
|||
Iterator insertNode(Node *p)
|
||||
in
|
||||
{
|
||||
//debug(slowcheck)
|
||||
assert(!hasIterator(p), "inserNode: Node is already in the list");
|
||||
}
|
||||
body
|
||||
|
|
|
@ -68,16 +68,21 @@ struct Dbg
|
|||
void init()
|
||||
{
|
||||
static if(defaultLogToStdout)
|
||||
enableStdout();
|
||||
}
|
||||
|
||||
void enableStdout()
|
||||
{
|
||||
dbgOut = dout;
|
||||
}
|
||||
|
||||
void log(char[] msg)
|
||||
void log(char[] msg, Thread *tr = null)
|
||||
{
|
||||
if(dbgOut !is null)
|
||||
{
|
||||
int logLevel = getFStackLevel();
|
||||
int extLevel = getExtLevel();
|
||||
int trdIndex = getThreadIndex();
|
||||
int trdIndex = getThreadIndex(tr);
|
||||
|
||||
char[] str = format("(trd=%s,ext=%s,lev=%s)",
|
||||
trdIndex,
|
||||
|
@ -134,9 +139,11 @@ struct Dbg
|
|||
return 0;
|
||||
}
|
||||
|
||||
int getThreadIndex()
|
||||
int getThreadIndex(Thread *tr = null)
|
||||
{
|
||||
if(cthread !is null)
|
||||
if(tr !is null)
|
||||
return tr.getIndex();
|
||||
else if(cthread !is null)
|
||||
return cthread.getIndex();
|
||||
else return 0;
|
||||
}
|
||||
|
|
|
@ -65,11 +65,6 @@ struct StackPoint
|
|||
|
||||
MonsterObject *obj; // "this"-pointer for the function
|
||||
|
||||
// Could have an afterStack to check that the function has the
|
||||
// correct imprint (corresponding to an imprint-var in Function.)
|
||||
|
||||
int *frame; // Stack frame for this function
|
||||
|
||||
// Get the class owning the function
|
||||
MonsterClass getCls()
|
||||
{
|
||||
|
@ -185,15 +180,22 @@ struct FunctionStack
|
|||
else assert(list.length == 0);
|
||||
}
|
||||
|
||||
// Set the global stack frame pointer to correspond to the current
|
||||
// entry in the fstack. Must be called when putting a thread in the
|
||||
// foreground.
|
||||
void restoreFrame()
|
||||
// Get the thread associated with this function stack. Depends on
|
||||
// the fact that we are the first member of the Thread, so our
|
||||
// pointers are the same. If this changes, you MUST change this
|
||||
// function as well.
|
||||
Thread *getThread()
|
||||
{
|
||||
if(cur !is null)
|
||||
stack.setFrame(cur.frame);
|
||||
else
|
||||
stack.setFrame(null);
|
||||
return cast(Thread*)this;
|
||||
}
|
||||
|
||||
// Used for debug logging
|
||||
void log(char[] msg)
|
||||
{
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log(msg, getThread());
|
||||
}
|
||||
}
|
||||
|
||||
void killAll()
|
||||
|
@ -236,7 +238,6 @@ struct FunctionStack
|
|||
// Puts a new node at the beginning of the list
|
||||
cur = list.getNew();
|
||||
cur.obj = obj;
|
||||
cur.frame = stack.setFrame();
|
||||
}
|
||||
|
||||
// Set the stack point up as a function. Allows obj to be null.
|
||||
|
@ -261,7 +262,7 @@ struct FunctionStack
|
|||
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log("+++ " ~ cur.toString());
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,7 +286,7 @@ struct FunctionStack
|
|||
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log("+++ " ~ cur.toString());
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,7 +301,7 @@ struct FunctionStack
|
|||
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log("+++ " ~ cur.toString());
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,7 +318,7 @@ struct FunctionStack
|
|||
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log("+++ " ~ cur.toString());
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,7 +337,7 @@ struct FunctionStack
|
|||
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log(" -- " ~ cur.toString());
|
||||
log(" -- " ~ cur.toString());
|
||||
}
|
||||
|
||||
// Remove the topmost node from the list, and set cur.
|
||||
|
@ -344,13 +345,11 @@ struct FunctionStack
|
|||
list.remove(cur);
|
||||
cur = list.getHead();
|
||||
|
||||
restoreFrame();
|
||||
|
||||
assert(list.length != 0 || cur is null);
|
||||
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log("");
|
||||
log("");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import monster.compiler.properties;
|
|||
import monster.compiler.scopes;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.stack;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.vm;
|
||||
import monster.vm.dbg;
|
||||
|
@ -125,9 +126,8 @@ void doMonsterInit()
|
|||
// Initialize the debugger structure
|
||||
dbg.init();
|
||||
|
||||
// Initialize compiler constructs
|
||||
// Initialize tokenizer
|
||||
initTokenizer();
|
||||
initProperties();
|
||||
|
||||
// initScope depends on doVMInit setting vm.vfs
|
||||
vm.doVMInit();
|
||||
|
@ -138,6 +138,12 @@ void doMonsterInit()
|
|||
stack.init();
|
||||
arrays.initialize();
|
||||
|
||||
// Compiles the 'Object' class
|
||||
MonsterClass.initialize();
|
||||
|
||||
// Depends on 'Object'
|
||||
initProperties();
|
||||
|
||||
// Load modules
|
||||
static if(loadModules)
|
||||
initAllModules();
|
||||
|
|
|
@ -85,11 +85,37 @@ final class MonsterClass
|
|||
return
|
||||
Block.isNext(tokens, TT.Class) ||
|
||||
Block.isNext(tokens, TT.Singleton) ||
|
||||
Block.isNext(tokens, TT.Abstract) ||
|
||||
Block.isNext(tokens, TT.Module);
|
||||
}
|
||||
|
||||
static uint getTotalObjects() { return allObjects.length; }
|
||||
|
||||
// Sets up the Object class, which is the parent of all other
|
||||
// classes.
|
||||
static void initialize()
|
||||
{
|
||||
assert(baseObject is null,
|
||||
"MonsterClass.initialize() run more than once");
|
||||
assert(global !is null);
|
||||
|
||||
// The Object class is empty
|
||||
baseObject = vm.loadString("abstract class Object;", "Object");
|
||||
assert(baseObject !is null);
|
||||
|
||||
// Set up the class. createScope() etc will make a special case
|
||||
// for us when it detects that it is running on baseObject.
|
||||
baseObject.requireCompile();
|
||||
}
|
||||
|
||||
private static MonsterClass baseObject;
|
||||
|
||||
// Returns the class called 'Object'
|
||||
static MonsterClass getObject()
|
||||
{
|
||||
return baseObject;
|
||||
}
|
||||
|
||||
final:
|
||||
|
||||
/*******************************************************
|
||||
|
@ -110,8 +136,7 @@ final class MonsterClass
|
|||
PackageScope pack;
|
||||
|
||||
ObjectType objType; // Type for objects of this class
|
||||
Type classType; // Type for class references to this class (not
|
||||
// implemented yet)
|
||||
Type classType; // Type for class references to this class
|
||||
|
||||
// Pointer to the C++ wrapper class, if any. Could be used for other
|
||||
// wrapper languages at well, but only one at a time.
|
||||
|
@ -126,34 +151,6 @@ final class MonsterClass
|
|||
|
||||
public:
|
||||
|
||||
bool isParsed() { return flags.has(CFlags.Parsed); }
|
||||
bool isScoped() { return flags.has(CFlags.Scoped); }
|
||||
bool isResolved() { return flags.has(CFlags.Resolved); }
|
||||
bool isCompiled() { return flags.has(CFlags.Compiled); }
|
||||
|
||||
bool isSingleton() { return flags.has(CFlags.Singleton); }
|
||||
bool isModule() { return flags.has(CFlags.Module); }
|
||||
bool isAbstract() { return flags.has(CFlags.Abstract); }
|
||||
|
||||
// Call whenever you require this function to have its scope in
|
||||
// order. If the scope is missing, this will call createScope if
|
||||
// possible, or fail if the class has not been loaded.
|
||||
void requireScope()
|
||||
{
|
||||
if(isScoped) return;
|
||||
if(!isParsed)
|
||||
fail("Cannot use class '" ~ name.str ~
|
||||
"': not found or forward reference",
|
||||
name.loc);
|
||||
|
||||
createScope();
|
||||
}
|
||||
|
||||
// Called whenever we need a completely compiled class, for example
|
||||
// when creating an object. Compiles the class if it isn't done
|
||||
// already.
|
||||
void requireCompile() { if(!isCompiled) compileBody(); }
|
||||
|
||||
// Create a class belonging to the given package scope. Do not call
|
||||
// this yourself, use vm.load* to load classes.
|
||||
this(PackageScope psc = null)
|
||||
|
@ -224,7 +221,8 @@ final class MonsterClass
|
|||
return virtuals[ctree][findex];
|
||||
}
|
||||
|
||||
// Find a given callable function, virtually.
|
||||
// Find a given callable function, looking up parent classes if
|
||||
// necessary.
|
||||
Function *findFunction(char[] name)
|
||||
{
|
||||
requireScope();
|
||||
|
@ -617,6 +615,34 @@ final class MonsterClass
|
|||
* *
|
||||
*******************************************************/
|
||||
|
||||
bool isParsed() { return flags.has(CFlags.Parsed); }
|
||||
bool isScoped() { return flags.has(CFlags.Scoped); }
|
||||
bool isResolved() { return flags.has(CFlags.Resolved); }
|
||||
bool isCompiled() { return flags.has(CFlags.Compiled); }
|
||||
|
||||
bool isSingleton() { return flags.has(CFlags.Singleton); }
|
||||
bool isModule() { return flags.has(CFlags.Module); }
|
||||
bool isAbstract() { return flags.has(CFlags.Abstract); }
|
||||
|
||||
// Call whenever you require this function to have its scope in
|
||||
// order. If the scope is missing, this will call createScope if
|
||||
// possible, or fail if the class has not been loaded.
|
||||
void requireScope()
|
||||
{
|
||||
if(isScoped) return;
|
||||
if(!isParsed)
|
||||
fail("Cannot use class '" ~ name.str ~
|
||||
"': not found or forward reference",
|
||||
name.loc);
|
||||
|
||||
createScope();
|
||||
}
|
||||
|
||||
// Called whenever we need a completely compiled class, for example
|
||||
// when creating an object. Compiles the class if it isn't done
|
||||
// already.
|
||||
void requireCompile() { if(!isCompiled) compileBody(); }
|
||||
|
||||
// Check if this class is a child of cls.
|
||||
bool childOf(MonsterClass cls)
|
||||
{
|
||||
|
@ -707,35 +733,87 @@ final class MonsterClass
|
|||
natNew.name.str = "native 'new' callback";
|
||||
natNew.owner = this;
|
||||
|
||||
// TODO: Check for a list of keywords here. class, module,
|
||||
// abstract, final. They can come in any order, but only certain
|
||||
// combinations are legal. For example, class and module cannot
|
||||
// both be present, and most other keywords only apply to
|
||||
// classes. 'function' is not allowed at all, but should be
|
||||
// checked for to make sure we're loading the right kind of
|
||||
// file. If neither class nor module are found, that is also
|
||||
// illegal in class files.
|
||||
|
||||
if(isNext(tokens, TT.Module))
|
||||
// Parse keywords. Uses a few local variables, so let's start a
|
||||
// block.
|
||||
{
|
||||
flags.set(CFlags.Module);
|
||||
flags.set(CFlags.Singleton);
|
||||
assert(tokens.length > 0);
|
||||
Floc loc = tokens[0].loc;
|
||||
|
||||
// Check for / set a given keyword flag. Returns true if it
|
||||
// was NOT found.
|
||||
bool check(TT type, ref bool flag)
|
||||
{
|
||||
Token tok;
|
||||
if(isNext(tokens, type, tok))
|
||||
{
|
||||
if(flag)
|
||||
fail("Keyword '" ~ tok.str ~
|
||||
"' was specified twice", tok.loc);
|
||||
flag = true;
|
||||
return false;
|
||||
}
|
||||
else if(isNext(tokens, TT.Singleton))
|
||||
flags.set(CFlags.Singleton);
|
||||
else if(!isNext(tokens, TT.Class))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flags for the various keywords
|
||||
bool absSet;
|
||||
bool classSet;
|
||||
bool moduleSet;
|
||||
bool singleSet;
|
||||
bool something;
|
||||
|
||||
while(true)
|
||||
{
|
||||
if(check(TT.Class, classSet) &&
|
||||
check(TT.Abstract, absSet) &&
|
||||
check(TT.Module, moduleSet) &&
|
||||
check(TT.Singleton, singleSet))
|
||||
// Abort if nothing was found this round
|
||||
break;
|
||||
|
||||
// If we get here at least once, then one of the keywords
|
||||
// were present.
|
||||
something = true;
|
||||
}
|
||||
|
||||
// Was anything found?
|
||||
if(!something)
|
||||
fail("File must begin with a class or module statement", tokens);
|
||||
|
||||
// Module and singleton imply class as well
|
||||
if(moduleSet || singleSet)
|
||||
classSet = true;
|
||||
|
||||
// Do error checking
|
||||
if(moduleSet && singleSet)
|
||||
fail("Cannot specify both 'module' and 'singleton' (module implies singleton)",
|
||||
loc);
|
||||
if(!classSet)
|
||||
fail("Class must start with one of 'class', 'singleton' or 'module'",
|
||||
loc);
|
||||
|
||||
// Module implies singleton
|
||||
if(moduleSet)
|
||||
singleSet = true;
|
||||
|
||||
// But singletons cannot be abstract
|
||||
if(singleSet && absSet)
|
||||
fail("Modules and singletons cannot be abstract", loc);
|
||||
|
||||
// Process flags
|
||||
if(singleSet) flags.set(CFlags.Singleton);
|
||||
if(moduleSet) flags.set(CFlags.Module);
|
||||
if(absSet) flags.set(CFlags.Abstract);
|
||||
}
|
||||
|
||||
if(!isNext(tokens, TT.Identifier, name))
|
||||
fail("Class statement expected identifier", tokens);
|
||||
|
||||
// Module implies singleton
|
||||
assert(isSingleton || !isModule);
|
||||
assert(!isSingleton || !isAbstract);
|
||||
|
||||
if(isSingleton && isAbstract)
|
||||
fail("Modules and singletons cannot be abstract", name.loc);
|
||||
|
||||
// Insert ourselves into the global scope. This will also
|
||||
// Insert ourselves into the package scope. This will also
|
||||
// resolve forward references to this class, if any.
|
||||
pack.insertClass(this);
|
||||
|
||||
|
@ -757,10 +835,6 @@ final class MonsterClass
|
|||
}
|
||||
|
||||
isNext(tokens, TT.Semicolon);
|
||||
/*
|
||||
if(!isNext(tokens, TT.Semicolon))
|
||||
fail("Missing semicolon after class statement", name.loc);
|
||||
*/
|
||||
|
||||
if(parents.length > 1)
|
||||
fail("Multiple inheritance is currently not supported", name.loc);
|
||||
|
@ -866,6 +940,13 @@ final class MonsterClass
|
|||
fail("Cannot bind to non-native function '" ~ name ~ "'");
|
||||
}
|
||||
|
||||
// Check that the function really belongs to this class. We cannot
|
||||
// bind functions belonging to parent classes.
|
||||
assert(fn.owner !is null);
|
||||
if(fn.owner !is this)
|
||||
fail("Cannot bind to function " ~ fn.toString() ~
|
||||
" - it is not a direct member of class " ~ toString());
|
||||
|
||||
fn.ftype = ft;
|
||||
|
||||
return fn;
|
||||
|
@ -978,7 +1059,6 @@ final class MonsterClass
|
|||
pName.loc);
|
||||
auto mc = sl.mc;
|
||||
assert(mc !is null);
|
||||
//MonsterClass mc = vm.load(pName.str);
|
||||
mc.requireScope();
|
||||
|
||||
assert(mc !is null);
|
||||
|
@ -1006,9 +1086,21 @@ final class MonsterClass
|
|||
// For now we only support one parent class.
|
||||
assert(parents.length <= 1);
|
||||
|
||||
// Since there's only one parent, we can copy its tree and add
|
||||
// ourselv to the list. TODO: At some point we need to
|
||||
// automatically add Object to this list.
|
||||
// initialize() must already have been called before we get here
|
||||
assert(baseObject !is null);
|
||||
|
||||
// If we don't have a parent, and we aren't Object, then set
|
||||
// Object as our parent.
|
||||
if(parents.length != 1 && this !is baseObject)
|
||||
parents = [baseObject];
|
||||
|
||||
// So at this point we either have a parent, or we are Object.
|
||||
assert(parents.length == 1 ||
|
||||
this is baseObject);
|
||||
|
||||
// Since there are only linear (single) inheritance graphs at
|
||||
// the moment, we can just copy our parent's tree list and add
|
||||
// ourself to it.
|
||||
if(parents.length == 1)
|
||||
tree = parents[0].tree;
|
||||
else
|
||||
|
@ -1024,8 +1116,14 @@ final class MonsterClass
|
|||
// package scope if there is no parent.
|
||||
Scope parSc;
|
||||
if(parents.length != 0) parSc = parents[0].sc;
|
||||
// TODO: Should only be allowed for Object
|
||||
else parSc = pack;
|
||||
else
|
||||
{
|
||||
// For Object, use the package scope (which should be the
|
||||
// global scope)
|
||||
assert(this is baseObject);
|
||||
assert(pack is global);
|
||||
parSc = pack;
|
||||
}
|
||||
|
||||
assert(parSc !is null);
|
||||
|
||||
|
@ -1140,8 +1238,7 @@ final class MonsterClass
|
|||
// This calls resolve on the interior of functions and states.
|
||||
void resolveBody()
|
||||
{
|
||||
if(!isScoped)
|
||||
createScope();
|
||||
requireScope();
|
||||
|
||||
assert(!isResolved, getName() ~ " is already resolved");
|
||||
|
||||
|
|
|
@ -298,6 +298,8 @@ struct MonsterObject
|
|||
if(fn.paramSize > 0)
|
||||
fail("thread(): function " ~ fn.name.str ~ " cannot have parameters");
|
||||
|
||||
fn = fn.findVirtual(this);
|
||||
|
||||
Thread *trd = Thread.getNew();
|
||||
|
||||
// Schedule the function to run the next frame
|
||||
|
@ -305,11 +307,6 @@ struct MonsterObject
|
|||
assert(trd.isPaused);
|
||||
assert(trd.fstack.cur !is null);
|
||||
|
||||
// pushFunc will mess with the stack frame though, so fix it.
|
||||
trd.fstack.cur.frame = stack.getStart();
|
||||
if(cthread !is null)
|
||||
cthread.fstack.restoreFrame();
|
||||
|
||||
return trd;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import std.stdio;
|
|||
import std.utf;
|
||||
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.functions;
|
||||
import monster.options;
|
||||
|
||||
import monster.vm.mobject;
|
||||
|
@ -40,7 +41,35 @@ import monster.vm.error;
|
|||
// copies when they need it.
|
||||
CodeStack stack;
|
||||
|
||||
// A simple stack frame. All data are in chunks of 4 bytes
|
||||
struct FunctionRef
|
||||
{
|
||||
MIndex obj;
|
||||
int fIndex;
|
||||
|
||||
MonsterObject *getObject()
|
||||
{ return getMObject(obj); }
|
||||
|
||||
Function *getFunctionNonVirtual()
|
||||
{ return functionList[fIndex]; }
|
||||
|
||||
Function *getFunction()
|
||||
{
|
||||
auto f = getFunctionNonVirtual();
|
||||
return f.findVirtual(getObject());
|
||||
}
|
||||
|
||||
void set(Function* fn, MonsterObject *mo)
|
||||
{
|
||||
assert(fn !is null);
|
||||
assert(mo !is null);
|
||||
assert(mo.cls.childOf(fn.owner));
|
||||
fIndex = fn.getGIndex();
|
||||
obj = mo.getIndex();
|
||||
}
|
||||
}
|
||||
static assert(FunctionRef.sizeof == 8);
|
||||
|
||||
// A simple stack. All data are in chunks of 4 bytes
|
||||
struct CodeStack
|
||||
{
|
||||
private:
|
||||
|
@ -49,11 +78,6 @@ struct CodeStack
|
|||
int left, total;
|
||||
int *pos; // Current position
|
||||
|
||||
// Frame pointer, used for accessing local variables and parameters
|
||||
int *frame;
|
||||
int fleft; // A security measure to make sure we don't access
|
||||
// variables outside the stack
|
||||
|
||||
public:
|
||||
void init()
|
||||
{
|
||||
|
@ -61,7 +85,6 @@ struct CodeStack
|
|||
left = maxStack;
|
||||
total = maxStack;
|
||||
pos = data.ptr;
|
||||
frame = null;
|
||||
}
|
||||
|
||||
// Get the current position index.
|
||||
|
@ -70,37 +93,11 @@ struct CodeStack
|
|||
return total-left;
|
||||
}
|
||||
|
||||
// Sets the current position as the 'frame pointer', and return it.
|
||||
int *setFrame()
|
||||
{
|
||||
frame = pos;
|
||||
fleft = left;
|
||||
//writefln("setFrame(): new=%s, old=%s", frame, old);
|
||||
return frame;
|
||||
}
|
||||
|
||||
// Sets the given frame pointer
|
||||
void setFrame(int *frm)
|
||||
{
|
||||
//writefln("restoring frame: %s", frm);
|
||||
frame = frm;
|
||||
if(frm is null)
|
||||
{
|
||||
fleft = 0;
|
||||
return;
|
||||
}
|
||||
fleft = left + (pos-frm);
|
||||
assert(fleft >= 0 && fleft <= total);
|
||||
}
|
||||
|
||||
// Reset the stack level to zero.
|
||||
void reset()
|
||||
{
|
||||
left = total;
|
||||
pos = data.ptr;
|
||||
|
||||
fleft = 0;
|
||||
frame = null;
|
||||
}
|
||||
|
||||
void pushInt(int i)
|
||||
|
@ -135,12 +132,6 @@ struct CodeStack
|
|||
return *(cast(long*)pos);
|
||||
}
|
||||
|
||||
// Get the pointer from the start of the stack
|
||||
int *getStart()
|
||||
{
|
||||
return cast(int*)data.ptr;
|
||||
}
|
||||
|
||||
// Get the pointer to an int at the given position backwards from
|
||||
// the current stack pointer. 0 means the first int, ie. the one we
|
||||
// would get if we called popInt. 1 is the next, etc
|
||||
|
@ -182,18 +173,6 @@ struct CodeStack
|
|||
pos+=arr.length;
|
||||
}
|
||||
|
||||
// Get an int a given position from frame pointer. Can be negative
|
||||
// or positive. 0 means the int at the pointer, -1 is the one before
|
||||
// and 1 the one after, etc.
|
||||
int *getFrameInt(int ptr)
|
||||
{
|
||||
assert(frame !is null);
|
||||
assert(frame <= pos);
|
||||
if(ptr < (fleft-total) || ptr >= fleft)
|
||||
fail("CodeStack.getFrameInt() pointer out of range");
|
||||
return frame+ptr;
|
||||
}
|
||||
|
||||
// Pushing and poping objects of the stack - will actually push/pop
|
||||
// their index.
|
||||
void pushObject(MonsterObject *mo)
|
||||
|
@ -202,6 +181,9 @@ struct CodeStack
|
|||
MonsterObject *popObject()
|
||||
{ return getMObject(cast(MIndex)popInt()); }
|
||||
|
||||
MonsterObject *peekObject()
|
||||
{ return getMObject(cast(MIndex)peekInt()); }
|
||||
|
||||
// Push arrays of objects. TODO: These do memory allocation, and I'm
|
||||
// not sure that belongs here. I will look into it later.
|
||||
void pushObjects(MonsterObject *objs[])
|
||||
|
@ -234,6 +216,8 @@ struct CodeStack
|
|||
{ return arrays.getRef(cast(AIndex)popInt()); }
|
||||
ArrayRef *getArray(int i)
|
||||
{ return arrays.getRef(cast(AIndex)*getInt(i)); }
|
||||
ArrayRef *peekArray()
|
||||
{ return getArray(0); }
|
||||
|
||||
// More easy versions. Note that pushArray() will create a new array
|
||||
// reference each time it is called! Only use it if this is what you
|
||||
|
@ -264,6 +248,8 @@ struct CodeStack
|
|||
{ pushArray(toUTF32(str)); }
|
||||
char[] popString8()
|
||||
{ return toUTF8(popString()); }
|
||||
char[] peekString8()
|
||||
{ return toUTF8(peekArray().carr); }
|
||||
|
||||
// For multibyte arrays
|
||||
void pushArray(int[] str, int size)
|
||||
|
@ -271,9 +257,7 @@ struct CodeStack
|
|||
|
||||
// Various convenient conversion templates. These will be inlined,
|
||||
// so don't worry :) The *4() functions are for types that are 4
|
||||
// bytes long. These are mostly intended for use in native
|
||||
// functions, so there is no equivalent of getFrameInt and similar
|
||||
// functions.
|
||||
// bytes long.
|
||||
void push4(T)(T var)
|
||||
{
|
||||
static assert(T.sizeof == 4);
|
||||
|
@ -285,8 +269,15 @@ struct CodeStack
|
|||
int i = popInt();
|
||||
return *(cast(T*)&i);
|
||||
}
|
||||
// Gets a pointer to a given stack value. Counts from the head - 0
|
||||
// is the first int, 1 is the second, etc. Note that it counts in
|
||||
// ints (four bytes) no matter what the type T is - this is by
|
||||
// design.
|
||||
T* get4(T)(int ptr) { return cast(T*)getInt(ptr); }
|
||||
|
||||
// Returns the first value on the stack without poping it
|
||||
T peek4(T)() { return *(cast(T*)getInt(0)); }
|
||||
|
||||
// 64 bit version
|
||||
void push8(T)(T var)
|
||||
{
|
||||
|
@ -314,32 +305,54 @@ struct CodeStack
|
|||
alias push4!(MIndex) pushMIndex;
|
||||
alias pop4!(MIndex) popMIndex;
|
||||
alias get4!(MIndex) getMIndex;
|
||||
alias peek4!(MIndex) peekMIndex;
|
||||
|
||||
alias push4!(AIndex) pushAIndex;
|
||||
alias pop4!(AIndex) popAIndex;
|
||||
alias get4!(AIndex) getAIndex;
|
||||
alias peek4!(AIndex) peekAIndex;
|
||||
|
||||
alias peek4!(int) peekInt;
|
||||
|
||||
alias push4!(uint) pushUint;
|
||||
alias pop4!(uint) popUint;
|
||||
alias get4!(uint) getUint;
|
||||
alias peek4!(uint) peekUint;
|
||||
|
||||
alias get4!(long) getLong;
|
||||
alias peek4!(long) peekLong;
|
||||
|
||||
alias push8!(ulong) pushUlong;
|
||||
alias pop8!(ulong) popUlong;
|
||||
alias get4!(ulong) getUlong;
|
||||
alias peek4!(ulong) peekUlong;
|
||||
|
||||
alias push4!(float) pushFloat;
|
||||
alias pop4!(float) popFloat;
|
||||
alias get4!(float) getFloat;
|
||||
alias peek4!(float) peekFloat;
|
||||
|
||||
alias push8!(double) pushDouble;
|
||||
alias pop8!(double) popDouble;
|
||||
alias get4!(double) getDouble;
|
||||
alias peek4!(double) peekDouble;
|
||||
|
||||
alias push4!(dchar) pushChar;
|
||||
alias pop4!(dchar) popChar;
|
||||
alias get4!(dchar) getChar;
|
||||
alias peek4!(dchar) peekDchar;
|
||||
|
||||
alias push8!(FunctionRef) pushFuncRef;
|
||||
alias pop8!(FunctionRef) popFuncRef;
|
||||
alias get4!(FunctionRef) getFuncRef;
|
||||
alias peek4!(FunctionRef) peekFuncRef;
|
||||
|
||||
void pushFuncRef(Function *fn, MonsterObject *obj)
|
||||
{
|
||||
FunctionRef f;
|
||||
f.set(fn,obj);
|
||||
pushFuncRef(f);
|
||||
}
|
||||
|
||||
void pushFail(T)(T t)
|
||||
{
|
||||
|
@ -389,7 +402,10 @@ struct CodeStack
|
|||
pos -= num;
|
||||
}
|
||||
|
||||
// Pop off and ignore given values, but remember the top values
|
||||
// Pop off and ignore given values, but remember the top
|
||||
// values. Equivalent to popping of (and storing) 'keep' ints, then
|
||||
// poping away 'num' ints, and finally pushing the kept ints
|
||||
// back. The final stack imprint is -num.
|
||||
void pop(uint num, uint keep)
|
||||
{
|
||||
assert(keep>0);
|
||||
|
|
|
@ -56,7 +56,6 @@ import std.math : floor;
|
|||
extern(C) void* memmove(void *dest, void *src, size_t n);
|
||||
|
||||
// Enable this to print bytecode instructions to stdout
|
||||
//debug=traceOps;
|
||||
|
||||
import monster.util.list;
|
||||
alias _lstNode!(Thread) _tmp1;
|
||||
|
@ -77,8 +76,9 @@ struct Thread
|
|||
* *
|
||||
*******************************************************/
|
||||
|
||||
// This has been copied from ScheduleStruct, which is now merged
|
||||
// with Thread. We'll sort it out later.
|
||||
// Function stack for this thread. This MUST be the first member of
|
||||
// Thread, to make sure fstack.getThread() works properly.
|
||||
FunctionStack fstack;
|
||||
|
||||
// Some generic variables that idle functions can use to store
|
||||
// temporary data off the stack.
|
||||
|
@ -92,9 +92,6 @@ struct Thread
|
|||
// state is changed from within a function in active code.
|
||||
bool shouldExit;
|
||||
|
||||
// Function stack for this thread
|
||||
FunctionStack fstack;
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Private variables *
|
||||
|
@ -175,6 +172,11 @@ struct Thread
|
|||
// Stop the execution of a thread and cancel any scheduling.
|
||||
void stop()
|
||||
{
|
||||
static if(traceThreads)
|
||||
{
|
||||
dbg.log("Thread.stop()");
|
||||
}
|
||||
|
||||
assert(!isDead);
|
||||
|
||||
// TODO: We also have to handle (forbid) cases where we are
|
||||
|
@ -190,7 +192,7 @@ struct Thread
|
|||
if(fstack.hasNatives)
|
||||
fail("Cannot stop thread, there are native functions on the stack.");
|
||||
|
||||
// Kill the stack tell execute() to stop running
|
||||
// Kill the stack and tell execute() to stop running
|
||||
stack.reset();
|
||||
shouldExit = true;
|
||||
}
|
||||
|
@ -225,6 +227,9 @@ struct Thread
|
|||
// Schedule this thread to run state code the next frame
|
||||
void scheduleState(MonsterObject *obj, int offs)
|
||||
{
|
||||
static if(logThreads)
|
||||
dbg.log(format("------ scheduling state in thread %s ------", getIndex));
|
||||
|
||||
assert(!isDead);
|
||||
assert(!isScheduled,
|
||||
"cannot schedule an already scheduled thread");
|
||||
|
@ -293,6 +298,11 @@ struct Thread
|
|||
// Reenter this thread to the point where it was previously stopped.
|
||||
void reenter()
|
||||
{
|
||||
static if(traceThreads)
|
||||
{
|
||||
dbg.log("Thread.reenter()");
|
||||
}
|
||||
|
||||
assert(!isDead);
|
||||
assert(cthread is null,
|
||||
"cannot reenter when another thread is running");
|
||||
|
@ -407,8 +417,6 @@ struct Thread
|
|||
assert(!isTransient,
|
||||
"cannot restore a transent thread with stack");
|
||||
|
||||
|
||||
|
||||
// Push the values back, and free the buffer
|
||||
stack.pushInts(sstack);
|
||||
Buffers.free(sstack);
|
||||
|
@ -416,9 +424,6 @@ struct Thread
|
|||
sstack = null;
|
||||
}
|
||||
|
||||
// Restore the stack frame pointer
|
||||
fstack.restoreFrame();
|
||||
|
||||
// Set ourselves as the running thread
|
||||
cthread = this;
|
||||
}
|
||||
|
@ -458,10 +463,15 @@ struct Thread
|
|||
.fail(msg, fl);
|
||||
}
|
||||
|
||||
// Parse the BC.CallIdle instruction parameters and schedule the
|
||||
// given idle function. Return true if we should exit execute()
|
||||
bool callIdle(MonsterObject *iObj)
|
||||
// Handle the call and scheduling of an given idle function. Return
|
||||
// true if we should exit execute().
|
||||
bool callIdle(MonsterObject *iObj, Function *idle)
|
||||
{
|
||||
static if(traceThreads)
|
||||
{
|
||||
dbg.log("Thread.callIdle()");
|
||||
}
|
||||
|
||||
assert(isRunning);
|
||||
assert(!isScheduled, "Thread is already scheduled");
|
||||
assert(iObj !is null);
|
||||
|
@ -469,16 +479,8 @@ struct Thread
|
|||
if(fstack.hasNatives)
|
||||
fail("Cannot run idle function: there are native functions on the stack");
|
||||
|
||||
CodeStream *code = &fstack.cur.code;
|
||||
|
||||
// Get the class from the index
|
||||
auto cls = iObj.cls.upcast(code.getInt());
|
||||
|
||||
// Get the function
|
||||
Function *idle = cls.findFunction(code.getInt());
|
||||
assert(idle !is null && idle.isIdle);
|
||||
assert(cls is idle.owner);
|
||||
assert(iObj.cls.childOf(cls));
|
||||
assert(idle !is null);
|
||||
assert(idle.isIdle);
|
||||
|
||||
// The IdleFunction object bound to this function is stored in
|
||||
// idle.idleFunc
|
||||
|
@ -517,8 +519,11 @@ struct Thread
|
|||
assert(res == IS.Manual || res == IS.Kill);
|
||||
|
||||
// The only difference between Manual and Kill is what list the
|
||||
// thread ends in. If the thread is in the transient list, it will
|
||||
// be killed automatically when it's no longer running.
|
||||
// thread ends in. The idle function itself is responsible for
|
||||
// putting the thread in the correct list when returning
|
||||
// IS.Manual. If the thread is left in the transient list (ie. if
|
||||
// the idle doesn't move the thread), it will be killed
|
||||
// automatically when it is no longer running.
|
||||
assert( (res == IS.Kill) == isTransient,
|
||||
res == IS.Manual ? "Manually scheduled threads must be moved to another list." : "Killed threads cannot be moved to another list.");
|
||||
|
||||
|
@ -561,30 +566,46 @@ struct Thread
|
|||
// exception.
|
||||
int *popPtr()
|
||||
{
|
||||
// TODO: A better optimization here would be to pop the entire
|
||||
// structure all at once. Once we work more on references,
|
||||
// create a structure that takes care of all the conversions
|
||||
// for us.
|
||||
|
||||
PT type;
|
||||
int index;
|
||||
decodePtr(stack.popInt(), type, index);
|
||||
|
||||
int *res;
|
||||
|
||||
// Null pointer?
|
||||
if(type == PT.Null)
|
||||
fail("Cannot access value, null pointer");
|
||||
|
||||
// Stack variable?
|
||||
if(type == PT.Stack)
|
||||
return stack.getFrameInt(index);
|
||||
{
|
||||
res = stack.getInt(index);
|
||||
stack.pop(2);
|
||||
}
|
||||
|
||||
// Variable in this object
|
||||
if(type == PT.DataOffs)
|
||||
return obj.getDataInt(cls.treeIndex, index);
|
||||
else if(type == PT.DataOffs)
|
||||
{
|
||||
res = obj.getDataInt(cls.treeIndex, index);
|
||||
stack.pop(2);
|
||||
}
|
||||
|
||||
// This object, but another (parent) class
|
||||
if(type == PT.DataOffsCls)
|
||||
else if(type == PT.DataOffsCls)
|
||||
{
|
||||
// We have to pop the class index of the stack as well
|
||||
return obj.getDataInt(stack.popInt, index);
|
||||
res = obj.getDataInt(stack.popInt, index);
|
||||
stack.popInt();
|
||||
}
|
||||
|
||||
// Far pointer, with offset. Both the class index and the object
|
||||
// reference is on the stack.
|
||||
if(type == PT.FarDataOffs)
|
||||
else if(type == PT.FarDataOffs)
|
||||
{
|
||||
int clsIndex = stack.popInt();
|
||||
|
||||
|
@ -592,11 +613,11 @@ struct Thread
|
|||
MonsterObject *tmp = stack.popObject();
|
||||
|
||||
// Return the correct pointer
|
||||
return tmp.getDataInt(clsIndex, index);
|
||||
res = tmp.getDataInt(clsIndex, index);
|
||||
}
|
||||
|
||||
// Array pointer
|
||||
if(type == PT.ArrayIndex)
|
||||
else if(type == PT.ArrayIndex)
|
||||
{
|
||||
assert(index==0);
|
||||
// Array indices are on the stack
|
||||
|
@ -611,10 +632,13 @@ struct Thread
|
|||
if(index < 0 || index >= arf.iarr.length)
|
||||
fail("Array index " ~ .toString(index/arf.elemSize) ~
|
||||
" out of bounds (array length " ~ .toString(arf.length) ~ ")");
|
||||
return &arf.iarr[index];
|
||||
res = &arf.iarr[index];
|
||||
}
|
||||
|
||||
else
|
||||
fail("Unable to handle pointer type " ~ toString(cast(int)type));
|
||||
|
||||
assert(res !is null);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Various temporary stuff
|
||||
|
@ -643,7 +667,7 @@ struct Thread
|
|||
|
||||
ubyte opCode = code.get();
|
||||
|
||||
debug(traceOps)
|
||||
static if(traceVMOps)
|
||||
{
|
||||
writefln("exec: %s (at stack %s)",
|
||||
bcToString[opCode], stack.getPos);
|
||||
|
@ -675,18 +699,20 @@ struct Thread
|
|||
MonsterObject *mo;
|
||||
Function *fn;
|
||||
|
||||
// Call function in this object
|
||||
case BC.Call:
|
||||
fn = Function.fromIndex(stack.popInt());
|
||||
mo = obj;
|
||||
goto CallCommon;
|
||||
goto FindFunc;
|
||||
|
||||
// Call function in another object
|
||||
case BC.CallFar:
|
||||
fn = Function.fromIndex(stack.popInt());
|
||||
mo = stack.popObject();
|
||||
|
||||
CallCommon:
|
||||
|
||||
FindFunc:
|
||||
// Get the correct function from the virtual table
|
||||
val = code.getInt(); // Class index
|
||||
fn = mo.cls.findVirtualFunc(val, code.getInt());
|
||||
fn = fn.findVirtual(mo);
|
||||
|
||||
if(fn.isNormal)
|
||||
{
|
||||
|
@ -699,6 +725,12 @@ struct Thread
|
|||
obj = mo;
|
||||
assert(obj is fstack.cur.obj);
|
||||
}
|
||||
else if(fn.isIdle)
|
||||
{
|
||||
if(callIdle(mo, fn)) return;
|
||||
}
|
||||
else if(fn.isAbstract)
|
||||
fail("Cannot call abstract function " ~ fn.toString());
|
||||
else
|
||||
{
|
||||
// Native function. Let Function handle it.
|
||||
|
@ -709,16 +741,6 @@ struct Thread
|
|||
break;
|
||||
}
|
||||
|
||||
case BC.CallIdle:
|
||||
if(callIdle(obj))
|
||||
return;
|
||||
break;
|
||||
|
||||
case BC.CallIdleFar:
|
||||
if(callIdle(stack.popObject()))
|
||||
return;
|
||||
break;
|
||||
|
||||
case BC.Return:
|
||||
// Remove the given number of bytes from the stack, and
|
||||
// exit the function.
|
||||
|
@ -730,8 +752,7 @@ struct Thread
|
|||
goto case BC.Exit;
|
||||
|
||||
case BC.ReturnValN:
|
||||
val = code.getInt(); // Get the value first, since order
|
||||
// of evaluation is important.
|
||||
val = code.getInt(); // Get the param number
|
||||
stack.pop(val, code.getInt());
|
||||
goto case BC.Exit;
|
||||
|
||||
|
@ -739,6 +760,7 @@ struct Thread
|
|||
val = code.getInt(); // State index
|
||||
val2 = code.getInt(); // Label index
|
||||
// Get the class index and let setState handle everything
|
||||
|
||||
obj.setState(val, val2, code.getInt());
|
||||
if(shouldExit) return;
|
||||
break;
|
||||
|
@ -858,19 +880,19 @@ struct Thread
|
|||
|
||||
case BC.PushData:
|
||||
stack.pushInt(code.getInt());
|
||||
debug(traceOps) writefln(" Data: %s", *stack.getInt(0));
|
||||
static if(traceVMOps) writefln(" Data: %s", *stack.getInt(0));
|
||||
break;
|
||||
|
||||
case BC.PushLocal:
|
||||
debug(traceOps)
|
||||
static if(traceVMOps)
|
||||
{
|
||||
auto p = code.getInt();
|
||||
auto v = *stack.getFrameInt(p);
|
||||
auto v = *stack.getInt(p);
|
||||
stack.pushInt(v);
|
||||
writefln(" Pushed %s from position %s",v,p);
|
||||
}
|
||||
else
|
||||
stack.pushInt(*stack.getFrameInt(code.getInt()));
|
||||
stack.pushInt(*stack.getInt(code.getInt()));
|
||||
break;
|
||||
|
||||
case BC.PushClassVar:
|
||||
|
@ -1258,7 +1280,6 @@ struct Thread
|
|||
case BC.CastI2L:
|
||||
if(*stack.getInt(0) < 0) stack.pushInt(-1);
|
||||
else stack.pushInt(0);
|
||||
//stack.pushLong(stack.popInt());
|
||||
break;
|
||||
|
||||
// Cast int to float
|
||||
|
@ -1344,6 +1365,17 @@ struct Thread
|
|||
stack.pushUlong(cast(ulong)stack.popDouble);
|
||||
break;
|
||||
|
||||
case BC.CastS2C:
|
||||
arf = stack.popArray(); // Get array
|
||||
if(arf.carr.length == 0)
|
||||
fail("Cannot cast empty string to 'char'");
|
||||
assert(arf.elemSize == 1);
|
||||
if(arf.carr.length > 1)
|
||||
fail("Cannot cast string of non-unit length " ~
|
||||
.toString(arf.carr.length) ~ " to char");
|
||||
stack.pushChar(arf.carr[0]);
|
||||
break;
|
||||
|
||||
case BC.CastT2S:
|
||||
{
|
||||
// Get the type to cast from
|
||||
|
@ -1370,6 +1402,13 @@ struct Thread
|
|||
}
|
||||
break;
|
||||
|
||||
case BC.RefFunc:
|
||||
// Convert function reference into name
|
||||
val = stack.popInt(); // Function index
|
||||
stack.popInt(); // Ignore the object index
|
||||
stack.pushString(functionList[val].toString());
|
||||
break;
|
||||
|
||||
case BC.FetchElem:
|
||||
// This is not very optimized
|
||||
val = stack.popInt(); // Index
|
||||
|
@ -1489,7 +1528,7 @@ struct Thread
|
|||
arf = stack.popArray(); // Right array
|
||||
if(arf.isNull)
|
||||
{
|
||||
// right is empty, just copy the left
|
||||
// Right is empty, just copy the left
|
||||
arf = stack.popArray();
|
||||
if(arf.isNull) stack.pushArray(arf);
|
||||
else stack.pushArray(arf.iarr.dup, arf.elemSize);
|
||||
|
@ -1570,7 +1609,7 @@ struct Thread
|
|||
case BC.IterUpdate:
|
||||
val = code.getInt(); // Get stack index of iterator reference
|
||||
if(val < 0) fail("Invalid argument to IterUpdate");
|
||||
val = *stack.getFrameInt(val); // Iterator index
|
||||
val = *stack.getInt(val); // Iterator index
|
||||
iterators.update(cast(IIndex)val); // Do the update
|
||||
break;
|
||||
|
||||
|
|
|
@ -40,9 +40,9 @@ void initMonsterScripts()
|
|||
// Add the script directories
|
||||
vm.addPath("mscripts/");
|
||||
|
||||
// Import some modules into the global scope, so we won't have to
|
||||
// import them manually in each script.
|
||||
global.registerImport("io", "random", "timer");
|
||||
// Import some modules into scope of Object, so we won't have to
|
||||
// import them manually into each script.
|
||||
MonsterClass.getObject().sc.registerImport("random", "timer");
|
||||
|
||||
// Get the Config singleton object
|
||||
config.mo = vm.load("Config").getSing();
|
||||
|
|
Loading…
Reference in a new issue