mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-02-28 16:09:41 +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
|
// Set up a prototype object
|
||||||
if(mc is null)
|
if(mc is null)
|
||||||
mc = MonsterClass.find(clsName);
|
mc = vm.load(clsName);
|
||||||
proto = mc.createObject();
|
proto = mc.createObject();
|
||||||
|
|
||||||
proto.setString8("id", id);
|
proto.setString8("id", id);
|
||||||
|
|
|
@ -381,10 +381,11 @@ struct Assembler
|
||||||
addi(func);
|
addi(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
void newObj(int i)
|
void newObj(int clsIndex, int params)
|
||||||
{
|
{
|
||||||
cmd(BC.New);
|
cmd(BC.New);
|
||||||
addi(i);
|
addi(clsIndex);
|
||||||
|
addi(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cloneObj() { cmd(BC.Clone); }
|
void cloneObj() { cmd(BC.Clone); }
|
||||||
|
@ -721,8 +722,8 @@ struct Assembler
|
||||||
|
|
||||||
void mov(int s)
|
void mov(int s)
|
||||||
{
|
{
|
||||||
if(s < 3) cmd2(BC.StoreRet, BC.StoreRet8, s);
|
if(s < 3) cmd2(BC.Store, BC.Store8, s);
|
||||||
else cmdmult(BC.StoreRet, BC.StoreRetMult, s);
|
else cmdmult(BC.Store, BC.StoreMult, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set object state to the given index
|
// Set object state to the given index
|
||||||
|
@ -734,6 +735,38 @@ struct Assembler
|
||||||
addi(cls);
|
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
|
// Fetch an element from an array
|
||||||
void fetchElement() { cmd(BC.FetchElem); }
|
void fetchElement() { cmd(BC.FetchElem); }
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,21 @@ abstract class Block
|
||||||
return true;
|
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)
|
static void reqNext(ref TokenArray toks, TT type, out Token tok)
|
||||||
{
|
{
|
||||||
if(!isNext(toks, type, tok))
|
if(!isNext(toks, type, tok))
|
||||||
|
|
|
@ -68,9 +68,23 @@ enum BC
|
||||||
// index must also be -1, and the class index
|
// index must also be -1, and the class index
|
||||||
// is ignored.
|
// 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
|
New, // Create a new object. Followed by an int
|
||||||
// giving the class index (in the file lookup
|
// 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
|
Clone, // Clones an object - create a new object of
|
||||||
// the same class, then copy variable values
|
// the same class, then copy variable values
|
||||||
|
@ -136,19 +150,15 @@ enum BC
|
||||||
// stack. Equivalent to: a=pop; push a; push
|
// stack. Equivalent to: a=pop; push a; push
|
||||||
// a;
|
// a;
|
||||||
|
|
||||||
StoreRet, // Basic operation for moving data to
|
Store, // Basic operation for moving data to
|
||||||
// memory. Schematically pops a Ptr of the
|
// memory. Schematically pops a Ptr of the
|
||||||
// stack, pops a value, moves the value into
|
// 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
|
Store8, // Same as Store except two ints are popped
|
||||||
// value back. Not implemented, but will later
|
|
||||||
// replace storeret completely.
|
|
||||||
|
|
||||||
StoreRet8, // Same as StoreRet except two ints are popped
|
|
||||||
// from the stack and moved into the data.
|
// 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
|
IAdd, // Standard addition, operates on the two next
|
||||||
// ints in the stack, and stores the result in
|
// ints in the stack, and stores the result in
|
||||||
|
@ -275,12 +285,11 @@ enum BC
|
||||||
// pushed as 1 then 2.
|
// pushed as 1 then 2.
|
||||||
|
|
||||||
CopyArray, // Pops two array indices from the stack, and
|
CopyArray, // Pops two array indices from the stack, and
|
||||||
// copies the data from one to another. Pushes
|
// copies the data from one to another. The
|
||||||
// back the array index of the
|
// destination array is popped first, then the
|
||||||
// destination. The destination array is
|
// source. The lengths must match. The arrays
|
||||||
// popped first, then the source. The lengths
|
// may overlap in memory without unexpected
|
||||||
// must match. If the arrays may overlap in
|
// effects.
|
||||||
// memory without unexpected effects.
|
|
||||||
|
|
||||||
DupArray, // Pops an array index of the stack, creates a
|
DupArray, // Pops an array index of the stack, creates a
|
||||||
// copy of the array, and pushes the index of
|
// copy of the array, and pushes the index of
|
||||||
|
@ -298,10 +307,9 @@ enum BC
|
||||||
// new array that is a slice of the original.
|
// new array that is a slice of the original.
|
||||||
|
|
||||||
FillArray, // Fill an array. Pop an array index, then a
|
FillArray, // Fill an array. Pop an array index, then a
|
||||||
// value (int). Sets all the elements in the
|
// value. Sets all the elements in the array
|
||||||
// array to the value. Pushes the array index
|
// to the value. Takes an int specifying the
|
||||||
// back. Takes an int specifying the element
|
// element/value size.
|
||||||
// size.
|
|
||||||
|
|
||||||
CatArray, // Concatinate two arrays, on the stack.
|
CatArray, // Concatinate two arrays, on the stack.
|
||||||
|
|
||||||
|
@ -464,7 +472,8 @@ int codePtr(PT type, int index)
|
||||||
t.type = type;
|
t.type = type;
|
||||||
t.val24 = index;
|
t.val24 = index;
|
||||||
|
|
||||||
assert(t.remains == 0);
|
assert((index >= 0 && t.remains == 0) ||
|
||||||
|
(index < 0 && t.remains == 255));
|
||||||
|
|
||||||
return t.val32;
|
return t.val32;
|
||||||
}
|
}
|
||||||
|
@ -474,6 +483,13 @@ void decodePtr(int ptr, out PT type, out int index)
|
||||||
_CodePtr t;
|
_CodePtr t;
|
||||||
t.val32 = ptr;
|
t.val32 = ptr;
|
||||||
|
|
||||||
|
// Manage negative numbers
|
||||||
|
if(t.val24 >= 0x800000)
|
||||||
|
{
|
||||||
|
t.remains = 255;
|
||||||
|
assert(t.val24 < 0);
|
||||||
|
}
|
||||||
|
|
||||||
type = cast(PT) t.type;
|
type = cast(PT) t.type;
|
||||||
index = t.val24;
|
index = t.val24;
|
||||||
|
|
||||||
|
@ -506,6 +522,10 @@ char[][] bcToString =
|
||||||
BC.ReturnVal: "ReturnVal",
|
BC.ReturnVal: "ReturnVal",
|
||||||
BC.ReturnValN: "ReturnValN",
|
BC.ReturnValN: "ReturnValN",
|
||||||
BC.State: "State",
|
BC.State: "State",
|
||||||
|
BC.EnumValue: "EnumValue",
|
||||||
|
BC.EnumField: "EnumField",
|
||||||
|
BC.EnumValToIndex: "EnumValToIndex",
|
||||||
|
BC.EnumNameToIndex: "EnumNameToIndex",
|
||||||
BC.New: "New",
|
BC.New: "New",
|
||||||
BC.Jump: "Jump",
|
BC.Jump: "Jump",
|
||||||
BC.JumpZ: "JumpZ",
|
BC.JumpZ: "JumpZ",
|
||||||
|
@ -525,10 +545,9 @@ char[][] bcToString =
|
||||||
BC.Pop: "Pop",
|
BC.Pop: "Pop",
|
||||||
BC.PopN: "PopN",
|
BC.PopN: "PopN",
|
||||||
BC.Dup: "Dup",
|
BC.Dup: "Dup",
|
||||||
BC.StoreRet: "StoreRet",
|
|
||||||
BC.Store: "Store",
|
BC.Store: "Store",
|
||||||
BC.StoreRet8: "StoreRet8",
|
BC.Store8: "Store8",
|
||||||
BC.StoreRetMult: "StoreRetMult",
|
BC.StoreMult: "StoreMult",
|
||||||
BC.FetchElem: "FetchElem",
|
BC.FetchElem: "FetchElem",
|
||||||
BC.GetArrLen: "GetArrLen",
|
BC.GetArrLen: "GetArrLen",
|
||||||
BC.IMul: "IMul",
|
BC.IMul: "IMul",
|
||||||
|
|
|
@ -25,43 +25,109 @@ module monster.compiler.enums;
|
||||||
|
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
import monster.compiler.types;
|
import monster.compiler.types;
|
||||||
|
import monster.compiler.expression;
|
||||||
import monster.compiler.statement;
|
import monster.compiler.statement;
|
||||||
import monster.compiler.tokenizer;
|
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
|
class EnumDeclaration : TypeDeclaration
|
||||||
{
|
{
|
||||||
static bool canParse(TokenArray toks)
|
static bool canParse(TokenArray toks)
|
||||||
{ return toks.isNext(TT.Enum); }
|
{ return toks.isNext(TT.Enum); }
|
||||||
|
|
||||||
Token name;
|
|
||||||
EnumType type;
|
EnumType type;
|
||||||
|
|
||||||
override:
|
override:
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
|
type = new EnumType();
|
||||||
|
|
||||||
reqNext(toks, TT.Enum);
|
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);
|
reqNext(toks, TT.LeftCurl);
|
||||||
|
|
||||||
// Just skip everything until the matching }. This lets us
|
type.name = type.nameTok.str;
|
||||||
// define some enums and play around in the scripts, even if it
|
type.loc = type.nameTok.loc;
|
||||||
// doesn't actually work.
|
|
||||||
while(!isNext(toks, TT.RightCurl)) next(toks);
|
// 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);
|
isNext(toks, TT.Semicolon);
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertType(TFVScope last)
|
void insertType(TFVScope last)
|
||||||
{
|
{
|
||||||
type = new EnumType(this);
|
|
||||||
|
|
||||||
// Insert ourselves into the parent scope
|
// Insert ourselves into the parent scope
|
||||||
assert(last !is null);
|
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)
|
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.types;
|
||||||
import monster.compiler.assembler;
|
import monster.compiler.assembler;
|
||||||
import monster.compiler.block;
|
import monster.compiler.block;
|
||||||
|
import monster.compiler.statement;
|
||||||
import monster.compiler.variables;
|
import monster.compiler.variables;
|
||||||
import monster.compiler.functions;
|
import monster.compiler.functions;
|
||||||
|
|
||||||
|
@ -87,7 +88,13 @@ abstract class Expression : Block
|
||||||
else if(isNext(toks, TT.Semicolon, ln))
|
else if(isNext(toks, TT.Semicolon, ln))
|
||||||
fail("Use {} for empty statements, not ;", 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);
|
b.parse(toks);
|
||||||
|
|
||||||
|
@ -175,19 +182,13 @@ abstract class Expression : Block
|
||||||
Floc loc;
|
Floc loc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operators handled below. Don't really need a special function for
|
// Operators handled below.
|
||||||
// this...
|
|
||||||
static bool getNextOp(ref TokenArray toks, ref Token t)
|
static bool getNextOp(ref TokenArray toks, ref Token t)
|
||||||
{
|
{
|
||||||
if(toks.length == 0) return false;
|
if(toks.length == 0) return false;
|
||||||
TT tt = toks[0].type;
|
TT tt = toks[0].type;
|
||||||
if(/*tt == TT.Dot || */tt == TT.Equals || tt == TT.Plus ||
|
if(tt == TT.Plus || tt == TT.Minus || tt == TT.Mult ||
|
||||||
tt == TT.Minus || tt == TT.Mult || tt == TT.Div ||
|
tt == TT.Div || tt == TT.Rem || tt == TT.IDiv ||
|
||||||
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 ||
|
|
||||||
|
|
||||||
tt == TT.Cat ||
|
tt == TT.Cat ||
|
||||||
|
|
||||||
|
@ -257,12 +258,8 @@ abstract class Expression : Block
|
||||||
|
|
||||||
// Create the compound expression. Replace the right node,
|
// Create the compound expression. Replace the right node,
|
||||||
// since it already has the correct operator.
|
// since it already has the correct operator.
|
||||||
if(assign)
|
assert(!assign);
|
||||||
right.value.exp = new AssignOperator(left.value.exp,
|
if(boolop)
|
||||||
right.value.exp,
|
|
||||||
left.value.nextOp,
|
|
||||||
left.value.loc);
|
|
||||||
else if(boolop)
|
|
||||||
right.value.exp = new BooleanOperator(left.value.exp,
|
right.value.exp = new BooleanOperator(left.value.exp,
|
||||||
right.value.exp,
|
right.value.exp,
|
||||||
left.value.nextOp,
|
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
|
// Now sort through the operators according to precedence. This
|
||||||
// is the precedence I use, it should be ok. (Check it against
|
// is the precedence I use, it should be ok. (Check it against
|
||||||
// something else)
|
// something else)
|
||||||
|
@ -368,11 +345,6 @@ abstract class Expression : Block
|
||||||
|
|
||||||
findBool(TT.Or, TT.And);
|
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");
|
assert(exprList.length == 1, "Cannot reduce expression list to one element");
|
||||||
return exprList.getHead().value.exp;
|
return exprList.getHead().value.exp;
|
||||||
}
|
}
|
||||||
|
@ -465,8 +437,15 @@ class TypeofExpression : Expression
|
||||||
type = tt.getBase().getMeta();
|
type = tt.getBase().getMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't actually produce anything
|
// We can always determine the type at compile time
|
||||||
void evalAsm() {}
|
bool isCTime() { return true; }
|
||||||
|
|
||||||
|
int[] evalCTime() { return null; }
|
||||||
|
|
||||||
|
char[] toString()
|
||||||
|
{
|
||||||
|
return "typeof(" ~ tt.exp.toString ~ ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// new-expressions, ie. (new Sometype[]).
|
// new-expressions, ie. (new Sometype[]).
|
||||||
|
@ -485,6 +464,9 @@ class NewExpression : Expression
|
||||||
|
|
||||||
CIndex clsInd;
|
CIndex clsInd;
|
||||||
|
|
||||||
|
NamedParam[] params;
|
||||||
|
Variable* varlist[];
|
||||||
|
|
||||||
static bool canParse(TokenArray toks)
|
static bool canParse(TokenArray toks)
|
||||||
{ return isNext(toks, TT.New); }
|
{ return isNext(toks, TT.New); }
|
||||||
|
|
||||||
|
@ -492,16 +474,38 @@ class NewExpression : Expression
|
||||||
{
|
{
|
||||||
reqNext(toks, TT.New, loc);
|
reqNext(toks, TT.New, loc);
|
||||||
type = Type.identify(toks, true, exArr);
|
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()
|
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)
|
void resolve(Scope sc)
|
||||||
{
|
{
|
||||||
type.resolve(sc);
|
type.resolve(sc);
|
||||||
|
MonsterClass mc;
|
||||||
|
|
||||||
if(type.isReplacer)
|
if(type.isReplacer)
|
||||||
type = type.getBase();
|
type = type.getBase();
|
||||||
|
@ -510,7 +514,7 @@ class NewExpression : Expression
|
||||||
{
|
{
|
||||||
// We need to find the index associated with this class, and
|
// We need to find the index associated with this class, and
|
||||||
// pass it to the assembler.
|
// pass it to the assembler.
|
||||||
auto mc = (cast(ObjectType)type).getClass();
|
mc = (cast(ObjectType)type).getClass();
|
||||||
assert(mc !is null);
|
assert(mc !is null);
|
||||||
clsInd = mc.getIndex();
|
clsInd = mc.getIndex();
|
||||||
|
|
||||||
|
@ -545,9 +549,7 @@ class NewExpression : Expression
|
||||||
e.resolve(sc);
|
e.resolve(sc);
|
||||||
|
|
||||||
// Check the type
|
// Check the type
|
||||||
try intType.typeCast(e);
|
intType.typeCast(e, "array index");
|
||||||
catch(TypeException)
|
|
||||||
fail("Cannot convert array index " ~ e.toString ~ " to int", loc);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -576,6 +578,33 @@ class NewExpression : Expression
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
fail("Cannot use 'new' with type " ~ type.toString, loc);
|
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()
|
void evalAsm()
|
||||||
|
@ -584,12 +613,28 @@ class NewExpression : Expression
|
||||||
|
|
||||||
if(type.isObject)
|
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
|
// Create a new object. This is done through a special byte code
|
||||||
// instruction.
|
// instruction.
|
||||||
tasm.newObj(clsInd);
|
tasm.newObj(clsInd, params.length);
|
||||||
}
|
}
|
||||||
else if(type.isArray)
|
else if(type.isArray)
|
||||||
{
|
{
|
||||||
|
assert(params.length == 0);
|
||||||
|
|
||||||
int initVal[] = baseType.defaultInit();
|
int initVal[] = baseType.defaultInit();
|
||||||
|
|
||||||
int rank = exArr.length; // Array nesting level
|
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
|
// Check that all elements are of a usable type, and convert
|
||||||
// if necessary.
|
// if necessary.
|
||||||
try base.typeCast(par);
|
base.typeCast(par, "array element");
|
||||||
catch(TypeException)
|
|
||||||
fail(format("Cannot convert %s of type %s to type %s", par,
|
|
||||||
par.typeString(), params[0].typeString()), getLoc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cls = sc.getClass();
|
cls = sc.getClass();
|
||||||
|
@ -730,7 +772,7 @@ class ArrayLiteralExpr : Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expression representing a literal or other special single-token
|
// Expression representing a literal or other special single-token
|
||||||
// values. Supported tokens are StringLiteral, NumberLiteral,
|
// values. Supported tokens are StringLiteral, Int/FloatLiteral,
|
||||||
// CharLiteral, True, False, Null, Dollar and This. Array literals are
|
// CharLiteral, True, False, Null, Dollar and This. Array literals are
|
||||||
// handled by ArrayLiteralExpr.
|
// handled by ArrayLiteralExpr.
|
||||||
class LiteralExpr : Expression
|
class LiteralExpr : Expression
|
||||||
|
@ -773,7 +815,8 @@ class LiteralExpr : Expression
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
isNext(toks, TT.StringLiteral) ||
|
isNext(toks, TT.StringLiteral) ||
|
||||||
isNext(toks, TT.NumberLiteral) ||
|
isNext(toks, TT.IntLiteral) ||
|
||||||
|
isNext(toks, TT.FloatLiteral) ||
|
||||||
isNext(toks, TT.CharLiteral) ||
|
isNext(toks, TT.CharLiteral) ||
|
||||||
isNext(toks, TT.True) ||
|
isNext(toks, TT.True) ||
|
||||||
isNext(toks, TT.False) ||
|
isNext(toks, TT.False) ||
|
||||||
|
@ -795,6 +838,14 @@ class LiteralExpr : Expression
|
||||||
|
|
||||||
bool isRes = false;
|
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)
|
void resolve(Scope sc)
|
||||||
{
|
{
|
||||||
isRes = true;
|
isRes = true;
|
||||||
|
@ -811,23 +862,20 @@ class LiteralExpr : Expression
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeric literal.
|
// Numeric literals
|
||||||
if(value.type == TT.NumberLiteral)
|
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;
|
type = BasicType.getInt;
|
||||||
ival = atoi(value.str);
|
ival = parseIntLiteral(value);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Floats
|
||||||
|
if(value.type == TT.FloatLiteral)
|
||||||
|
{
|
||||||
|
type = BasicType.getFloat;
|
||||||
|
fval = atof(value.str);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The $ token. Only allowed in array indices.
|
// The $ token. Only allowed in array indices.
|
||||||
|
@ -1008,7 +1056,7 @@ class CastExpression : Expression
|
||||||
int[] evalCTime()
|
int[] evalCTime()
|
||||||
{
|
{
|
||||||
// Let the type do the conversion
|
// Let the type do the conversion
|
||||||
int[] res = type.typeCastCTime(orig);
|
int[] res = type.typeCastCTime(orig, "");
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -1040,12 +1088,17 @@ class ImportHolder : Expression
|
||||||
{
|
{
|
||||||
mc = pmc;
|
mc = pmc;
|
||||||
|
|
||||||
|
// The imported class must be resolved at this point
|
||||||
|
mc.requireScope();
|
||||||
|
|
||||||
// Importing singletons and modules is like importing
|
// Importing singletons and modules is like importing
|
||||||
// the object itself
|
// the object itself
|
||||||
if(mc.isSingleton)
|
if(mc.isSingleton)
|
||||||
type = mc.objType;
|
type = mc.objType;
|
||||||
else
|
else
|
||||||
type = mc.classType;
|
type = mc.classType;
|
||||||
|
|
||||||
|
assert(type !is null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All lookups in this import is done through this function. Can be
|
// All lookups in this import is done through this function. Can be
|
||||||
|
@ -1066,6 +1119,7 @@ class ImportHolder : Expression
|
||||||
|
|
||||||
void evalAsm()
|
void evalAsm()
|
||||||
{
|
{
|
||||||
|
mc.requireCompile();
|
||||||
if(mc.isSingleton)
|
if(mc.isSingleton)
|
||||||
{
|
{
|
||||||
assert(type.isObject);
|
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.stream;
|
||||||
import std.string;
|
import std.string;
|
||||||
|
|
||||||
|
// TODO/FIXME: Make tango compatible before release
|
||||||
|
import std.traits;
|
||||||
|
|
||||||
// One problem with these split compiler / vm classes is that we
|
// 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
|
// 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
|
// messy interface. The problem with splitting is that we duplicate
|
||||||
|
@ -94,6 +97,7 @@ struct Function
|
||||||
Type type; // Return type
|
Type type; // Return type
|
||||||
FuncType ftype; // Function type
|
FuncType ftype; // Function type
|
||||||
Variable* params[]; // List of parameters
|
Variable* params[]; // List of parameters
|
||||||
|
int[][] defaults; // Default parameter values (if specified, null otherwise)
|
||||||
int index; // Unique function identifier within its class
|
int index; // Unique function identifier within its class
|
||||||
|
|
||||||
int paramSize;
|
int paramSize;
|
||||||
|
@ -126,6 +130,108 @@ struct Function
|
||||||
// this is a function that takes a variable number of arguments.
|
// this is a function that takes a variable number of arguments.
|
||||||
bool isVararg() { return params.length && params[$-1].isVararg; }
|
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
|
// This is used to call the given function from native code. Note
|
||||||
// that this is used internally for native functions, but not for
|
// that this is used internally for native functions, but not for
|
||||||
// any other type. Idle functions can NOT be called directly from
|
// 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.
|
// 'mc'. If no class is given, use an empty internal class.
|
||||||
void compile(char[] file, MonsterClass mc = null)
|
void compile(char[] file, MonsterClass mc = null)
|
||||||
{
|
{
|
||||||
|
vm.init();
|
||||||
|
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
if(!vm.findFile(file))
|
if(!vm.vfs.has(file))
|
||||||
fail("File not found: " ~ file);
|
fail("File not found: " ~ file);
|
||||||
|
|
||||||
// Create the stream and pass it on
|
// Create the stream and pass it on
|
||||||
auto bf = new BufferedFile(file);
|
auto bf = vm.vfs.open(file);
|
||||||
compile(file, bf, mc);
|
compile(file, bf, mc);
|
||||||
delete bf;
|
delete bf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void compile(char[] file, Stream str, MonsterClass mc = null)
|
void compile(char[] file, Stream str, MonsterClass mc = null)
|
||||||
{
|
{
|
||||||
|
vm.init();
|
||||||
|
|
||||||
// Get the BOM and tokenize the stream
|
// Get the BOM and tokenize the stream
|
||||||
auto ef = new EndianStream(str);
|
auto ef = new EndianStream(str);
|
||||||
int bom = ef.readBOM();
|
int bom = ef.readBOM();
|
||||||
|
@ -249,6 +359,8 @@ struct Function
|
||||||
|
|
||||||
void compile(char[] file, ref TokenArray tokens, MonsterClass mc = null)
|
void compile(char[] file, ref TokenArray tokens, MonsterClass mc = null)
|
||||||
{
|
{
|
||||||
|
vm.init();
|
||||||
|
|
||||||
assert(name.str == "",
|
assert(name.str == "",
|
||||||
"Function " ~ name.str ~ " has already been set up");
|
"Function " ~ name.str ~ " has already been set up");
|
||||||
|
|
||||||
|
@ -256,20 +368,9 @@ struct Function
|
||||||
if(MonsterClass.canParse(tokens))
|
if(MonsterClass.canParse(tokens))
|
||||||
fail("Cannot run " ~ file ~ " - it is a class or module.");
|
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(mc is null)
|
||||||
{
|
mc = getIntMC();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto fd = new FuncDeclaration;
|
auto fd = new FuncDeclaration;
|
||||||
// Parse and comile the function
|
// Parse and comile the function
|
||||||
|
@ -283,17 +384,89 @@ struct Function
|
||||||
delete fd;
|
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()
|
// Returns the function name, on the form Class.func()
|
||||||
char[] toString()
|
char[] toString()
|
||||||
{ return owner.name.str ~ "." ~ name.str ~ "()"; }
|
{ return owner.name.str ~ "." ~ name.str ~ "()"; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
// Empty class / object used internally
|
||||||
static const char[] int_class = "class _ScriptFile_;";
|
static const char[] int_class = "class _ScriptFile_;";
|
||||||
static MonsterClass int_mc;
|
static MonsterClass int_mc;
|
||||||
static MonsterObject *int_mo;
|
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.
|
// Responsible for parsing, analysing and compiling functions.
|
||||||
class FuncDeclaration : Statement
|
class FuncDeclaration : Statement
|
||||||
{
|
{
|
||||||
|
@ -405,7 +578,7 @@ class FuncDeclaration : Statement
|
||||||
// In any case, parse the rest of the declaration
|
// In any case, parse the rest of the declaration
|
||||||
parseParams(toks);
|
parseParams(toks);
|
||||||
|
|
||||||
isNext(toks, TT.Semicolon);
|
reqSep(toks);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -420,7 +593,7 @@ class FuncDeclaration : Statement
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
// Create a Function struct. Will change later.
|
// Create a Function struct.
|
||||||
fn = new Function;
|
fn = new Function;
|
||||||
|
|
||||||
// Default function type is normal
|
// Default function type is normal
|
||||||
|
@ -444,8 +617,10 @@ class FuncDeclaration : Statement
|
||||||
|
|
||||||
if(fn.isAbstract || fn.isNative || fn.isIdle)
|
if(fn.isAbstract || fn.isNative || fn.isIdle)
|
||||||
{
|
{
|
||||||
|
reqSep(toks);
|
||||||
// Check that the function declaration ends with a ; rather
|
// Check that the function declaration ends with a ; rather
|
||||||
// than a code block.
|
// than a code block.
|
||||||
|
/*
|
||||||
if(!isNext(toks, TT.Semicolon))
|
if(!isNext(toks, TT.Semicolon))
|
||||||
{
|
{
|
||||||
if(fn.isAbstract)
|
if(fn.isAbstract)
|
||||||
|
@ -456,6 +631,7 @@ class FuncDeclaration : Statement
|
||||||
fail("Idle function declaration expected ;", toks);
|
fail("Idle function declaration expected ;", toks);
|
||||||
else assert(0);
|
else assert(0);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -568,7 +744,7 @@ class FuncDeclaration : Statement
|
||||||
fn.type.resolve(last);
|
fn.type.resolve(last);
|
||||||
|
|
||||||
if(fn.type.isVar)
|
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)
|
if(fn.type.isReplacer)
|
||||||
fn.type = fn.type.getBase();
|
fn.type = fn.type.getBase();
|
||||||
|
@ -581,7 +757,7 @@ class FuncDeclaration : Statement
|
||||||
fn.paramSize = 0;
|
fn.paramSize = 0;
|
||||||
foreach(vd; paramList)
|
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
|
// size
|
||||||
vd.resolveParam(sc);
|
vd.resolveParam(sc);
|
||||||
assert(!vd.var.type.isReplacer);
|
assert(!vd.var.type.isReplacer);
|
||||||
|
@ -674,6 +850,24 @@ class FuncDeclaration : Statement
|
||||||
fail("function " ~ fn.name.str ~
|
fail("function " ~ fn.name.str ~
|
||||||
" doesn't override anything", fn.name.loc);
|
" 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
|
// Resolve the interior of the function
|
||||||
|
@ -685,6 +879,7 @@ class FuncDeclaration : Statement
|
||||||
foreach(p; fn.params)
|
foreach(p; fn.params)
|
||||||
p.type.validate(fn.name.loc);
|
p.type.validate(fn.name.loc);
|
||||||
|
|
||||||
|
// Resolve the function body
|
||||||
if(code !is null)
|
if(code !is null)
|
||||||
code.resolve(sc);
|
code.resolve(sc);
|
||||||
}
|
}
|
||||||
|
@ -716,11 +911,23 @@ class FuncDeclaration : Statement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NamedParam
|
||||||
|
{
|
||||||
|
Token name;
|
||||||
|
Expression value;
|
||||||
|
}
|
||||||
|
|
||||||
// Expression representing a function call
|
// Expression representing a function call
|
||||||
class FunctionCallExpr : MemberExpression
|
class FunctionCallExpr : MemberExpression
|
||||||
{
|
{
|
||||||
Token name;
|
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;
|
Function* fd;
|
||||||
|
|
||||||
bool isVararg;
|
bool isVararg;
|
||||||
|
@ -734,28 +941,50 @@ class FunctionCallExpr : MemberExpression
|
||||||
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
|
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a parameter list (a,b,...)
|
// Read a function parameter list (a,b,v1=c,v2=d,...)
|
||||||
static ExprArray getParams(ref TokenArray toks)
|
static void getParams(ref TokenArray toks,
|
||||||
|
out ExprArray parms,
|
||||||
|
out NamedParam[] named)
|
||||||
{
|
{
|
||||||
ExprArray res;
|
parms = null;
|
||||||
if(!isNext(toks, TT.LeftParen)) return res;
|
named = null;
|
||||||
|
|
||||||
Expression exp;
|
if(!isNext(toks, TT.LeftParen)) return;
|
||||||
|
|
||||||
// No parameters?
|
// No parameters?
|
||||||
if(isNext(toks, TT.RightParen)) return res;
|
if(isNext(toks, TT.RightParen)) return;
|
||||||
|
|
||||||
// Read the first parameter
|
// Read the comma-separated list of parameters
|
||||||
res ~= Expression.identify(toks);
|
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))
|
while(isNext(toks, TT.Comma))
|
||||||
res ~= Expression.identify(toks);
|
|
||||||
|
|
||||||
if(!isNext(toks, TT.RightParen))
|
if(!isNext(toks, TT.RightParen))
|
||||||
fail("Parameter list expected ')'", toks);
|
fail("Parameter list expected ')'", toks);
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
|
@ -763,7 +992,7 @@ class FunctionCallExpr : MemberExpression
|
||||||
name = next(toks);
|
name = next(toks);
|
||||||
loc = name.loc;
|
loc = name.loc;
|
||||||
|
|
||||||
params = getParams(toks);
|
getParams(toks, params, named);
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] toString()
|
char[] toString()
|
||||||
|
@ -804,11 +1033,10 @@ class FunctionCallExpr : MemberExpression
|
||||||
fail("Undefined function "~name.str, name.loc);
|
fail("Undefined function "~name.str, name.loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isVararg = fd.isVararg;
|
||||||
type = fd.type;
|
type = fd.type;
|
||||||
assert(type !is null);
|
assert(type !is null);
|
||||||
|
|
||||||
isVararg = fd.isVararg;
|
|
||||||
|
|
||||||
if(isVararg)
|
if(isVararg)
|
||||||
{
|
{
|
||||||
// The vararg parameter can match a variable number of
|
// 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",
|
fail(format("%s() expected at least %s parameters, got %s",
|
||||||
name.str, fd.params.length-1, params.length),
|
name.str, fd.params.length-1, params.length),
|
||||||
name.loc);
|
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
|
// Check parameter types except for the vararg parameter
|
||||||
foreach(int i, par; fd.params)
|
foreach(int i, par; fd.params[0..$-1])
|
||||||
{
|
{
|
||||||
// Handle varargs below
|
params[i].resolve(sc);
|
||||||
if(isVararg && i == fd.params.length-1)
|
par.type.typeCast(params[i], "parameter " ~ par.name.str);
|
||||||
break;
|
}
|
||||||
|
|
||||||
params[i].resolve(sc);
|
// Loop through remaining arguments
|
||||||
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)
|
|
||||||
{
|
|
||||||
int start = fd.params.length-1;
|
int start = fd.params.length-1;
|
||||||
|
|
||||||
assert(fd.params[start].type.isArray);
|
assert(fd.params[start].type.isArray);
|
||||||
|
@ -853,23 +1063,85 @@ class FunctionCallExpr : MemberExpression
|
||||||
{
|
{
|
||||||
par.resolve(sc);
|
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 type itself, then we are sending an actual
|
||||||
// array. Treat it like a normal parameter.
|
// array. Treat it like a normal parameter.
|
||||||
if(i == 0 && start == params.length-1 &&
|
if(i == 0 && start == params.length-1 &&
|
||||||
par.type == fd.params[start].type)
|
par.type == fd.params[start].type)
|
||||||
{
|
{
|
||||||
isVararg = false;
|
isVararg = false;
|
||||||
|
coverage = params;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, cast the type to the array base type.
|
// Otherwise, cast the type to the array base type.
|
||||||
try base.typeCast(par);
|
base.typeCast(par, "array base type");
|
||||||
catch(TypeException)
|
|
||||||
fail(format("Cannot convert %s of type %s to %s", par.toString,
|
|
||||||
par.typeString, base.toString), par.loc);
|
|
||||||
}
|
}
|
||||||
|
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
|
// 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
|
// in cases like obj.func(expr); Here expr is evaluated first, then
|
||||||
// obj, and then finally the far function call. This is because the
|
// 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.
|
// far function call needs to read 'obj' off the top of the stack.
|
||||||
|
bool pdone;
|
||||||
void evalParams()
|
void evalParams()
|
||||||
{
|
{
|
||||||
assert(pdone == false);
|
assert(pdone == false);
|
||||||
|
pdone = true;
|
||||||
|
|
||||||
foreach(i, ex; params)
|
// Again, let's handle the vararg case separately
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isVararg)
|
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.
|
// Compute the length of the vararg array.
|
||||||
int len = params.length - fd.params.length + 1;
|
int len = params.length - fd.params.length + 1;
|
||||||
|
|
||||||
|
@ -904,23 +1184,48 @@ class FunctionCallExpr : MemberExpression
|
||||||
// (0 is always null).
|
// (0 is always null).
|
||||||
if(len == 0) tasm.push(0);
|
if(len == 0) tasm.push(0);
|
||||||
else
|
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
|
// Non-vararg case
|
||||||
// pushed. This will work for both normal and vararg parameters.
|
assert(!isVararg);
|
||||||
if(fd.params.length != 0 && fd.params[$-1].isConst)
|
assert(coverage.length == fd.params.length);
|
||||||
|
foreach(i, ex; coverage)
|
||||||
{
|
{
|
||||||
assert(fd.params[$-1].type.isArray);
|
if(ex !is null)
|
||||||
tasm.makeArrayConst();
|
{
|
||||||
|
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()
|
void evalAsm()
|
||||||
{
|
{
|
||||||
if(dotImport !is null && recurse)
|
if(dotImport !is null && recurse)
|
||||||
|
|
|
@ -30,10 +30,13 @@ import monster.compiler.tokenizer;
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
import monster.compiler.types;
|
import monster.compiler.types;
|
||||||
import monster.compiler.functions;
|
import monster.compiler.functions;
|
||||||
|
import monster.compiler.enums;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.vm.arrays;
|
import monster.vm.arrays;
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
import std.string;
|
||||||
|
import std.utf;
|
||||||
|
|
||||||
// Handles - ! ++ --
|
// Handles - ! ++ --
|
||||||
class UnaryOperator : Expression
|
class UnaryOperator : Expression
|
||||||
|
@ -199,6 +202,8 @@ class ArrayOperator : OperatorExpr
|
||||||
bool isFill; // Set during assignment if we're filling the array
|
bool isFill; // Set during assignment if we're filling the array
|
||||||
// with one single value
|
// with one single value
|
||||||
|
|
||||||
|
int isEnum; // Used for enum types
|
||||||
|
|
||||||
// name[], name[index], name[index..index2]
|
// name[], name[index], name[index..index2]
|
||||||
Expression name, index, index2;
|
Expression name, index, index2;
|
||||||
|
|
||||||
|
@ -233,6 +238,41 @@ class ArrayOperator : OperatorExpr
|
||||||
// Copy the type of the name expression
|
// Copy the type of the name expression
|
||||||
type = name.type;
|
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.
|
// Check that we are indeed an array.
|
||||||
if(!type.isArray)
|
if(!type.isArray)
|
||||||
fail("Expression '" ~ name.toString ~ "' of type '" ~ name.typeString
|
fail("Expression '" ~ name.toString ~ "' of type '" ~ name.typeString
|
||||||
|
@ -252,17 +292,13 @@ class ArrayOperator : OperatorExpr
|
||||||
|
|
||||||
// The indices must be ints
|
// The indices must be ints
|
||||||
Type tpint = BasicType.getInt();
|
Type tpint = BasicType.getInt();
|
||||||
try tpint.typeCast(index);
|
tpint.typeCast(index, "array index");
|
||||||
catch(TypeException)
|
|
||||||
fail("Cannot convert array index " ~ index.toString ~ " to int");
|
|
||||||
|
|
||||||
if(index2 !is null)
|
if(index2 !is null)
|
||||||
{
|
{
|
||||||
// slice, name[index..index2]
|
// slice, name[index..index2]
|
||||||
index2.resolve(isc);
|
index2.resolve(isc);
|
||||||
try tpint.typeCast(index2);
|
tpint.typeCast(index2, "array index");
|
||||||
catch(TypeException)
|
|
||||||
fail("Cannot convert array index " ~ index2.toString ~ " to int");
|
|
||||||
isSlice = true;
|
isSlice = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -276,6 +312,8 @@ class ArrayOperator : OperatorExpr
|
||||||
|
|
||||||
bool isCTime()
|
bool isCTime()
|
||||||
{
|
{
|
||||||
|
if(isEnum) return index.isCTime;
|
||||||
|
|
||||||
if(isDest) return false;
|
if(isDest) return false;
|
||||||
|
|
||||||
// a[b] and a[b..c]; is compile time if a, b and c is.
|
// a[b] and a[b..c]; is compile time if a, b and c is.
|
||||||
|
@ -288,6 +326,32 @@ class ArrayOperator : OperatorExpr
|
||||||
|
|
||||||
int[] evalCTime()
|
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
|
// Get the array index
|
||||||
int[] arr = name.evalCTime();
|
int[] arr = name.evalCTime();
|
||||||
assert(arr.length == 1);
|
assert(arr.length == 1);
|
||||||
|
@ -331,6 +395,23 @@ class ArrayOperator : OperatorExpr
|
||||||
|
|
||||||
void evalAsm()
|
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
|
// Push the array index first
|
||||||
name.eval();
|
name.eval();
|
||||||
|
|
||||||
|
@ -438,6 +519,9 @@ class DotOperator : OperatorExpr
|
||||||
owner.resolve(sc);
|
owner.resolve(sc);
|
||||||
|
|
||||||
Type ot = owner.type;
|
Type ot = owner.type;
|
||||||
|
assert(ot !is null);
|
||||||
|
|
||||||
|
ot.getMemberScope();
|
||||||
|
|
||||||
if(ot.getMemberScope() is null)
|
if(ot.getMemberScope() is null)
|
||||||
fail(owner.toString() ~ " of type " ~ owner.typeString()
|
fail(owner.toString() ~ " of type " ~ owner.typeString()
|
||||||
|
@ -500,191 +584,92 @@ class DotOperator : OperatorExpr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assignment operators, =, +=, *=, /=, %=, ~=
|
// KILLME
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void evalAsm()
|
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=
|
// Boolean operators: ==, !=, <, >, <=, >=, &&, ||, =i=, =I=, !=i=, !=I=
|
||||||
class BooleanOperator : BinaryOperator
|
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)
|
this(Expression left, Expression right, TT opType, Floc loc)
|
||||||
{ super(left, right, opType, loc); }
|
{ super(left, right, opType, loc); }
|
||||||
|
|
||||||
|
override:
|
||||||
|
|
||||||
void resolve(Scope sc)
|
void resolve(Scope sc)
|
||||||
{
|
{
|
||||||
left.resolve(sc);
|
left.resolve(sc);
|
||||||
right.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
|
// Cast to a common type
|
||||||
try Type.castCommon(left, right);
|
Type.castCommon(left, right);
|
||||||
catch(TypeException)
|
|
||||||
fail("Boolean operator " ~tokenList[opType] ~ " not allowed for types " ~
|
|
||||||
left.typeString() ~ " and " ~ right.typeString(), loc);
|
|
||||||
|
|
||||||
// At this point the types must match
|
// At this point the types must match
|
||||||
assert(left.type == right.type);
|
assert(left.type == right.type);
|
||||||
|
|
||||||
type = BasicType.getBool;
|
|
||||||
|
|
||||||
// TODO: We might allow < and > for strings at some point.
|
// TODO: We might allow < and > for strings at some point.
|
||||||
if(opType == TT.Less || opType == TT.More || opType == TT.LessEq ||
|
if(opType == TT.Less || opType == TT.More || opType == TT.LessEq ||
|
||||||
opType == TT.MoreEq)
|
opType == TT.MoreEq)
|
||||||
|
@ -724,6 +709,10 @@ class BooleanOperator : BinaryOperator
|
||||||
// static initialization.
|
// static initialization.
|
||||||
bool isCTime()
|
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.
|
// Only compute && and || for now, add the rest later.
|
||||||
if(!(opType == TT.And || opType == TT.Or)) return false;
|
if(!(opType == TT.And || opType == TT.Or)) return false;
|
||||||
|
|
||||||
|
@ -744,34 +733,15 @@ class BooleanOperator : BinaryOperator
|
||||||
return false;
|
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()
|
int[] evalCTime()
|
||||||
{
|
{
|
||||||
if(opType == TT.And)
|
if(opType == TT.And)
|
||||||
ctimeval = !(leftIs(false) || rightIs(false));
|
ctimeval = !(leftIs(false) || rightIs(false));
|
||||||
else if(opType == TT.Or)
|
else if(opType == TT.Or)
|
||||||
ctimeval = (leftIs(true) || rightIs(true));
|
ctimeval = (leftIs(true) || rightIs(true));
|
||||||
|
else
|
||||||
|
// For meta types, ctimeval is already set
|
||||||
|
assert(isMeta);
|
||||||
|
|
||||||
return (cast(int*)&ctimeval)[0..1];
|
return (cast(int*)&ctimeval)[0..1];
|
||||||
}
|
}
|
||||||
|
@ -939,7 +909,7 @@ class BinaryOperator : OperatorExpr
|
||||||
// Element with array?
|
// Element with array?
|
||||||
if(right.type.isArray && left.type.canCastOrEqual(right.type.getBase()))
|
if(right.type.isArray && left.type.canCastOrEqual(right.type.getBase()))
|
||||||
{
|
{
|
||||||
right.type.getBase().typeCast(left);
|
right.type.getBase().typeCast(left, "");
|
||||||
type = right.type;
|
type = right.type;
|
||||||
cat = CatElem.Left;
|
cat = CatElem.Left;
|
||||||
return;
|
return;
|
||||||
|
@ -948,7 +918,7 @@ class BinaryOperator : OperatorExpr
|
||||||
// Array with element?
|
// Array with element?
|
||||||
if(left.type.isArray && right.type.canCastOrEqual(left.type.getBase()))
|
if(left.type.isArray && right.type.canCastOrEqual(left.type.getBase()))
|
||||||
{
|
{
|
||||||
left.type.getBase().typeCast(right);
|
left.type.getBase().typeCast(right, "");
|
||||||
type = left.type;
|
type = left.type;
|
||||||
cat = CatElem.Right;
|
cat = CatElem.Right;
|
||||||
return;
|
return;
|
||||||
|
@ -963,14 +933,11 @@ class BinaryOperator : OperatorExpr
|
||||||
// case correctly.)
|
// case correctly.)
|
||||||
|
|
||||||
// Cast to a common type
|
// Cast to a common type
|
||||||
bool doFail = false;
|
Type.castCommon(left, right);
|
||||||
try Type.castCommon(left, right);
|
|
||||||
catch(TypeException)
|
|
||||||
doFail = true;
|
|
||||||
|
|
||||||
type = right.type;
|
type = right.type;
|
||||||
|
|
||||||
if(!type.isNumerical || doFail)
|
if(!type.isNumerical)
|
||||||
fail("Operator " ~tokenList[opType] ~ " not allowed for types " ~
|
fail("Operator " ~tokenList[opType] ~ " not allowed for types " ~
|
||||||
left.typeString() ~ " and " ~ right.typeString(), loc);
|
left.typeString() ~ " and " ~ right.typeString(), loc);
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,17 @@ abstract class SimplePropertyScope : PropertyScope
|
||||||
void inserts(char[] name, char[] tp, Action push)
|
void inserts(char[] name, char[] tp, Action push)
|
||||||
{ inserts(name, getType(tp), 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:
|
override:
|
||||||
|
|
||||||
// Return the stored type. If it is null, return the owner type
|
// Return the stored type. If it is null, return the owner type
|
||||||
|
|
|
@ -45,11 +45,11 @@ import monster.vm.error;
|
||||||
import monster.vm.vm;
|
import monster.vm.vm;
|
||||||
|
|
||||||
// The global scope
|
// The global scope
|
||||||
PackageScope global;
|
RootScope global;
|
||||||
|
|
||||||
void initScope()
|
void initScope()
|
||||||
{
|
{
|
||||||
global = new PackageScope(null, "global");
|
global = new RootScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
// List all identifier types
|
// List all identifier types
|
||||||
|
@ -170,28 +170,6 @@ abstract class Scope
|
||||||
// global scope.
|
// global scope.
|
||||||
Scope parent;
|
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(),
|
// Made protected since it is so easy to confuse with isStateCode(),
|
||||||
// and we never actually need it anywhere outside this file.
|
// and we never actually need it anywhere outside this file.
|
||||||
bool isState() { return false; }
|
bool isState() { return false; }
|
||||||
|
@ -219,6 +197,28 @@ abstract class Scope
|
||||||
importList = parent.importList;
|
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?
|
// Is this the root scope?
|
||||||
final bool isRoot()
|
final bool isRoot()
|
||||||
{
|
{
|
||||||
|
@ -342,7 +342,7 @@ abstract class Scope
|
||||||
void registerImport(char[][] cls ...)
|
void registerImport(char[][] cls ...)
|
||||||
{
|
{
|
||||||
foreach(c; cls)
|
foreach(c; cls)
|
||||||
registerImport(MonsterClass.find(c));
|
registerImport(vm.load(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for summing up stack level. Redeclared in StackScope.
|
// Used for summing up stack level. Redeclared in StackScope.
|
||||||
|
@ -437,24 +437,73 @@ final class StateScope : Scope
|
||||||
bool isState() { return true; }
|
bool isState() { return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// A package scope is a scope that can contain classes.
|
final class RootScope : PackageScope
|
||||||
final class PackageScope : Scope
|
|
||||||
{
|
{
|
||||||
|
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
|
// List of classes in this package. This is case insensitive, so we
|
||||||
// can look up file names too.
|
// can look up file names too.
|
||||||
HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes;
|
HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes;
|
||||||
|
|
||||||
// Lookup by integer index. TODO: This should be in a global scope
|
public:
|
||||||
// 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;
|
|
||||||
|
|
||||||
this(Scope last, char[] name)
|
this(Scope last, char[] name)
|
||||||
{
|
{
|
||||||
super(last, name);
|
super(last, name);
|
||||||
|
@ -463,7 +512,6 @@ final class PackageScope : Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPackage() { return true; }
|
bool isPackage() { return true; }
|
||||||
bool allowRoot() { return true; }
|
|
||||||
|
|
||||||
// Insert a new class into the scope. The class is given a unique
|
// Insert a new class into the scope. The class is given a unique
|
||||||
// global index. If the class was previously forward referenced, the
|
// global index. If the class was previously forward referenced, the
|
||||||
|
@ -476,7 +524,7 @@ final class PackageScope : Scope
|
||||||
|
|
||||||
// Are we already in the list?
|
// Are we already in the list?
|
||||||
MonsterClass c2;
|
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.
|
// 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.
|
// referenced, then an index has already been assigned.
|
||||||
CIndex ci;
|
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
|
// ci is set, remove the entry from the forwards hashmap
|
||||||
assert(ci != 0);
|
assert(ci != 0);
|
||||||
forwards.remove(cls.name.str);
|
global.forwards.remove(cls.name.str);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
// Get a new index
|
// 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
|
// Assign the index and insert class into both lists
|
||||||
cls.gIndex = ci;
|
cls.gIndex = ci;
|
||||||
classes[cls.name.str] = cls;
|
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,
|
// Case insensitive lookups. Used for comparing with file names,
|
||||||
|
@ -548,20 +596,10 @@ final class PackageScope : Scope
|
||||||
return mc;
|
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)
|
override ScopeLookup lookup(Token name)
|
||||||
{
|
{
|
||||||
// Type names can never be overwritten, so we check findClass
|
// Type names can never be overwritten, so we check the class
|
||||||
// and the built-in types. We might move the builtin type check
|
// list and the built-in types.
|
||||||
// to a "global" scope at some point.
|
|
||||||
if(BasicType.isBasic(name.str))
|
if(BasicType.isBasic(name.str))
|
||||||
return ScopeLookup(name, LType.Type, BasicType.get(name.str), this);
|
return ScopeLookup(name, LType.Type, BasicType.get(name.str), this);
|
||||||
|
|
||||||
|
@ -573,81 +611,6 @@ final class PackageScope : Scope
|
||||||
assert(isRoot());
|
assert(isRoot());
|
||||||
return super.lookup(name);
|
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.
|
// A scope that can contain variables.
|
||||||
|
@ -759,10 +722,10 @@ class TFVScope : FVScope
|
||||||
structs[sd.name.str] = sd.type;
|
structs[sd.name.str] = sd.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertEnum(EnumDeclaration sd)
|
void insertEnum(EnumType sd)
|
||||||
{
|
{
|
||||||
clearId(sd.name);
|
clearId(sd.nameTok);
|
||||||
enums[sd.name.str] = sd.type;
|
enums[sd.name] = sd;
|
||||||
}
|
}
|
||||||
|
|
||||||
override:
|
override:
|
||||||
|
@ -789,24 +752,126 @@ class TFVScope : FVScope
|
||||||
// to handle enum members.
|
// to handle enum members.
|
||||||
final class EnumScope : SimplePropertyScope
|
final class EnumScope : SimplePropertyScope
|
||||||
{
|
{
|
||||||
this() { super("EnumScope", GenericProperties.singleton); }
|
this(EnumType _et)
|
||||||
|
|
||||||
int index; // Index in a global enum index list. Make a static list
|
|
||||||
// here or something.
|
|
||||||
|
|
||||||
void setup()
|
|
||||||
{
|
{
|
||||||
/*
|
super("EnumScope", GenericProperties.singleton);
|
||||||
insert("name", ArrayType.getString(), { tasm.getEnumName(index); });
|
et = _et;
|
||||||
|
|
||||||
// Replace these with the actual fields of the enum
|
insert("value", "long", &enumVal);
|
||||||
insert("value", type1, { tasm.getEnumValue(index, field); });
|
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
|
void enumMin()
|
||||||
inserts("length", "int", { tasm.push(length); });
|
{ tasm.push8(et.minVal); }
|
||||||
inserts("last", "owner", { tasm.push(last); });
|
|
||||||
inserts("first", "owner", { tasm.push(first); });
|
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;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
void push(int i) { expStack += i; }
|
void push(int i) { expStack += i; }
|
||||||
void push(Type t) { push(t.getSize); }
|
void push(Type t) { push(t.getSize); }
|
||||||
void pop(int i) { expStack -= i; }
|
void pop(int i) { expStack -= i; }
|
||||||
void pop(Type t) { pop(t.getSize); }
|
void pop(Type t) { pop(t.getSize); }
|
||||||
|
*/
|
||||||
|
|
||||||
// Get the number of local variables in the current scope. In
|
// Get the number of local variables in the current scope. In
|
||||||
// reality it gives the number of ints. A variable 8 bytes long will
|
// 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.)
|
// blocks (break and continue.)
|
||||||
int getTotLocals() { return sumLocals; }
|
int getTotLocals() { return sumLocals; }
|
||||||
|
|
||||||
// Get instra-expression stack
|
// Get intra-expression stack (not used yet)
|
||||||
int getExpStack() { return expStack; }
|
int getExpStack() { return expStack; }
|
||||||
|
|
||||||
// Get total stack position, including expression stack values. This
|
// Get total stack position, including expression stack values. This
|
||||||
|
|
|
@ -34,11 +34,13 @@ import monster.compiler.types;
|
||||||
import monster.compiler.block;
|
import monster.compiler.block;
|
||||||
import monster.compiler.variables;
|
import monster.compiler.variables;
|
||||||
import monster.compiler.states;
|
import monster.compiler.states;
|
||||||
|
import monster.compiler.operators;
|
||||||
import monster.compiler.functions;
|
import monster.compiler.functions;
|
||||||
import monster.compiler.assembler;
|
import monster.compiler.assembler;
|
||||||
|
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
|
import monster.vm.vm;
|
||||||
|
|
||||||
alias Statement[] StateArray;
|
alias Statement[] StateArray;
|
||||||
|
|
||||||
|
@ -48,24 +50,6 @@ abstract class Statement : Block
|
||||||
void compile();
|
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:
|
/* Handles:
|
||||||
import type1, type2, ...; // module scope
|
import type1, type2, ...; // module scope
|
||||||
import exp1, exp2, ...; // local scope (not implemented yet)
|
import exp1, exp2, ...; // local scope (not implemented yet)
|
||||||
|
@ -110,7 +94,7 @@ class ImportStatement : Statement
|
||||||
{
|
{
|
||||||
// form: import ImportList;
|
// form: import ImportList;
|
||||||
getList();
|
getList();
|
||||||
reqNext(toks, TT.Semicolon);
|
reqSep(toks);
|
||||||
}
|
}
|
||||||
else if(isNext(toks, TT.With, loc))
|
else if(isNext(toks, TT.With, loc))
|
||||||
{
|
{
|
||||||
|
@ -344,7 +328,7 @@ class GotoStatement : Statement, LabelUser
|
||||||
if(!isNext(toks, TT.Identifier, labelName))
|
if(!isNext(toks, TT.Identifier, labelName))
|
||||||
fail("goto expected label identifier", toks);
|
fail("goto expected label identifier", toks);
|
||||||
|
|
||||||
reqNext(toks, TT.Semicolon);
|
reqSep(toks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void resolve(Scope sc)
|
void resolve(Scope sc)
|
||||||
|
@ -400,13 +384,12 @@ class ContinueBreakStatement : Statement
|
||||||
if(isBreak) errName = "break";
|
if(isBreak) errName = "break";
|
||||||
else errName = "continue";
|
else errName = "continue";
|
||||||
|
|
||||||
if(!isNext(toks, TT.Semicolon))
|
if(!isSep(toks))
|
||||||
{
|
{
|
||||||
if(!isNext(toks, TT.Identifier, labelName))
|
if(!isNext(toks, TT.Identifier, labelName))
|
||||||
fail(errName ~ " expected ; or label", toks);
|
fail(errName ~ " expected ; or label", toks);
|
||||||
|
|
||||||
if(!isNext(toks, TT.Semicolon))
|
reqSep(toks);
|
||||||
fail(errName ~ " expected ;", toks);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -774,9 +757,9 @@ class ForeachStatement : Statement
|
||||||
if(isClass)
|
if(isClass)
|
||||||
{
|
{
|
||||||
// Class loops
|
// Class loops
|
||||||
|
clsInfo = vm.load(className.str);
|
||||||
clsInfo = global.findClass(className);
|
|
||||||
assert(clsInfo !is null);
|
assert(clsInfo !is null);
|
||||||
|
clsInfo.requireScope();
|
||||||
|
|
||||||
if(index !is null)
|
if(index !is null)
|
||||||
fail("Index not allowed in class iteration");
|
fail("Index not allowed in class iteration");
|
||||||
|
@ -977,7 +960,8 @@ class ForeachStatement : Statement
|
||||||
*/
|
*/
|
||||||
class ForStatement : Statement
|
class ForStatement : Statement
|
||||||
{
|
{
|
||||||
Expression init, condition, iter;
|
ExprStatement init, iter;
|
||||||
|
Expression condition;
|
||||||
VarDeclStatement varDec;
|
VarDeclStatement varDec;
|
||||||
|
|
||||||
Token labelName;
|
Token labelName;
|
||||||
|
@ -993,25 +977,26 @@ class ForStatement : Statement
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
if(!isNext(toks, TT.For, loc))
|
reqNext(toks, TT.For, loc);
|
||||||
assert(0);
|
reqNext(toks, TT.LeftParen);
|
||||||
|
|
||||||
if(!isNext(toks, TT.LeftParen))
|
|
||||||
fail("for statement expected '('", toks);
|
|
||||||
|
|
||||||
// Check if the init as a variable declaration, if so then parse
|
// Check if the init as a variable declaration, if so then parse
|
||||||
// it as a statement (since that takes care of multiple
|
// it as a statement (since that takes care of multiple
|
||||||
// variables as well.)
|
// variables as well.)
|
||||||
if(VarDeclStatement.canParse(toks))
|
if(VarDeclStatement.canParse(toks))
|
||||||
{
|
{
|
||||||
varDec = new VarDeclStatement;
|
varDec = new VarDeclStatement(true);
|
||||||
varDec.parse(toks); // This also kills the trailing ;
|
varDec.parse(toks); // This also kills the trailing ;
|
||||||
}
|
}
|
||||||
// Is it an empty init statement?
|
// Is it an empty init statement?
|
||||||
else if(!isNext(toks, TT.Semicolon))
|
else if(!isNext(toks, TT.Semicolon))
|
||||||
{
|
{
|
||||||
// If not, then assume it's an expression
|
// 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))
|
if(!isNext(toks, TT.Semicolon))
|
||||||
fail("initialization expression " ~ init.toString() ~
|
fail("initialization expression " ~ init.toString() ~
|
||||||
" must be followed by a ;", toks);
|
" must be followed by a ;", toks);
|
||||||
|
@ -1026,13 +1011,13 @@ class ForStatement : Statement
|
||||||
" must be followed by a ;", toks);
|
" must be followed by a ;", toks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally the last expression
|
// Finally the last expression statement
|
||||||
if(!isNext(toks, TT.RightParen))
|
if(!isNext(toks, TT.RightParen))
|
||||||
{
|
{
|
||||||
iter = Expression.identify(toks);
|
iter = new ExprStatement;
|
||||||
|
iter.term = false;
|
||||||
if(!isNext(toks, TT.RightParen))
|
iter.parse(toks);
|
||||||
fail("for statement expected ')'", toks);
|
reqNext(toks, TT.RightParen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is there a loop label?
|
// Is there a loop label?
|
||||||
|
@ -1094,7 +1079,7 @@ class ForStatement : Statement
|
||||||
// Push any local variables on the stack, or do initialization.
|
// Push any local variables on the stack, or do initialization.
|
||||||
if(varDec !is null) varDec.compile();
|
if(varDec !is null) varDec.compile();
|
||||||
else if(init !is null)
|
else if(init !is null)
|
||||||
init.evalPop();
|
init.compile();
|
||||||
|
|
||||||
int outer;
|
int outer;
|
||||||
|
|
||||||
|
@ -1120,7 +1105,7 @@ class ForStatement : Statement
|
||||||
cont.compile();
|
cont.compile();
|
||||||
|
|
||||||
// Do the iteration step, if any
|
// 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
|
// Push the conditionel expression, or assume it's true if
|
||||||
// missing.
|
// missing.
|
||||||
|
@ -1175,8 +1160,7 @@ class StateStatement : Statement, LabelUser
|
||||||
if(!isNext(toks, TT.Identifier, labelName))
|
if(!isNext(toks, TT.Identifier, labelName))
|
||||||
fail("state label expected after .", toks);
|
fail("state label expected after .", toks);
|
||||||
|
|
||||||
if(!isNext(toks, TT.Semicolon))
|
reqSep(toks);
|
||||||
fail("state statement expected ;", toks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the label to jump to. This is called from the state
|
// 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))
|
if(!isNext(toks, TT.Return, loc))
|
||||||
assert(0, "Internal error in ReturnStatement");
|
assert(0, "Internal error in ReturnStatement");
|
||||||
|
|
||||||
if(!isNext(toks, TT.Semicolon))
|
if(!isSep(toks))
|
||||||
{
|
{
|
||||||
exp = Expression.identify(toks);
|
exp = Expression.identify(toks);
|
||||||
|
|
||||||
if(!isNext(toks, TT.Semicolon))
|
reqSep(toks);
|
||||||
fail("Return statement expected ;", toks);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1404,13 +1387,7 @@ class ReturnStatement : Statement
|
||||||
|
|
||||||
exp.resolve(sc);
|
exp.resolve(sc);
|
||||||
|
|
||||||
try fn.type.typeCast(exp);
|
fn.type.typeCast(exp, "return value");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void compile()
|
void compile()
|
||||||
|
|
|
@ -31,6 +31,7 @@ import std.stdio;
|
||||||
import monster.util.string : begins;
|
import monster.util.string : begins;
|
||||||
|
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
|
import monster.options;
|
||||||
|
|
||||||
alias Token[] TokenArray;
|
alias Token[] TokenArray;
|
||||||
|
|
||||||
|
@ -109,11 +110,14 @@ enum TT
|
||||||
Last, // Tokens after this do not have a specific string
|
Last, // Tokens after this do not have a specific string
|
||||||
// associated with them.
|
// associated with them.
|
||||||
|
|
||||||
StringLiteral, // "something"
|
StringLiteral, // "something"
|
||||||
NumberLiteral, // Anything that starts with a number
|
IntLiteral, // Anything that starts with a number, except
|
||||||
CharLiteral, // 'a'
|
// floats
|
||||||
Identifier, // user-named identifier
|
FloatLiteral, // Any number which contains a period symbol
|
||||||
EOF // end of file
|
CharLiteral, // 'a'
|
||||||
|
Identifier, // user-named identifier
|
||||||
|
EOF, // end of file
|
||||||
|
EMPTY // empty line (not stored)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,6 +127,9 @@ struct Token
|
||||||
char[] str;
|
char[] str;
|
||||||
Floc loc;
|
Floc loc;
|
||||||
|
|
||||||
|
// True if this token was the first on its line.
|
||||||
|
bool newline;
|
||||||
|
|
||||||
char[] toString() { return str; }
|
char[] toString() { return str; }
|
||||||
|
|
||||||
static Token opCall(char[] name, Floc loc)
|
static Token opCall(char[] name, Floc loc)
|
||||||
|
@ -139,9 +146,12 @@ struct Token
|
||||||
|
|
||||||
// Used to look up keywords.
|
// Used to look up keywords.
|
||||||
TT keywordLookup[char[]];
|
TT keywordLookup[char[]];
|
||||||
|
bool lookupSetup = false;
|
||||||
|
|
||||||
void initTokenizer()
|
void initTokenizer()
|
||||||
{
|
{
|
||||||
|
assert(!lookupSetup);
|
||||||
|
|
||||||
// Insert the keywords into the lookup table
|
// Insert the keywords into the lookup table
|
||||||
for(TT t = TT.Class; t < TT.Last; t++)
|
for(TT t = TT.Class; t < TT.Last; t++)
|
||||||
{
|
{
|
||||||
|
@ -150,6 +160,8 @@ void initTokenizer()
|
||||||
assert((tok in keywordLookup) == null);
|
assert((tok in keywordLookup) == null);
|
||||||
keywordLookup[tok] = t;
|
keywordLookup[tok] = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lookupSetup = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index table of all the tokens
|
// Index table of all the tokens
|
||||||
|
@ -172,10 +184,12 @@ const char[][] tokenList =
|
||||||
|
|
||||||
TT.IsEqual : "==",
|
TT.IsEqual : "==",
|
||||||
TT.NotEqual : "!=",
|
TT.NotEqual : "!=",
|
||||||
|
|
||||||
TT.IsCaseEqual : "=i=",
|
TT.IsCaseEqual : "=i=",
|
||||||
TT.IsCaseEqual2 : "=I=",
|
TT.IsCaseEqual2 : "=I=",
|
||||||
TT.NotCaseEqual : "!=i=",
|
TT.NotCaseEqual : "!=i=",
|
||||||
TT.NotCaseEqual2 : "!=I=",
|
TT.NotCaseEqual2 : "!=I=",
|
||||||
|
|
||||||
TT.Less : "<",
|
TT.Less : "<",
|
||||||
TT.More : ">",
|
TT.More : ">",
|
||||||
TT.LessEq : "<=",
|
TT.LessEq : "<=",
|
||||||
|
@ -250,13 +264,15 @@ const char[][] tokenList =
|
||||||
|
|
||||||
// These are only used in error messages
|
// These are only used in error messages
|
||||||
TT.StringLiteral : "string literal",
|
TT.StringLiteral : "string literal",
|
||||||
TT.NumberLiteral : "number literal",
|
TT.IntLiteral : "integer literal",
|
||||||
|
TT.FloatLiteral : "floating point literal",
|
||||||
TT.CharLiteral : "character literal",
|
TT.CharLiteral : "character literal",
|
||||||
TT.Identifier : "identifier",
|
TT.Identifier : "identifier",
|
||||||
TT.EOF : "end of file"
|
TT.EOF : "end of file",
|
||||||
|
TT.EMPTY : "empty line - you should never see this"
|
||||||
];
|
];
|
||||||
|
|
||||||
class StreamTokenizer
|
class Tokenizer
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// Line buffer. Don't worry, this is perfectly safe. It is used by
|
// Line buffer. Don't worry, this is perfectly safe. It is used by
|
||||||
|
@ -267,8 +283,9 @@ class StreamTokenizer
|
||||||
char[300] buffer;
|
char[300] buffer;
|
||||||
char[] line; // The rest of the current line
|
char[] line; // The rest of the current line
|
||||||
Stream inf;
|
Stream inf;
|
||||||
uint lineNum;
|
int lineNum=-1;
|
||||||
char[] fname;
|
char[] fname;
|
||||||
|
bool newline;
|
||||||
|
|
||||||
// Make a token of given type with given string, and remove it from
|
// Make a token of given type with given string, and remove it from
|
||||||
// the input line.
|
// the input line.
|
||||||
|
@ -277,6 +294,7 @@ class StreamTokenizer
|
||||||
Token t;
|
Token t;
|
||||||
t.type = type;
|
t.type = type;
|
||||||
t.str = str;
|
t.str = str;
|
||||||
|
t.newline = newline;
|
||||||
t.loc.fname = fname;
|
t.loc.fname = fname;
|
||||||
t.loc.line = lineNum;
|
t.loc.line = lineNum;
|
||||||
|
|
||||||
|
@ -285,6 +303,9 @@ class StreamTokenizer
|
||||||
if(type == TT.IsCaseEqual2) t.type = TT.IsCaseEqual;
|
if(type == TT.IsCaseEqual2) t.type = TT.IsCaseEqual;
|
||||||
if(type == TT.NotCaseEqual2) t.type = TT.NotCaseEqual;
|
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
|
// Remove the string from 'line', along with any following witespace
|
||||||
remWord(str);
|
remWord(str);
|
||||||
return t;
|
return t;
|
||||||
|
@ -306,13 +327,17 @@ class StreamTokenizer
|
||||||
Token t;
|
Token t;
|
||||||
t.str = "<end of file>";
|
t.str = "<end of file>";
|
||||||
t.type = TT.EOF;
|
t.type = TT.EOF;
|
||||||
|
t.newline = true;
|
||||||
t.loc.line = lineNum;
|
t.loc.line = lineNum;
|
||||||
t.loc.fname = fname;
|
t.loc.fname = fname;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token empty;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
final:
|
final:
|
||||||
|
// Used when reading tokens from a file or a stream
|
||||||
this(char[] fname, Stream inf, int bom)
|
this(char[] fname, Stream inf, int bom)
|
||||||
{
|
{
|
||||||
assert(inf !is null);
|
assert(inf !is null);
|
||||||
|
@ -339,52 +364,51 @@ class StreamTokenizer
|
||||||
|
|
||||||
this.inf = inf;
|
this.inf = inf;
|
||||||
this.fname = fname;
|
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; }
|
~this() { if(inf !is null) delete inf; }
|
||||||
|
|
||||||
void fail(char[] msg)
|
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
|
Normal, // Normal mode
|
||||||
enum
|
Block, // Block comment
|
||||||
{
|
Nest // Nested block comment
|
||||||
Normal, // Normal mode
|
}
|
||||||
Block, // Block comment
|
int mode = Normal;
|
||||||
Nest // Nested block comment
|
int nests = 0; // Nest level
|
||||||
}
|
|
||||||
int mode = Normal;
|
// Get the next token from the line, if any
|
||||||
int nests = 0; // Nest level
|
Token getNextFromLine()
|
||||||
|
{
|
||||||
|
assert(lookupSetup,
|
||||||
|
"Internal error: The tokenizer lookup table has not been set up!");
|
||||||
|
|
||||||
restart:
|
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)
|
if(mode == Block)
|
||||||
{
|
{
|
||||||
|
@ -395,27 +419,23 @@ class StreamTokenizer
|
||||||
{
|
{
|
||||||
mode = Normal;
|
mode = Normal;
|
||||||
|
|
||||||
// Cut it the comment from the input
|
// Cut the comment from the input
|
||||||
remWord("*/", index);
|
remWord("*/", index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Comment not ended on this line, try the next
|
// Comment was not terminated on this line, try the next
|
||||||
line = null;
|
line = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start over
|
|
||||||
goto restart;
|
|
||||||
}
|
}
|
||||||
|
else if(mode == Nest)
|
||||||
if(mode == Nest)
|
|
||||||
{
|
{
|
||||||
// Check for nested /+ and +/ in here, but go to restart if
|
// Check for nested /+ and +/ in here, but go to restart if
|
||||||
// none is found (meaning the comment continues on the next
|
// none is found (meaning the comment continues on the next
|
||||||
// line), or reset mode and go to restart if nest level ever
|
// line), or reset mode and go to restart if nest level ever
|
||||||
// gets to 0.
|
// gets to 0.
|
||||||
|
|
||||||
do
|
while(line.length >= 2)
|
||||||
{
|
{
|
||||||
int incInd = -1;
|
int incInd = -1;
|
||||||
int decInd = -1;
|
int decInd = -1;
|
||||||
|
@ -443,7 +463,7 @@ class StreamTokenizer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a nest level when '+/' is found
|
// Remove a nest level when '+/' is found
|
||||||
if(decInd != -1)
|
else if(decInd != -1)
|
||||||
{
|
{
|
||||||
// Remove the +/ from input
|
// Remove the +/ from input
|
||||||
remWord("+/", decInd);
|
remWord("+/", decInd);
|
||||||
|
@ -461,20 +481,23 @@ class StreamTokenizer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing found on this line, try the next
|
// Nothing found on this line, try the next
|
||||||
line = null;
|
|
||||||
break;
|
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("//"))
|
if(line.begins("//"))
|
||||||
{
|
line = null;
|
||||||
line = null;
|
|
||||||
goto restart;
|
// If the line is empty at this point, there's nothing more to
|
||||||
}
|
// be done
|
||||||
|
if(line == "")
|
||||||
|
return empty;
|
||||||
|
|
||||||
// Block comment
|
// Block comment
|
||||||
if(line.begins("/*"))
|
if(line.begins("/*"))
|
||||||
|
@ -519,13 +542,13 @@ class StreamTokenizer
|
||||||
// '\n', '\'', or unicode stuff won't work.)
|
// '\n', '\'', or unicode stuff won't work.)
|
||||||
if(line[0] == '\'')
|
if(line[0] == '\'')
|
||||||
{
|
{
|
||||||
if(line.length < 2 || line[2] != '\'')
|
if(line.length < 3 || line[2] != '\'')
|
||||||
fail("Malformed character literal " ~line);
|
fail("Malformed character literal " ~line);
|
||||||
return retToken(TT.CharLiteral, line[0..3].dup);
|
return retToken(TT.CharLiteral, line[0..3].dup);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numerical literals - if it starts with a number, we accept
|
// 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
|
// also accept numbers on the form .NUM. We do not try to parse
|
||||||
// the number here.
|
// the number here.
|
||||||
if(numericalChar(line[0]) ||
|
if(numericalChar(line[0]) ||
|
||||||
|
@ -539,6 +562,7 @@ class StreamTokenizer
|
||||||
// also explicitly allow '.' dots.
|
// also explicitly allow '.' dots.
|
||||||
int len = 1;
|
int len = 1;
|
||||||
bool lastDot = false; // Was the last char a '.'?
|
bool lastDot = false; // Was the last char a '.'?
|
||||||
|
int dots; // Number of dots
|
||||||
foreach(char ch; line[1..$])
|
foreach(char ch; line[1..$])
|
||||||
{
|
{
|
||||||
if(ch == '.')
|
if(ch == '.')
|
||||||
|
@ -547,10 +571,13 @@ class StreamTokenizer
|
||||||
// operator.
|
// operator.
|
||||||
if(lastDot)
|
if(lastDot)
|
||||||
{
|
{
|
||||||
len--; // Remove the last dot and exit.
|
// Remove the last dot and exit.
|
||||||
|
len--;
|
||||||
|
dots--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lastDot = true;
|
lastDot = true;
|
||||||
|
dots++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -562,7 +589,10 @@ class StreamTokenizer
|
||||||
// This was a valid character, count it
|
// This was a valid character, count it
|
||||||
len++;
|
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
|
// Check for identifiers
|
||||||
|
@ -603,12 +633,22 @@ class StreamTokenizer
|
||||||
TT match;
|
TT match;
|
||||||
int mlen = 0;
|
int mlen = 0;
|
||||||
foreach(int i, char[] tok; tokenList[0..TT.Class])
|
foreach(int i, char[] tok; tokenList[0..TT.Class])
|
||||||
if(line.begins(tok) && tok.length >= mlen)
|
{
|
||||||
{
|
// Skip =i= and family, if monster.options tells us to
|
||||||
assert(tok.length > mlen, "Two matching tokens of the same length");
|
static if(!ciStringOps)
|
||||||
mlen = tok.length;
|
{
|
||||||
match = cast(TT) i;
|
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]);
|
if(mlen) return retToken(match, tokenList[match]);
|
||||||
|
|
||||||
|
@ -616,14 +656,50 @@ class StreamTokenizer
|
||||||
fail("Invalid token " ~ line);
|
fail("Invalid token " ~ line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require a specific token
|
// Get the next token from a stream
|
||||||
bool isToken(TT tok)
|
Token getNext()
|
||||||
{
|
{
|
||||||
Token tt = getNext();
|
assert(inf !is null, "getNext() found a null stream");
|
||||||
return tt.type == tok;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// 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;
|
TokenArray tokenArray;
|
||||||
|
|
||||||
StreamTokenizer tok = new StreamTokenizer(fname, stream, bom);
|
Tokenizer tok = new Tokenizer(fname, stream, bom);
|
||||||
Token tt;
|
Token tt;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
|
|
@ -61,18 +61,6 @@ import std.string;
|
||||||
MetaType (type of type expressions, like writeln(int);)
|
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
|
// A class that represents a type. The Type class is abstract, and the
|
||||||
// different types are actually handled by various subclasses of Type
|
// different types are actually handled by various subclasses of Type
|
||||||
|
@ -197,6 +185,7 @@ abstract class Type : Block
|
||||||
bool isArray() { return arrays() != 0; }
|
bool isArray() { return arrays() != 0; }
|
||||||
bool isObject() { return false; }
|
bool isObject() { return false; }
|
||||||
bool isStruct() { return false; }
|
bool isStruct() { return false; }
|
||||||
|
bool isEnum() { return false; }
|
||||||
|
|
||||||
bool isReplacer() { return false; }
|
bool isReplacer() { return false; }
|
||||||
|
|
||||||
|
@ -322,7 +311,7 @@ abstract class Type : Block
|
||||||
|
|
||||||
// Cast the expression orig to this type. Uses canCastTo to
|
// Cast the expression orig to this type. Uses canCastTo to
|
||||||
// determine if a cast is possible.
|
// 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;
|
if(orig.type == this) return;
|
||||||
|
|
||||||
|
@ -332,12 +321,14 @@ abstract class Type : Block
|
||||||
if(orig.type.canCastTo(this))
|
if(orig.type.canCastTo(this))
|
||||||
orig = new CastExpression(orig, this);
|
orig = new CastExpression(orig, this);
|
||||||
else
|
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
|
// Do compile-time type casting. Gets orig.evalCTime() and returns
|
||||||
// the converted result.
|
// the converted result.
|
||||||
final int[] typeCastCTime(Expression orig)
|
final int[] typeCastCTime(Expression orig, char[] to)
|
||||||
{
|
{
|
||||||
int[] res = orig.evalCTime();
|
int[] res = orig.evalCTime();
|
||||||
|
|
||||||
|
@ -346,7 +337,9 @@ abstract class Type : Block
|
||||||
if(orig.type.canCastTo(this))
|
if(orig.type.canCastTo(this))
|
||||||
res = orig.type.doCastCTime(res, this);
|
res = orig.type.doCastCTime(res, this);
|
||||||
else
|
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);
|
assert(res.length == getSize);
|
||||||
|
|
||||||
|
@ -398,10 +391,8 @@ abstract class Type : Block
|
||||||
void parse(ref TokenArray toks) {assert(0, name);}
|
void parse(ref TokenArray toks) {assert(0, name);}
|
||||||
void resolve(Scope sc) {assert(0, name);}
|
void resolve(Scope sc) {assert(0, name);}
|
||||||
|
|
||||||
/* Cast two expressions to their common type, if any. Throw a
|
/* Cast two expressions to their common type, if any. Fail if not
|
||||||
TypeException exception if not possible. This exception should be
|
possible. Examples of possible outcomes:
|
||||||
caught elsewhere to give a more useful error message. Examples of
|
|
||||||
possible outcomes:
|
|
||||||
|
|
||||||
int, int -> does nothing
|
int, int -> does nothing
|
||||||
float, int -> converts the second paramter to float
|
float, int -> converts the second paramter to float
|
||||||
|
@ -451,13 +442,13 @@ abstract class Type : Block
|
||||||
{
|
{
|
||||||
// Find the common type
|
// Find the common type
|
||||||
if(t1.canCastTo(t2)) common = t2; else
|
if(t1.canCastTo(t2)) common = t2; else
|
||||||
if(t2.canCastTo(t1)) common = t1;
|
if(t2.canCastTo(t1)) common = t1; else
|
||||||
else throw new TypeException(t1, t2);
|
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.
|
// Wrap the expressions in CastExpression blocks if necessary.
|
||||||
common.typeCast(e1);
|
common.typeCast(e1, "");
|
||||||
common.typeCast(e2);
|
common.typeCast(e2, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,7 +471,7 @@ class NullType : InternalType
|
||||||
|
|
||||||
bool canCastTo(Type to)
|
bool canCastTo(Type to)
|
||||||
{
|
{
|
||||||
return to.isArray || to.isObject;
|
return to.isArray || to.isObject || to.isEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
void evalCastTo(Type to)
|
void evalCastTo(Type to)
|
||||||
|
@ -921,6 +912,8 @@ class ObjectType : Type
|
||||||
// Members of objects are resolved in the class scope.
|
// Members of objects are resolved in the class scope.
|
||||||
Scope getMemberScope()
|
Scope getMemberScope()
|
||||||
{
|
{
|
||||||
|
assert(getClass !is null);
|
||||||
|
assert(getClass.sc !is null);
|
||||||
return getClass().sc;
|
return getClass().sc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1036,25 +1029,178 @@ class ArrayType : Type
|
||||||
|
|
||||||
class EnumType : Type
|
class EnumType : Type
|
||||||
{
|
{
|
||||||
// The scope contains the actual enum values
|
// Enum entries
|
||||||
|
EnumEntry[] entries;
|
||||||
EnumScope sc;
|
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;
|
auto p = val in valueAA;
|
||||||
loc = ed.name.loc;
|
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[] defaultInit() { return [0]; }
|
||||||
int getSize() { return 1; }
|
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()
|
Scope getMemberScope()
|
||||||
{ return GenericProperties.singleton; }
|
{ return sc; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class StructType : Type
|
class StructType : Type
|
||||||
|
|
|
@ -29,6 +29,7 @@ import monster.compiler.tokenizer;
|
||||||
import monster.compiler.expression;
|
import monster.compiler.expression;
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
import monster.compiler.statement;
|
import monster.compiler.statement;
|
||||||
|
import monster.compiler.states;
|
||||||
import monster.compiler.block;
|
import monster.compiler.block;
|
||||||
import monster.compiler.operators;
|
import monster.compiler.operators;
|
||||||
import monster.compiler.assembler;
|
import monster.compiler.assembler;
|
||||||
|
@ -38,6 +39,7 @@ import std.stdio;
|
||||||
|
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
|
import monster.vm.vm;
|
||||||
|
|
||||||
enum VarType
|
enum VarType
|
||||||
{
|
{
|
||||||
|
@ -54,7 +56,10 @@ struct Variable
|
||||||
|
|
||||||
VarScope sc; // Scope that owns this 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 isRef; // Is this a reference variable?
|
||||||
bool isConst; // Used for function parameters
|
bool isConst; // Used for function parameters
|
||||||
|
@ -274,22 +279,23 @@ class VarDeclaration : Block
|
||||||
if(isParam && var.type.isArray())
|
if(isParam && var.type.isArray())
|
||||||
allowConst = true;
|
allowConst = true;
|
||||||
|
|
||||||
// Handle initial value normally
|
// Handle initial value, if present
|
||||||
if(init !is null)
|
if(init !is null)
|
||||||
{
|
{
|
||||||
init.resolve(sc);
|
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' is present, just copy the type of the init value
|
||||||
if(var.type.isVar)
|
if(var.type.isVar)
|
||||||
var.type = init.type;
|
var.type = init.type;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Convert type, if necessary.
|
// Convert type, if necessary.
|
||||||
try var.type.typeCast(init);
|
var.type.typeCast(init, var.name.str);
|
||||||
catch(TypeException)
|
|
||||||
fail(format("Cannot initialize %s of type %s with %s of type %s",
|
|
||||||
var.name.str, var.type,
|
|
||||||
init, init.type), loc);
|
|
||||||
}
|
}
|
||||||
assert(init.type == var.type);
|
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
|
// Represents a reference to a variable. Simply stores the token
|
||||||
// representing the identifier. Evaluation is handled by the variable
|
// representing the identifier. Evaluation is handled by the variable
|
||||||
// declaration itself. This allows us to use this class for local and
|
// 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,
|
// Still no match. Might be an unloaded class however,
|
||||||
// lookup() doesn't load classes. Try loading it.
|
// 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.
|
// No match at all.
|
||||||
fail("Undefined identifier "~name.str, name.loc);
|
fail("Undefined identifier "~name.str, name.loc);
|
||||||
|
|
||||||
|
@ -813,6 +971,14 @@ class VariableExpr : MemberExpression
|
||||||
class VarDeclStatement : Statement
|
class VarDeclStatement : Statement
|
||||||
{
|
{
|
||||||
VarDeclaration[] vars;
|
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)
|
static bool canParse(TokenArray toks)
|
||||||
{
|
{
|
||||||
|
@ -844,8 +1010,14 @@ class VarDeclStatement : Statement
|
||||||
vars ~= varDec;
|
vars ~= varDec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(reqSemi)
|
||||||
|
reqNext(toks, TT.Semicolon);
|
||||||
|
else
|
||||||
|
reqSep(toks);
|
||||||
|
/*
|
||||||
if(!isNext(toks, TT.Semicolon))
|
if(!isNext(toks, TT.Semicolon))
|
||||||
fail("Declaration statement expected ;", toks);
|
fail("Declaration statement expected ;", toks);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] toString()
|
char[] toString()
|
||||||
|
|
|
@ -31,12 +31,28 @@ import monster.modules.frames;
|
||||||
import monster.modules.random;
|
import monster.modules.random;
|
||||||
import monster.modules.threads;
|
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()
|
void initAllModules()
|
||||||
{
|
{
|
||||||
initIOModule();
|
static if(moduleList.has("io")) initIOModule();
|
||||||
initTimerModule();
|
static if(moduleList.has("timer")) initTimerModule();
|
||||||
initFramesModule();
|
static if(moduleList.has("frames")) initFramesModule();
|
||||||
initThreadModule();
|
static if(moduleList.has("thread")) initThreadModule();
|
||||||
initRandomModule();
|
static if(moduleList.has("random")) initRandomModule();
|
||||||
initMathModule();
|
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;
|
static MonsterClass mc;
|
||||||
if(mc !is null) return;
|
if(mc !is null) return;
|
||||||
|
|
||||||
mc = new MonsterClass(MC.String, moduleDef, "frames");
|
mc = vm.loadString(moduleDef, "frames");
|
||||||
|
|
||||||
// Bind the idle
|
// Bind the idle
|
||||||
mc.bind("fsleep", new IdleFrameSleep);
|
mc.bind("fsleep", new IdleFrameSleep);
|
||||||
|
|
|
@ -80,7 +80,7 @@ void initIOModule()
|
||||||
static MonsterClass mc;
|
static MonsterClass mc;
|
||||||
if(mc !is null) return;
|
if(mc !is null) return;
|
||||||
|
|
||||||
mc = new MonsterClass(MC.String, moduleDef, "io");
|
mc = vm.loadString(moduleDef, "io");
|
||||||
|
|
||||||
mc.bind("write", { doWrite(false); });
|
mc.bind("write", { doWrite(false); });
|
||||||
mc.bind("writeln", { doWrite(false); writefln(); });
|
mc.bind("writeln", { doWrite(false); writefln(); });
|
||||||
|
|
|
@ -28,6 +28,7 @@ module monster.modules.math;
|
||||||
|
|
||||||
import monster.monster;
|
import monster.monster;
|
||||||
import std.math;
|
import std.math;
|
||||||
|
import monster.vm.mclass;
|
||||||
|
|
||||||
const char[] moduleDef =
|
const char[] moduleDef =
|
||||||
"module math;
|
"module math;
|
||||||
|
@ -85,7 +86,7 @@ void initMathModule()
|
||||||
static MonsterClass mc;
|
static MonsterClass mc;
|
||||||
if(mc !is null) return;
|
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("sin", { stack.pushDouble(sin(stack.popDouble)); });
|
||||||
mc.bind("cos", { stack.pushDouble(cos(stack.popDouble)); });
|
mc.bind("cos", { stack.pushDouble(cos(stack.popDouble)); });
|
||||||
|
|
|
@ -62,7 +62,7 @@ void initRandomModule()
|
||||||
static MonsterClass mc;
|
static MonsterClass mc;
|
||||||
if(mc !is null) return;
|
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("rand", { stack.pushInt(rand()); });
|
||||||
mc.bind("frand", { stack.pushFloat(rand()*_frandFactor); });
|
mc.bind("frand", { stack.pushFloat(rand()*_frandFactor); });
|
||||||
|
|
|
@ -28,6 +28,10 @@
|
||||||
module monster.modules.threads;
|
module monster.modules.threads;
|
||||||
|
|
||||||
import monster.monster;
|
import monster.monster;
|
||||||
|
import monster.vm.mobject;
|
||||||
|
import monster.vm.idlefunction;
|
||||||
|
import monster.vm.thread;
|
||||||
|
import monster.vm.mclass;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
const char[] moduleDef =
|
const char[] moduleDef =
|
||||||
|
@ -69,12 +73,13 @@ thread start(char[] name)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The char[] name stuff above will of course be replaced with real
|
The char[] name stuff above will of course be replaced with real
|
||||||
function pointers once those are done. We will also add:
|
function pointers once those are done. When closures are done we
|
||||||
|
will also add:
|
||||||
|
|
||||||
function() wrap(function f())
|
function() wrap(function f())
|
||||||
{
|
{
|
||||||
var t = create(f);
|
var t = create(f);
|
||||||
return {{ t.call(); }
|
return { t.call(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@ -268,7 +273,7 @@ void initThreadModule()
|
||||||
if(_threadClass !is null)
|
if(_threadClass !is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_threadClass = new MonsterClass(MC.String, moduleDef, "thread");
|
_threadClass = vm.loadString(moduleDef, "thread");
|
||||||
trdSing = _threadClass.getSing();
|
trdSing = _threadClass.getSing();
|
||||||
|
|
||||||
_threadClass.bind("kill", new Kill);
|
_threadClass.bind("kill", new Kill);
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
module monster.modules.timer;
|
module monster.modules.timer;
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import std.date;
|
|
||||||
|
|
||||||
// For some utterly idiotic reason, DMD's public imports will suddenly
|
// For some utterly idiotic reason, DMD's public imports will suddenly
|
||||||
// stop working from time to time.
|
// stop working from time to time.
|
||||||
|
@ -39,14 +38,18 @@ import monster.vm.thread;
|
||||||
import monster.vm.idlefunction;
|
import monster.vm.idlefunction;
|
||||||
|
|
||||||
import monster.monster;
|
import monster.monster;
|
||||||
|
import monster.options;
|
||||||
|
|
||||||
const char[] moduleDef =
|
const char[] moduleDef =
|
||||||
"singleton timer;
|
"singleton timer;
|
||||||
idle sleep(float secs);
|
idle sleep(float secs);
|
||||||
"; //"
|
"; //"
|
||||||
|
|
||||||
|
static if(timer_useClock)
|
||||||
|
{
|
||||||
// Sleep a given amount of time. This implementation uses the system
|
// Sleep a given amount of time. This implementation uses the system
|
||||||
// clock and is the default.
|
// clock.
|
||||||
|
import std.date;
|
||||||
class IdleSleep_SystemClock : IdleFunction
|
class IdleSleep_SystemClock : IdleFunction
|
||||||
{
|
{
|
||||||
override:
|
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
|
// This implementation uses a user-driven timer instead of the system
|
||||||
// clock. It's more efficient, but requires the user to update the
|
// 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
|
// bound to the default timer, but it's possible to create multiple
|
||||||
// independent timers.
|
// independent timers.
|
||||||
class IdleSleep_Timer : IdleFunction
|
class IdleSleep_Timer : IdleFunction
|
||||||
|
@ -161,35 +166,25 @@ class SleepManager
|
||||||
}
|
}
|
||||||
|
|
||||||
SleepManager idleTime;
|
SleepManager idleTime;
|
||||||
|
}
|
||||||
|
|
||||||
MonsterClass _timerClass;
|
MonsterClass _timerClass;
|
||||||
|
|
||||||
void initTimerModule()
|
void initTimerModule()
|
||||||
{
|
{
|
||||||
if(_timerClass !is null)
|
if(_timerClass !is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_timerClass = vm.loadString(moduleDef, "timer");
|
||||||
|
|
||||||
|
static if(timer_useClock)
|
||||||
{
|
{
|
||||||
assert(idleTime !is null);
|
_timerClass.bind("sleep", new IdleSleep_SystemClock);
|
||||||
return;
|
}
|
||||||
|
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.arrays;
|
||||||
import monster.vm.params;
|
import monster.vm.params;
|
||||||
import monster.vm.error;
|
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) {}
|
version(LittleEndian) {}
|
||||||
else static assert(0, "This library does not yet support big endian systems.");
|
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
|
done
|
||||||
|
|
||||||
svn st
|
svn st
|
||||||
|
|
||||||
|
svn diff options.openmw options.d
|
||||||
|
|
|
@ -148,6 +148,18 @@ struct GrowArray(T)
|
||||||
return *this;
|
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 opApply(int delegate(ref int, ref T) dg)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import monster.compiler.states;
|
||||||
import monster.compiler.functions;
|
import monster.compiler.functions;
|
||||||
import monster.compiler.linespec;
|
import monster.compiler.linespec;
|
||||||
|
|
||||||
|
import monster.options;
|
||||||
import monster.util.freelist;
|
import monster.util.freelist;
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
@ -44,6 +45,7 @@ enum SPType
|
||||||
Function, // A function (script or native)
|
Function, // A function (script or native)
|
||||||
Idle, // Idle function
|
Idle, // Idle function
|
||||||
State, // State code
|
State, // State code
|
||||||
|
External, // An external function represented on the function stack
|
||||||
}
|
}
|
||||||
|
|
||||||
// One entry in the function stack
|
// One entry in the function stack
|
||||||
|
@ -55,6 +57,7 @@ struct StackPoint
|
||||||
{
|
{
|
||||||
Function *func; // What function we are in (if any)
|
Function *func; // What function we are in (if any)
|
||||||
State *state; // What state the function belongs to (if any)
|
State *state; // What state the function belongs to (if any)
|
||||||
|
char[] extName; // Name of external function
|
||||||
}
|
}
|
||||||
|
|
||||||
SPType ftype;
|
SPType ftype;
|
||||||
|
@ -92,6 +95,9 @@ struct StackPoint
|
||||||
bool isNormal()
|
bool isNormal()
|
||||||
{ return isState || (isFunc && func.isNormal); }
|
{ return isState || (isFunc && func.isNormal); }
|
||||||
|
|
||||||
|
bool isExternal()
|
||||||
|
{ return ftype == SPType.External; }
|
||||||
|
|
||||||
// Get the current source position (file name and line
|
// Get the current source position (file name and line
|
||||||
// number). Mostly used for error messages.
|
// number). Mostly used for error messages.
|
||||||
Floc getFloc()
|
Floc getFloc()
|
||||||
|
@ -116,6 +122,9 @@ struct StackPoint
|
||||||
|
|
||||||
char[] toString()
|
char[] toString()
|
||||||
{
|
{
|
||||||
|
if(isExternal)
|
||||||
|
return "external function " ~ extName;
|
||||||
|
|
||||||
assert(func !is null);
|
assert(func !is null);
|
||||||
|
|
||||||
char[] type, cls, name;
|
char[] type, cls, name;
|
||||||
|
@ -144,11 +153,14 @@ struct StackPoint
|
||||||
alias FreeList!(StackPoint) StackList;
|
alias FreeList!(StackPoint) StackList;
|
||||||
alias StackList.TNode *StackNode;
|
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
|
struct FunctionStack
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// Guard against infinite recursion
|
|
||||||
static const maxStack = 100;
|
|
||||||
|
|
||||||
// Number of native functions on the stack
|
// Number of native functions on the stack
|
||||||
int natives;
|
int natives;
|
||||||
|
@ -185,8 +197,7 @@ struct FunctionStack
|
||||||
|
|
||||||
void killAll()
|
void killAll()
|
||||||
{
|
{
|
||||||
assert(natives == 0);
|
natives = 0;
|
||||||
|
|
||||||
while(list.length)
|
while(list.length)
|
||||||
{
|
{
|
||||||
assert(cur !is null);
|
assert(cur !is null);
|
||||||
|
@ -215,7 +226,7 @@ struct FunctionStack
|
||||||
// Sets up the next stack point and assigns the given object
|
// Sets up the next stack point and assigns the given object
|
||||||
private void push(MonsterObject *obj)
|
private void push(MonsterObject *obj)
|
||||||
{
|
{
|
||||||
if(list.length >= maxStack)
|
if(list.length >= maxFStack)
|
||||||
fail("Function stack overflow - infinite recursion?");
|
fail("Function stack overflow - infinite recursion?");
|
||||||
|
|
||||||
assert(cur is null || !cur.isIdle,
|
assert(cur is null || !cur.isIdle,
|
||||||
|
@ -276,6 +287,16 @@ struct FunctionStack
|
||||||
cur.code.setData(st.bcode);
|
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)
|
void pushIdle(Function *func, MonsterObject *obj)
|
||||||
{
|
{
|
||||||
push(obj);
|
push(obj);
|
||||||
|
@ -299,7 +320,7 @@ struct FunctionStack
|
||||||
|
|
||||||
assert(list.length >= 1);
|
assert(list.length >= 1);
|
||||||
|
|
||||||
if(cur.isNative)
|
if(cur.isNative || cur.isExternal)
|
||||||
natives--;
|
natives--;
|
||||||
assert(natives >= 0);
|
assert(natives >= 0);
|
||||||
|
|
||||||
|
@ -331,11 +352,25 @@ struct FunctionStack
|
||||||
if(i == 0)
|
if(i == 0)
|
||||||
msg = " (<---- current function)";
|
msg = " (<---- current function)";
|
||||||
else if(i == list.length-1)
|
else if(i == list.length-1)
|
||||||
msg = " (<---- start of function stack)";
|
msg = " (<---- first Monster function)";
|
||||||
res = c.toString ~ msg ~ '\n' ~ res;
|
res ~= c.toString ~ msg ~ '\n';
|
||||||
i++;
|
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;
|
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.arrays;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.vm.vm;
|
import monster.vm.vm;
|
||||||
|
import monster.vm.stack;
|
||||||
|
import monster.vm.thread;
|
||||||
import monster.vm.mobject;
|
import monster.vm.mobject;
|
||||||
|
|
||||||
import monster.util.flags;
|
import monster.util.flags;
|
||||||
|
@ -48,23 +50,12 @@ import monster.util.freelist;
|
||||||
|
|
||||||
import std.string;
|
import std.string;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import std.file;
|
|
||||||
import std.stream;
|
import std.stream;
|
||||||
|
|
||||||
typedef void *MClass; // Pointer to C++ equivalent of MonsterClass.
|
typedef void *MClass; // Pointer to C++ equivalent of MonsterClass.
|
||||||
|
|
||||||
typedef int CIndex;
|
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
|
enum CFlags
|
||||||
{
|
{
|
||||||
None = 0x00, // Initial value
|
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)
|
static bool canParse(TokenArray tokens)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
@ -125,6 +107,7 @@ final class MonsterClass
|
||||||
CIndex gIndex; // Global index of this class
|
CIndex gIndex; // Global index of this class
|
||||||
|
|
||||||
ClassScope sc;
|
ClassScope sc;
|
||||||
|
PackageScope pack;
|
||||||
|
|
||||||
ObjectType objType; // Type for objects of this class
|
ObjectType objType; // Type for objects of this class
|
||||||
Type classType; // Type for class references to this class (not
|
Type classType; // Type for class references to this class (not
|
||||||
|
@ -167,92 +150,11 @@ final class MonsterClass
|
||||||
// already.
|
// already.
|
||||||
void requireCompile() { if(!isCompiled) compileBody(); }
|
void requireCompile() { if(!isCompiled) compileBody(); }
|
||||||
|
|
||||||
|
// 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.
|
||||||
* Constructors *
|
this(int internal = 0) { assert(internal == -14,
|
||||||
* *
|
"Don't create MonsterClasses directly, use vm.load()"); }
|
||||||
*******************************************************/
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************
|
/*******************************************************
|
||||||
* *
|
* *
|
||||||
|
@ -277,6 +179,20 @@ final class MonsterClass
|
||||||
void bind(char[] name, IdleFunction idle)
|
void bind(char[] name, IdleFunction idle)
|
||||||
{ bind_locate(name, FuncType.Idle).idleFunc = 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
|
// Find a function by index. Used internally, and works for all
|
||||||
// function types.
|
// function types.
|
||||||
Function *findFunction(int index)
|
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)
|
void bindConst(dg_callback nf)
|
||||||
{
|
{
|
||||||
assert(natConst.ftype == FuncType.Native,
|
assert(natConst.ftype == FuncType.Native,
|
||||||
|
@ -360,6 +288,30 @@ final class MonsterClass
|
||||||
natConst.natFunc_c = nf;
|
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);
|
assert(singObj !is null);
|
||||||
return singObj;
|
return singObj;
|
||||||
}
|
}
|
||||||
|
alias getSing getSingleton;
|
||||||
|
|
||||||
MonsterObject* createObject()
|
MonsterObject* createObject(bool callConst = true)
|
||||||
{ return createClone(null); }
|
{ 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
|
// Get the whole allocated buffer belonging to this object
|
||||||
private int[] getDataBlock(MonsterObject *obj)
|
private int[] getDataBlock(MonsterObject *obj)
|
||||||
|
@ -501,7 +483,7 @@ final class MonsterClass
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new object based on an existing object
|
// Create a new object based on an existing object
|
||||||
MonsterObject* createClone(MonsterObject *source)
|
MonsterObject* createClone(MonsterObject *source, bool callConst = true)
|
||||||
{
|
{
|
||||||
requireCompile();
|
requireCompile();
|
||||||
|
|
||||||
|
@ -555,7 +537,7 @@ final class MonsterClass
|
||||||
foreach(i, c; tree)
|
foreach(i, c; tree)
|
||||||
{
|
{
|
||||||
// Just get the slice - the actual data is already set up.
|
// 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
|
// Insert ourselves into the per-class list. We've already
|
||||||
// allocated size for a node, we just have to add it to the
|
// 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
|
// At this point we should have used up the entire slice
|
||||||
assert(slice.length == 0);
|
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
|
// Set the same state as the source
|
||||||
if(source !is null)
|
if(source !is null)
|
||||||
obj.setState(source.state, null);
|
obj.setState(source.state, null);
|
||||||
|
else
|
||||||
|
// Use the default state and label
|
||||||
|
obj.setState(defState, defLabel);
|
||||||
|
|
||||||
// Make sure that getDataBlock works
|
// Make sure that getDataBlock works
|
||||||
assert(getDataBlock(obj).ptr == odata.ptr &&
|
assert(getDataBlock(obj).ptr == odata.ptr &&
|
||||||
getDataBlock(obj).length == odata.length);
|
getDataBlock(obj).length == odata.length);
|
||||||
|
|
||||||
|
// Call constructors
|
||||||
|
if(callConst)
|
||||||
|
callConstOn(obj);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,261 +654,25 @@ final class MonsterClass
|
||||||
char[] toString() { return getName(); }
|
char[] toString() { return getName(); }
|
||||||
uint numObjects() { return objects.length; }
|
uint numObjects() { return objects.length; }
|
||||||
|
|
||||||
private:
|
// 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)
|
||||||
* *
|
|
||||||
* 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()
|
|
||||||
{
|
{
|
||||||
assert(sc !is null && sc.isClass(), "Class does not have a class scope");
|
defState = st;
|
||||||
uint dataSize = sc.getDataSize;
|
defLabel = lb;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load file based on file name, class name, or both. The order of
|
// Set the initial state and label for this class. This will affect
|
||||||
// the strings doesn't matter, and name2 can be empty. useCase
|
// all newly created objects, but not cloned objects.
|
||||||
// determines if we require a case sensitive match between the given
|
void setDefaultState(char[] st, char[] lb="")
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
if(FuncDeclaration.canParse(toks))
|
if(lb == "")
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
auto sd = new StructDeclaration;
|
setDefaultState(findState(st), null);
|
||||||
sd.parse(toks);
|
return;
|
||||||
structdecs ~= sd;
|
|
||||||
}
|
}
|
||||||
else if(EnumDeclaration.canParse(toks))
|
auto pr = findState(st, lb);
|
||||||
{
|
setDefaultState(pr.state, pr.label);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a stream to tokens and parses it.
|
// Converts a stream to tokens and parses it.
|
||||||
|
@ -950,6 +693,9 @@ final class MonsterClass
|
||||||
natConst.ftype = FuncType.Native;
|
natConst.ftype = FuncType.Native;
|
||||||
natConst.name.str = "native constructor";
|
natConst.name.str = "native constructor";
|
||||||
natConst.owner = this;
|
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,
|
// TODO: Check for a list of keywords here. class, module,
|
||||||
// abstract, final. They can come in any order, but only certain
|
// abstract, final. They can come in any order, but only certain
|
||||||
|
@ -1000,8 +746,11 @@ final class MonsterClass
|
||||||
while(isNext(tokens, TT.Comma));
|
while(isNext(tokens, TT.Comma));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isNext(tokens, TT.Semicolon);
|
||||||
|
/*
|
||||||
if(!isNext(tokens, TT.Semicolon))
|
if(!isNext(tokens, TT.Semicolon))
|
||||||
fail("Missing semicolon after class statement", name.loc);
|
fail("Missing semicolon after class statement", name.loc);
|
||||||
|
*/
|
||||||
|
|
||||||
if(parents.length > 1)
|
if(parents.length > 1)
|
||||||
fail("Multiple inheritance is currently not supported", name.loc);
|
fail("Multiple inheritance is currently not supported", name.loc);
|
||||||
|
@ -1015,6 +764,181 @@ final class MonsterClass
|
||||||
flags.set(CFlags.Parsed);
|
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
|
// Insert the class into the scope system. All parent classes must
|
||||||
// be loaded before this is called.
|
// be loaded before this is called.
|
||||||
void createScope()
|
void createScope()
|
||||||
|
@ -1036,9 +960,10 @@ final class MonsterClass
|
||||||
parents.length = parentNames.length;
|
parents.length = parentNames.length;
|
||||||
foreach(int i, pName; parentNames)
|
foreach(int i, pName; parentNames)
|
||||||
{
|
{
|
||||||
// Find the class. findClass guarantees that the returned
|
// Find the class. vm.load() returns the existing class if
|
||||||
// class is scoped.
|
// it has already been loaded.
|
||||||
MonsterClass mc = global.findClass(pName);
|
MonsterClass mc = vm.load(pName.str);
|
||||||
|
mc.requireScope();
|
||||||
|
|
||||||
assert(mc !is null);
|
assert(mc !is null);
|
||||||
assert(mc.isScoped);
|
assert(mc.isScoped);
|
||||||
|
@ -1208,6 +1133,13 @@ final class MonsterClass
|
||||||
foreach(func; funcdecs)
|
foreach(func; funcdecs)
|
||||||
func.resolveBody();
|
func.resolveBody();
|
||||||
|
|
||||||
|
// Including the constructor
|
||||||
|
if(scptConst !is null)
|
||||||
|
{
|
||||||
|
scptConst.resolve(sc);
|
||||||
|
assert(scptConst.fn.owner is this);
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve states
|
// Resolve states
|
||||||
foreach(state; statedecs)
|
foreach(state; statedecs)
|
||||||
state.resolve(sc);
|
state.resolve(sc);
|
||||||
|
@ -1222,13 +1154,41 @@ final class MonsterClass
|
||||||
foreach(var; vardecs)
|
foreach(var; vardecs)
|
||||||
var.validate();
|
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);
|
flags.set(CFlags.Resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
alias int[] ia;
|
alias int[] ia;
|
||||||
// These are platform dependent:
|
// This is platform dependent:
|
||||||
static const iasize = ia.sizeof / int.sizeof;
|
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()
|
void compileBody()
|
||||||
{
|
{
|
||||||
assert(!isCompiled, getName() ~ " is already compiled");
|
assert(!isCompiled, getName() ~ " is already compiled");
|
||||||
|
@ -1240,20 +1200,21 @@ final class MonsterClass
|
||||||
foreach(mc; tree[0..$-1])
|
foreach(mc; tree[0..$-1])
|
||||||
mc.requireCompile();
|
mc.requireCompile();
|
||||||
|
|
||||||
// Generate data segment and byte code for functions and
|
// Generate byte code for functions and states.
|
||||||
// states. The result is stored in the respective objects.
|
|
||||||
foreach(f; funcdecs) f.compile();
|
foreach(f; funcdecs) f.compile();
|
||||||
foreach(s; statedecs) s.compile();
|
foreach(s; statedecs) s.compile();
|
||||||
|
if(scptConst !is null) scptConst.compile();
|
||||||
|
|
||||||
// Set the data segment for this class.
|
// Get the data segment size for this class
|
||||||
data = getDataSegment();
|
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
|
// Calculate the total data size we need to allocate for each
|
||||||
// object
|
// object
|
||||||
uint tsize = 0;
|
uint tsize = 0;
|
||||||
foreach(c; tree)
|
foreach(c; tree)
|
||||||
{
|
{
|
||||||
tsize += c.data.length; // Data segment size
|
tsize += c.dataSize; // Data segment size
|
||||||
tsize += MonsterObject.exSize; // Extra data per object
|
tsize += MonsterObject.exSize; // Extra data per object
|
||||||
tsize += iasize; // The size of our entry in the data[]
|
tsize += iasize; // The size of our entry in the data[]
|
||||||
// table
|
// table
|
||||||
|
@ -1262,7 +1223,7 @@ final class MonsterClass
|
||||||
// Allocate the buffer
|
// Allocate the buffer
|
||||||
totalData = new int[tsize];
|
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[] slice = totalData;
|
||||||
int[] get(int ints)
|
int[] get(int ints)
|
||||||
{
|
{
|
||||||
|
@ -1272,14 +1233,16 @@ final class MonsterClass
|
||||||
return res;
|
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);
|
get(iasize*tree.length);
|
||||||
|
|
||||||
// Assign the data segment values
|
// Set up the slice list
|
||||||
foreach(c; tree)
|
totalSliced.length = tree.length;
|
||||||
|
foreach(i,c; tree)
|
||||||
{
|
{
|
||||||
int[] d = get(c.data.length);
|
// Data segment slice
|
||||||
d[] = c.data[];
|
totalSliced[i] = get(c.dataSize);
|
||||||
|
|
||||||
// Skip the extra data
|
// Skip the extra data
|
||||||
get(MonsterObject.exSize);
|
get(MonsterObject.exSize);
|
||||||
|
@ -1287,6 +1250,44 @@ final class MonsterClass
|
||||||
|
|
||||||
// At this point we should have used up the entire slice
|
// At this point we should have used up the entire slice
|
||||||
assert(slice.length == 0);
|
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);
|
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 a named function directly. The function is executed
|
||||||
// call() returns when the function is finished. The function is
|
// immediately, and call() returns when the function is
|
||||||
// called virtually, so any child class function that overrides it
|
// finished. The function is called virtually, so any child class
|
||||||
// will take precedence.
|
// 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)
|
void call(char[] name)
|
||||||
{
|
{
|
||||||
cls.findFunction(name).call(this);
|
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
|
// Create a paused thread that's set up to call the given
|
||||||
// function. It must be started with Thread.call() or
|
// function. It must be started with Thread.call() or
|
||||||
// Thread.restart().
|
// Thread.restart().
|
||||||
|
@ -302,13 +325,6 @@ struct MonsterObject
|
||||||
return trd;
|
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
|
/* Set state. Invoked by the statement "state = statename;". This
|
||||||
function can be called in several situations, with various
|
function can be called in several situations, with various
|
||||||
results:
|
results:
|
||||||
|
|
|
@ -29,6 +29,7 @@ import std.stdio;
|
||||||
import std.utf;
|
import std.utf;
|
||||||
|
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
|
import monster.options;
|
||||||
|
|
||||||
import monster.vm.mobject;
|
import monster.vm.mobject;
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
|
@ -56,13 +57,9 @@ struct CodeStack
|
||||||
public:
|
public:
|
||||||
void init()
|
void init()
|
||||||
{
|
{
|
||||||
// 100 is just a random number - it should probably be quite a bit
|
data.length = maxStack;
|
||||||
// larger (but NOT dynamic, since we want to catch run-away code.)
|
left = maxStack;
|
||||||
const int size = 100;
|
total = maxStack;
|
||||||
|
|
||||||
data.length = size;
|
|
||||||
left = size;
|
|
||||||
total = size;
|
|
||||||
pos = data.ptr;
|
pos = data.ptr;
|
||||||
frame = null;
|
frame = null;
|
||||||
}
|
}
|
||||||
|
@ -344,6 +341,46 @@ struct CodeStack
|
||||||
alias pop4!(dchar) popChar;
|
alias pop4!(dchar) popChar;
|
||||||
alias get4!(dchar) getChar;
|
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
|
// Pop off and ignore a given amount of values
|
||||||
void pop(int num)
|
void pop(int num)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,6 +29,7 @@ import std.uni;
|
||||||
import std.c.string;
|
import std.c.string;
|
||||||
|
|
||||||
import monster.util.freelist;
|
import monster.util.freelist;
|
||||||
|
import monster.options;
|
||||||
|
|
||||||
import monster.compiler.bytecode;
|
import monster.compiler.bytecode;
|
||||||
import monster.compiler.linespec;
|
import monster.compiler.linespec;
|
||||||
|
@ -46,12 +47,15 @@ import monster.vm.arrays;
|
||||||
import monster.vm.iterators;
|
import monster.vm.iterators;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.vm.fstack;
|
import monster.vm.fstack;
|
||||||
|
import monster.vm.vm;
|
||||||
|
|
||||||
import std.math : floor;
|
import std.math : floor;
|
||||||
|
|
||||||
// Used for array copy below. It handles overlapping data for us.
|
// Used for array copy below. It handles overlapping data for us.
|
||||||
extern(C) void* memmove(void *dest, void *src, size_t n);
|
extern(C) void* memmove(void *dest, void *src, size_t n);
|
||||||
|
|
||||||
|
//debug=traceOps;
|
||||||
|
|
||||||
import monster.util.list;
|
import monster.util.list;
|
||||||
alias _lstNode!(Thread) _tmp1;
|
alias _lstNode!(Thread) _tmp1;
|
||||||
alias __FreeNode!(Thread) _tmp2;
|
alias __FreeNode!(Thread) _tmp2;
|
||||||
|
@ -111,6 +115,8 @@ struct Thread
|
||||||
// Get a new thread. It starts in the 'transient' list.
|
// Get a new thread. It starts in the 'transient' list.
|
||||||
static Thread* getNew()
|
static Thread* getNew()
|
||||||
{
|
{
|
||||||
|
vm.init();
|
||||||
|
|
||||||
auto cn = scheduler.transient.getNew();
|
auto cn = scheduler.transient.getNew();
|
||||||
cn.list = &scheduler.transient;
|
cn.list = &scheduler.transient;
|
||||||
|
|
||||||
|
@ -124,6 +130,14 @@ struct Thread
|
||||||
return cn;
|
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
|
// Schedule the function to run the next frame. Can only be used on
|
||||||
// paused threads.
|
// paused threads.
|
||||||
void restart()
|
void restart()
|
||||||
|
@ -320,7 +334,8 @@ struct Thread
|
||||||
"Thread already has a stack");
|
"Thread already has a stack");
|
||||||
assert(isRunning,
|
assert(isRunning,
|
||||||
"cannot put a non-running thread in the background");
|
"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
|
// We're no longer the current thread
|
||||||
cthread = null;
|
cthread = null;
|
||||||
|
@ -391,6 +406,7 @@ struct Thread
|
||||||
// Move this node to another list.
|
// Move this node to another list.
|
||||||
void moveTo(NodeList *to)
|
void moveTo(NodeList *to)
|
||||||
{
|
{
|
||||||
|
if(list is to) return;
|
||||||
assert(list !is null);
|
assert(list !is null);
|
||||||
list.moveTo(*to, this);
|
list.moveTo(*to, this);
|
||||||
list = to;
|
list = to;
|
||||||
|
@ -500,13 +516,9 @@ struct Thread
|
||||||
// Execute instructions in the current function stack entry. This is
|
// Execute instructions in the current function stack entry. This is
|
||||||
// the main workhorse of the VM, the "byte-code CPU". The function
|
// the main workhorse of the VM, the "byte-code CPU". The function
|
||||||
// is called (possibly recursively) whenever a byte-code function is
|
// 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()
|
void execute()
|
||||||
{
|
{
|
||||||
// The maximum amount of instructions we execute before assuming
|
|
||||||
// an infinite loop.
|
|
||||||
static const long limit = 10000000;
|
|
||||||
|
|
||||||
assert(!isDead);
|
assert(!isDead);
|
||||||
assert(fstack.cur !is null,
|
assert(fstack.cur !is null,
|
||||||
"Thread.execute called but there is no code on the function stack.");
|
"Thread.execute called but there is no code on the function stack.");
|
||||||
|
@ -595,26 +607,37 @@ struct Thread
|
||||||
int val, val2;
|
int val, val2;
|
||||||
long lval;
|
long lval;
|
||||||
|
|
||||||
// Disable this for now.
|
static if(enableExecLimit)
|
||||||
// or at least a compile time option.
|
long count = 0;
|
||||||
//for(long i=0;i<limit;i++)
|
|
||||||
for(;;)
|
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();
|
ubyte opCode = code.get();
|
||||||
|
|
||||||
//writefln("stack=", stack.getPos);
|
debug(traceOps)
|
||||||
//writefln("exec(%s): %s", code.getLine, bcToString[opCode]);
|
{
|
||||||
|
writefln("exec: %s", bcToString[opCode]);
|
||||||
|
writefln("stack=", stack.getPos);
|
||||||
|
}
|
||||||
|
|
||||||
switch(opCode)
|
switch(opCode)
|
||||||
{
|
{
|
||||||
|
|
||||||
case BC.Exit:
|
case BC.Exit:
|
||||||
// Step down once on the function stack
|
// Step down once on the function stack
|
||||||
fstack.pop();
|
fstack.pop();
|
||||||
|
|
||||||
|
// Leave execute() when the next level down is not a
|
||||||
|
// script function
|
||||||
if(!fstack.isNormal())
|
if(!fstack.isNormal())
|
||||||
// The current function isn't a script function, so
|
|
||||||
// exit.
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
assert(!shouldExit);
|
assert(!shouldExit);
|
||||||
|
@ -700,15 +723,101 @@ struct Thread
|
||||||
if(shouldExit) return;
|
if(shouldExit) return;
|
||||||
break;
|
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:
|
case BC.New:
|
||||||
// Create a new object. Look up the class index in the
|
{
|
||||||
// global class table, and create an object from it.
|
// Create a new object. Look up the class index in the
|
||||||
stack.pushObject(global.getClass(cast(CIndex)code.getInt())
|
// global class table, and create an object from it. Do
|
||||||
.createObject());
|
// 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;
|
break;
|
||||||
|
|
||||||
case BC.Clone:
|
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;
|
break;
|
||||||
|
|
||||||
case BC.Jump:
|
case BC.Jump:
|
||||||
|
@ -782,23 +891,23 @@ struct Thread
|
||||||
|
|
||||||
case BC.Dup: stack.pushInt(*stack.getInt(0)); break;
|
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
|
// Get the pointer off the stack, and convert it to a real
|
||||||
// pointer.
|
// pointer.
|
||||||
ptr = popPtr();
|
ptr = popPtr();
|
||||||
// Read the value and store it, but leave it in the stack
|
// Pop the value and store it
|
||||||
*ptr = *stack.getInt(0);
|
*ptr = stack.popInt();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BC.StoreRet8:
|
case BC.Store8:
|
||||||
ptr = popPtr();
|
ptr = popPtr();
|
||||||
*(cast(long*)ptr) = *stack.getLong(1);
|
*(cast(long*)ptr) = stack.popLong();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BC.StoreRetMult:
|
case BC.StoreMult:
|
||||||
val = code.getInt(); // Size
|
val = code.getInt(); // Size
|
||||||
ptr = popPtr();
|
ptr = popPtr();
|
||||||
ptr[0..val] = stack.getInts(val-1, val);
|
ptr[0..val] = stack.popInts(val);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Int / uint operations
|
// Int / uint operations
|
||||||
|
@ -1224,8 +1333,6 @@ struct Thread
|
||||||
if(arf.iarr.length != iarr.length)
|
if(arf.iarr.length != iarr.length)
|
||||||
fail(format("Array length mismatch (%s != %s)",
|
fail(format("Array length mismatch (%s != %s)",
|
||||||
arf.iarr.length, iarr.length));
|
arf.iarr.length, iarr.length));
|
||||||
// Push back the destination
|
|
||||||
stack.pushArray(arf);
|
|
||||||
|
|
||||||
// Use memmove, since it will handle overlapping data
|
// Use memmove, since it will handle overlapping data
|
||||||
memmove(arf.iarr.ptr, iarr.ptr, iarr.length*4);
|
memmove(arf.iarr.ptr, iarr.ptr, iarr.length*4);
|
||||||
|
@ -1298,8 +1405,6 @@ struct Thread
|
||||||
assert(arf.iarr.length % val == 0);
|
assert(arf.iarr.length % val == 0);
|
||||||
for(int i=0; i<arf.iarr.length; i+=val)
|
for(int i=0; i<arf.iarr.length; i+=val)
|
||||||
arf.iarr[i..i+val] = iarr[];
|
arf.iarr[i..i+val] = iarr[];
|
||||||
|
|
||||||
stack.pushArray(arf);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BC.CatArray:
|
case BC.CatArray:
|
||||||
|
@ -1411,8 +1516,7 @@ struct Thread
|
||||||
bcToString[opCode], opCode));
|
bcToString[opCode], opCode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fail(format("Execution unterminated after %s instructions.", limit,
|
assert(0);
|
||||||
" Possibly an infinite loop, aborting."));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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.thread;
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
import monster.vm.mobject;
|
import monster.vm.mobject;
|
||||||
|
import monster.vm.init;
|
||||||
|
import monster.vm.fstack;
|
||||||
|
|
||||||
import monster.compiler.tokenizer;
|
import monster.compiler.tokenizer;
|
||||||
import monster.compiler.linespec;
|
import monster.compiler.linespec;
|
||||||
|
@ -36,8 +38,14 @@ import monster.compiler.scopes;
|
||||||
|
|
||||||
import monster.modules.timer;
|
import monster.modules.timer;
|
||||||
import monster.modules.frames;
|
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;
|
import monster.util.string;
|
||||||
|
|
||||||
VM vm;
|
VM vm;
|
||||||
|
@ -48,6 +56,8 @@ struct VM
|
||||||
// is given, an instance of an empty class is used.
|
// is given, an instance of an empty class is used.
|
||||||
Thread *run(char[] file, MonsterObject *obj = null)
|
Thread *run(char[] file, MonsterObject *obj = null)
|
||||||
{
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
Thread *trd;
|
Thread *trd;
|
||||||
auto func = new Function;
|
auto func = new Function;
|
||||||
if(obj !is null)
|
if(obj !is null)
|
||||||
|
@ -65,44 +75,316 @@ struct VM
|
||||||
|
|
||||||
void frame(float time = 0)
|
void frame(float time = 0)
|
||||||
{
|
{
|
||||||
if(time != 0)
|
static if(!timer_useClock)
|
||||||
idleTime.add(time);
|
{
|
||||||
|
if(time != 0)
|
||||||
|
idleTime.add(time);
|
||||||
|
}
|
||||||
|
|
||||||
updateFrames(time);
|
updateFrames(time);
|
||||||
|
|
||||||
scheduler.doFrame();
|
scheduler.doFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path to search for script files. Extremely simple at the moment.
|
// Execute a single statement. Context-dependent statements such as
|
||||||
private char[][] includes = [""];
|
// '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)
|
void addPath(char[] path)
|
||||||
{
|
{
|
||||||
// Make sure the path is slash terminated.
|
init();
|
||||||
if(!path.ends("/") && !path.ends("\\"))
|
addVFS(new FileVFS(path));
|
||||||
path ~= '/';
|
|
||||||
|
|
||||||
includes ~= path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for a file in the various paths. Returns true if found,
|
void addVFS(VFS fs) { init(); vfs.add(fs); }
|
||||||
// false otherwise. Changes fname to point to the correct path.
|
void addVFSFirst(VFS fs) { init(); vfs.addFirst(fs); }
|
||||||
bool findFile(ref char[] fname)
|
|
||||||
|
void init()
|
||||||
{
|
{
|
||||||
// Check against our include paths. In the future we will replace
|
if(!initHasRun)
|
||||||
// this with a more flexible system, allowing virtual file systems,
|
doMonsterInit();
|
||||||
// archive files, complete platform independence, improved error
|
}
|
||||||
// checking etc.
|
|
||||||
foreach(path; includes)
|
// 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;
|
fname = name1;
|
||||||
if(exists(res))
|
cname = name2;
|
||||||
{
|
}
|
||||||
fname = res;
|
else
|
||||||
return true;
|
{
|
||||||
}
|
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
|
// Covers all alchemy apparatus - mortars, retorts, etc
|
||||||
class Apparatus : InventoryItem;
|
class Apparatus : InventoryItem;
|
||||||
|
|
||||||
enum AppaType
|
enum AppaType : char[] altName
|
||||||
{
|
{
|
||||||
MortarPestle = 0 : "Mortar and Pestle",
|
MortarPestle = 0 : "Mortar and Pestle",
|
||||||
Albemic = 1,
|
Albemic = 1,
|
||||||
|
|
|
@ -35,9 +35,6 @@ import std.string;
|
||||||
// Set up the base Monster classes we need in OpenMW
|
// Set up the base Monster classes we need in OpenMW
|
||||||
void initMonsterScripts()
|
void initMonsterScripts()
|
||||||
{
|
{
|
||||||
initAllModules();
|
|
||||||
setSleepMethod(SleepMethod.Timer);
|
|
||||||
|
|
||||||
// Add the script directories
|
// Add the script directories
|
||||||
vm.addPath("mscripts/");
|
vm.addPath("mscripts/");
|
||||||
vm.addPath("mscripts/gameobjects/");
|
vm.addPath("mscripts/gameobjects/");
|
||||||
|
@ -48,7 +45,7 @@ void initMonsterScripts()
|
||||||
global.registerImport("io", "random", "timer");
|
global.registerImport("io", "random", "timer");
|
||||||
|
|
||||||
// Get the Config singleton object
|
// Get the Config singleton object
|
||||||
config.mo = (new MonsterClass("Config")).getSing();
|
config.mo = vm.load("Config").getSing();
|
||||||
|
|
||||||
// Set up the GUI Monster module
|
// Set up the GUI Monster module
|
||||||
setupGUIScripts();
|
setupGUIScripts();
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
module ogre.gui;
|
module ogre.gui;
|
||||||
|
|
||||||
import monster.monster;
|
import monster.monster;
|
||||||
|
import monster.vm.mclass;
|
||||||
import ogre.bindings;
|
import ogre.bindings;
|
||||||
import std.string;
|
import std.string;
|
||||||
|
|
||||||
|
@ -232,8 +233,8 @@ void setupGUIScripts()
|
||||||
{
|
{
|
||||||
vm.addPath("mscripts/gui/");
|
vm.addPath("mscripts/gui/");
|
||||||
vm.addPath("mscripts/gui/module/");
|
vm.addPath("mscripts/gui/module/");
|
||||||
gmc = new MonsterClass("gui", "gui.mn");
|
gmc = vm.load("gui", "gui.mn");
|
||||||
wid_mc = new MonsterClass("Widget", "widget.mn");
|
wid_mc = vm.load("Widget", "widget.mn");
|
||||||
/*
|
/*
|
||||||
but_mc = new MonsterClass("Button", "button.mn");
|
but_mc = new MonsterClass("Button", "button.mn");
|
||||||
tex_mc = new MonsterClass("Text", "text.mn");
|
tex_mc = new MonsterClass("Text", "text.mn");
|
||||||
|
|
|
@ -12,7 +12,7 @@ void loadGameSettings()
|
||||||
{
|
{
|
||||||
// Load the GameSettings Monster class, and get the singleton
|
// Load the GameSettings Monster class, and get the singleton
|
||||||
// instance
|
// instance
|
||||||
MonsterClass mc = MonsterClass.find("GMST");
|
MonsterClass mc = vm.load("GMST");
|
||||||
gmstObj = mc.getSing();
|
gmstObj = mc.getSing();
|
||||||
|
|
||||||
foreach(a, b; gameSettings.names)
|
foreach(a, b; gameSettings.names)
|
||||||
|
|
|
@ -68,7 +68,7 @@ struct Music
|
||||||
assert(controlC is null);
|
assert(controlC is null);
|
||||||
assert(controlM is null);
|
assert(controlM is null);
|
||||||
|
|
||||||
jukeC = MonsterClass.get("Jukebox");
|
jukeC = vm.load("Jukebox");
|
||||||
jukeC.bind("waitUntilFinished",
|
jukeC.bind("waitUntilFinished",
|
||||||
new Idle_waitUntilFinished);
|
new Idle_waitUntilFinished);
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ struct Music
|
||||||
|
|
||||||
jukeC.bindConst({new Jukebox(params.obj()); });
|
jukeC.bindConst({new Jukebox(params.obj()); });
|
||||||
|
|
||||||
controlC = MonsterClass.get("Music");
|
controlC = vm.load("Music");
|
||||||
controlM = controlC.getSing();
|
controlM = controlC.getSing();
|
||||||
controlM.call("setup");
|
controlM.call("setup");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue