1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-28 15:09:40 +00:00

Updated to Monster 0.12 (from SVN)

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@97 ea6a568a-9f4f-0410-981a-c910a81bb256
This commit is contained in:
nkorslund 2009-04-20 17:41:27 +00:00
parent 8dd3bfa896
commit 600583cb89
40 changed files with 3878 additions and 1292 deletions

View file

@ -162,7 +162,7 @@ template LoadTT(T)
// Set up a prototype object
if(mc is null)
mc = MonsterClass.find(clsName);
mc = vm.load(clsName);
proto = mc.createObject();
proto.setString8("id", id);

View file

@ -381,10 +381,11 @@ struct Assembler
addi(func);
}
void newObj(int i)
void newObj(int clsIndex, int params)
{
cmd(BC.New);
addi(i);
addi(clsIndex);
addi(params);
}
void cloneObj() { cmd(BC.Clone); }
@ -721,8 +722,8 @@ struct Assembler
void mov(int s)
{
if(s < 3) cmd2(BC.StoreRet, BC.StoreRet8, s);
else cmdmult(BC.StoreRet, BC.StoreRetMult, s);
if(s < 3) cmd2(BC.Store, BC.Store8, s);
else cmdmult(BC.Store, BC.StoreMult, s);
}
// Set object state to the given index
@ -734,6 +735,38 @@ struct Assembler
addi(cls);
}
// Get the given field of an enum. If field is -1, get the "value"
// field.
void getEnumValue(int tIndex, int field=-1)
{
assert(Type.typeList[tIndex].isEnum,
"given type index is not an enum");
if(field == -1)
cmd(BC.EnumValue);
else
{
cmd(BC.EnumField);
addi(field);
}
addi(tIndex);
}
void enumValToIndex(int tIndex)
{
assert(Type.typeList[tIndex].isEnum,
"given type index is not an enum");
cmd(BC.EnumValToIndex);
addi(tIndex);
}
void enumNameToIndex(int tIndex)
{
assert(Type.typeList[tIndex].isEnum,
"given type index is not an enum");
cmd(BC.EnumNameToIndex);
addi(tIndex);
}
// Fetch an element from an array
void fetchElement() { cmd(BC.FetchElem); }

View file

@ -76,6 +76,21 @@ abstract class Block
return true;
}
// Is the next token a separator, ie. a ; or a new line
static bool isSep(ref TokenArray toks, TT symbol = TT.Semicolon)
{
if( toks.length == 0 ) return true;
if( toks[0].newline ) return true;
return isNext(toks, symbol);
}
// Require either a line break or a given character (default ;)
static void reqSep(ref TokenArray toks, TT symbol = TT.Semicolon)
{
if(!isSep(toks, symbol))
fail("Expected '" ~ tokenList[symbol] ~ "' or newline", toks);
}
static void reqNext(ref TokenArray toks, TT type, out Token tok)
{
if(!isNext(toks, type, tok))

View file

@ -68,9 +68,23 @@ enum BC
// index must also be -1, and the class index
// is ignored.
EnumValue, // Get the 'value' field of the enum variable
// on the stack. Takes an enum type index.
EnumField, // Get the given field of an enum. Takes the
// field number and the enum type index, both
// ints.
EnumValToIndex, // Used to look up enum names (string index)
EnumNameToIndex, // or value (long) and returns the enum index.
New, // Create a new object. Followed by an int
// giving the class index (in the file lookup
// table)
// table) and one giving the paramter
// number. The parameter indices, class
// indices and values are popped of the stack,
// see NewExpression.evalAsm for details.
Clone, // Clones an object - create a new object of
// the same class, then copy variable values
@ -136,19 +150,15 @@ enum BC
// stack. Equivalent to: a=pop; push a; push
// a;
StoreRet, // Basic operation for moving data to
Store, // Basic operation for moving data to
// memory. Schematically pops a Ptr of the
// stack, pops a value, moves the value into
// the Ptr, and then pushes the value back.
// the Ptr.
Store, // Same as StoreRet but does not push the
// value back. Not implemented, but will later
// replace storeret completely.
StoreRet8, // Same as StoreRet except two ints are popped
Store8, // Same as Store except two ints are popped
// from the stack and moved into the data.
StoreRetMult, // Takes the size as an int parameter
StoreMult, // Takes the size as an int parameter
IAdd, // Standard addition, operates on the two next
// ints in the stack, and stores the result in
@ -275,12 +285,11 @@ enum BC
// pushed as 1 then 2.
CopyArray, // Pops two array indices from the stack, and
// copies the data from one to another. Pushes
// back the array index of the
// destination. The destination array is
// popped first, then the source. The lengths
// must match. If the arrays may overlap in
// memory without unexpected effects.
// copies the data from one to another. The
// destination array is popped first, then the
// source. The lengths must match. The arrays
// may overlap in memory without unexpected
// effects.
DupArray, // Pops an array index of the stack, creates a
// copy of the array, and pushes the index of
@ -298,10 +307,9 @@ enum BC
// new array that is a slice of the original.
FillArray, // Fill an array. Pop an array index, then a
// value (int). Sets all the elements in the
// array to the value. Pushes the array index
// back. Takes an int specifying the element
// size.
// value. Sets all the elements in the array
// to the value. Takes an int specifying the
// element/value size.
CatArray, // Concatinate two arrays, on the stack.
@ -464,7 +472,8 @@ int codePtr(PT type, int index)
t.type = type;
t.val24 = index;
assert(t.remains == 0);
assert((index >= 0 && t.remains == 0) ||
(index < 0 && t.remains == 255));
return t.val32;
}
@ -474,6 +483,13 @@ void decodePtr(int ptr, out PT type, out int index)
_CodePtr t;
t.val32 = ptr;
// Manage negative numbers
if(t.val24 >= 0x800000)
{
t.remains = 255;
assert(t.val24 < 0);
}
type = cast(PT) t.type;
index = t.val24;
@ -506,6 +522,10 @@ char[][] bcToString =
BC.ReturnVal: "ReturnVal",
BC.ReturnValN: "ReturnValN",
BC.State: "State",
BC.EnumValue: "EnumValue",
BC.EnumField: "EnumField",
BC.EnumValToIndex: "EnumValToIndex",
BC.EnumNameToIndex: "EnumNameToIndex",
BC.New: "New",
BC.Jump: "Jump",
BC.JumpZ: "JumpZ",
@ -525,10 +545,9 @@ char[][] bcToString =
BC.Pop: "Pop",
BC.PopN: "PopN",
BC.Dup: "Dup",
BC.StoreRet: "StoreRet",
BC.Store: "Store",
BC.StoreRet8: "StoreRet8",
BC.StoreRetMult: "StoreRetMult",
BC.Store8: "Store8",
BC.StoreMult: "StoreMult",
BC.FetchElem: "FetchElem",
BC.GetArrLen: "GetArrLen",
BC.IMul: "IMul",

View file

@ -25,43 +25,109 @@ module monster.compiler.enums;
import monster.compiler.scopes;
import monster.compiler.types;
import monster.compiler.expression;
import monster.compiler.statement;
import monster.compiler.tokenizer;
import monster.vm.error;
// Definition of a field
struct FieldDef
{
Token name;
Type type;
}
// Definition of each entry in the enum
struct EnumEntry
{
Token name; // Entry identifier
int index; // Enum index value
char[] stringValue; // Returned when printing the value
long value; // Numeric value assigned to the enum. Can be set by the
// user, but defaults to 0 for the first entry and to
// the previous entry+1 when no value is set.
Expression exp[]; // Field values (before resolving)
int[][] fields; // Field values
}
class EnumDeclaration : TypeDeclaration
{
static bool canParse(TokenArray toks)
{ return toks.isNext(TT.Enum); }
Token name;
EnumType type;
override:
void parse(ref TokenArray toks)
{
type = new EnumType();
reqNext(toks, TT.Enum);
reqNext(toks, TT.Identifier, name);
reqNext(toks, TT.Identifier, type.nameTok);
// Field definitions?
while(isNext(toks, TT.Colon))
{
FieldDef fd;
fd.type = Type.identify(toks);
reqNext(toks, TT.Identifier, fd.name);
type.fields ~= fd;
}
reqNext(toks, TT.LeftCurl);
// Just skip everything until the matching }. This lets us
// define some enums and play around in the scripts, even if it
// doesn't actually work.
while(!isNext(toks, TT.RightCurl)) next(toks);
type.name = type.nameTok.str;
type.loc = type.nameTok.loc;
// Parse the entries and their fields
int lastVal = -1;
while(!isNext(toks, TT.RightCurl))
{
EnumEntry entry;
reqNext(toks, TT.Identifier, entry.name);
// Get the given value, if any
if(isNext(toks, TT.Equals))
{
Token num;
reqNext(toks, TT.IntLiteral, num);
lastVal = LiteralExpr.parseIntLiteral(num);
}
else
// No given value, just increase the last one given
lastVal++;
entry.value = lastVal;
while(isNext(toks, TT.Colon))
entry.exp ~= Expression.identify(toks);
reqSep(toks, TT.Comma);
type.entries ~= entry;
}
if(type.entries.length == 0)
fail("Enum is empty", type.loc);
isNext(toks, TT.Semicolon);
}
void insertType(TFVScope last)
{
type = new EnumType(this);
// Insert ourselves into the parent scope
assert(last !is null);
last.insertEnum(this);
assert(type !is null);
last.insertEnum(type);
}
// Very little to resolve, really. It's purely a declarative
// statement.
void resolve(Scope last)
{}
{
// Delegate to the type, since all the variables are defined
// there.
type.resolve(last);
}
}

View file

@ -30,6 +30,7 @@ import monster.compiler.operators;
import monster.compiler.types;
import monster.compiler.assembler;
import monster.compiler.block;
import monster.compiler.statement;
import monster.compiler.variables;
import monster.compiler.functions;
@ -87,7 +88,13 @@ abstract class Expression : Block
else if(isNext(toks, TT.Semicolon, ln))
fail("Use {} for empty statements, not ;", ln);
else fail("Cannot interpret expression " ~ toks[0].str, toks[0].loc);
else
{
if(toks.length != 0)
fail("Cannot interpret expression " ~ toks[0].str, toks[0].loc);
else
fail("Missing expression");
}
b.parse(toks);
@ -175,19 +182,13 @@ abstract class Expression : Block
Floc loc;
}
// Operators handled below. Don't really need a special function for
// this...
// Operators handled below.
static bool getNextOp(ref TokenArray toks, ref Token t)
{
if(toks.length == 0) return false;
TT tt = toks[0].type;
if(/*tt == TT.Dot || */tt == TT.Equals || tt == TT.Plus ||
tt == TT.Minus || tt == TT.Mult || tt == TT.Div ||
tt == TT.Rem || tt == TT.IDiv ||
tt == TT.PlusEq || tt == TT.MinusEq || tt == TT.MultEq ||
tt == TT.DivEq || tt == TT.RemEq || tt == TT.IDivEq ||
tt == TT.CatEq ||
if(tt == TT.Plus || tt == TT.Minus || tt == TT.Mult ||
tt == TT.Div || tt == TT.Rem || tt == TT.IDiv ||
tt == TT.Cat ||
@ -257,12 +258,8 @@ abstract class Expression : Block
// Create the compound expression. Replace the right node,
// since it already has the correct operator.
if(assign)
right.value.exp = new AssignOperator(left.value.exp,
right.value.exp,
left.value.nextOp,
left.value.loc);
else if(boolop)
assert(!assign);
if(boolop)
right.value.exp = new BooleanOperator(left.value.exp,
right.value.exp,
left.value.nextOp,
@ -320,26 +317,6 @@ abstract class Expression : Block
}
}
// As find(), but searches from the right, and inserts
// assignment operators.
void findAssign(TT types[] ...)
{
auto it = exprList.getTail();
while(it !is null)
{
// Is it the right operator?
if(types.has(it.value.nextOp))
{
// Find the next element to the left
auto nxt = it.getPrev();
replace(it.getNext(), true);
it=nxt;
}
else
it = it.getPrev();
}
}
// Now sort through the operators according to precedence. This
// is the precedence I use, it should be ok. (Check it against
// something else)
@ -368,11 +345,6 @@ abstract class Expression : Block
findBool(TT.Or, TT.And);
// Find assignment operators. These use a different Expression
// class and are searched in reverse order.
findAssign(TT.Equals, TT.PlusEq, TT.MinusEq, TT.MultEq, TT.DivEq,
TT.RemEq, TT.IDivEq, TT.CatEq);
assert(exprList.length == 1, "Cannot reduce expression list to one element");
return exprList.getHead().value.exp;
}
@ -465,8 +437,15 @@ class TypeofExpression : Expression
type = tt.getBase().getMeta();
}
// Don't actually produce anything
void evalAsm() {}
// We can always determine the type at compile time
bool isCTime() { return true; }
int[] evalCTime() { return null; }
char[] toString()
{
return "typeof(" ~ tt.exp.toString ~ ")";
}
}
// new-expressions, ie. (new Sometype[]).
@ -485,6 +464,9 @@ class NewExpression : Expression
CIndex clsInd;
NamedParam[] params;
Variable* varlist[];
static bool canParse(TokenArray toks)
{ return isNext(toks, TT.New); }
@ -492,16 +474,38 @@ class NewExpression : Expression
{
reqNext(toks, TT.New, loc);
type = Type.identify(toks, true, exArr);
// Parameters?
ExprArray exps;
FunctionCallExpr.getParams(toks, exps, params);
if(exps.length != 0)
fail("'new' can only take names parameters", loc);
}
char[] toString()
{
return "(new " ~ typeString ~ ")";
char[] res = "new " ~ typeString ~ "";
if(params.length)
{
res ~= "(";
foreach(i, v; params)
{
res ~= v.name.str;
res ~= "=";
res ~= v.value.toString;
if(i < params.length-1)
res ~= ", ";
}
res ~= ")";
}
return res;
}
void resolve(Scope sc)
{
type.resolve(sc);
MonsterClass mc;
if(type.isReplacer)
type = type.getBase();
@ -510,7 +514,7 @@ class NewExpression : Expression
{
// We need to find the index associated with this class, and
// pass it to the assembler.
auto mc = (cast(ObjectType)type).getClass();
mc = (cast(ObjectType)type).getClass();
assert(mc !is null);
clsInd = mc.getIndex();
@ -545,9 +549,7 @@ class NewExpression : Expression
e.resolve(sc);
// Check the type
try intType.typeCast(e);
catch(TypeException)
fail("Cannot convert array index " ~ e.toString ~ " to int", loc);
intType.typeCast(e, "array index");
}
else
{
@ -576,6 +578,33 @@ class NewExpression : Expression
}
else
fail("Cannot use 'new' with type " ~ type.toString, loc);
// Resolve the parameters
if(params.length != 0)
{
if(!type.isObject)
fail("New can take parameters to class types, not " ~ type.toString, loc);
assert(mc !is null);
varlist.length = params.length;
foreach(int i, ref v; params)
{
// Lookup the variable in the class
auto look = mc.sc.lookup(v.name);
if(!look.isVar)
fail(v.name.str ~ " is not a variable in class " ~ type.toString, loc);
// Store the looked up variable
varlist[i] = look.var;
// Next resolve the expression
v.value.resolve(sc);
// Finally, convert the type
look.type.typeCast(v.value, v.name.str);
}
}
}
void evalAsm()
@ -584,12 +613,28 @@ class NewExpression : Expression
if(type.isObject)
{
// Then push the variable pointers and values on the stack
foreach(i, v; params)
{
auto lvar = varlist[i];
assert(v.value !is null);
assert(lvar !is null);
assert(lvar.sc !is null);
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
}
// Create a new object. This is done through a special byte code
// instruction.
tasm.newObj(clsInd);
tasm.newObj(clsInd, params.length);
}
else if(type.isArray)
{
assert(params.length == 0);
int initVal[] = baseType.defaultInit();
int rank = exArr.length; // Array nesting level
@ -672,10 +717,7 @@ class ArrayLiteralExpr : Expression
{
// Check that all elements are of a usable type, and convert
// if necessary.
try base.typeCast(par);
catch(TypeException)
fail(format("Cannot convert %s of type %s to type %s", par,
par.typeString(), params[0].typeString()), getLoc);
base.typeCast(par, "array element");
}
cls = sc.getClass();
@ -730,7 +772,7 @@ class ArrayLiteralExpr : Expression
}
// Expression representing a literal or other special single-token
// values. Supported tokens are StringLiteral, NumberLiteral,
// values. Supported tokens are StringLiteral, Int/FloatLiteral,
// CharLiteral, True, False, Null, Dollar and This. Array literals are
// handled by ArrayLiteralExpr.
class LiteralExpr : Expression
@ -773,7 +815,8 @@ class LiteralExpr : Expression
{
return
isNext(toks, TT.StringLiteral) ||
isNext(toks, TT.NumberLiteral) ||
isNext(toks, TT.IntLiteral) ||
isNext(toks, TT.FloatLiteral) ||
isNext(toks, TT.CharLiteral) ||
isNext(toks, TT.True) ||
isNext(toks, TT.False) ||
@ -795,6 +838,14 @@ class LiteralExpr : Expression
bool isRes = false;
// Convert a token to an integer. TODO: Will do base conversions etc
// later on.
static long parseIntLiteral(Token t)
{
assert(t.type == TT.IntLiteral);
return atoi(t.str);
}
void resolve(Scope sc)
{
isRes = true;
@ -811,23 +862,20 @@ class LiteralExpr : Expression
return;
}
// Numeric literal.
if(value.type == TT.NumberLiteral)
// Numeric literals
if(value.type == TT.IntLiteral)
{
// Parse number strings. Simple hack for now, assume it's an
// int unless it contains a period, then it's a float. TODO:
// Improve this later, see how it is done elsewhere.
if(value.str.find('.') != -1)
{
type = BasicType.getFloat;
fval = atof(value.str);
return;
}
type = BasicType.getInt;
ival = atoi(value.str);
ival = parseIntLiteral(value);
return;
}
// Floats
if(value.type == TT.FloatLiteral)
{
type = BasicType.getFloat;
fval = atof(value.str);
return;
}
// The $ token. Only allowed in array indices.
@ -1008,7 +1056,7 @@ class CastExpression : Expression
int[] evalCTime()
{
// Let the type do the conversion
int[] res = type.typeCastCTime(orig);
int[] res = type.typeCastCTime(orig, "");
return res;
}
@ -1040,12 +1088,17 @@ class ImportHolder : Expression
{
mc = pmc;
// The imported class must be resolved at this point
mc.requireScope();
// Importing singletons and modules is like importing
// the object itself
if(mc.isSingleton)
type = mc.objType;
else
type = mc.classType;
assert(type !is null);
}
// All lookups in this import is done through this function. Can be
@ -1066,6 +1119,7 @@ class ImportHolder : Expression
void evalAsm()
{
mc.requireCompile();
if(mc.isSingleton)
{
assert(type.isObject);
@ -1073,3 +1127,208 @@ class ImportHolder : Expression
}
}
}
// An expression that works as a statement. This also handles all
// assignments, ie. expr1 = expr2. Supported operators are
// =, +=, *= /=, %=, ~=
class ExprStatement : Statement
{
Expression left;
// For assignment
TT opType;
Expression right;
Type type;
bool catElem; // For concatinations (~=), true when the right hand
// side is a single element rather than an array.
// Require a terminating semicolon or line break
bool term = true;
void parse(ref TokenArray toks)
{
if(toks.length == 0)
fail("Expected statement, found end of stream.");
loc = toks[0].loc;
left = Expression.identify(toks);
Token tok;
if(toks.isNext(TT.Equals, tok) ||
toks.isNext(TT.PlusEq, tok) ||
toks.isNext(TT.MinusEq, tok) ||
toks.isNext(TT.MultEq, tok) ||
toks.isNext(TT.DivEq, tok) ||
toks.isNext(TT.RemEq, tok) ||
toks.isNext(TT.IDivEq, tok) ||
toks.isNext(TT.CatEq, tok))
{
opType = tok.type;
right = Expression.identify(toks);
}
if(term) reqSep(toks);
}
char[] toString()
{
char[] res = left.toString();
if(right !is null)
{
res ~= tokenList[opType];
res ~= right.toString();
}
return res;
}
void resolve(Scope sc)
{
left.resolve(sc);
loc = left.loc;
type = left.type;
// If this isn't an assignment, we're done.
if(right is null)
return;
right.resolve(sc);
// Check that we are allowed assignment
if(!left.isLValue)
fail("Cannot assign to expression '" ~ left.toString ~ "'", loc);
// Operators other than = and ~= are only allowed for numerical
// types.
if(opType != TT.Equals && opType != TT.CatEq && !type.isNumerical)
fail("Assignment " ~tokenList[opType] ~
" not allowed for non-numerical type " ~ left.toString(), loc);
// Is the left hand expression a sliced array?
auto arr = cast(ArrayOperator) left;
if(arr !is null && arr.isSlice)
{
assert(type.isArray);
if(opType == TT.CatEq)
fail("Cannot use ~= on array slice " ~ left.toString, loc);
// For array slices on the right hand side, the left hand
// type must macth exactly, without implisit casting. For
// example, the following is not allowed, even though 3 can
// implicitly be cast to char[]:
// char[] a;
// a[] = 3; // Error
// We are, on the other hand, allowed to assign a single
// value to a slice, eg:
// int[] i = new int[5];
// i[] = 3; // Set all elements to 3.
// In this case we ARE allowed to typecast, though.
if(right.type == left.type) return;
Type base = type.getBase();
base.typeCast(right, left.toString);
// Inform arr.store() that we're filling in a single element
arr.isFill = true;
return;
}
// Handle concatination ~=
if(opType == TT.CatEq)
{
if(!left.type.isArray)
fail("Opertaor ~= can only be used on arrays, not " ~ left.toString ~
" of type " ~ left.typeString, left.loc);
// Array with array
if(left.type == right.type) catElem = false;
// Array with element
else if(right.type.canCastOrEqual(left.type.getBase()))
{
left.type.getBase().typeCast(right, "");
catElem = true;
}
else
fail("Cannot use operator ~= on types " ~ left.typeString ~
" and " ~ right.typeString, left.loc);
return;
}
// Cast the right side to the left type, if possible.
type.typeCast(right, left.toString);
assert(left.type == right.type);
}
void compile()
{
if(right is null)
{
// Simple expression, no assignment.
left.evalPop();
return;
}
int s = right.type.getSize;
// += -= etc are implemented without adding new
// instructions. This might change later. The "downside" to this
// approach is that the left side is evaluated two times, which
// might not matter much for lvalues anyway, but it does give
// more M-code vs native code, and thus more overhead. I'm not
// sure if a function call can ever be an lvalue or if lvalues
// can otherwise have side effects in the future. If that
// becomes the case, then this implementation will have to
// change. Right now, the expression i += 3 will be exactly
// equivalent to i = i + 3.
if(opType != TT.Equals)
{
// Get the values of both sides
left.eval();
right.eval();
setLine();
// Concatination
if(opType == TT.CatEq)
{
assert(type.isArray);
if(catElem)
// Append one element onto the array
tasm.catArrayRight(s);
else
// Concatinate two arrays.
tasm.catArray();
}
// Perform the arithmetic operation. This puts the result of
// the addition on the stack. The evalDest() and mov() below
// will store it in the right place.
else if(type.isNumerical)
{
if(opType == TT.PlusEq) tasm.add(type);
else if(opType == TT.MinusEq) tasm.sub(type);
else if(opType == TT.MultEq) tasm.mul(type);
else if(opType == TT.DivEq) tasm.div(type);
else if(opType == TT.IDivEq) tasm.idiv(type);
else if(opType == TT.RemEq) tasm.divrem(type);
else fail("Unhandled assignment operator", loc);
}
else assert(0, "Type not handled");
}
else right.eval();
// Store the value on the stack into the left expression.
setLine();
left.store();
}
}

View file

@ -58,6 +58,9 @@ import std.stdio;
import std.stream;
import std.string;
// TODO/FIXME: Make tango compatible before release
import std.traits;
// One problem with these split compiler / vm classes is that we
// likely end up with data (or at least pointers) we don't need, and a
// messy interface. The problem with splitting is that we duplicate
@ -94,6 +97,7 @@ struct Function
Type type; // Return type
FuncType ftype; // Function type
Variable* params[]; // List of parameters
int[][] defaults; // Default parameter values (if specified, null otherwise)
int index; // Unique function identifier within its class
int paramSize;
@ -126,6 +130,108 @@ struct Function
// this is a function that takes a variable number of arguments.
bool isVararg() { return params.length && params[$-1].isVararg; }
// Bind the given function template parameter to this Function. The
// function is assumed to have compatible parameter and return
// types, and the values are pushed and popped of the Monster stack
// automatically.
void bindT(alias func)()
{
assert(isNative, "cannot bind to non-native function " ~ name.str);
alias ParameterTypeTuple!(func) P;
alias ReturnType!(func) R;
assert(P.length == params.length, format("function %s has %s parameters, but binding function has %s", name.str, params.length, P.length));
// Check parameter types
foreach(int i, p; P)
assert(params[i].type.isDType(typeid(p)), format(
"binding %s: type mismatch in parameter %s, %s != %s",
name.str, i, params[i].type, typeid(p)));
// Check the return type
static if(is(R == void))
{
assert(type.isVoid, format("binding %s: expected to return type %s",
name.str, type));
}
else
{
assert(!type.isVoid, format(
"binding %s: function does not have return value %s",
name.str, typeid(R)));
assert(type.isDType(typeid(R)), format(
"binding %s: mismatch in return type, %s != %s",
name.str, type, typeid(R)));
}
// This is the actual function that is bound, and called each time
// the native function is invoked.
void delegate() dg =
{
P parr;
foreach_reverse(int i, PT; P)
{
parr[i] = stack.popType!(PT)();
}
static if(is(R == void))
{
func(parr);
}
else
{
R r = func(parr);
stack.pushType!(R)(r);
}
};
// Store the function
ftype = FuncType.NativeDDel;
natFunc_dg = dg;
}
template callT(T)
{
T callT(A...)(MonsterObject *mo, A a)
{
// Check parameter types
foreach(int i, p; A)
assert(params[i].type.isDType(typeid(p)), format(
"calling %s: type mismatch in parameter %s, %s != %s",
name.str, i, params[i].type, typeid(p)));
// Check the return type
static if(is(T == void))
{
assert(type.isVoid, format("calling %s: expected to return type %s",
name.str, type));
}
else
{
assert(!type.isVoid, format(
"calling %s: function does not have return value %s",
name.str, typeid(T)));
assert(type.isDType(typeid(T)), format(
"calling %s: mismatch in return type, %s != %s",
name.str, type, typeid(T)));
}
// Push all the values
foreach(i, AT; A)
stack.pushType!(AT)(a[i]);
call(mo);
static if(!is(T == void))
// Get the return value
return stack.popType!(T)();
}
}
// This is used to call the given function from native code. Note
// that this is used internally for native functions, but not for
// any other type. Idle functions can NOT be called directly from
@ -227,18 +333,22 @@ struct Function
// 'mc'. If no class is given, use an empty internal class.
void compile(char[] file, MonsterClass mc = null)
{
vm.init();
// Check if the file exists
if(!vm.findFile(file))
if(!vm.vfs.has(file))
fail("File not found: " ~ file);
// Create the stream and pass it on
auto bf = new BufferedFile(file);
auto bf = vm.vfs.open(file);
compile(file, bf, mc);
delete bf;
}
void compile(char[] file, Stream str, MonsterClass mc = null)
{
vm.init();
// Get the BOM and tokenize the stream
auto ef = new EndianStream(str);
int bom = ef.readBOM();
@ -249,6 +359,8 @@ struct Function
void compile(char[] file, ref TokenArray tokens, MonsterClass mc = null)
{
vm.init();
assert(name.str == "",
"Function " ~ name.str ~ " has already been set up");
@ -256,20 +368,9 @@ struct Function
if(MonsterClass.canParse(tokens))
fail("Cannot run " ~ file ~ " - it is a class or module.");
// Set mc to an empty class if no class is given
// Set mc to the empty class if no class is given
if(mc is null)
{
if(int_mc is null)
{
assert(int_mo is null);
int_mc = new MonsterClass(MC.String, int_class);
int_mo = int_mc.createObject;
}
assert(int_mo !is null);
mc = int_mc;
}
mc = getIntMC();
auto fd = new FuncDeclaration;
// Parse and comile the function
@ -283,17 +384,89 @@ struct Function
delete fd;
}
static MonsterClass getIntMC()
{
if(int_mc is null)
{
assert(int_mo is null);
int_mc = vm.loadString(int_class);
int_mo = int_mc.createObject;
}
assert(int_mo !is null);
return int_mc;
}
static MonsterObject *getIntMO()
{
getIntMC();
return int_mo;
}
// Returns the function name, on the form Class.func()
char[] toString()
{ return owner.name.str ~ "." ~ name.str ~ "()"; }
private:
// Empty class / object used internally
static const char[] int_class = "class _ScriptFile_;";
static MonsterClass int_mc;
static MonsterObject *int_mo;
}
// A specialized function declaration that handles class constructors
class Constructor : FuncDeclaration
{
static bool canParse(TokenArray toks)
{ return toks.isNext(TT.New); }
void parse(ref TokenArray toks)
{
// Create a Function struct.
fn = new Function;
// Default function type is normal
fn.ftype = FuncType.Normal;
// No return value
fn.type = BasicType.getVoid;
// Parse
toks.reqNext(TT.New, fn.name);
loc = fn.name.loc;
code = new CodeBlock;
code.parse(toks);
}
char[] toString()
{
char[] res = "Constructor:\n";
assert(code !is null);
res ~= code.toString();
return res;
}
// Resolve the constructor
void resolve(Scope last)
{
assert(fn.type !is null);
// Create a local scope for this function
sc = new FuncScope(last, fn);
// Set the owner class
auto cls = sc.getClass();
fn.owner = cls;
// Make sure we're assigned to the class
assert(cls.scptConst is this);
// Resolve the function body
assert(code !is null);
code.resolve(sc);
}
}
// Responsible for parsing, analysing and compiling functions.
class FuncDeclaration : Statement
{
@ -405,7 +578,7 @@ class FuncDeclaration : Statement
// In any case, parse the rest of the declaration
parseParams(toks);
isNext(toks, TT.Semicolon);
reqSep(toks);
}
else
{
@ -420,7 +593,7 @@ class FuncDeclaration : Statement
void parse(ref TokenArray toks)
{
// Create a Function struct. Will change later.
// Create a Function struct.
fn = new Function;
// Default function type is normal
@ -444,8 +617,10 @@ 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)
@ -456,6 +631,7 @@ class FuncDeclaration : Statement
fail("Idle function declaration expected ;", toks);
else assert(0);
}
*/
}
else
{
@ -568,7 +744,7 @@ class FuncDeclaration : Statement
fn.type.resolve(last);
if(fn.type.isVar)
fail("var not allowed here", fn.type.loc);
fail("var not allowed as function return type", fn.type.loc);
if(fn.type.isReplacer)
fn.type = fn.type.getBase();
@ -581,7 +757,7 @@ class FuncDeclaration : Statement
fn.paramSize = 0;
foreach(vd; paramList)
{
// Resolve the variable first, to make sure we get the rigth
// Resolve the variable first, to make sure we get the right
// size
vd.resolveParam(sc);
assert(!vd.var.type.isReplacer);
@ -674,6 +850,24 @@ class FuncDeclaration : Statement
fail("function " ~ fn.name.str ~
" doesn't override anything", fn.name.loc);
}
// Get the values of parameters which have default values
// assigned
fn.defaults.length = paramList.length;
foreach(i, dec; paramList)
{
if(dec.init !is null)
{
if(fn.isVararg)
fail("Vararg functions cannot have default parameter values", fn.name.loc);
// Get the value and store it. Fails if the expression
// is not computable at compile time.
fn.defaults[i] = dec.getCTimeValue();
assert(fn.defaults[i].length > 0);
}
}
}
// Resolve the interior of the function
@ -685,6 +879,7 @@ class FuncDeclaration : Statement
foreach(p; fn.params)
p.type.validate(fn.name.loc);
// Resolve the function body
if(code !is null)
code.resolve(sc);
}
@ -716,11 +911,23 @@ class FuncDeclaration : Statement
}
}
struct NamedParam
{
Token name;
Expression value;
}
// Expression representing a function call
class FunctionCallExpr : MemberExpression
{
Token name;
ExprArray params;
ExprArray params; // Normal (non-named) parameters
NamedParam[] named; // Named parameters
ExprArray coverage; // Expressions sorted in the same order as the
// function parameter list. Null expressions
// means we must use the default value. Never
// used for vararg functions.
Function* fd;
bool isVararg;
@ -734,28 +941,50 @@ class FunctionCallExpr : MemberExpression
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
}
// Read a parameter list (a,b,...)
static ExprArray getParams(ref TokenArray toks)
// Read a function parameter list (a,b,v1=c,v2=d,...)
static void getParams(ref TokenArray toks,
out ExprArray parms,
out NamedParam[] named)
{
ExprArray res;
if(!isNext(toks, TT.LeftParen)) return res;
parms = null;
named = null;
Expression exp;
if(!isNext(toks, TT.LeftParen)) return;
// No parameters?
if(isNext(toks, TT.RightParen)) return res;
if(isNext(toks, TT.RightParen)) return;
// Read the first parameter
res ~= Expression.identify(toks);
// Read the comma-separated list of parameters
do
{
if(toks.length < 2)
fail("Unexpected end of stream");
// Are there more?
// Named paramter?
if(toks[1].type == TT.Equals)
{
NamedParam np;
reqNext(toks, TT.Identifier, np.name);
reqNext(toks, TT.Equals);
np.value = Expression.identify(toks);
named ~= np;
}
else
{
// Normal parameter
if(named.length)
fail("Cannot use non-named parameters after a named one",
toks[0].loc);
parms ~= Expression.identify(toks);
}
}
while(isNext(toks, TT.Comma))
res ~= Expression.identify(toks);
if(!isNext(toks, TT.RightParen))
fail("Parameter list expected ')'", toks);
return res;
}
void parse(ref TokenArray toks)
@ -763,7 +992,7 @@ class FunctionCallExpr : MemberExpression
name = next(toks);
loc = name.loc;
params = getParams(toks);
getParams(toks, params, named);
}
char[] toString()
@ -804,11 +1033,10 @@ class FunctionCallExpr : MemberExpression
fail("Undefined function "~name.str, name.loc);
}
isVararg = fd.isVararg;
type = fd.type;
assert(type !is null);
isVararg = fd.isVararg;
if(isVararg)
{
// The vararg parameter can match a variable number of
@ -817,33 +1045,15 @@ class FunctionCallExpr : MemberExpression
fail(format("%s() expected at least %s parameters, got %s",
name.str, fd.params.length-1, params.length),
name.loc);
}
else
// Non-vararg functions must match function parameter number
// exactly
if(params.length != fd.params.length)
fail(format("%s() expected %s parameters, got %s",
name.str, fd.params.length, params.length),
name.loc);
// Check parameter types
foreach(int i, par; fd.params)
{
// Handle varargs below
if(isVararg && i == fd.params.length-1)
break;
// Check parameter types except for the vararg parameter
foreach(int i, par; fd.params[0..$-1])
{
params[i].resolve(sc);
par.type.typeCast(params[i], "parameter " ~ par.name.str);
}
params[i].resolve(sc);
try par.type.typeCast(params[i]);
catch(TypeException)
fail(format("%s() expected parameter %s to be type %s, not type %s",
name.str, i+1, par.type.toString, params[i].typeString),
name.loc);
}
// Loop through remaining arguments
if(isVararg)
{
// Loop through remaining arguments
int start = fd.params.length-1;
assert(fd.params[start].type.isArray);
@ -853,23 +1063,85 @@ class FunctionCallExpr : MemberExpression
{
par.resolve(sc);
// If the first and last vararg parameter is of the
// If the first and only vararg parameter is of the
// 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)
{
isVararg = false;
coverage = params;
break;
}
// Otherwise, cast the type to the array base type.
try base.typeCast(par);
catch(TypeException)
fail(format("Cannot convert %s of type %s to %s", par.toString,
par.typeString, base.toString), par.loc);
base.typeCast(par, "array base type");
}
return;
}
// Non-vararg case. Non-vararg functions must cover at least all
// the non-optional function parameters.
// Make the coverage list of all the parameters.
int parNum = fd.params.length;
coverage = new Expression[parNum];
// Mark all the parameters which are present
foreach(i,p; params)
{
assert(coverage[i] is null);
assert(p !is null);
coverage[i] = p;
}
// Add named parameters to the list
foreach(p; named)
{
// Look up the named parameter
int index = -1;
foreach(i, fp; fd.params)
if(fp.name.str == p.name.str)
{
index = i;
break;
}
if(index == -1)
fail(format("Function %s() has no paramter named %s",
name.str, p.name.str),
p.name.loc);
assert(index<parNum);
// Check that the parameter isn't already set
if(coverage[index] !is null)
fail("Parameter " ~ p.name.str ~ " set multiple times ",
p.name.loc);
// Finally, set the parameter
coverage[index] = p.value;
assert(coverage[index] !is null);
}
// Check that all non-optional parameters are present
assert(fd.defaults.length == coverage.length);
foreach(i, cv; coverage)
if(cv is null && fd.defaults[i].length == 0)
fail(format("Non-optional parameter %s is missing in call to %s()",
fd.params[i].name.str, name.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;
cov.resolve(sc);
par.type.typeCast(cov, "parameter " ~ par.name.str);
}
}
// Used in cases where the parameters need to be evaluated
@ -877,26 +1149,34 @@ class FunctionCallExpr : MemberExpression
// in cases like obj.func(expr); Here expr is evaluated first, then
// obj, and then finally the far function call. This is because the
// far function call needs to read 'obj' off the top of the stack.
bool pdone;
void evalParams()
{
assert(pdone == false);
pdone = true;
foreach(i, ex; params)
{
ex.eval();
// Convert 'const' parameters to actual constant references
if(i < fd.params.length-1) // Skip the last parameter (in
// case of vararg functions)
if(fd.params[i].isConst)
{
assert(fd.params[i].type.isArray);
tasm.makeArrayConst();
}
}
// Again, let's handle the vararg case separately
if(isVararg)
{
assert(coverage is null);
// Eval the parameters
foreach(i, ex; params)
{
ex.eval();
// The rest only applies to non-vararg parameters
if(i >= fd.params.length-1)
continue;
// Convert 'const' parameters to actual constant references
if(fd.params[i].isConst)
{
assert(fd.params[i].type.isArray);
tasm.makeArrayConst();
}
}
// Compute the length of the vararg array.
int len = params.length - fd.params.length + 1;
@ -904,23 +1184,48 @@ class FunctionCallExpr : MemberExpression
// (0 is always null).
if(len == 0) tasm.push(0);
else
// Converte the pushed values to an array index
tasm.popToArray(len, params[$-1].type.getSize());
{
// Convert the pushed values to an array index
tasm.popToArray(len, params[$-1].type.getSize());
// Convert the vararg array to 'const' if needed
if(fd.params[$-1].isConst)
{
assert(fd.params[$-1].type.isArray);
tasm.makeArrayConst();
}
}
return;
}
// Handle the last parameter after everything has been
// pushed. This will work for both normal and vararg parameters.
if(fd.params.length != 0 && fd.params[$-1].isConst)
// Non-vararg case
assert(!isVararg);
assert(coverage.length == fd.params.length);
foreach(i, ex; coverage)
{
assert(fd.params[$-1].type.isArray);
tasm.makeArrayConst();
if(ex !is null)
{
assert(ex.type == fd.params[i].type);
ex.eval();
}
else
{
// No param specified, use default value
assert(fd.defaults[i].length ==
fd.params[i].type.getSize);
assert(fd.params[i].type.getSize > 0);
tasm.pushArray(fd.defaults[i]);
}
// Convert 'const' parameters to actual constant references
if(fd.params[i].isConst)
{
assert(fd.params[i].type.isArray);
tasm.makeArrayConst();
}
}
pdone = true;
}
bool pdone;
void evalAsm()
{
if(dotImport !is null && recurse)

View file

@ -30,10 +30,13 @@ import monster.compiler.tokenizer;
import monster.compiler.scopes;
import monster.compiler.types;
import monster.compiler.functions;
import monster.compiler.enums;
import monster.vm.error;
import monster.vm.arrays;
import std.stdio;
import std.string;
import std.utf;
// Handles - ! ++ --
class UnaryOperator : Expression
@ -199,6 +202,8 @@ class ArrayOperator : OperatorExpr
bool isFill; // Set during assignment if we're filling the array
// with one single value
int isEnum; // Used for enum types
// name[], name[index], name[index..index2]
Expression name, index, index2;
@ -233,6 +238,41 @@ class ArrayOperator : OperatorExpr
// Copy the type of the name expression
type = name.type;
// May be used on enums as well
if(type.isEnum ||
(type.isMeta && type.getBase.isEnum))
{
if(type.isMeta)
type = type.getBase();
assert(type.isEnum);
if(index is null)
fail("Missing lookup value", loc);
if(index2 !is null)
fail("Slices are not allowed for enums", loc);
index.resolve(sc);
// The indices must be ints
Type lng = BasicType.getLong();
if(index.type.canCastTo(lng))
{
lng.typeCast(index, "");
isEnum = 1;
}
else
{
if(!index.type.isString)
fail("Enum lookup value must be a number or a string, not type "
~index.typeString, loc);
isEnum = 2;
}
return;
}
// Check that we are indeed an array.
if(!type.isArray)
fail("Expression '" ~ name.toString ~ "' of type '" ~ name.typeString
@ -252,17 +292,13 @@ class ArrayOperator : OperatorExpr
// The indices must be ints
Type tpint = BasicType.getInt();
try tpint.typeCast(index);
catch(TypeException)
fail("Cannot convert array index " ~ index.toString ~ " to int");
tpint.typeCast(index, "array index");
if(index2 !is null)
{
// slice, name[index..index2]
index2.resolve(isc);
try tpint.typeCast(index2);
catch(TypeException)
fail("Cannot convert array index " ~ index2.toString ~ " to int");
tpint.typeCast(index2, "array index");
isSlice = true;
}
else
@ -276,6 +312,8 @@ class ArrayOperator : OperatorExpr
bool isCTime()
{
if(isEnum) return index.isCTime;
if(isDest) return false;
// a[b] and a[b..c]; is compile time if a, b and c is.
@ -288,6 +326,32 @@ class ArrayOperator : OperatorExpr
int[] evalCTime()
{
if(isEnum)
{
assert(index !is null && index2 is null);
auto et = cast(EnumType)type;
assert(et !is null);
EnumEntry *ptr;
if(isEnum == 1)
{
long val = *(cast(long*)index.evalCTime().ptr);
ptr = et.lookup(val);
if(ptr is null)
fail("No matching value " ~ .toString(val) ~ " in enum", loc);
}
else
{
assert(isEnum == 2);
AIndex ai = cast(AIndex)index.evalCTime()[0];
char[] str = toUTF8(arrays.getRef(ai).carr);
ptr = et.lookup(str);
if(ptr is null)
fail("No matching value " ~ str ~ " in enum", loc);
}
return (&(ptr.index))[0..1];
}
// Get the array index
int[] arr = name.evalCTime();
assert(arr.length == 1);
@ -331,6 +395,23 @@ class ArrayOperator : OperatorExpr
void evalAsm()
{
if(isEnum)
{
assert(index !is null && index2 is null);
assert(type.isEnum);
assert((isEnum == 1 && index.type.isLong) ||
(isEnum == 2 && index.type.isString));
// All we need is the index value
index.eval();
if(isEnum == 1)
tasm.enumValToIndex(type.tIndex);
else
tasm.enumNameToIndex(type.tIndex);
return;
}
// Push the array index first
name.eval();
@ -438,6 +519,9 @@ class DotOperator : OperatorExpr
owner.resolve(sc);
Type ot = owner.type;
assert(ot !is null);
ot.getMemberScope();
if(ot.getMemberScope() is null)
fail(owner.toString() ~ " of type " ~ owner.typeString()
@ -500,191 +584,92 @@ class DotOperator : OperatorExpr
}
}
// Assignment operators, =, +=, *=, /=, %=, ~=
class AssignOperator : BinaryOperator
{
bool catElem; // For concatinations (~=), true when the right hand
// side is a single element rather than an array.
this(Expression left, Expression right, TT opType, Floc loc)
{ super(left, right, opType, loc); }
void resolve(Scope sc)
{
left.resolve(sc);
right.resolve(sc);
// The final type is always from the left expression
type = left.type;
// Check that we are allowed assignment
if(!left.isLValue)
fail("Cannot assign to expression '" ~ left.toString ~ "'", loc);
// Operators other than = and ~= are only allowed for numerical
// types.
if(opType != TT.Equals && opType != TT.CatEq && !type.isNumerical)
fail("Assignment " ~tokenList[opType] ~
" not allowed for non-numerical type " ~ typeString(), loc);
// Is the left hand expression a sliced array?
auto arr = cast(ArrayOperator) left;
if(arr !is null && arr.isSlice)
{
assert(type.isArray);
if(opType == TT.CatEq)
fail("Cannot use ~= on array slice " ~ left.toString, loc);
// For array slices on the right hand side, the left hand
// type must macth exactly, without implisit casting. For
// example, the following is not allowed, even though 3 can
// implicitly be cast to char[]:
// char[] a;
// a[] = 3; // Error
// We are, on the other hand, allowed to assign a single
// value to a slice, eg:
// int[] i = new int[5];
// i[] = 3; // Set all elements to 3.
// In this case we ARE allowed to typecast, though.
if(right.type == left.type) return;
Type base = type.getBase();
try base.typeCast(right);
catch(TypeException)
fail("Cannot assign " ~ right.toString ~ " of type " ~ right.typeString
~ " to slice " ~ left.toString ~ " of type "
~ left.typeString, loc);
// Inform arr.store() that we're filling in a single element
arr.isFill = true;
return;
}
// Handle concatination ~=
if(opType == TT.CatEq)
{
if(!left.type.isArray)
fail("Opertaor ~= can only be used on arrays, not " ~ left.toString ~
" of type " ~ left.typeString, left.loc);
// Array with array
if(left.type == right.type) catElem = false;
// Array with element
else if(right.type.canCastOrEqual(left.type.getBase()))
{
left.type.getBase().typeCast(right);
catElem = true;
}
else
fail("Cannot use operator ~= on types " ~ left.typeString ~
" and " ~ right.typeString, left.loc);
return;
}
// Cast the right side to the left type, if possible.
try type.typeCast(right);
catch(TypeException)
fail("Assignment " ~tokenList[opType] ~ ": cannot implicitly cast " ~
right.typeString() ~ " to " ~ left.typeString(), loc);
assert(left.type == right.type);
}
// KILLME
/*
void evalAsm()
{
int s = right.type.getSize;
// += -= etc are implemented without adding new
// instructions. This might change later. The "downside" to this
// approach is that the left side is evaluated two times, which
// might not matter much for lvalues anyway, but it does give
// more M-code vs native code, and thus more overhead. I'm not
// sure if a function call can ever be an lvalue or if lvalues
// can otherwise have side effects in the future. If that
// becomes the case, then this implementation will have to
// change. Right now, the expression i += 3 will be exactly
// equivalent to i = i + 3.
if(opType != TT.Equals)
{
// Get the values of both sides
left.eval();
right.eval();
setLine();
// Concatination
if(opType == TT.CatEq)
{
assert(type.isArray);
if(catElem)
// Append one element onto the array
tasm.catArrayRight(s);
else
// Concatinate two arrays.
tasm.catArray();
}
// Perform the arithmetic operation. This puts the result of
// the addition on the stack. The evalDest() and mov() below
// will store it in the right place.
else if(type.isNumerical)
{
if(opType == TT.PlusEq) tasm.add(type);
else if(opType == TT.MinusEq) tasm.sub(type);
else if(opType == TT.MultEq) tasm.mul(type);
else if(opType == TT.DivEq) tasm.div(type);
else if(opType == TT.IDivEq) tasm.idiv(type);
else if(opType == TT.RemEq) tasm.divrem(type);
else fail("Unhandled assignment operator", loc);
}
else assert(0, "Type not handled");
}
else right.eval();
// Store the value on the stack into the left expression.
setLine();
left.store();
/*
left.evalDest();
setLine();
// Left hand value has been modified, notify it.
left.postWrite();
*/
}
}
// */
// Boolean operators: ==, !=, <, >, <=, >=, &&, ||, =i=, =I=, !=i=, !=I=
class BooleanOperator : BinaryOperator
{
bool leftIs(bool t)
{
if(!left.isCTime) return false;
int[] val = left.evalCTime();
assert(val.length == 1);
if(val[0]) return t;
else return !t;
}
bool rightIs(bool t)
{
if(!right.isCTime) return false;
int[] val = right.evalCTime();
assert(val.length == 1);
if(val[0]) return t;
else return !t;
}
bool ctimeval;
bool isMeta = false;
public:
this(Expression left, Expression right, TT opType, Floc loc)
{ super(left, right, opType, loc); }
override:
void resolve(Scope sc)
{
left.resolve(sc);
right.resolve(sc);
type = BasicType.getBool;
// Check if one or both types are meta-types first
if(left.type.isMeta || right.type.isMeta)
{
isMeta = true;
if(opType != TT.IsEqual && opType != TT.NotEqual)
fail("Cannot use operator " ~ tokenList[opType] ~ " on types", loc);
assert(isCTime());
// This means we have one of the following cases:
// i == int
// int == int
// In these cases, we compare the types only, and ignore the
// values of any expressions (they are not computed.)
// Get base types
Type lb = left.type;
Type rb = right.type;
if(lb.isMeta) lb = lb.getBase();
if(rb.isMeta) rb = rb.getBase();
// Compare the base types and store the result
ctimeval = (lb == rb) != 0;
if(opType == TT.NotEqual)
ctimeval = !ctimeval;
else
assert(opType == TT.IsEqual);
return;
}
// Cast to a common type
try Type.castCommon(left, right);
catch(TypeException)
fail("Boolean operator " ~tokenList[opType] ~ " not allowed for types " ~
left.typeString() ~ " and " ~ right.typeString(), loc);
Type.castCommon(left, right);
// At this point the types must match
assert(left.type == right.type);
type = BasicType.getBool;
// TODO: We might allow < and > for strings at some point.
if(opType == TT.Less || opType == TT.More || opType == TT.LessEq ||
opType == TT.MoreEq)
@ -724,6 +709,10 @@ class BooleanOperator : BinaryOperator
// static initialization.
bool isCTime()
{
// If both types are meta-types, then we might be ctime.
if(isMeta)
return (opType == TT.IsEqual || opType == TT.NotEqual);
// Only compute && and || for now, add the rest later.
if(!(opType == TT.And || opType == TT.Or)) return false;
@ -744,34 +733,15 @@ class BooleanOperator : BinaryOperator
return false;
}
private
{
bool leftIs(bool t)
{
if(!left.isCTime) return false;
int[] val = left.evalCTime();
assert(val.length == 1);
if(val[0]) return t;
else return !t;
}
bool rightIs(bool t)
{
if(!right.isCTime) return false;
int[] val = right.evalCTime();
assert(val.length == 1);
if(val[0]) return t;
else return !t;
}
bool ctimeval;
}
int[] evalCTime()
{
if(opType == TT.And)
ctimeval = !(leftIs(false) || rightIs(false));
else if(opType == TT.Or)
ctimeval = (leftIs(true) || rightIs(true));
else
// For meta types, ctimeval is already set
assert(isMeta);
return (cast(int*)&ctimeval)[0..1];
}
@ -939,7 +909,7 @@ class BinaryOperator : OperatorExpr
// Element with array?
if(right.type.isArray && left.type.canCastOrEqual(right.type.getBase()))
{
right.type.getBase().typeCast(left);
right.type.getBase().typeCast(left, "");
type = right.type;
cat = CatElem.Left;
return;
@ -948,7 +918,7 @@ class BinaryOperator : OperatorExpr
// Array with element?
if(left.type.isArray && right.type.canCastOrEqual(left.type.getBase()))
{
left.type.getBase().typeCast(right);
left.type.getBase().typeCast(right, "");
type = left.type;
cat = CatElem.Right;
return;
@ -963,14 +933,11 @@ class BinaryOperator : OperatorExpr
// case correctly.)
// Cast to a common type
bool doFail = false;
try Type.castCommon(left, right);
catch(TypeException)
doFail = true;
Type.castCommon(left, right);
type = right.type;
if(!type.isNumerical || doFail)
if(!type.isNumerical)
fail("Operator " ~tokenList[opType] ~ " not allowed for types " ~
left.typeString() ~ " and " ~ right.typeString(), loc);

View file

@ -251,6 +251,17 @@ abstract class SimplePropertyScope : PropertyScope
void inserts(char[] name, char[] tp, Action push)
{ inserts(name, getType(tp), push); }
// TODO: These are hacks to work around a silly but irritating DMD
// feature. Whenever there's an error somewhere, function literals
// like { something; } get resolved as int delegate() instead of
// void delegate() for some reason. This gives a ton of error
// messages, but these overloads will prevent that.
alias int delegate() FakeIt;
void insert(char[],char[],FakeIt,FakeIt pop = null) {assert(0);}
void insert(char[],Type,FakeIt,FakeIt pop = null) {assert(0);}
void inserts(char[],char[],FakeIt) {assert(0);}
void inserts(char[],Type,FakeIt) {assert(0);}
override:
// Return the stored type. If it is null, return the owner type

View file

@ -45,11 +45,11 @@ import monster.vm.error;
import monster.vm.vm;
// The global scope
PackageScope global;
RootScope global;
void initScope()
{
global = new PackageScope(null, "global");
global = new RootScope;
}
// List all identifier types
@ -170,28 +170,6 @@ abstract class Scope
// global scope.
Scope parent;
// Verify that an identifier is not declared in this scope. If the
// identifier is found, give a duplicate identifier compiler
// error. Recurses through parent scopes.
final void clearId(Token name)
{
// Lookup checks all parent scopes so we only have to call it
// once.
auto sl = lookup(name);
assert(sl.name.str == name.str);
if(!sl.isNone)
{
if(sl.isProperty)
fail(name.str ~ " is a property and cannot be redeclared",
name.loc);
fail(format("%s is already declared (at %s) as a %s",
name.str, name.loc, LTypeName[sl.ltype]),
name.loc);
}
}
// Made protected since it is so easy to confuse with isStateCode(),
// and we never actually need it anywhere outside this file.
bool isState() { return false; }
@ -219,6 +197,28 @@ abstract class Scope
importList = parent.importList;
}
// Verify that an identifier is not declared in this scope. If the
// identifier is found, give a duplicate identifier compiler
// error. Recurses through parent scopes.
final void clearId(Token name)
{
// Lookup checks all parent scopes so we only have to call it
// once.
auto sl = lookup(name);
assert(sl.name.str == name.str);
if(!sl.isNone)
{
if(sl.isProperty)
fail(name.str ~ " is a property and cannot be redeclared",
name.loc);
fail(format("%s is already declared (at %s) as a %s",
name.str, name.loc, LTypeName[sl.ltype]),
name.loc);
}
}
// Is this the root scope?
final bool isRoot()
{
@ -342,7 +342,7 @@ abstract class Scope
void registerImport(char[][] cls ...)
{
foreach(c; cls)
registerImport(MonsterClass.find(c));
registerImport(vm.load(c));
}
// Used for summing up stack level. Redeclared in StackScope.
@ -437,24 +437,73 @@ final class StateScope : Scope
bool isState() { return true; }
}
// A package scope is a scope that can contain classes.
final class PackageScope : Scope
final class RootScope : PackageScope
{
private:
// Lookup by integer index. The indices are global, that is why they
// are stored in the root scope rather than in the individual
// packages.
HashTable!(CIndex, MonsterClass) indexList;
// Forward references. Refers to unloaded and non-existing
// classes. TODO: This mechanism probably won't work very well with
// multiple packages. It should be possible to have forward
// references to multiple classes with the same name, as long as
// they are in the correct packages. Think it over some more.
HashTable!(char[], CIndex) forwards;
// Unique global index to give the next class.
CIndex next = 1;
public:
this() { super(null, "global"); }
bool allowRoot() { return true; }
// Get a class by index
MonsterClass getClass(CIndex ind)
{
MonsterClass mc;
if(!indexList.inList(ind, mc))
fail("Invalid class index encountered");
mc.requireScope();
return mc;
}
// Gets the index of a class, or inserts a forward reference to it
// if cannot be found. Subsequent calls for the same name will
// insert the same index, and when/if the class is actually loaded
// it will get the same index.
CIndex getForwardIndex(char[] name)
{
MonsterClass mc;
mc = vm.loadNoFail(name);
if(mc !is null) return mc.getIndex();
// Called when an existing forward does not exist
void inserter(ref CIndex v)
{ v = next++; }
// Return the index in forwards, or create a new one if none
// exists.
return forwards.get(name, &inserter);
}
// Returns true if a given class has been inserted into the scope.
bool isLoaded(CIndex ci)
{
return indexList.inList(ci);
}
}
// A package scope is a scope that can contain classes.
class PackageScope : Scope
{
private:
// List of classes in this package. This is case insensitive, so we
// can look up file names too.
HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes;
// Lookup by integer index. TODO: This should be in a global scope
// rather than per package. We can think about that when we
// implement packages.
HashTable!(CIndex, MonsterClass) indexList;
// Forward references. Refers to unloaded and non-existing classes.
HashTable!(char[], CIndex) forwards;
// Unique global index to give the next class. TODO: Ditto
CIndex next = 1;
public:
this(Scope last, char[] name)
{
super(last, name);
@ -463,7 +512,6 @@ final class PackageScope : Scope
}
bool isPackage() { return true; }
bool allowRoot() { return true; }
// Insert a new class into the scope. The class is given a unique
// global index. If the class was previously forward referenced, the
@ -476,7 +524,7 @@ final class PackageScope : Scope
// Are we already in the list?
MonsterClass c2;
if(global.ciInList(cls.name.str, c2))
if(ciInList(cls.name.str, c2))
{
// That's not allowed. Determine what error message to give.
@ -498,24 +546,24 @@ final class PackageScope : Scope
// referenced, then an index has already been assigned.
CIndex ci;
if(forwards.inList(cls.name.str, ci))
if(global.forwards.inList(cls.name.str, ci))
{
// ci is set, remove the entry from the forwards hashmap
assert(ci != 0);
forwards.remove(cls.name.str);
global.forwards.remove(cls.name.str);
}
else
// Get a new index
ci = next++;
ci = global.next++;
assert(!indexList.inList(ci));
assert(!global.indexList.inList(ci));
// Assign the index and insert class into both lists
cls.gIndex = ci;
classes[cls.name.str] = cls;
indexList[ci] = cls;
global.indexList[ci] = cls;
assert(indexList.inList(ci));
assert(global.indexList.inList(ci));
}
// Case insensitive lookups. Used for comparing with file names,
@ -548,20 +596,10 @@ final class PackageScope : Scope
return mc;
}
MonsterClass getClass(CIndex ind)
{
MonsterClass mc;
if(!indexList.inList(ind, mc))
fail("Invalid class index encountered");
mc.requireScope();
return mc;
}
override ScopeLookup lookup(Token name)
{
// Type names can never be overwritten, so we check findClass
// and the built-in types. We might move the builtin type check
// to a "global" scope at some point.
// Type names can never be overwritten, so we check the class
// list and the built-in types.
if(BasicType.isBasic(name.str))
return ScopeLookup(name, LType.Type, BasicType.get(name.str), this);
@ -573,81 +611,6 @@ final class PackageScope : Scope
assert(isRoot());
return super.lookup(name);
}
// Find a parsed class of the given name. Looks in the list of
// loaded classes and in the file system. Returns null if the class
// cannot be found.
MonsterClass findParsed(char[] name)
{
MonsterClass result = null;
// TODO: We must handle package structures etc later.
// Check if class is already loaded.
if(!classes.inList(name, result))
{
// Class not loaded. Check if the file exists.
char[] fname = classToFile(name);
if(vm.findFile(fname))
{
// File exists. Load it right away. If the class is
// already forward referenced, this will be taken care
// of automatically by the load process, through
// insertClass. The last parameter makes sure findFile
// isn't called twice.
result = new MonsterClass(name, fname, false);
assert(classes.inList(name));
}
else
return null;
}
assert(result !is null);
assert(result.isParsed);
return result;
}
// Find a class given its name. The class must be parsed or a file
// must exist which can be parsed, otherwise the function
// fails. createScope is also called on the class before it is
// returned.
MonsterClass findClass(Token t) { return findClass(t.str, t.loc); }
MonsterClass findClass(char[] name, Floc loc = Floc.init)
{
MonsterClass res = findParsed(name);
if(res is null)
fail("Failed to find class '" ~ name ~ "'", loc);
res.requireScope();
assert(res.isScoped);
return res;
}
// Gets the index of a class, or inserts a forward reference to it
// if cannot be found. Subsequent calls for the same name will
// insert the same index, and when/if the class is actually loaded
// it will get the same index.
CIndex getForwardIndex(char[] name)
{
MonsterClass mc;
mc = findParsed(name);
if(mc !is null) return mc.getIndex();
// Called when an existing forward does not exist
void inserter(ref CIndex v)
{ v = next++; }
// Return the index in forwards, or create a new one if none
// exists.
return forwards.get(name, &inserter);
}
// Returns true if a given class has been inserted into the scope.
bool isLoaded(CIndex ci)
{
return indexList.inList(ci);
}
}
// A scope that can contain variables.
@ -759,10 +722,10 @@ class TFVScope : FVScope
structs[sd.name.str] = sd.type;
}
void insertEnum(EnumDeclaration sd)
void insertEnum(EnumType sd)
{
clearId(sd.name);
enums[sd.name.str] = sd.type;
clearId(sd.nameTok);
enums[sd.name] = sd;
}
override:
@ -789,24 +752,126 @@ class TFVScope : FVScope
// to handle enum members.
final class EnumScope : SimplePropertyScope
{
this() { super("EnumScope", GenericProperties.singleton); }
int index; // Index in a global enum index list. Make a static list
// here or something.
void setup()
this(EnumType _et)
{
/*
insert("name", ArrayType.getString(), { tasm.getEnumName(index); });
super("EnumScope", GenericProperties.singleton);
et = _et;
// Replace these with the actual fields of the enum
insert("value", type1, { tasm.getEnumValue(index, field); });
insert("value", "long", &enumVal);
inserts("length", "int", &enumLen);
inserts("first", et, &enumFirst);
inserts("last", et, &enumLast);
inserts("min", "long", &enumMin);
inserts("max", "long", &enumMax);
}
// Some of them are static
inserts("length", "int", { tasm.push(length); });
inserts("last", "owner", { tasm.push(last); });
inserts("first", "owner", { tasm.push(first); });
*/
void enumMin()
{ tasm.push8(et.minVal); }
void enumMax()
{ tasm.push8(et.maxVal); }
void enumFirst()
{ tasm.push(et.entries[0].index); }
void enumLast()
{ tasm.push(et.entries[$-1].index); }
void enumLen()
{ tasm.push(et.entries.length); }
void enumVal()
{ tasm.getEnumValue(et.tIndex); }
EnumType et;
override:
// Intercept all the enum entry names when used as properties
Type getType(char[] name, Type oType)
{
// Is it one of the enum values?
auto ee = et.lookup(name);
if(ee !is null)
{
assert(et is oType ||
et.getMeta() is oType);
return et;
}
// Maybe one of the fields?
int fe = et.findField(name);
if(fe != -1)
return et.fields[fe].type;
return super.getType(name, oType);
}
void getValue(char[] name, Type oType)
{
auto ee = et.lookup(name);
if(ee !is null)
{
assert(et is oType ||
et.getMeta() is oType);
tasm.push(ee.index);
return;
}
int fe = et.findField(name);
if(fe != -1)
{
// We're getting a field from a variable. Eg.
// 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);
return;
}
super.getValue(name, oType);
}
bool hasProperty(char[] name)
{
auto ee = et.lookup(name);
if(ee !is null) return true;
if(et.findField(name) != -1)
return true;
return super.hasProperty(name);
}
bool isStatic(char[] name, Type oType)
{
auto ee = et.lookup(name);
if(ee !is null)
{
assert(et is oType ||
et.getMeta() is oType);
return true;
}
// The fields are always non-static, they depend on the actual
// enum value supplied.
if(et.findField(name) != -1)
return false;
return super.isStatic(name, oType);
}
bool isLValue(char[] name, Type oType)
{
// We can't override anything in an enum
auto ee = et.lookup(name);
if(ee !is null)
{
assert(et is oType ||
et.getMeta() is oType);
return false;
}
if(et.findField(name) != -1)
return false;
return super.isLValue(name, oType);
}
}
@ -939,10 +1004,12 @@ abstract class StackScope : VarScope
return tmp;
}
/*
void push(int i) { expStack += i; }
void push(Type t) { push(t.getSize); }
void pop(int i) { expStack -= i; }
void pop(Type t) { pop(t.getSize); }
*/
// Get the number of local variables in the current scope. In
// reality it gives the number of ints. A variable 8 bytes long will
@ -954,7 +1021,7 @@ abstract class StackScope : VarScope
// blocks (break and continue.)
int getTotLocals() { return sumLocals; }
// Get instra-expression stack
// Get intra-expression stack (not used yet)
int getExpStack() { return expStack; }
// Get total stack position, including expression stack values. This

View file

@ -34,11 +34,13 @@ import monster.compiler.types;
import monster.compiler.block;
import monster.compiler.variables;
import monster.compiler.states;
import monster.compiler.operators;
import monster.compiler.functions;
import monster.compiler.assembler;
import monster.vm.error;
import monster.vm.mclass;
import monster.vm.vm;
alias Statement[] StateArray;
@ -48,24 +50,6 @@ abstract class Statement : Block
void compile();
}
// An expression that works as a statement
class ExprStatement : Statement
{
Expression exp;
void parse(ref TokenArray toks)
{
exp = Expression.identify(toks);
reqNext(toks, TT.Semicolon, loc);
}
char[] toString() { return "ExprStatement: " ~ exp.toString(); }
void resolve(Scope sc) { exp.resolve(sc); }
void compile() { exp.evalPop(); }
}
/* Handles:
import type1, type2, ...; // module scope
import exp1, exp2, ...; // local scope (not implemented yet)
@ -110,7 +94,7 @@ class ImportStatement : Statement
{
// form: import ImportList;
getList();
reqNext(toks, TT.Semicolon);
reqSep(toks);
}
else if(isNext(toks, TT.With, loc))
{
@ -344,7 +328,7 @@ class GotoStatement : Statement, LabelUser
if(!isNext(toks, TT.Identifier, labelName))
fail("goto expected label identifier", toks);
reqNext(toks, TT.Semicolon);
reqSep(toks);
}
void resolve(Scope sc)
@ -400,13 +384,12 @@ class ContinueBreakStatement : Statement
if(isBreak) errName = "break";
else errName = "continue";
if(!isNext(toks, TT.Semicolon))
if(!isSep(toks))
{
if(!isNext(toks, TT.Identifier, labelName))
fail(errName ~ " expected ; or label", toks);
if(!isNext(toks, TT.Semicolon))
fail(errName ~ " expected ;", toks);
reqSep(toks);
}
}
@ -774,9 +757,9 @@ class ForeachStatement : Statement
if(isClass)
{
// Class loops
clsInfo = global.findClass(className);
clsInfo = vm.load(className.str);
assert(clsInfo !is null);
clsInfo.requireScope();
if(index !is null)
fail("Index not allowed in class iteration");
@ -977,7 +960,8 @@ class ForeachStatement : Statement
*/
class ForStatement : Statement
{
Expression init, condition, iter;
ExprStatement init, iter;
Expression condition;
VarDeclStatement varDec;
Token labelName;
@ -993,25 +977,26 @@ class ForStatement : Statement
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.For, loc))
assert(0);
if(!isNext(toks, TT.LeftParen))
fail("for statement expected '('", toks);
reqNext(toks, TT.For, loc);
reqNext(toks, TT.LeftParen);
// Check if the init as a variable declaration, if so then parse
// it as a statement (since that takes care of multiple
// variables as well.)
if(VarDeclStatement.canParse(toks))
{
varDec = new VarDeclStatement;
varDec = new VarDeclStatement(true);
varDec.parse(toks); // This also kills the trailing ;
}
// Is it an empty init statement?
else if(!isNext(toks, TT.Semicolon))
{
// If not, then assume it's an expression
init = Expression.identify(toks);
// statement. (Expression statements also handle
// assignments.)
init = new ExprStatement;
init.term = false;
init.parse(toks);
if(!isNext(toks, TT.Semicolon))
fail("initialization expression " ~ init.toString() ~
" must be followed by a ;", toks);
@ -1026,13 +1011,13 @@ class ForStatement : Statement
" must be followed by a ;", toks);
}
// Finally the last expression
// Finally the last expression statement
if(!isNext(toks, TT.RightParen))
{
iter = Expression.identify(toks);
if(!isNext(toks, TT.RightParen))
fail("for statement expected ')'", toks);
iter = new ExprStatement;
iter.term = false;
iter.parse(toks);
reqNext(toks, TT.RightParen);
}
// Is there a loop label?
@ -1094,7 +1079,7 @@ class ForStatement : Statement
// Push any local variables on the stack, or do initialization.
if(varDec !is null) varDec.compile();
else if(init !is null)
init.evalPop();
init.compile();
int outer;
@ -1120,7 +1105,7 @@ class ForStatement : Statement
cont.compile();
// Do the iteration step, if any
if(iter !is null) iter.evalPop();
if(iter !is null) iter.compile();
// Push the conditionel expression, or assume it's true if
// missing.
@ -1175,8 +1160,7 @@ class StateStatement : Statement, LabelUser
if(!isNext(toks, TT.Identifier, labelName))
fail("state label expected after .", toks);
if(!isNext(toks, TT.Semicolon))
fail("state statement expected ;", toks);
reqSep(toks);
}
// Set the label to jump to. This is called from the state
@ -1348,12 +1332,11 @@ class ReturnStatement : Statement
if(!isNext(toks, TT.Return, loc))
assert(0, "Internal error in ReturnStatement");
if(!isNext(toks, TT.Semicolon))
if(!isSep(toks))
{
exp = Expression.identify(toks);
if(!isNext(toks, TT.Semicolon))
fail("Return statement expected ;", toks);
reqSep(toks);
}
}
@ -1404,13 +1387,7 @@ class ReturnStatement : Statement
exp.resolve(sc);
try fn.type.typeCast(exp);
catch(TypeException)
fail(format(
"Function '%s' expected return type '%s', not '%s' of type '%s'",
fn.name.str, fn.type.toString(),
exp, exp.type.toString()),
exp.getLoc);
fn.type.typeCast(exp, "return value");
}
void compile()

View file

@ -31,6 +31,7 @@ import std.stdio;
import monster.util.string : begins;
import monster.vm.error;
import monster.options;
alias Token[] TokenArray;
@ -109,11 +110,14 @@ enum TT
Last, // Tokens after this do not have a specific string
// associated with them.
StringLiteral, // "something"
NumberLiteral, // Anything that starts with a number
CharLiteral, // 'a'
Identifier, // user-named identifier
EOF // end of file
StringLiteral, // "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)
}
@ -123,6 +127,9 @@ struct Token
char[] str;
Floc loc;
// True if this token was the first on its line.
bool newline;
char[] toString() { return str; }
static Token opCall(char[] name, Floc loc)
@ -139,9 +146,12 @@ struct Token
// Used to look up keywords.
TT keywordLookup[char[]];
bool lookupSetup = false;
void initTokenizer()
{
assert(!lookupSetup);
// Insert the keywords into the lookup table
for(TT t = TT.Class; t < TT.Last; t++)
{
@ -150,6 +160,8 @@ void initTokenizer()
assert((tok in keywordLookup) == null);
keywordLookup[tok] = t;
}
lookupSetup = true;
}
// Index table of all the tokens
@ -172,10 +184,12 @@ const char[][] tokenList =
TT.IsEqual : "==",
TT.NotEqual : "!=",
TT.IsCaseEqual : "=i=",
TT.IsCaseEqual2 : "=I=",
TT.NotCaseEqual : "!=i=",
TT.NotCaseEqual2 : "!=I=",
TT.Less : "<",
TT.More : ">",
TT.LessEq : "<=",
@ -250,13 +264,15 @@ const char[][] tokenList =
// These are only used in error messages
TT.StringLiteral : "string literal",
TT.NumberLiteral : "number literal",
TT.IntLiteral : "integer literal",
TT.FloatLiteral : "floating point literal",
TT.CharLiteral : "character literal",
TT.Identifier : "identifier",
TT.EOF : "end of file"
TT.EOF : "end of file",
TT.EMPTY : "empty line - you should never see this"
];
class StreamTokenizer
class Tokenizer
{
private:
// Line buffer. Don't worry, this is perfectly safe. It is used by
@ -267,8 +283,9 @@ class StreamTokenizer
char[300] buffer;
char[] line; // The rest of the current line
Stream inf;
uint lineNum;
int lineNum=-1;
char[] fname;
bool newline;
// Make a token of given type with given string, and remove it from
// the input line.
@ -277,6 +294,7 @@ class StreamTokenizer
Token t;
t.type = type;
t.str = str;
t.newline = newline;
t.loc.fname = fname;
t.loc.line = lineNum;
@ -285,6 +303,9 @@ class StreamTokenizer
if(type == TT.IsCaseEqual2) t.type = TT.IsCaseEqual;
if(type == TT.NotCaseEqual2) t.type = TT.NotCaseEqual;
// Treat } as a separator
if(type == TT.RightCurl) t.newline = true;
// Remove the string from 'line', along with any following witespace
remWord(str);
return t;
@ -306,13 +327,17 @@ class StreamTokenizer
Token t;
t.str = "<end of file>";
t.type = TT.EOF;
t.newline = true;
t.loc.line = lineNum;
t.loc.fname = fname;
return t;
}
Token empty;
public:
final:
// Used when reading tokens from a file or a stream
this(char[] fname, Stream inf, int bom)
{
assert(inf !is null);
@ -339,52 +364,51 @@ class StreamTokenizer
this.inf = inf;
this.fname = fname;
empty.type = TT.EMPTY;
}
// This is used for single-line mode, such as in a console.
this()
{
empty.type = TT.EMPTY;
}
void setLine(char[] ln)
{
assert(inf is null, "setLine only supported in line mode");
line = ln;
}
~this() { if(inf !is null) delete inf; }
void fail(char[] msg)
{
throw new MonsterException(format("%s:%s: %s", fname, lineNum, msg));
if(inf !is null)
// File mode
throw new MonsterException(format("%s:%s: %s", fname, lineNum, msg));
else
// Line mode
throw new MonsterException(msg);
}
Token getNext()
// Various parsing modes
enum
{
// Various parsing modes
enum
{
Normal, // Normal mode
Block, // Block comment
Nest // Nested block comment
}
int mode = Normal;
int nests = 0; // Nest level
Normal, // Normal mode
Block, // Block comment
Nest // Nested block comment
}
int mode = Normal;
int nests = 0; // Nest level
// Get the next token from the line, if any
Token getNextFromLine()
{
assert(lookupSetup,
"Internal error: The tokenizer lookup table has not been set up!");
restart:
// Get the next line, if the current is empty
while(line.length == 0)
{
// No more information, we're done
if(inf.eof())
{
if(mode == Block) fail("Unterminated block comment");
if(mode == Nest) fail("Unterminated nested comment");
return eofToken();
}
// Read a line and remove leading and trailing whitespace
line = inf.readLine(buffer).strip();
lineNum++;
}
assert(line.length > 0);
// Skip the first line if it begins with #!
if(lineNum == 1 && line.begins("#!"))
{
line = null;
goto restart;
}
if(mode == Block)
{
@ -395,27 +419,23 @@ class StreamTokenizer
{
mode = Normal;
// Cut it the comment from the input
// Cut the comment from the input
remWord("*/", index);
}
else
{
// Comment not ended on this line, try the next
// Comment was not terminated on this line, try the next
line = null;
}
// Start over
goto restart;
}
if(mode == Nest)
else if(mode == Nest)
{
// Check for nested /+ and +/ in here, but go to restart if
// none is found (meaning the comment continues on the next
// line), or reset mode and go to restart if nest level ever
// gets to 0.
do
while(line.length >= 2)
{
int incInd = -1;
int decInd = -1;
@ -443,7 +463,7 @@ class StreamTokenizer
}
// Remove a nest level when '+/' is found
if(decInd != -1)
else if(decInd != -1)
{
// Remove the +/ from input
remWord("+/", decInd);
@ -461,20 +481,23 @@ class StreamTokenizer
}
// Nothing found on this line, try the next
line = null;
break;
}
while(line.length >= 2);
goto restart;
// If we're still in nested comment mode, ignore the rest of
// the line
if(mode == Nest)
line = null;
}
// Comment - start next line
// Comment - ignore the rest of the line
if(line.begins("//"))
{
line = null;
goto restart;
}
line = null;
// If the line is empty at this point, there's nothing more to
// be done
if(line == "")
return empty;
// Block comment
if(line.begins("/*"))
@ -519,13 +542,13 @@ class StreamTokenizer
// '\n', '\'', or unicode stuff won't work.)
if(line[0] == '\'')
{
if(line.length < 2 || line[2] != '\'')
if(line.length < 3 || line[2] != '\'')
fail("Malformed character literal " ~line);
return retToken(TT.CharLiteral, line[0..3].dup);
}
// Numerical literals - if it starts with a number, we accept
// it, until it is interupted by an unacceptible character. We
// it, until it is interupted by an unacceptable character. We
// also accept numbers on the form .NUM. We do not try to parse
// the number here.
if(numericalChar(line[0]) ||
@ -539,6 +562,7 @@ class StreamTokenizer
// also explicitly allow '.' dots.
int len = 1;
bool lastDot = false; // Was the last char a '.'?
int dots; // Number of dots
foreach(char ch; line[1..$])
{
if(ch == '.')
@ -547,10 +571,13 @@ class StreamTokenizer
// operator.
if(lastDot)
{
len--; // Remove the last dot and exit.
// Remove the last dot and exit.
len--;
dots--;
break;
}
lastDot = true;
dots++;
}
else
{
@ -562,7 +589,10 @@ class StreamTokenizer
// This was a valid character, count it
len++;
}
return retToken(TT.NumberLiteral, line[0..len].dup);
if(dots != 0)
return retToken(TT.FloatLiteral, line[0..len].dup);
else
return retToken(TT.IntLiteral, line[0..len].dup);
}
// Check for identifiers
@ -603,12 +633,22 @@ class StreamTokenizer
TT match;
int mlen = 0;
foreach(int i, char[] tok; tokenList[0..TT.Class])
if(line.begins(tok) && tok.length >= mlen)
{
assert(tok.length > mlen, "Two matching tokens of the same length");
mlen = tok.length;
match = cast(TT) i;
}
{
// Skip =i= and family, if monster.options tells us to
static if(!ciStringOps)
{
if(i == TT.IsCaseEqual || i == TT.IsCaseEqual2 ||
i == TT.NotCaseEqual || i == TT.NotCaseEqual2)
continue;
}
if(line.begins(tok) && tok.length >= mlen)
{
assert(tok.length > mlen, "Two matching tokens of the same length");
mlen = tok.length;
match = cast(TT) i;
}
}
if(mlen) return retToken(match, tokenList[match]);
@ -616,14 +656,50 @@ class StreamTokenizer
fail("Invalid token " ~ line);
}
// Require a specific token
bool isToken(TT tok)
// Get the next token from a stream
Token getNext()
{
Token tt = getNext();
return tt.type == tok;
}
assert(inf !is null, "getNext() found a null stream");
bool notToken(TT tok) { return !isToken(tok); }
if(lineNum == -1) lineNum = 0;
restart:
newline = false;
// Get the next line, if the current is empty
while(line.length == 0)
{
// No more information, we're done
if(inf.eof())
{
if(mode == Block) fail("Unterminated block comment");
if(mode == Nest) fail("Unterminated nested comment");
return eofToken();
}
// Read a line and remove leading and trailing whitespace
line = inf.readLine(buffer).strip();
lineNum++;
newline = true;
}
assert(line.length > 0);
static if(skipHashes)
{
// Skip the line if it begins with #.
if(/*lineNum == 1 && */line.begins("#"))
{
line = null;
goto restart;
}
}
Token tt = getNextFromLine();
if(tt.type == TT.EMPTY)
goto restart;
return tt;
}
}
// Read the entire file into an array of tokens. This includes the EOF
@ -632,7 +708,7 @@ TokenArray tokenizeStream(char[] fname, Stream stream, int bom)
{
TokenArray tokenArray;
StreamTokenizer tok = new StreamTokenizer(fname, stream, bom);
Tokenizer tok = new Tokenizer(fname, stream, bom);
Token tt;
do
{

View file

@ -61,18 +61,6 @@ import std.string;
MetaType (type of type expressions, like writeln(int);)
*/
class TypeException : Exception
{
Type type1, type2;
this(Type t1, Type t2)
{
type1 = t1;
type2 = t2;
super("Unhandled TypeException on types " ~ type1.toString ~ " and " ~
type2.toString ~ ".");
}
}
// A class that represents a type. The Type class is abstract, and the
// different types are actually handled by various subclasses of Type
@ -197,6 +185,7 @@ abstract class Type : Block
bool isArray() { return arrays() != 0; }
bool isObject() { return false; }
bool isStruct() { return false; }
bool isEnum() { return false; }
bool isReplacer() { return false; }
@ -322,7 +311,7 @@ abstract class Type : Block
// Cast the expression orig to this type. Uses canCastTo to
// determine if a cast is possible.
final void typeCast(ref Expression orig)
final void typeCast(ref Expression orig, char[] to)
{
if(orig.type == this) return;
@ -332,12 +321,14 @@ abstract class Type : Block
if(orig.type.canCastTo(this))
orig = new CastExpression(orig, this);
else
throw new TypeException(this, orig.type);
fail(format("Cannot cast %s of type %s to %s of type %s",
orig.toString, orig.typeString,
to, this), orig.loc);
}
// Do compile-time type casting. Gets orig.evalCTime() and returns
// the converted result.
final int[] typeCastCTime(Expression orig)
final int[] typeCastCTime(Expression orig, char[] to)
{
int[] res = orig.evalCTime();
@ -346,7 +337,9 @@ abstract class Type : Block
if(orig.type.canCastTo(this))
res = orig.type.doCastCTime(res, this);
else
throw new TypeException(this, orig.type);
fail(format("Cannot cast %s of type %s to %s of type %s",
orig.toString, orig.typeString,
to, this), orig.loc);
assert(res.length == getSize);
@ -398,10 +391,8 @@ abstract class Type : Block
void parse(ref TokenArray toks) {assert(0, name);}
void resolve(Scope sc) {assert(0, name);}
/* Cast two expressions to their common type, if any. Throw a
TypeException exception if not possible. This exception should be
caught elsewhere to give a more useful error message. Examples of
possible outcomes:
/* Cast two expressions to their common type, if any. Fail if not
possible. Examples of possible outcomes:
int, int -> does nothing
float, int -> converts the second paramter to float
@ -451,13 +442,13 @@ abstract class Type : Block
{
// Find the common type
if(t1.canCastTo(t2)) common = t2; else
if(t2.canCastTo(t1)) common = t1;
else throw new TypeException(t1, t2);
if(t2.canCastTo(t1)) common = t1; else
fail(format("Cannot cast %s of type %s to %s of type %s, or vice versa.", e1, t1, e2, t2), e1.loc);
}
// Wrap the expressions in CastExpression blocks if necessary.
common.typeCast(e1);
common.typeCast(e2);
common.typeCast(e1, "");
common.typeCast(e2, "");
}
}
@ -480,7 +471,7 @@ class NullType : InternalType
bool canCastTo(Type to)
{
return to.isArray || to.isObject;
return to.isArray || to.isObject || to.isEnum;
}
void evalCastTo(Type to)
@ -921,6 +912,8 @@ class ObjectType : Type
// Members of objects are resolved in the class scope.
Scope getMemberScope()
{
assert(getClass !is null);
assert(getClass.sc !is null);
return getClass().sc;
}
@ -1036,25 +1029,178 @@ class ArrayType : Type
class EnumType : Type
{
// The scope contains the actual enum values
// Enum entries
EnumEntry[] entries;
EnumScope sc;
char[] initString = " (not set)";
this(EnumDeclaration ed)
// Lookup tables
EnumEntry* nameAA[char[]];
EnumEntry* valueAA[long];
// Fields
FieldDef fields[];
long
minVal = long.max,
maxVal = long.min;
Token nameTok;
EnumEntry *lookup(long val)
{
name = ed.name.str;
loc = ed.name.loc;
auto p = val in valueAA;
if(p is null)
return null;
return *p;
}
EnumEntry *lookup(char[] str)
{
auto p = str in nameAA;
if(p is null) return null;
return *p;
}
int findField(char[] str)
{
foreach(i, fd; fields)
if(fd.name.str == str)
return i;
return -1;
}
override:
bool isEnum() { return true; }
int[] defaultInit() { return [0]; }
int getSize() { return 1; }
void resolve(Scope sc) {}
void resolve(Scope last)
{
assert(sc is null, "resolve() called more than once");
// can cast to int and to string, but not back
initString = name ~ initString;
foreach(i, ref ent; entries)
{
// Make sure there are no naming conflicts.
last.clearId(ent.name);
// Assign an internal value to each entry
ent.index = i+1;
// Set the printed value to be "Enum.Name"
ent.stringValue = name ~ "." ~ ent.name.str;
// Create an AA for values, and one for the names. This is also
// where we check for duplicates in both.
if(ent.name.str in nameAA)
fail("Duplicate entry '" ~ ent.name.str ~ "' in enum", ent.name.loc);
if(ent.value in valueAA)
fail("Duplicate value " ~ .toString(ent.value) ~ " in enum", ent.name.loc);
nameAA[ent.name.str] = &ent;
valueAA[ent.value] = &ent;
if(ent.value > maxVal) maxVal = ent.value;
if(ent.value < minVal) minVal = ent.value;
}
// Create the scope
sc = new EnumScope(this);
// Check the fields
foreach(ref fd; fields)
{
last.clearId(fd.name);
if(fd.name.str in nameAA)
fail("Field name cannot match value name " ~ fd.name.str, fd.name.loc);
fd.type.resolve(last);
if(fd.type.isReplacer)
fd.type = fd.type.getBase();
fd.type.validate(fd.name.loc);
}
// Resolve and check field expressions.
foreach(ref ent; entries)
{
// Check number of expressions
if(ent.exp.length > fields.length)
fail(format("Too many fields in enum line (expected %s, found %s)",
fields.length, ent.exp.length),
ent.name.loc);
ent.fields.length = fields.length;
foreach(i, ref fe; ent.exp)
{
assert(fe !is null);
fe.resolve(last);
// Check the types
fields[i].type.typeCast(fe, format("field %s (%s)",
i+1, fields[i].name.str));
// And that they are all compile time expressions
if(!fe.isCTime)
fail("Cannot evaluate " ~ fe.toString ~ " at compile time", fe.loc);
}
// Finally, get the values
foreach(i, ref int[] data; ent.fields)
{
if(i < ent.exp.length)
data = ent.exp[i].evalCTime();
else
// Use the init value if no field is value is given
data = fields[i].type.defaultInit();
assert(data.length == fields[i].type.getSize);
}
// Clear the expression array since we don't need it anymore
ent.exp = null;
}
}
// Can only cast to string for now
bool canCastTo(Type to)
{ return to.isString; }
void evalCastTo(Type to)
{
assert(to.isString);
tasm.castToString(tIndex);
}
int[] doCastCTime(int[] data, Type to)
{
assert(to.isString);
return [valToStringIndex(data)];
}
// TODO: In this case, we could override valToStringIndex as well,
// and return a cached, constant string index to further optimize
// memory usage.
char[] valToString(int[] data)
{
assert(data.length == 1);
int v = data[0];
assert(v >= 0 && v <= entries.length);
// v == 0 means that no value is set - return a default string
// value
if(v == 0)
return initString;
else return entries[v-1].stringValue;
}
// Scope getMemberScope() { return sc; }
Scope getMemberScope()
{ return GenericProperties.singleton; }
{ return sc; }
}
class StructType : Type

View file

@ -29,6 +29,7 @@ import monster.compiler.tokenizer;
import monster.compiler.expression;
import monster.compiler.scopes;
import monster.compiler.statement;
import monster.compiler.states;
import monster.compiler.block;
import monster.compiler.operators;
import monster.compiler.assembler;
@ -38,6 +39,7 @@ import std.stdio;
import monster.vm.mclass;
import monster.vm.error;
import monster.vm.vm;
enum VarType
{
@ -54,7 +56,10 @@ struct Variable
VarScope sc; // Scope that owns this variable
int number; // Index used in bytecode to reference this variable
int number; // Index used in bytecode to reference this variable. It
// corresponds to the stack offset for local variables /
// parameters, and the data segment offset internally
// for the given class for class variables.
bool isRef; // Is this a reference variable?
bool isConst; // Used for function parameters
@ -274,22 +279,23 @@ class VarDeclaration : Block
if(isParam && var.type.isArray())
allowConst = true;
// Handle initial value normally
// Handle initial value, if present
if(init !is null)
{
init.resolve(sc);
// For now, var is disallowed for function parameters (will
// be enabled again when dynamic types are implemented.)
if(var.type.isVar && isParam)
fail("var type not allowed in parameters", var.type.loc);
// If 'var' is present, just copy the type of the init value
if(var.type.isVar)
var.type = init.type;
else
{
// Convert type, if necessary.
try var.type.typeCast(init);
catch(TypeException)
fail(format("Cannot initialize %s of type %s with %s of type %s",
var.name.str, var.type,
init, init.type), loc);
var.type.typeCast(init, var.name.str);
}
assert(init.type == var.type);
}
@ -375,6 +381,158 @@ class VarDeclaration : Block
}
}
// Declaration that overrides a default variable value in a parent
// class. Only used at the class scope. Takes the form
// varname=expression; Is also used to set the default state.
class ClassVarSet : Block
{
Token name;
Expression value;
Variable *var;
StateStatement stateSet;
// The class owning the variable. NOT the same as the class we're
// defined in.
MonsterClass cls;
static bool canParse(TokenArray toks)
{
if(isNext(toks, TT.Identifier) &&
isNext(toks, TT.Equals))
return true;
if(isNext(toks, TT.State) &&
isNext(toks, TT.Equals))
return true;
return false;
}
bool isState()
{ return stateSet !is null; }
void parse(ref TokenArray toks)
{
if(isNext(toks, TT.Identifier, name))
{
// Setting normal variable
reqNext(toks, TT.Equals);
value = Expression.identify(toks);
loc = name.loc;
}
else
{
// Setting the state - use a StateStatement to handle
// everything.
assert(toks.length >= 1 && toks[0].type == TT.State);
stateSet = new StateStatement;
stateSet.parse(toks);
loc = stateSet.loc;
}
reqSep(toks);
}
void resolve(Scope sc)
{
// Get the class we're defined in
assert(sc.isClass);
auto lc = sc.getClass();
assert(lc !is null);
// Are we setting the state?
if(stateSet !is null)
{
assert(value is null);
// Make stateSet find the state and label pointers
stateSet.resolve(sc);
// Tell the class about us
StateLabel *lb = null;
if(stateSet.label !is null)
lb = stateSet.label.lb;
lc.setDefaultState(stateSet.stt, lb);
return;
}
// We're setting a normal variable
assert(stateSet is null);
// Find the variable
assert(lc.parents.length <= 1);
var = null;
// TODO: If we do mi later, create a parentLookup which handles
// standard error cases for us.
if(lc.parents.length == 1)
{
auto ln = lc.parents[0].sc.lookup(name);
if(ln.isVar)
{
var = ln.var;
cls = ln.sc.getClass();
}
}
if(var is null)
{
// No var found. It's possible that there's a match in our
// own class though - that's still an error, but should give
// a slightly different error message
auto ln = lc.sc.lookup(name);
if(ln.isVar)
fail("Cannot override variable " ~ name.str ~
" because it was defined in the same class", name.loc);
fail("No variable named " ~ name.str ~
" in parent classes of " ~ lc.name.str, name.loc);
}
assert(var.vtype == VarType.Class);
assert(cls !is null);
assert(cls.parentOf(lc));
value.resolve(sc);
// Convert the type
var.type.typeCast(value, var.name.str);
if(!value.isCTime)
fail("Expression " ~ value.toString ~ " cannot be computed at compile time", value.loc);
}
// Apply this data change to the given data segment
void apply(int[] data)
{
assert(!isState);
assert(var !is null);
assert(cls.dataSize == data.length);
assert(var.number >= 0);
assert(var.number+var.type.getSize <= data.length);
// Copy the data
int start = var.number;
int end = var.number + var.type.getSize;
data[start..end] = value.evalCTime();
}
char[] toString()
{
char[] res;
if(cls !is null)
res = cls.getName() ~ ".";
if(isState)
{
res ~= "state=" ~ stateSet.stateName.str;
if(stateSet.labelName.str != "")
res ~= "." ~ stateSet.labelName.str;
}
else
res ~= name.str ~ "=" ~ value.toString;
return res;
}
}
// Represents a reference to a variable. Simply stores the token
// representing the identifier. Evaluation is handled by the variable
// declaration itself. This allows us to use this class for local and
@ -612,7 +770,7 @@ class VariableExpr : MemberExpression
{
// Still no match. Might be an unloaded class however,
// lookup() doesn't load classes. Try loading it.
if(global.findParsed(name.str) is null)
if(vm.loadNoFail(name.str) is null)
// No match at all.
fail("Undefined identifier "~name.str, name.loc);
@ -813,6 +971,14 @@ class VariableExpr : MemberExpression
class VarDeclStatement : Statement
{
VarDeclaration[] vars;
bool reqSemi;
// Pass 'true' to the constructor to require a semi-colon (used eg
// in for loops)
this(bool rs=false)
{
reqSemi = rs;
}
static bool canParse(TokenArray toks)
{
@ -844,8 +1010,14 @@ class VarDeclStatement : Statement
vars ~= varDec;
}
if(reqSemi)
reqNext(toks, TT.Semicolon);
else
reqSep(toks);
/*
if(!isNext(toks, TT.Semicolon))
fail("Declaration statement expected ;", toks);
*/
}
char[] toString()

View file

@ -31,12 +31,28 @@ import monster.modules.frames;
import monster.modules.random;
import monster.modules.threads;
import monster.options;
bool has(char[] str, char[] sub)
{
if(sub.length == 0) return false;
int diff = str.length;
int sln = sub.length;
diff -= sln;
for(int i=0; i<=diff; i++)
if(str[i..i+sln] == sub[])
return true;
return false;
}
void initAllModules()
{
initIOModule();
initTimerModule();
initFramesModule();
initThreadModule();
initRandomModule();
initMathModule();
static if(moduleList.has("io")) initIOModule();
static if(moduleList.has("timer")) initTimerModule();
static if(moduleList.has("frames")) initFramesModule();
static if(moduleList.has("thread")) initThreadModule();
static if(moduleList.has("random")) initRandomModule();
static if(moduleList.has("math")) initMathModule();
}

422
monster/modules/console.d Normal file
View file

@ -0,0 +1,422 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007-2009 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (console.d) is part of the Monster script language package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module monster.modules.console;
// This file implements an optimized way of running Monster
// interactively, what is sometimes called "line mode" or "interactive
// mode". It is ideally suited for making ingame consoles.
// The main idea is to retain all reusable data structures and
// minimize the number of heap allocations at runtime. The current
// implemention is not perfected in that regard, but later
// implementations will be.
// All input into the console is given through the input() function,
// and all output is sent to the output() callback function. You can
// also poll for output manually using output();
import monster.util.growarray;
import monster.compiler.tokenizer;
import monster.compiler.statement;
import monster.compiler.variables;
import monster.compiler.functions;
import monster.compiler.scopes;
import monster.compiler.bytecode;
import monster.compiler.assembler;
import monster.compiler.types;
import monster.compiler.expression;
import std.stdio;
import std.string;
import monster.monster;
// Console results
enum CR
{
Ok, // Command was executed
Error, // An error occurred
More, // An unterminated multi-line statement was entered, need
// more input
Empty, // The line was empty (nothing was executed)
}
class Console
{
private:
Tokenizer tn;
GrowArray!(Token) tokArr;
GrowArray!(char) outBuf;
Function fn;
FuncScope sc;
MonsterObject *obj;
Variable* varList[];
uint varSize;
// The thread that we run console commands in. It's put in the
// background when not in use.
Thread *trd;
// The thread that was running when we started (if any)
Thread *store;
int paren, curl, square;
void delegate(char[] str) output_cb;
bool hasCallback;
char[] norm_prompt = ">>> ";
int tab = 4;
char[] ml_prompt = "... ";
char[] cmt_prompt = "(comment) ";
public:
bool allowVar = false;
this(MonsterObject *ob = null)
{
tn = new Tokenizer();
// Set the context object
obj = ob;
if(obj is null)
obj = Function.getIntMO();
// Next set up the function and the scope
fn.name.str = "console";
fn.owner = obj.cls;
sc = new FuncScope(obj.cls.sc, &fn);
// Get a new thread
trd = Thread.getPaused();
}
private:
void put(char[] str, bool newLine=false)
{
if(hasCallback)
{
output_cb(str);
if(newLine)
output_cb("\n");
}
else
{
outBuf ~= str;
if(newLine)
outBuf ~= '\n';
}
}
void putln(char[] str) { put(str, true); }
Statement parse(TokenArray toks)
{
Statement b = null;
if(VarDeclStatement.canParse(toks))
{
if(!allowVar) fail("Variable declaration not allowed here");
b = new VarDeclStatement;
}
else if(CodeBlock.canParse(toks)) b = new CodeBlock;
else if(IfStatement.canParse(toks)) b = new IfStatement;
else if(DoWhileStatement.canParse(toks)) b = new DoWhileStatement;
else if(WhileStatement.canParse(toks)) b = new WhileStatement;
else if(ForStatement.canParse(toks)) b = new ForStatement;
else if(ForeachStatement.canParse(toks)) b = new ForeachStatement;
else if(ImportStatement.canParse(toks)) b = new ImportStatement(true);
// If this is not one of the above, default to an expression
// statement.
else b = new ExprStatement;
b.parse(toks);
return b;
}
int sumParen()
{
// Clean up if we had an unmatched end bracket somewhere
if(paren < 0) paren = 0;
if(curl < 0) curl = 0;
if(square < 0) square = 0;
return paren + curl + square;
}
bool isComment()
{
return tn.mode != Tokenizer.Normal;
}
// Resets the console to a usable state. Does not delete variables.
void reset()
{
paren = 0;
curl = 0;
square = 0;
tn.mode = Tokenizer.Normal;
if(cthread is trd)
{
// Reset the function stack.
trd.fstack.killAll();
assert(trd.fstack.isEmpty);
assert(!trd.fstack.hasNatives);
// Our variables should still be on the stack though.
if(stack.getPos > varSize)
stack.popInts(stack.getPos - varSize);
else
assert(stack.getPos == varSize);
// Make sure the thread is still in the 'paused' mode
trd.moveTo(&scheduler.paused);
// Background the thread - this will also capture the stack
trd.background();
}
assert(trd !is cthread);
// Restore the previous thread (if any)
if(store !is null)
store.foreground();
}
// Push the input into the compiler and run it
CR runInput(char[] str)
{
// Set up the tokenizer
tn.setLine(str);
// Reset the token buffer, unless we're in a multiline command
if(paren == 0 && curl == 0 && square == 0)
tokArr.length = 0;
// Phase I, tokenize
Token t = tn.getNextFromLine();
// Mark the first token as a newline / separator
t.newline = true;
while(t.type != TT.EMPTY)
{
if(t.type == TT.LeftParen)
paren++;
else if(t.type == TT.LeftCurl)
curl++;
else if(t.type == TT.LeftSquare)
square++;
else if(t.type == TT.RightParen)
paren--;
else if(t.type == TT.RightCurl)
curl--;
else if(t.type == TT.RightSquare)
square--;
tokArr ~= t;
t = tn.getNextFromLine();
}
if(paren < 0 || curl < 0 || square < 0)
fail("Unmatched end bracket(s)");
// Wait for more input inside a bracket
if(sumParen() > 0)
return CR.More;
// Ditto for block comments
if(isComment())
return CR.More;
// Ignore empty token lists
if(tokArr.length == 0)
return CR.Empty;
// Phase II, parse
TokenArray toks = tokArr.arrayCopy();
Statement st = parse(toks);
delete toks;
// Phase III, resolve
st.resolve(sc);
// Phase IV, compile
tasm.newFunc();
// Is it an expression?
auto es = cast(ExprStatement)st;
if(es !is null)
{
// Yes. But is the type usable?
if(es.left.type.canCastTo(ArrayType.getString())
&& es.right is null)
{
// Yup. Get the type, and cast the expression to string.
scope auto ce = new CastExpression(es.left, ArrayType.getString());
ce.eval();
}
else es = null;
}
// No expression is being used, so compile the statement
if(es is null)
st.compile();
fn.bcode = tasm.assemble(fn.lines);
fn.bcode ~= cast(ubyte)BC.Exit; // Non-optimal hack
// Phase V, call the function
// First, background the current thread (if any) and bring up
// our own.
store = cthread;
if(store !is null)
store.background();
assert(trd !is null);
trd.foreground();
// We have to push ourselves on the function stack, or call()
// will see that it's empty and kill the thread upon exit
trd.fstack.pushExt("Console");
fn.call(obj);
trd.fstack.pop();
// Finally, get the expression result, if any, and print it
if(es !is null)
putln(stack.popString8());
// In the case of new variable declarations, we have to make
// sure they are accessible to any subsequent calls to the
// function. Since the stack frame gets set at each call, we
// have to access the variables outside the function frame,
// ie. the same way we treat function parameters. We do this by
// giving the variables negative indices.
auto vs = cast(VarDeclStatement)st;
if(vs !is null)
{
// Add the new vars to the list
foreach(v; vs.vars)
{
varList ~= v.var;
// Add the size as well
varSize += v.var.type.getSize;
}
// Recalculate all the indices backwards from zero
int place = 0;
foreach_reverse(v; varList)
{
place -= v.type.getSize;
v.number = place;
}
}
// Reset the console to a usable state
reset();
return CR.Ok;
}
public:
void prompt()
{
int sum = sumParen();
bool isBracket = (sum != 0);
sum = sum * tab + norm_prompt.length;
if(isComment)
{
sum -= cmt_prompt.length;
while(sum-->0)
put(" ");
put(cmt_prompt);
}
else if(isBracket)
{
sum -= ml_prompt.length;
while(sum-->0)
put(" ");
put(ml_prompt);
}
else
put(norm_prompt);
}
void addImports(char[][] str...)
{
assert(sc !is null);
sc.registerImport(str);
}
// Get the accumulated output since the last call. Includes
// newlines. Will only work if you have not set an output callback
// function.
char[] output()
{
assert(!hasCallback);
char[] res = outBuf.arrayCopy();
outBuf.length = 0;
return res;
}
// Sets the command prompt (default is "> ")
void setPrompt(char[] prmt)
{ norm_prompt = prmt; }
// Sets the multi-line prompt (default is "... ")
void setMLPrompt(char[] prmt)
{ ml_prompt = prmt; }
// Set tab size (default 4)
void setTabSize(int i)
{ tab = i; }
// Get input. Will sometimes expect multi-line input, for example if
// case a line contains an open brace or an unterminated block
// comment. In that case the console will produce another prompt
// (when you call prompt()), and also return true.
CR input(char[] str)
{
str = str.strip();
if(str == "") return CR.Empty;
try return runInput(str);
catch(MonsterException e)
{
putln(e.toString);
reset();
return CR.Error;
}
}
}

View file

@ -99,7 +99,7 @@ void initFramesModule()
static MonsterClass mc;
if(mc !is null) return;
mc = new MonsterClass(MC.String, moduleDef, "frames");
mc = vm.loadString(moduleDef, "frames");
// Bind the idle
mc.bind("fsleep", new IdleFrameSleep);

View file

@ -80,7 +80,7 @@ void initIOModule()
static MonsterClass mc;
if(mc !is null) return;
mc = new MonsterClass(MC.String, moduleDef, "io");
mc = vm.loadString(moduleDef, "io");
mc.bind("write", { doWrite(false); });
mc.bind("writeln", { doWrite(false); writefln(); });

View file

@ -28,6 +28,7 @@ module monster.modules.math;
import monster.monster;
import std.math;
import monster.vm.mclass;
const char[] moduleDef =
"module math;
@ -85,7 +86,7 @@ void initMathModule()
static MonsterClass mc;
if(mc !is null) return;
mc = new MonsterClass(MC.String, moduleDef, "math");
mc = vm.loadString(moduleDef, "math");
mc.bind("sin", { stack.pushDouble(sin(stack.popDouble)); });
mc.bind("cos", { stack.pushDouble(cos(stack.popDouble)); });

View file

@ -62,7 +62,7 @@ void initRandomModule()
static MonsterClass mc;
if(mc !is null) return;
mc = new MonsterClass(MC.String, moduleDef, "random");
mc = vm.loadString(moduleDef, "random");
mc.bind("rand", { stack.pushInt(rand()); });
mc.bind("frand", { stack.pushFloat(rand()*_frandFactor); });

View file

@ -28,6 +28,10 @@
module monster.modules.threads;
import monster.monster;
import monster.vm.mobject;
import monster.vm.idlefunction;
import monster.vm.thread;
import monster.vm.mclass;
import std.stdio;
const char[] moduleDef =
@ -69,12 +73,13 @@ thread start(char[] name)
/*
The char[] name stuff above will of course be replaced with real
function pointers once those are done. We will also add:
function pointers once those are done. When closures are done we
will also add:
function() wrap(function f())
{
var t = create(f);
return {{ t.call(); }
return { t.call(); }
}
*/
@ -268,7 +273,7 @@ void initThreadModule()
if(_threadClass !is null)
return;
_threadClass = new MonsterClass(MC.String, moduleDef, "thread");
_threadClass = vm.loadString(moduleDef, "thread");
trdSing = _threadClass.getSing();
_threadClass.bind("kill", new Kill);

View file

@ -28,7 +28,6 @@
module monster.modules.timer;
import std.stdio;
import std.date;
// For some utterly idiotic reason, DMD's public imports will suddenly
// stop working from time to time.
@ -39,14 +38,18 @@ import monster.vm.thread;
import monster.vm.idlefunction;
import monster.monster;
import monster.options;
const char[] moduleDef =
"singleton timer;
idle sleep(float secs);
"; //"
static if(timer_useClock)
{
// Sleep a given amount of time. This implementation uses the system
// clock and is the default.
// clock.
import std.date;
class IdleSleep_SystemClock : IdleFunction
{
override:
@ -72,9 +75,11 @@ class IdleSleep_SystemClock : IdleFunction
}
}
} else { // If timer_useClock is NOT set:
// This implementation uses a user-driven timer instead of the system
// clock. It's more efficient, but requires the user to update the
// given timer manuall each frame. The default sleep (timer.sleep) is
// given timer manually each frame. The default sleep (timer.sleep) is
// bound to the default timer, but it's possible to create multiple
// independent timers.
class IdleSleep_Timer : IdleFunction
@ -161,35 +166,25 @@ class SleepManager
}
SleepManager idleTime;
}
MonsterClass _timerClass;
void initTimerModule()
{
if(_timerClass !is null)
return;
_timerClass = vm.loadString(moduleDef, "timer");
static if(timer_useClock)
{
assert(idleTime !is null);
return;
_timerClass.bind("sleep", new IdleSleep_SystemClock);
}
else
{
assert(idleTime is null);
idleTime = new SleepManager(_timerClass.getSing());
_timerClass.bind("sleep", new IdleSleep_Timer);
}
_timerClass = new MonsterClass(MC.String, moduleDef, "timer");
assert(idleTime is null);
idleTime = new SleepManager(_timerClass.getSing());
setSleepMethod(SleepMethod.Clock);
}
enum SleepMethod
{ Clock, Timer }
void setSleepMethod(SleepMethod meth)
{
initTimerModule();
if(meth == SleepMethod.Clock)
_timerClass.bind("sleep", new IdleSleep_SystemClock);
else if(meth == SleepMethod.Timer)
_timerClass.bind("sleep", new IdleSleep_Timer);
else assert(0, "unknown timer method");
}

179
monster/modules/vfs.d Normal file
View file

@ -0,0 +1,179 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007-2009 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (vfs.d) is part of the Monster script language package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module monster.modules.vfs;
import std.file;
import std.stream;
import std.string;
import monster.util.string;
import monster.vm.error;
abstract class VFS
{
// Abstract functions. These must be implemented in child classes.
// Return true if a file exists. Should not return true for
// directories.
abstract bool has(char[] file);
// Open the given file and return it as a stream.
abstract Stream open(char[] file);
static final char[] getBaseName(char[] fullname)
{
foreach_reverse(i, c; fullname)
{
version(Win32)
{
if(c == ':' || c == '\\' || c == '/')
return fullname[i+1..$];
}
version(Posix)
{
if (fullname[i] == '/')
return fullname[i+1..$];
}
}
return fullname;
}
}
// A VFS that contains a list of other VFS objects
class ListVFS : VFS
{
private:
VFS[] list;
public:
this(VFS v[] ...)
{ list = v; }
void add(VFS v[] ...)
{ list ~= v; }
void addFirst(VFS v[] ...)
{ list = v ~ list; }
bool has(char[] file)
{
foreach(l; list)
if(l.has(file)) return true;
return false;
}
Stream open(char[] file)
{
foreach(l; list)
if(l.has(file)) return l.open(file);
fail("No member VFS contains file " ~ file);
}
}
// A VFS that reads files from a given path in the OS file
// system. Disallows filenames that escape the given path,
// ie. filenames such as:
//
// /etc/passwd
// dir/../../file
// c:\somefile
class FileVFS : VFS
{
private:
char[] sysPath;
char[] buffer;
char[] getPath(char[] file)
{
// Make sure the buffer is large enough
if(buffer.length < file.length+sysPath.length)
buffer.length = file.length + sysPath.length + 50;
// Check for invalid file names. This makes sure the caller
// cannot read files outside the designated subdirectory.
if(file.begins([from]) || file.begins([to]))
fail("Filename " ~ file ~ " cannot begin with a path separator");
if(file.find(":") != -1)
fail("Filename " ~ file ~ " cannot contain colons");
if(file.find("..") != -1)
fail("Filename " ~ file ~ " cannot contain '..'");
// Copy the file name over
buffer[sysPath.length .. sysPath.length+file.length]
= file[];
// Convert the path characters
convPath();
// Return the result
return buffer[0..sysPath.length+file.length];
}
// Convert path separators
void convPath()
{
foreach(ref c; buffer)
if(c == from)
c = to;
}
version(Windows)
{
const char from = '/';
const char to = '\\';
}
else
{
const char from = '\\';
const char to = '/';
}
public:
this(char[] path = "")
{
// Set up the initial buffer
buffer.length = path.length + 50;
if(path.length)
{
// Slice the beginning of it and copy the path over
sysPath = buffer[0..path.length];
sysPath[] = path[];
}
convPath();
// Make sure the last char in the path is a path separator
if(!path.ends([to]))
{
sysPath = buffer[0..path.length+1];
sysPath[$-1] = to;
}
}
bool has(char[] file)
{ return exists(getPath(file)) != 0; }
Stream open(char[] file)
{ return new BufferedFile(getPath(file)); }
}

View file

@ -36,26 +36,7 @@ public
import monster.vm.arrays;
import monster.vm.params;
import monster.vm.error;
import monster.modules.all;
}
private import monster.compiler.tokenizer;
private import monster.compiler.properties;
private import monster.compiler.scopes;
version(LittleEndian) {}
else static assert(0, "This library does not yet support big endian systems.");
static this()
{
// Initialize compiler constructs
initTokenizer();
initProperties();
initScope();
// Initialize VM
scheduler.init();
stack.init();
arrays.initialize();
}

150
monster/options.d Normal file
View file

@ -0,0 +1,150 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007-2009 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (options.d) is part of the Monster script language
package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module monster.options;
/*
The purpose of this file is to set compile time options for the
Monster library - including compiler, VM and modules. This allows
the user to customize the language in various ways to fit each
project individually.
For changes to take effect, you must recompile and reinstall the
library.
If you have suggestions for additional options and ways to customize
the language, let us know!
*/
const:
static:
/*********************************************************
Language options
*********************************************************/
// Set to false to make the entire language case insensitive. Affects
// all identifier and keyword matching. (Not implemented yet!)
bool caseSensitive = true;
// Include the case-insensitive string (and character) operators =i=,
// =I=, !=i= and !=I=.
bool ciStringOps = true;
// Skip lines beginning with a hash character '#'
bool skipHashes = true;
/*********************************************************
VM options
*********************************************************/
// Whether to add the current working directory to the VFS at
// startup. If false, you must add your own directories (using
// vm.addPath) or your own VFS implementations, otherwise the library
// will not be able to find any script files.
bool vmAddCWD = false;
// Maximum stack size. Prevents stack overflow through infinite
// recursion and other bugs.
int maxStack = 100;
// Maximum function stack size
int maxFStack = 100;
// Whether we should limit the number of instructions that execute()
// can run at once. Enabling this will prevent infinite loops.
bool enableExecLimit = true;
// Maximum number of instructions to allow in once call to execute() (if
long execLimit = 10000000;
/*********************************************************
Debugging options
*********************************************************/
// Enable tracing of external functions on the function stack. If
// true, you may use vm.trace() and vm.untrace() to add your own
// functions to the internal Monster function stack for debug purposes
// (the functions will show up in debug output.) If false, these
// functions will be empty and most likely optimized away completely
// by the D compiler (in release builds).
bool enableTrace = true;
/*********************************************************
Modules
*********************************************************/
// Load modules at startup? If false, you can still load modules
// manually using monster.modules.all.initAllModules().
bool loadModules = true;
// List of modules to load when initAllModules is called (and at
// startup if loadModules is true.)
char[] moduleList = "io math timer frames random thread";
/*********************************************************
Timer module
*********************************************************/
// When true, idle function sleep() uses the system clock. When false,
// the time is only updated manually when the user calls vm.frame().
// The system clock is fine for small applications, but the manual
// method is much more optimized. It is highly recommended to use
// vm.frame() manually for games and other projects that use a
// rendering loop.
bool timer_useClock = false;

150
monster/options.openmw Normal file
View file

@ -0,0 +1,150 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007-2009 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (options.d) is part of the Monster script language
package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module monster.options;
/*
The purpose of this file is to set compile time options for the
Monster library - including compiler, VM and modules. This allows
the user to customize the language in various ways to fit each
project individually.
For changes to take effect, you must recompile and reinstall the
library.
If you have suggestions for additional options and ways to customize
the language, let us know!
*/
const:
static:
/*********************************************************
Language options
*********************************************************/
// Set to false to make the entire language case insensitive. Affects
// all identifier and keyword matching. (Not implemented yet!)
bool caseSensitive = true;
// Include the case-insensitive string (and character) operators =i=,
// =I=, !=i= and !=I=.
bool ciStringOps = true;
// Skip lines beginning with a hash character '#'
bool skipHashes = true;
/*********************************************************
VM options
*********************************************************/
// Whether to add the current working directory to the VFS at
// startup. If false, you must add your own directories (using
// vm.addPath) or your own VFS implementations, otherwise the library
// will not be able to find any script files.
bool vmAddCWD = false;
// Maximum stack size. Prevents stack overflow through infinite
// recursion and other bugs.
int maxStack = 100;
// Maximum function stack size
int maxFStack = 100;
// Whether we should limit the number of instructions that execute()
// can run at once. Enabling this will prevent infinite loops.
bool enableExecLimit = true;
// Maximum number of instructions to allow in once call to execute() (if
long execLimit = 10000000;
/*********************************************************
Debugging options
*********************************************************/
// Enable tracing of external functions on the function stack. If
// true, you may use vm.trace() and vm.untrace() to add your own
// functions to the internal Monster function stack for debug purposes
// (the functions will show up in debug output.) If false, these
// functions will be empty and most likely optimized away completely
// by the D compiler (in release builds).
bool enableTrace = true;
/*********************************************************
Modules
*********************************************************/
// Load modules at startup? If false, you can still load modules
// manually using monster.modules.all.initAllModules().
bool loadModules = true;
// List of modules to load when initAllModules is called (and at
// startup if loadModules is true.)
char[] moduleList = "io math timer frames random thread";
/*********************************************************
Timer module
*********************************************************/
// When true, idle function sleep() uses the system clock. When false,
// the time is only updated manually when the user calls vm.frame().
// The system clock is fine for small applications, but the manual
// method is much more optimized. It is highly recommended to use
// vm.frame() manually for games and other projects that use a
// rendering loop.
bool timer_useClock = false;

View file

@ -9,3 +9,5 @@ for a in $(find -iname \*.d); do
done
svn st
svn diff options.openmw options.d

View file

@ -148,6 +148,18 @@ struct GrowArray(T)
return *this;
}
// Get a contiguous array copy containg all the elements.
T[] arrayCopy()
{
T[] res = new T[length()];
// Non-optimized!
foreach(i, ref r; res)
r = opIndex(i);
return res;
}
int opApply(int delegate(ref int, ref T) dg)
{
int res;

View file

@ -33,6 +33,7 @@ import monster.compiler.states;
import monster.compiler.functions;
import monster.compiler.linespec;
import monster.options;
import monster.util.freelist;
import std.stdio;
@ -44,6 +45,7 @@ enum SPType
Function, // A function (script or native)
Idle, // Idle function
State, // State code
External, // An external function represented on the function stack
}
// One entry in the function stack
@ -55,6 +57,7 @@ struct StackPoint
{
Function *func; // What function we are in (if any)
State *state; // What state the function belongs to (if any)
char[] extName; // Name of external function
}
SPType ftype;
@ -92,6 +95,9 @@ struct StackPoint
bool isNormal()
{ return isState || (isFunc && func.isNormal); }
bool isExternal()
{ return ftype == SPType.External; }
// Get the current source position (file name and line
// number). Mostly used for error messages.
Floc getFloc()
@ -116,6 +122,9 @@ struct StackPoint
char[] toString()
{
if(isExternal)
return "external function " ~ extName;
assert(func !is null);
char[] type, cls, name;
@ -144,11 +153,14 @@ struct StackPoint
alias FreeList!(StackPoint) StackList;
alias StackList.TNode *StackNode;
// External functions that are pushed when there is no active
// thread. These will not prevent any future thread from being put in
// the background.
FunctionStack externals;
struct FunctionStack
{
private:
// Guard against infinite recursion
static const maxStack = 100;
// Number of native functions on the stack
int natives;
@ -185,8 +197,7 @@ struct FunctionStack
void killAll()
{
assert(natives == 0);
natives = 0;
while(list.length)
{
assert(cur !is null);
@ -215,7 +226,7 @@ struct FunctionStack
// Sets up the next stack point and assigns the given object
private void push(MonsterObject *obj)
{
if(list.length >= maxStack)
if(list.length >= maxFStack)
fail("Function stack overflow - infinite recursion?");
assert(cur is null || !cur.isIdle,
@ -276,6 +287,16 @@ struct FunctionStack
cur.code.setData(st.bcode);
}
// Push an external (non-scripted) function on the function
// stack.
void pushExt(char[] name)
{
push(null);
natives++;
cur.ftype = SPType.External;
cur.extName = name;
}
void pushIdle(Function *func, MonsterObject *obj)
{
push(obj);
@ -299,7 +320,7 @@ struct FunctionStack
assert(list.length >= 1);
if(cur.isNative)
if(cur.isNative || cur.isExternal)
natives--;
assert(natives >= 0);
@ -331,11 +352,25 @@ struct FunctionStack
if(i == 0)
msg = " (<---- current function)";
else if(i == list.length-1)
msg = " (<---- start of function stack)";
res = c.toString ~ msg ~ '\n' ~ res;
msg = " (<---- first Monster function)";
res ~= c.toString ~ msg ~ '\n';
i++;
}
// If we're not the externals list, add that one too
i = 0;
if(this !is &externals)
{
foreach(ref c; externals.list)
{
char[] msg;
if(i == externals.list.length-1)
msg = " (<---- first external function)";
res ~= c.toString ~ msg ~ '\n';
i++;
}
}
return "Trace:\n" ~ res;
}
}

121
monster/vm/init.d Normal file
View file

@ -0,0 +1,121 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007-2009 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (init.d) is part of the Monster script language package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// This module makes sure that the library is initialized in all
// cases.
module monster.vm.init;
import monster.compiler.tokenizer;
import monster.compiler.properties;
import monster.compiler.scopes;
import monster.vm.thread;
import monster.vm.stack;
import monster.vm.arrays;
import monster.vm.vm;
import monster.modules.all;
import monster.options;
// D runtime stuff
version(Posix)
{
extern (C) void _STI_monitor_staticctor();
//extern (C) void _STD_monitor_staticdtor();
extern (C) void _STI_critical_init();
//extern (C) void _STD_critical_term();
}
version(Win32)
{
extern (C) void _minit();
}
extern (C) void gc_init();
//extern (C) void gc_term();
extern (C) void _moduleCtor();
//extern (C) void _moduleDtor();
extern (C) void _moduleUnitTests();
//extern (C) bool no_catch_exceptions;
bool initHasRun = false;
bool stHasRun = false;
static this()
{
assert(!stHasRun);
stHasRun = true;
// While we're here, run the initializer right away if it hasn't run
// already.
if(!initHasRun)
doMonsterInit();
}
void doMonsterInit()
{
// Prevent recursion
assert(!initHasRun, "doMonsterInit should never run more than once");
initHasRun = true;
// First check if D has been initialized.
if(!stHasRun)
{
// Nope. This is normal though if we're running as a C++
// library. We have to init the D runtime manually.
version (Posix)
{
_STI_monitor_staticctor();
_STI_critical_init();
}
gc_init();
version (Win32)
{
_minit();
}
_moduleCtor();
_moduleUnitTests();
}
assert(stHasRun, "D library initializion failed");
// Next, initialize the Monster library
// Initialize compiler constructs
initTokenizer();
initProperties();
initScope();
// Initialize VM
vm.doVMInit();
scheduler.init();
stack.init();
arrays.initialize();
// Load modules
static if(loadModules)
initAllModules();
}

View file

@ -39,6 +39,8 @@ import monster.vm.idlefunction;
import monster.vm.arrays;
import monster.vm.error;
import monster.vm.vm;
import monster.vm.stack;
import monster.vm.thread;
import monster.vm.mobject;
import monster.util.flags;
@ -48,23 +50,12 @@ import monster.util.freelist;
import std.string;
import std.stdio;
import std.file;
import std.stream;
typedef void *MClass; // Pointer to C++ equivalent of MonsterClass.
typedef int CIndex;
// Parameter to the constructor. Decides how the class is
// created. TODO: This system will be removed again, because we'll
// stop using constructors.
enum MC
{
File, // Load class from file (default)
NoCase, // Load class from file, case insensitive name match
String, // Load class from string
}
enum CFlags
{
None = 0x00, // Initial value
@ -89,15 +80,6 @@ final class MonsterClass
* *
***********************************************/
// TODO: These should be moved to vm.vm
// Get a class with the given name. It must already be loaded.
static MonsterClass get(char[] name) { return global.getClass(name); }
// Find a class with the given name. Load the file if necessary, and
// fail if the class cannot be found.
static MonsterClass find(char[] name) { return global.findClass(name); }
static bool canParse(TokenArray tokens)
{
return
@ -125,6 +107,7 @@ final class MonsterClass
CIndex gIndex; // Global index of this class
ClassScope sc;
PackageScope pack;
ObjectType objType; // Type for objects of this class
Type classType; // Type for class references to this class (not
@ -167,92 +150,11 @@ final class MonsterClass
// already.
void requireCompile() { if(!isCompiled) compileBody(); }
/*******************************************************
* *
* Constructors *
* *
*******************************************************/
this() {}
this(MC type, char[] name1, char[] name2 = "", bool usePath = true)
{
if(type == MC.File || type == MC.NoCase)
{
if(type == MC.NoCase)
loadCI(name1, name2, usePath);
else
load(name1, name2, usePath);
return;
}
if(type == MC.String)
{
loadString(name1, name2);
return;
}
assert(0, "encountered unknown MC type");
}
this(MC type, Stream str, char[] nam = "")
{
}
this(ref TokenArray toks, char[] nam="")
{ loadTokens(toks, nam); }
this(Stream str, char[] nam="")
{ loadStream(str, nam); }
this(char[] nam1, char[] nam2 = "", bool usePath=true)
{ this(MC.File, nam1, nam2, usePath); }
/*******************************************************
* *
* Class loaders *
* *
*******************************************************/
// Load from file system. The names must specify a class name, a
// file name, or both. The class name, if specified, must match the
// loaded class name exactly. If usePath is true (default), the
// include paths are searched.
void load(char[] name1, char[] name2 = "", bool usePath=true)
{ doLoad(name1, name2, true, usePath); }
// Same as above, except the class name check is case insensitive.
void loadCI(char[] name1, char[] name2 = "", bool usePath=true)
{ doLoad(name1, name2, false, usePath); }
void loadString(char[] str, char[] fname="")
{
assert(str != "");
auto ms = new MemoryStream(str);
if(fname == "") fname = "(string)";
loadStream(ms, fname);
}
// Load a script from a stream. The filename parameter is only used
// for error messages.
void load(Stream str, char[] fname="(stream)")
{ loadStream(str, fname); }
void loadTokens(ref TokenArray toks, char[] name)
{ parse(toks, name); }
void loadStream(Stream str, char[] fname="(stream)", int bom = -1)
{
assert(str !is null);
// Parse the stream
parse(str, fname, bom);
}
// Constructor that only exists to keep people from using it. It's
// much safer to use the vm.load functions, since these check if the
// class already exists.
this(int internal = 0) { assert(internal == -14,
"Don't create MonsterClasses directly, use vm.load()"); }
/*******************************************************
* *
@ -277,6 +179,20 @@ final class MonsterClass
void bind(char[] name, IdleFunction idle)
{ bind_locate(name, FuncType.Idle).idleFunc = idle; }
void bindT(alias func)(char[] name="")
{
// Get the name from the alias parameter directly, if not
// specified.
if(name == "")
// Sort of a hack. func.stringof won't work (parses as a
// function call). (&func).stringof parses as "& funcname",
// but this could be implementation specific.
name = ((&func).stringof)[2..$];
// Let the Function handle the rest
findFunction(name).bindT!(func)();
}
// Find a function by index. Used internally, and works for all
// function types.
Function *findFunction(int index)
@ -332,10 +248,22 @@ final class MonsterClass
/*******************************************************
* *
* Binding of constructors *
* Binding of native constructors *
* *
*******************************************************/
// bindConst binds a native function that is run on all new
// objects. It is executed before the constructor defined in script
// code (if any.)
// bindNew binds a function that is run on all objects created
// within script code (with the 'new' expression or the 'clone'
// property), but not on objects that are created in native code
// through createObject/createClone. This is handy when you want to
// bind with a native class, and want to be able to create objects
// in both places. It's executed before both bindConst and the
// script constructor.
void bindConst(dg_callback nf)
{
assert(natConst.ftype == FuncType.Native,
@ -360,6 +288,30 @@ final class MonsterClass
natConst.natFunc_c = nf;
}
void bindNew(dg_callback nf)
{
assert(natNew.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set");
natNew.ftype = FuncType.NativeDDel;
natNew.natFunc_dg = nf;
}
void bindNew(fn_callback nf)
{
assert(natNew.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set");
natNew.ftype = FuncType.NativeDFunc;
natNew.natFunc_fn = nf;
}
void bindNew_c(c_callback nf)
{
assert(natNew.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set");
natNew.ftype = FuncType.NativeCFunc;
natNew.natFunc_c = nf;
}
/*******************************************************
* *
@ -482,9 +434,39 @@ final class MonsterClass
assert(singObj !is null);
return singObj;
}
alias getSing getSingleton;
MonsterObject* createObject()
{ return createClone(null); }
MonsterObject* createObject(bool callConst = true)
{ return createClone(null, callConst); }
// Call constructors on an object. If scriptNew is true, also call
// the natNew bindings (if any)
void callConstOn(MonsterObject *obj, bool scriptNew = false)
{
assert(obj.cls is this);
// Needed to make sure execute() exits when the constructor is
// done.
vm.pushExt("callConst");
// Call constructors
foreach(c; tree)
{
// Call 'new' callback if the object was created in script
if(scriptNew && c.natNew.ftype != FuncType.Native)
c.natNew.call(obj);
// Call native constructor
if(c.natConst.ftype != FuncType.Native)
c.natConst.call(obj);
// Call script constructor
if(c.scptConst !is null)
c.scptConst.fn.call(obj);
}
vm.popExt();
}
// Get the whole allocated buffer belonging to this object
private int[] getDataBlock(MonsterObject *obj)
@ -501,7 +483,7 @@ final class MonsterClass
}
// Create a new object based on an existing object
MonsterObject* createClone(MonsterObject *source)
MonsterObject* createClone(MonsterObject *source, bool callConst = true)
{
requireCompile();
@ -555,7 +537,7 @@ final class MonsterClass
foreach(i, c; tree)
{
// Just get the slice - the actual data is already set up.
obj.data[i] = get(c.data.length + MonsterObject.exSize);
obj.data[i] = get(c.dataSize + MonsterObject.exSize);
// Insert ourselves into the per-class list. We've already
// allocated size for a node, we just have to add it to the
@ -568,24 +550,21 @@ final class MonsterClass
// At this point we should have used up the entire slice
assert(slice.length == 0);
// Call constructors
foreach(c; tree)
{
// Custom native constructor
if(c.natConst.ftype != FuncType.Native)
natConst.call(obj);
// TODO: Call script constructors here
}
// Set the same state as the source
if(source !is null)
obj.setState(source.state, null);
else
// Use the default state and label
obj.setState(defState, defLabel);
// Make sure that getDataBlock works
assert(getDataBlock(obj).ptr == odata.ptr &&
getDataBlock(obj).length == odata.length);
// Call constructors
if(callConst)
callConstOn(obj);
return obj;
}
@ -675,261 +654,25 @@ final class MonsterClass
char[] toString() { return getName(); }
uint numObjects() { return objects.length; }
private:
/*******************************************************
* *
* Private variables *
* *
*******************************************************/
// Contains the entire class tree for this class, always with
// ourselves as the last entry. Any class in the list is always
// preceded by all the classes it inherits from.
MonsterClass tree[];
// List of variables and functions declared in this class, ordered
// by index.
Function* functions[];
Variable* vars[];
State* states[];
// Singleton object - used for singletons and modules only.
MonsterObject *singObj;
// Function table translation list. Same length as tree[]. For each
// class in the parent tree, this list holds a list equivalent to
// the functions[] list in that class. The difference is that all
// overrided functions have been replaced by their successors.
Function*[][] virtuals;
int[] data; // Contains the initial object data segment
int[] sdata; // Static data segment
// The total data segment that's assigned to each object. It
// includes the data segment of all parent objects and some
// additional internal data.
int[] totalData;
// Direct parents of this class
MonsterClass parents[];
Token parentNames[];
// Used at compile time
VarDeclStatement[] vardecs;
FuncDeclaration[] funcdecs;
StateDeclaration[] statedecs;
StructDeclaration[] structdecs;
EnumDeclaration[] enumdecs;
ImportStatement[] imports;
// Native constructor, if any
Function natConst;
/*******************************************************
* *
* Various private functions *
* *
*******************************************************/
// Create the data segment for this class. TODO: We will have to
// handle string literals and other array constants later. This is
// called at the very end, after all code has been compiled. That
// means that array literals can be inserted into the class in the
// compile phase and still "make it" into the data segment as static
// data.
int[] getDataSegment()
// Used internally. Use the string version below instead if you want
// to change the default state of a class.
void setDefaultState(State *st, StateLabel *lb)
{
assert(sc !is null && sc.isClass(), "Class does not have a class scope");
uint dataSize = sc.getDataSize;
int[] data = new int[dataSize];
int totSize = 0;
foreach(VarDeclStatement vds; vardecs)
foreach(VarDeclaration vd; vds.vars)
{
int size = vd.var.type.getSize();
int[] val;
totSize += size;
val = vd.getCTimeValue();
data[vd.var.number..vd.var.number+size] = val[];
}
// Make sure the total size of the variables match the total size
// requested by variables through addNewDataVar.
assert(totSize == dataSize, "Data size mismatch in scope");
return data;
defState = st;
defLabel = lb;
}
// Load file based on file name, class name, or both. The order of
// the strings doesn't matter, and name2 can be empty. useCase
// determines if we require a case sensitive match between the given
// class name and the loaded name. If usePath is true we search the
// include paths for scripts.
void doLoad(char[] name1, char[] name2, bool useCase, bool usePath)
{
char[] fname, cname;
if(name1 == "")
fail("Cannot give empty first parameter to load()");
if(name1.iEnds(".mn"))
{
fname = name1;
cname = name2;
}
else
{
fname = name2;
cname = name1;
}
if(cname.iEnds(".mn"))
fail("load() recieved two filenames: " ~ fname ~ " and " ~ cname);
// The filename must either be empty, or end with .mn
if(fname != "" && !fname.iEnds(".mn"))
fail("Neither " ~ name1 ~ " nor " ~ name2 ~
" is a valid script filename.");
// Remember if cname was originally set
bool cNameSet = (cname != "");
// Make sure both cname and fname have values.
if(!cNameSet)
cname = classFromFile(fname);
else if(fname == "")
fname = classToFile(cname);
else
// Both values were given, make sure they are sensible
if(icmp(classFromFile(fname),cname) != 0)
fail(format("Class name %s does not match file name %s",
cname, fname));
assert(cname != "" && !cname.iEnds(".mn"));
assert(fname.iEnds(".mn"));
bool checkFileName()
{
if(cname.length == 0)
return false;
if(!validFirstIdentChar(cname[0]))
return false;
foreach(char c; cname)
if(!validIdentChar(c)) return false;
return true;
}
if(!checkFileName())
fail(format("Invalid class name %s (file %s)", cname, fname));
if(usePath && !vm.findFile(fname))
fail("Cannot find script file " ~ fname);
// Create a temporary file stream and load it
auto bf = new BufferedFile(fname);
auto ef = new EndianStream(bf);
int bom = ef.readBOM();
loadStream(ef, fname, bom);
delete bf;
// After the class is loaded, we can check it's real name.
// If the name matches, we're done.
if(cname == name.str) return;
// Allow a case insensitive match if useCase is false or the name
// was not given.
if((!useCase || !cNameSet) && (icmp(cname, name.str) == 0)) return;
// Oops, name mismatch
fail(format("%s: Expected class name %s does not match loaded name %s",
fname, cname, name.str));
assert(0);
}
// Helper function for the bind() variants
Function* bind_locate(char[] name, FuncType ft)
{
requireScope();
// Look the function up in the scope
auto ln = sc.lookupName(name);
auto fn = ln.func;
if(!ln.isFunc)
fail("Cannot bind to '" ~ name ~ "': no such function");
if(ft == FuncType.Idle)
{
if(!fn.isIdle())
fail("Cannot bind to non-idle function '" ~ name ~ "'");
}
else
{
if(!fn.isNative())
fail("Cannot bind to non-native function '" ~ name ~ "'");
}
fn.ftype = ft;
return fn;
}
/*******************************************************
* *
* Compiler-related private functions *
* *
*******************************************************/
// Identify what kind of block the given set of tokens represent,
// parse them, and store it in the appropriate list;
void store(ref TokenArray toks)
// Set the initial state and label for this class. This will affect
// all newly created objects, but not cloned objects.
void setDefaultState(char[] st, char[] lb="")
{
if(FuncDeclaration.canParse(toks))
{
auto fd = new FuncDeclaration;
funcdecs ~= fd;
fd.parse(toks);
}
else if(VarDeclStatement.canParse(toks))
{
auto vd = new VarDeclStatement;
vd.parse(toks);
vardecs ~= vd;
}
else if(StateDeclaration.canParse(toks))
{
auto sd = new StateDeclaration;
sd.parse(toks);
statedecs ~= sd;
}
else if(StructDeclaration.canParse(toks))
if(lb == "")
{
auto sd = new StructDeclaration;
sd.parse(toks);
structdecs ~= sd;
setDefaultState(findState(st), null);
return;
}
else if(EnumDeclaration.canParse(toks))
{
auto sd = new EnumDeclaration;
sd.parse(toks);
enumdecs ~= sd;
}
else if(ImportStatement.canParse(toks))
{
auto sd = new ImportStatement;
sd.parse(toks);
imports ~= sd;
}
else
fail("Illegal type or declaration", toks);
auto pr = findState(st, lb);
setDefaultState(pr.state, pr.label);
}
// Converts a stream to tokens and parses it.
@ -950,6 +693,9 @@ final class MonsterClass
natConst.ftype = FuncType.Native;
natConst.name.str = "native constructor";
natConst.owner = this;
natNew.ftype = FuncType.Native;
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
@ -1000,8 +746,11 @@ final class MonsterClass
while(isNext(tokens, TT.Comma));
}
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);
@ -1015,6 +764,181 @@ final class MonsterClass
flags.set(CFlags.Parsed);
}
private:
/*******************************************************
* *
* Private variables *
* *
*******************************************************/
// Contains the entire class tree for this class, always with
// ourselves as the last entry. Any class in the list is always
// preceded by all the classes it inherits from.
MonsterClass tree[];
// List of variables and functions declared in this class, ordered
// by index.
Function* functions[];
Variable* vars[];
State* states[];
// Singleton object - used for singletons and modules only.
MonsterObject *singObj;
// Function table translation list. Same length as tree[]. For each
// class in the parent tree, this list holds a list equivalent to
// the functions[] list in that class. The difference is that all
// overrided functions have been replaced by their successors.
Function*[][] virtuals;
// Default state and label
State *defState = null;
StateLabel *defLabel = null;
// The total data segment that's assigned to each object. It
// includes the data segment of all parent objects and some
// additional internal data.
int[] totalData;
// Data segment size for *this* class, not including parents or
// extra information.
public int dataSize;
// Total data, sliced up to match the class tree
int[][] totalSliced;
// Direct parents of this class
public MonsterClass parents[];
Token parentNames[];
// Used at compile time
VarDeclStatement[] vardecs;
FuncDeclaration[] funcdecs;
StateDeclaration[] statedecs;
StructDeclaration[] structdecs;
EnumDeclaration[] enumdecs;
ImportStatement[] imports;
ClassVarSet[] varsets;
// Native constructors, if any
Function natConst, natNew;
// Script constructor, if any
public Constructor scptConst;
/*******************************************************
* *
* Various private functions *
* *
*******************************************************/
// Helper function for the bind() variants
Function* bind_locate(char[] name, FuncType ft)
{
requireScope();
// Look the function up in the scope
auto ln = sc.lookupName(name);
auto fn = ln.func;
if(!ln.isFunc)
fail("Cannot bind to '" ~ name ~ "': no such function");
if(ft == FuncType.Idle)
{
if(!fn.isIdle())
fail("Cannot bind to non-idle function '" ~ name ~ "'");
}
else
{
if(!fn.isNative())
fail("Cannot bind to non-native function '" ~ name ~ "'");
}
fn.ftype = ft;
return fn;
}
/*******************************************************
* *
* Compiler-related private functions *
* *
*******************************************************/
// Identify what kind of block the given set of tokens represent,
// parse them, and store it in the appropriate list;
void store(ref TokenArray toks)
{
if(FuncDeclaration.canParse(toks))
{
auto fd = new FuncDeclaration;
funcdecs ~= fd;
fd.parse(toks);
}
else if(Constructor.canParse(toks))
{
auto fd = new Constructor;
if(scptConst !is null)
fail("Class " ~ name.str ~ " cannot have more than one constructor", toks[0].loc);
scptConst = fd;
fd.parse(toks);
}
else if(ClassVarSet.canParse(toks))
{
auto cv = new ClassVarSet;
cv.parse(toks);
// Check if this variable is already set in this class
foreach(ocv; varsets)
{
if(cv.isState && ocv.isState)
fail(format("State already set on line %s",
ocv.loc.line), cv.loc);
else if(ocv.name.str == cv.name.str)
fail(format("Variable %s is already set on line %s",
cv.name.str, ocv.loc.line),
cv.loc);
}
varsets ~= cv;
}
else if(VarDeclStatement.canParse(toks))
{
auto vd = new VarDeclStatement;
vd.parse(toks);
vardecs ~= vd;
}
else if(StateDeclaration.canParse(toks))
{
auto sd = new StateDeclaration;
sd.parse(toks);
statedecs ~= sd;
}
else if(StructDeclaration.canParse(toks))
{
auto sd = new StructDeclaration;
sd.parse(toks);
structdecs ~= sd;
}
else if(EnumDeclaration.canParse(toks))
{
auto sd = new EnumDeclaration;
sd.parse(toks);
enumdecs ~= sd;
}
else if(ImportStatement.canParse(toks))
{
auto sd = new ImportStatement;
sd.parse(toks);
imports ~= sd;
}
else
fail("Illegal type or declaration", toks);
}
// Insert the class into the scope system. All parent classes must
// be loaded before this is called.
void createScope()
@ -1036,9 +960,10 @@ final class MonsterClass
parents.length = parentNames.length;
foreach(int i, pName; parentNames)
{
// Find the class. findClass guarantees that the returned
// class is scoped.
MonsterClass mc = global.findClass(pName);
// Find the class. vm.load() returns the existing class if
// it has already been loaded.
MonsterClass mc = vm.load(pName.str);
mc.requireScope();
assert(mc !is null);
assert(mc.isScoped);
@ -1208,6 +1133,13 @@ final class MonsterClass
foreach(func; funcdecs)
func.resolveBody();
// Including the constructor
if(scptConst !is null)
{
scptConst.resolve(sc);
assert(scptConst.fn.owner is this);
}
// Resolve states
foreach(state; statedecs)
state.resolve(sc);
@ -1222,13 +1154,41 @@ final class MonsterClass
foreach(var; vardecs)
var.validate();
// Resolve variable and state overrides. No other declarations
// depend on these (the values are only relevant at the
// compilation stage), so we can resolve these last.
foreach(dec; varsets)
dec.resolve(sc);
flags.set(CFlags.Resolved);
}
alias int[] ia;
// These are platform dependent:
// This is platform dependent:
static const iasize = ia.sizeof / int.sizeof;
// Fill the data segment for this class.
void getDataSegment(int[] data)
{
assert(data.length == dataSize);
int totSize = 0;
foreach(VarDeclStatement vds; vardecs)
foreach(VarDeclaration vd; vds.vars)
{
int size = vd.var.type.getSize();
int[] val;
totSize += size;
val = vd.getCTimeValue();
data[vd.var.number..vd.var.number+size] = val[];
}
// Make sure the total size of the variables match the total size
// requested by variables through addNewDataVar.
assert(totSize == dataSize, "Data size mismatch in scope");
}
void compileBody()
{
assert(!isCompiled, getName() ~ " is already compiled");
@ -1240,20 +1200,21 @@ final class MonsterClass
foreach(mc; tree[0..$-1])
mc.requireCompile();
// Generate data segment and byte code for functions and
// states. The result is stored in the respective objects.
// Generate byte code for functions and states.
foreach(f; funcdecs) f.compile();
foreach(s; statedecs) s.compile();
if(scptConst !is null) scptConst.compile();
// Set the data segment for this class.
data = getDataSegment();
// Get the data segment size for this class
assert(sc !is null && sc.isClass(), "Class does not have a class scope");
dataSize = sc.getDataSize;
// Calculate the total data size we need to allocate for each
// object
uint tsize = 0;
foreach(c; tree)
{
tsize += c.data.length; // Data segment size
tsize += c.dataSize; // Data segment size
tsize += MonsterObject.exSize; // Extra data per object
tsize += iasize; // The size of our entry in the data[]
// table
@ -1262,7 +1223,7 @@ final class MonsterClass
// Allocate the buffer
totalData = new int[tsize];
// Use this to get subslices of the data segment
// Used below to get subslices of the data segment
int[] slice = totalData;
int[] get(int ints)
{
@ -1272,14 +1233,16 @@ final class MonsterClass
return res;
}
// Skip the data[] list
// The first part of the buffer is used for storing the obj.data
// array itself - skip that now.
get(iasize*tree.length);
// Assign the data segment values
foreach(c; tree)
// Set up the slice list
totalSliced.length = tree.length;
foreach(i,c; tree)
{
int[] d = get(c.data.length);
d[] = c.data[];
// Data segment slice
totalSliced[i] = get(c.dataSize);
// Skip the extra data
get(MonsterObject.exSize);
@ -1287,6 +1250,44 @@ final class MonsterClass
// At this point we should have used up the entire slice
assert(slice.length == 0);
// Sanity check on the size
assert(totalSliced[$-1].length == dataSize);
// Fill our own data segment
getDataSegment(totalSliced[$-1]);
// The next part is only implemented for single inheritance
assert(parents.length <= 1);
if(parents.length == 1)
{
auto p = parents[0];
// Go through the parent's tree, and copy its data
// segments. This will make sure we include all cumulative
// variable changes from past classes.
assert(p.tree.length == tree.length - 1);
foreach(i,c; p.tree)
{
assert(tree[i] is c);
// Copy updated data segment for c from parent class
totalSliced[i][] = p.totalSliced[i][];
}
// Apply all variable changes defined in this class
foreach(vs; varsets)
{
if(vs.isState) continue;
assert(vs.cls !is null);
int ind = vs.cls.treeIndex;
assert(ind < p.tree.length);
assert(tree[ind] is vs.cls);
vs.apply(totalSliced[ind]);
}
}
flags.set(CFlags.Compiled);
@ -1298,37 +1299,3 @@ final class MonsterClass
}
}
}
// Convert between class name and file name. These are currently just
// guesses. TODO: Move these into MC, into the user functions, or
// eliminate them completely.
char[] classToFile(char[] cname)
{
return tolower(cname) ~ ".mn";
}
char[] classFromFile(char[] fname)
{
fname = getBaseName(fname);
assert(fname.ends(".mn"));
return fname[0..$-3];
}
// Utility functions, might move elsewhere.
char[] getBaseName(char[] fullname)
{
foreach_reverse(i, c; fullname)
{
version(Win32)
{
if(c == ':' || c == '\\' || c == '/')
return fullname[i+1..$];
}
version(Posix)
{
if (fullname[i] == '/')
return fullname[i+1..$];
}
}
return fullname;
}

View file

@ -255,15 +255,38 @@ struct MonsterObject
* *
*******************************************************/
// Call a named function. The function is executed immediately, and
// call() returns when the function is finished. The function is
// called virtually, so any child class function that overrides it
// will take precedence.
// Call a named function directly. The function is executed
// immediately, and call() returns when the function is
// finished. The function is called virtually, so any child class
// function that overrides it will take precedence. This is the 'low
// level' way to call functions, meaning that you have to handle
// parameters and return values on the stack manually. Use the
// template functions below for a more high level interface.
void call(char[] name)
{
cls.findFunction(name).call(this);
}
template callT(T)
{
T callT(A ...)(char[] name, A a)
{
return cls.findFunction(name).callT!(T)(this, a);
}
}
alias callT!(void) callVoid;
alias callT!(int) callInt;
alias callT!(uint) callUint;
alias callT!(float) callFloat;
alias callT!(double) callDouble;
alias callT!(long) callLong;
alias callT!(ulong) callUlong;
alias callT!(dchar) callChar;
alias callT!(MIndex) callMIndex;
alias callT!(AIndex) callAIndex;
// Create a paused thread that's set up to call the given
// function. It must be started with Thread.call() or
// Thread.restart().
@ -302,13 +325,6 @@ struct MonsterObject
return trd;
}
// Call a function non-virtually. In other words, ignore
// derived objects.
void nvcall(char[] name)
{
assert(0, "not implemented");
}
/* Set state. Invoked by the statement "state = statename;". This
function can be called in several situations, with various
results:

View file

@ -29,6 +29,7 @@ import std.stdio;
import std.utf;
import monster.compiler.scopes;
import monster.options;
import monster.vm.mobject;
import monster.vm.mclass;
@ -56,13 +57,9 @@ struct CodeStack
public:
void init()
{
// 100 is just a random number - it should probably be quite a bit
// larger (but NOT dynamic, since we want to catch run-away code.)
const int size = 100;
data.length = size;
left = size;
total = size;
data.length = maxStack;
left = maxStack;
total = maxStack;
pos = data.ptr;
frame = null;
}
@ -344,6 +341,46 @@ struct CodeStack
alias pop4!(dchar) popChar;
alias get4!(dchar) getChar;
void pushFail(T)(T t)
{
static assert(0, "pushType not yet implemented for " ~ T.stringof);
}
T popFail(T)()
{
static assert(0, "popType not yet implemented for " ~ T.stringof);
}
// Generic push template
template pushType(T)
{
static if(is(T == MIndex) || is(T == AIndex) ||
is(T == int) || is(T == uint) || is(T == float))
alias push4!(T) pushType;
else static if(is(T == long) || is(T == ulong) ||
is(T == double) || is(T == dchar))
alias push8!(T) pushType;
else
alias pushFail!(T) pushType;
}
// Ditto for pop
template popType(T)
{
static if(is(T == MIndex) || is(T == AIndex) ||
is(T == int) || is(T == uint) || is(T == float))
alias pop4!(T) popType;
else static if(is(T == long) || is(T == ulong) ||
is(T == double) || is(T == dchar))
alias pop8!(T) popType;
else
alias popFail!(T) popType;
}
// Pop off and ignore a given amount of values
void pop(int num)
{

View file

@ -29,6 +29,7 @@ import std.uni;
import std.c.string;
import monster.util.freelist;
import monster.options;
import monster.compiler.bytecode;
import monster.compiler.linespec;
@ -46,12 +47,15 @@ import monster.vm.arrays;
import monster.vm.iterators;
import monster.vm.error;
import monster.vm.fstack;
import monster.vm.vm;
import std.math : floor;
// Used for array copy below. It handles overlapping data for us.
extern(C) void* memmove(void *dest, void *src, size_t n);
//debug=traceOps;
import monster.util.list;
alias _lstNode!(Thread) _tmp1;
alias __FreeNode!(Thread) _tmp2;
@ -111,6 +115,8 @@ struct Thread
// Get a new thread. It starts in the 'transient' list.
static Thread* getNew()
{
vm.init();
auto cn = scheduler.transient.getNew();
cn.list = &scheduler.transient;
@ -124,6 +130,14 @@ struct Thread
return cn;
}
// Get a paused thread
static Thread *getPaused()
{
auto cn = getNew();
cn.moveTo(&scheduler.paused);
return cn;
}
// Schedule the function to run the next frame. Can only be used on
// paused threads.
void restart()
@ -320,7 +334,8 @@ struct Thread
"Thread already has a stack");
assert(isRunning,
"cannot put a non-running thread in the background");
assert(!fstack.hasNatives);
assert(!fstack.hasNatives,
"cannot put thread in the background, there are native functions on the stack");
// We're no longer the current thread
cthread = null;
@ -391,6 +406,7 @@ struct Thread
// Move this node to another list.
void moveTo(NodeList *to)
{
if(list is to) return;
assert(list !is null);
list.moveTo(*to, this);
list = to;
@ -500,13 +516,9 @@ struct Thread
// Execute instructions in the current function stack entry. This is
// the main workhorse of the VM, the "byte-code CPU". The function
// is called (possibly recursively) whenever a byte-code function is
// called, and returns when the function exits.
// called, and returns when the function exits. Function calls
void execute()
{
// The maximum amount of instructions we execute before assuming
// an infinite loop.
static const long limit = 10000000;
assert(!isDead);
assert(fstack.cur !is null,
"Thread.execute called but there is no code on the function stack.");
@ -595,26 +607,37 @@ struct Thread
int val, val2;
long lval;
// Disable this for now.
// or at least a compile time option.
//for(long i=0;i<limit;i++)
static if(enableExecLimit)
long count = 0;
for(;;)
{
static if(enableExecLimit)
{
count++;
if(count > execLimit)
fail(format("Execution unterminated after %s instructions. ",
execLimit, " Possibly an infinite loop, aborting."));
}
ubyte opCode = code.get();
//writefln("stack=", stack.getPos);
//writefln("exec(%s): %s", code.getLine, bcToString[opCode]);
debug(traceOps)
{
writefln("exec: %s", bcToString[opCode]);
writefln("stack=", stack.getPos);
}
switch(opCode)
{
case BC.Exit:
// Step down once on the function stack
fstack.pop();
// Leave execute() when the next level down is not a
// script function
if(!fstack.isNormal())
// The current function isn't a script function, so
// exit.
return;
assert(!shouldExit);
@ -700,15 +723,101 @@ struct Thread
if(shouldExit) return;
break;
case BC.EnumValue:
{
auto t = cast(EnumType)Type.typeList[code.getInt()];
assert(t !is null, "invalid type index");
val = stack.popInt(); // Get enum index
if(val-- == 0)
fail("'Null' enum encountered, cannot get value (type " ~ t.name ~ ")");
assert(val >= 0 && val < t.entries.length);
stack.pushLong(t.entries[val].value);
}
break;
case BC.EnumField:
{
val2 = code.getInt(); // Field index
auto t = cast(EnumType)Type.typeList[code.getInt()];
assert(t !is null, "invalid type index");
assert(val2 >= 0 && val2 < t.fields.length);
val = stack.popInt(); // Get enum index
if(val-- == 0)
fail("'Null' enum encountered, cannot get field '" ~ t.fields[val2].name.str ~ "' (type " ~ t.name ~ ")");
assert(val >= 0 && val < t.entries.length);
assert(t.entries[val].fields.length == t.fields.length);
stack.pushInts(t.entries[val].fields[val2]);
}
break;
case BC.EnumValToIndex:
{
auto t = cast(EnumType)Type.typeList[code.getInt()];
assert(t !is null, "invalid type index");
lval = stack.popLong(); // The value
auto eptr = t.lookup(lval);
if(eptr is null)
fail("No matching value " ~ .toString(lval) ~ " in enum");
stack.pushInt(eptr.index);
}
break;
case BC.EnumNameToIndex:
{
auto t = cast(EnumType)Type.typeList[code.getInt()];
assert(t !is null, "invalid type index");
auto str = stack.popString8(); // The value
auto eptr = t.lookup(str);
if(eptr is null)
fail("No matching value " ~ str ~ " in enum");
stack.pushInt(eptr.index);
}
break;
case BC.New:
// Create a new object. Look up the class index in the
// global class table, and create an object from it.
stack.pushObject(global.getClass(cast(CIndex)code.getInt())
.createObject());
{
// Create a new object. Look up the class index in the
// global class table, and create an object from it. Do
// not call constructors yet.
auto mo = global.getClass(cast(CIndex)code.getInt())
.createObject(false);
// Set up the variable parameters
val = code.getInt();
for(;val>0;val--)
{
// Get the variable pointer
int cIndex = stack.popInt();
int vIndex = stack.popInt();
int size = stack.popInt();
int[] dest = mo.getDataArray(cIndex, vIndex, size);
// Copy the value from the stack into place
dest[] = stack.popInts(size);
}
// Now we can call the constructors
mo.cls.callConstOn(mo, true);
// Push the resulting object
stack.pushObject(mo);
}
break;
case BC.Clone:
stack.pushObject(stack.popObject().clone());
{
auto mo = stack.popObject();
// Create a clone but don't call constructors
mo = mo.cls.createClone(mo, false);
// Call them manually, and make sure the natNew bindings
// are invoked
mo.cls.callConstOn(mo, true);
// Push the resulting object
stack.pushObject(mo);
}
break;
case BC.Jump:
@ -782,23 +891,23 @@ struct Thread
case BC.Dup: stack.pushInt(*stack.getInt(0)); break;
case BC.StoreRet:
case BC.Store:
// Get the pointer off the stack, and convert it to a real
// pointer.
ptr = popPtr();
// Read the value and store it, but leave it in the stack
*ptr = *stack.getInt(0);
// Pop the value and store it
*ptr = stack.popInt();
break;
case BC.StoreRet8:
case BC.Store8:
ptr = popPtr();
*(cast(long*)ptr) = *stack.getLong(1);
*(cast(long*)ptr) = stack.popLong();
break;
case BC.StoreRetMult:
case BC.StoreMult:
val = code.getInt(); // Size
ptr = popPtr();
ptr[0..val] = stack.getInts(val-1, val);
ptr[0..val] = stack.popInts(val);
break;
// Int / uint operations
@ -1224,8 +1333,6 @@ struct Thread
if(arf.iarr.length != iarr.length)
fail(format("Array length mismatch (%s != %s)",
arf.iarr.length, iarr.length));
// Push back the destination
stack.pushArray(arf);
// Use memmove, since it will handle overlapping data
memmove(arf.iarr.ptr, iarr.ptr, iarr.length*4);
@ -1298,8 +1405,6 @@ struct Thread
assert(arf.iarr.length % val == 0);
for(int i=0; i<arf.iarr.length; i+=val)
arf.iarr[i..i+val] = iarr[];
stack.pushArray(arf);
break;
case BC.CatArray:
@ -1411,8 +1516,7 @@ struct Thread
bcToString[opCode], opCode));
}
}
fail(format("Execution unterminated after %s instructions.", limit,
" Possibly an infinite loop, aborting."));
assert(0);
}
}

View file

@ -27,6 +27,8 @@ import monster.vm.error;
import monster.vm.thread;
import monster.vm.mclass;
import monster.vm.mobject;
import monster.vm.init;
import monster.vm.fstack;
import monster.compiler.tokenizer;
import monster.compiler.linespec;
@ -36,8 +38,14 @@ import monster.compiler.scopes;
import monster.modules.timer;
import monster.modules.frames;
import monster.modules.vfs;
import monster.options;
import std.file;
import std.stream;
import std.string;
import std.stdio;
import std.utf;
import std.format;
import monster.util.string;
VM vm;
@ -48,6 +56,8 @@ struct VM
// is given, an instance of an empty class is used.
Thread *run(char[] file, MonsterObject *obj = null)
{
init();
Thread *trd;
auto func = new Function;
if(obj !is null)
@ -65,44 +75,316 @@ struct VM
void frame(float time = 0)
{
if(time != 0)
idleTime.add(time);
static if(!timer_useClock)
{
if(time != 0)
idleTime.add(time);
}
updateFrames(time);
scheduler.doFrame();
}
// Path to search for script files. Extremely simple at the moment.
private char[][] includes = [""];
// Execute a single statement. Context-dependent statements such as
// 'return' or 'goto' are not allowed, and neither are
// declarations. Any return value (in case of expressions) is
// discarded. An optional monster object may be given as context.
void execute(char[] statm, MonsterObject *mo = null)
{
init();
// Get an empty object if none was specified
if(mo is null)
mo = Function.getIntMO();
// Push a dummy function on the stack, tokenize the statement, and
// parse the various statement types we allow. Assemble it and
// store the code in the dummy function. The VM handles the rest.
assert(0, "not done");
}
// This doesn't cover the 'console mode', or 'line mode', which is a
// bit more complicated. (It would search for matching brackets in
// the token list, print values back out, possibly allow variable
// declarations, etc.) I think we need a separate Console class to
// handle this, especially to store temporary values for
// optimization.
// Execute an expression and return the value. The template
// parameter gives the desired type, which must match the type of
// the expression. An optional object may be given as context.
T expressionT(T)(char[] expr, MonsterObject *mo = null)
{
init();
// Get an empty object if none was specified
if(mo is null)
mo = Function.getIntMO();
// Push a dummy function on the stack, tokenize and parse the
// expression. Get the type after resolving, and check it against
// the template parameter. Execute like for statements, and pop
// the return value.
assert(0, "not done");
}
// TODO: These have to check for existing classes first. Simply move
// doLoad over here and fix it up.
MonsterClass load(char[] nam1, char[] nam2 = "")
{ return doLoad(nam1, nam2, true, true); }
// Case insensitive with regards to the given class name
MonsterClass loadCI(char[] nam1, char[] nam2 = "")
{ return doLoad(nam1, nam2, false, true); }
// Does not fail if the class is not found, just returns null. It
// will still fail if the class exists and contains errors though.
MonsterClass loadNoFail(char[] nam1, char[] nam2 = "")
{ return doLoad(nam1, nam2, true, false); }
// Load a class from a stream. The filename parameter is only used
// for error messages.
MonsterClass load(Stream s, char name[] = "", int bom=-1)
{
init();
assert(s !is null, "Cannot load from null stream");
auto mc = new MonsterClass(-14);
mc.parse(s, name, bom);
return mc;
}
// Load a class from a token array. The filename parameter is only
// used for error messages.
MonsterClass load(ref TokenArray toks, char[] name = "")
{
init();
auto mc = new MonsterClass(-14);
mc.parse(toks, name);
return mc;
}
// Load a class from a string containing the script. The filename
// parameter is only used for error messages.
MonsterClass loadString(char[] str, char[] name="")
{
init();
assert(str != "", "Cannot load empty string");
auto ms = new MemoryStream(str);
if(name == "") name = "(string)";
return load(ms, name);
}
static if(enableTrace)
{
// Push the given string onto the function stack as an external
// function.
void tracef(...)
{
char[] s;
void putc(dchar c)
{
std.utf.encode(s, c);
}
std.format.doFormat(&putc, _arguments, _argptr);
trace(s);
}
void trace(char[] name) { pushExt(name); }
void untrace() { popExt(); }
}
else
{
void tracef(...) {}
void trace(char[]) {}
void untrace() {}
}
void pushExt(char[] name)
{
getFStack().pushExt(name);
}
// Pop the last function pushed by pushExt()
void popExt()
{
auto fs = getFStack();
assert(fs.cur !is null && fs.cur.isExternal,
"vm.untrace() used on a non-external function stack entry");
fs.pop();
}
// Get the active function stack, or the externals stack if no
// thread is active.
FunctionStack *getFStack()
{
if(cthread !is null)
{
assert(!cthread.fstack.isEmpty);
return &cthread.fstack;
}
return &externals;
}
// Return the current function stack printout
char[] getTrace()
{ return getFStack().toString(); }
void addPath(char[] path)
{
// Make sure the path is slash terminated.
if(!path.ends("/") && !path.ends("\\"))
path ~= '/';
includes ~= path;
init();
addVFS(new FileVFS(path));
}
// Search for a file in the various paths. Returns true if found,
// false otherwise. Changes fname to point to the correct path.
bool findFile(ref char[] fname)
void addVFS(VFS fs) { init(); vfs.add(fs); }
void addVFSFirst(VFS fs) { init(); vfs.addFirst(fs); }
void init()
{
// Check against our include paths. In the future we will replace
// this with a more flexible system, allowing virtual file systems,
// archive files, complete platform independence, improved error
// checking etc.
foreach(path; includes)
if(!initHasRun)
doMonsterInit();
}
// This is called from init(), you don't have to call it yourself.
void doVMInit()
{
assert(vfs is null);
vfs = new ListVFS;
static if(vmAddCWD) addPath("./");
}
ListVFS vfs;
private:
// Load file based on file name, class name, or both. The order of
// the strings doesn't matter, and name2 can be empty. useCase
// determines if we require a case sensitive match between the given
// class name and the loaded name. If doThrow is true, we throw an
// error if the class was not found, otherwise we just return null.
MonsterClass doLoad(char[] name1, char[] name2, bool useCase, bool doThrow)
{
init();
char[] fname, cname;
MonsterClass mc;
if(name1 == "")
fail("Cannot give empty first parameter to load()");
if(name1.iEnds(".mn"))
{
char[] res = path ~ fname;
if(exists(res))
{
fname = res;
return true;
}
fname = name1;
cname = name2;
}
else
{
fname = name2;
cname = name1;
}
return false;
if(cname.iEnds(".mn"))
fail("load() recieved two filenames: " ~ fname ~ " and " ~ cname);
// The filename must either be empty, or end with .mn
if(fname != "" && !fname.iEnds(".mn"))
fail("Neither " ~ name1 ~ " nor " ~ name2 ~
" is a valid script filename.");
// Remember if cname was originally set
bool cNameSet = (cname != "");
// Was a filename given?
if(fname != "")
{
// Derive the class name from the file name
char[] nameTmp = vfs.getBaseName(fname);
assert(fname.iEnds(".mn"));
nameTmp = fname[0..$-3];
if(!cNameSet)
// No class name given, set it to the derived name
cname = nameTmp;
else
// Both names were given, make sure they match
if(icmp(nameTmp,cname) != 0)
fail(format("Class name %s does not match file name %s",
cname, fname));
}
else
// No filename given. Derive it from the given class name.
fname = tolower(cname) ~ ".mn";
assert(cname != "" && !cname.iEnds(".mn"));
assert(fname.iEnds(".mn"));
bool checkFileName()
{
if(cname.length == 0)
return false;
if(!validFirstIdentChar(cname[0]))
return false;
foreach(char c; cname)
if(!validIdentChar(c)) return false;
return true;
}
if(!checkFileName())
fail(format("Invalid class name %s (file %s)", cname, fname));
// At this point, check if the class already exists.
if(global.ciInList(cname, mc))
{
// Match!
assert(mc !is null);
// If the class name was given, we must have an exact match.
if(cNameSet && (cname != mc.name.str))
fail(format("Searched for %s but could only find case insensitive match %s",
cname, mc.name.str));
// All is good, return the class.
return mc;
}
// No existing class. Search for the script file.
if(!vfs.has(fname))
{
if(doThrow)
fail("Cannot find script file " ~ fname);
else return null;
}
// Create a temporary file stream and load it
auto bf = vfs.open(fname);
auto ef = new EndianStream(bf);
int bom = ef.readBOM();
mc = new MonsterClass(-14);
mc.parse(ef, fname, bom);
delete bf;
// After the class is loaded, we can check its real name.
// If the name matches, we're done.
if(cname == mc.name.str) return mc;
// Allow a case insensitive match if useCase is false or the name
// was not given.
if((!useCase || !cNameSet) && (icmp(cname, mc.name.str) == 0)) return mc;
// Oops, name mismatch
fail(format("%s: Expected class name %s does not match loaded name %s",
fname, cname, mc.name.str));
assert(0);
}
}

View file

@ -24,7 +24,7 @@
// Covers all alchemy apparatus - mortars, retorts, etc
class Apparatus : InventoryItem;
enum AppaType
enum AppaType : char[] altName
{
MortarPestle = 0 : "Mortar and Pestle",
Albemic = 1,

View file

@ -35,9 +35,6 @@ import std.string;
// Set up the base Monster classes we need in OpenMW
void initMonsterScripts()
{
initAllModules();
setSleepMethod(SleepMethod.Timer);
// Add the script directories
vm.addPath("mscripts/");
vm.addPath("mscripts/gameobjects/");
@ -48,7 +45,7 @@ void initMonsterScripts()
global.registerImport("io", "random", "timer");
// Get the Config singleton object
config.mo = (new MonsterClass("Config")).getSing();
config.mo = vm.load("Config").getSing();
// Set up the GUI Monster module
setupGUIScripts();

View file

@ -24,6 +24,7 @@
module ogre.gui;
import monster.monster;
import monster.vm.mclass;
import ogre.bindings;
import std.string;
@ -232,8 +233,8 @@ void setupGUIScripts()
{
vm.addPath("mscripts/gui/");
vm.addPath("mscripts/gui/module/");
gmc = new MonsterClass("gui", "gui.mn");
wid_mc = new MonsterClass("Widget", "widget.mn");
gmc = vm.load("gui", "gui.mn");
wid_mc = vm.load("Widget", "widget.mn");
/*
but_mc = new MonsterClass("Button", "button.mn");
tex_mc = new MonsterClass("Text", "text.mn");

View file

@ -12,7 +12,7 @@ void loadGameSettings()
{
// Load the GameSettings Monster class, and get the singleton
// instance
MonsterClass mc = MonsterClass.find("GMST");
MonsterClass mc = vm.load("GMST");
gmstObj = mc.getSing();
foreach(a, b; gameSettings.names)

View file

@ -68,7 +68,7 @@ struct Music
assert(controlC is null);
assert(controlM is null);
jukeC = MonsterClass.get("Jukebox");
jukeC = vm.load("Jukebox");
jukeC.bind("waitUntilFinished",
new Idle_waitUntilFinished);
@ -79,7 +79,7 @@ struct Music
jukeC.bindConst({new Jukebox(params.obj()); });
controlC = MonsterClass.get("Music");
controlC = vm.load("Music");
controlM = controlC.getSing();
controlM.call("setup");
}