mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-02-28 10:39: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:
parent
8dd3bfa896
commit
600583cb89
40 changed files with 3878 additions and 1292 deletions
|
@ -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);
|
||||
|
|
|
@ -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); }
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
422
monster/modules/console.d
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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(); });
|
||||
|
|
|
@ -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)); });
|
||||
|
|
|
@ -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); });
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
179
monster/modules/vfs.d
Normal 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)); }
|
||||
}
|
|
@ -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
150
monster/options.d
Normal 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
150
monster/options.openmw
Normal 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;
|
|
@ -9,3 +9,5 @@ for a in $(find -iname \*.d); do
|
|||
done
|
||||
|
||||
svn st
|
||||
|
||||
svn diff options.openmw options.d
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
121
monster/vm/init.d
Normal 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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
332
monster/vm/vm.d
332
monster/vm/vm.d
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue