Adding in Monster
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@61 ea6a568a-9f4f-0410-981a-c910a81bb256actorid
parent
44f7b155f8
commit
3f1aeb3aef
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,95 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (block.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.compiler.block;
|
||||
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.assembler;
|
||||
|
||||
import monster.vm.error;
|
||||
|
||||
// Base class for all kinds of blocks. A 'block' is a token or
|
||||
// collection of tokens that belong together and form a syntactical
|
||||
// unit. A block might for example be a statement, a declaration, a
|
||||
// block of code, or the entire class.
|
||||
abstract class Block
|
||||
{
|
||||
protected:
|
||||
static Token next(ref TokenArray toks)
|
||||
{
|
||||
if(toks.length == 0)
|
||||
fail("Unexpected end of file");
|
||||
Token tt = toks[0];
|
||||
toks = toks[1..toks.length];
|
||||
return tt;
|
||||
}
|
||||
|
||||
static Floc getLoc(TokenArray toks)
|
||||
{
|
||||
Floc loc;
|
||||
if(toks.length) loc = toks[0].loc;
|
||||
return loc;
|
||||
}
|
||||
|
||||
static bool isNext(ref TokenArray toks, TT type)
|
||||
{
|
||||
Floc ln;
|
||||
return isNext(toks, type, ln);
|
||||
}
|
||||
|
||||
static bool isNext(ref TokenArray toks, TT type, out Floc loc)
|
||||
{
|
||||
if( toks.length == 0 ) return false;
|
||||
loc = toks[0].loc;
|
||||
if( toks[0].type != type ) return false;
|
||||
next(toks);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isNext(ref TokenArray toks, TT type, out Token tok)
|
||||
{
|
||||
if( toks.length == 0 ) return false;
|
||||
if( toks[0].type != type ) return false;
|
||||
tok = next(toks);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sets the assembler debug line to the line belonging to this
|
||||
// block.
|
||||
final void setLine() { tasm.setLine(loc.line); }
|
||||
|
||||
public:
|
||||
// File position where this block was defined
|
||||
Floc loc;
|
||||
|
||||
// Parse a list of tokens and attempt to understand how they belong
|
||||
// together. This is the syntactical level.
|
||||
void parse(ref TokenArray toks);
|
||||
|
||||
// This goes through the code, resolves names and types etc,
|
||||
// converts expressions into an intermediate form which can be
|
||||
// compiled to byte code later. This is basically the semantic level.
|
||||
void resolve(Scope sc);
|
||||
}
|
@ -0,0 +1,635 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (bytecode.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.compiler.bytecode;
|
||||
|
||||
// Byte code operands
|
||||
enum BC
|
||||
{
|
||||
Exit = 1, // Exit function.
|
||||
|
||||
Call, // Call function in this object. Takes a class
|
||||
// index and a function index, both ints
|
||||
|
||||
CallFar, // Call function in another object. Takes a
|
||||
// class index and a function index. The
|
||||
// object must be pushed on the stack.
|
||||
|
||||
CallIdle, // Calls an idle function (in this object.)
|
||||
// Takes a class index and a function index.
|
||||
|
||||
Return, // Takes a parameter nr (int). Equivalent to:
|
||||
// POPN nr (remove nr values of the stack)
|
||||
// EXIT
|
||||
|
||||
ReturnVal, // Takes parameter nr (int). Equivalent to:
|
||||
// POP value and store it in TMP
|
||||
// POPN nr (remove function parameters)
|
||||
// PUSH TMP (put return value back on stack)
|
||||
// EXIT
|
||||
|
||||
ReturnValN, // Same as ReturnVal, but takes a second
|
||||
// parameter (int). This gives the size of the
|
||||
// return value.
|
||||
|
||||
State, // Set state. Followed by an int giving the
|
||||
// state index, another int giving the label
|
||||
// index and a third int giving the class tree
|
||||
// index. State index -1 means the empty
|
||||
// state, and -1 for the label means no label
|
||||
// is specified (eg state = test; instead of
|
||||
// state = test.label). For state -1 the label
|
||||
// index must also be -1, and the class index
|
||||
// is ignored.
|
||||
|
||||
Halt, // Halt execution. Only allowed in state code.
|
||||
|
||||
New, // Create a new object. Followed by an int
|
||||
// giving the class index (in the file lookup
|
||||
// table)
|
||||
|
||||
Jump, // Jump to given position (int)
|
||||
|
||||
JumpZ, // Pop a value, if it is zero then jump to
|
||||
// given position (int)
|
||||
|
||||
JumpNZ, // Jump if non-zero
|
||||
|
||||
PushData, // Push the next four bytes verbatim
|
||||
|
||||
PushLocal, // Push value of a local variable or function
|
||||
// parameter, index given as a signed
|
||||
// int. Gives the index from the current stack
|
||||
// frame. 0 is the first int, 1 is the second
|
||||
// int, -1 is the last int in the function
|
||||
// parameter area, etc.
|
||||
|
||||
PushClassVar, // Push value in object data segment (in this
|
||||
// object). Parameter is an int offset.
|
||||
|
||||
PushParentVar, // Push value in data segment of parent
|
||||
// object. int class index, int offset
|
||||
|
||||
PushFarClassVar, // Push value from the data segment in another
|
||||
// object. The object reference is already on
|
||||
// the stack. int class index, int offset
|
||||
|
||||
PushFarClassMulti, // Pushes multiple ints from the data
|
||||
// segment. Takes the variable size (int) as
|
||||
// the first parameter, otherwise identical to
|
||||
// PushFarClassVar.
|
||||
|
||||
PushThis, // Push the 'this' object reference
|
||||
|
||||
// The Push*8 instructions are not implemented yet as they are
|
||||
// just optimizations of existing features. The names are reserved
|
||||
// for future use.
|
||||
|
||||
Push8, // Push the next 8 bytes (two ints)
|
||||
// verbatim.
|
||||
PushLocal8, // Does the same as PushLocal except it also
|
||||
// pushes the next int onto the stack. The
|
||||
// index must be > 0 or <= -2.
|
||||
PushClassVar8, // Same as PushClassVar but pushes two ints
|
||||
PushFarClassVar8,
|
||||
|
||||
|
||||
Pop, // Pop data and forget about it
|
||||
|
||||
PopN, // Takes a byte parameter N, equivalent to
|
||||
// calling Pop N times.
|
||||
|
||||
Dup, // Duplicate the next value on the
|
||||
// stack. Equivalent to: a=pop; push a; push
|
||||
// a;
|
||||
|
||||
StoreRet, // Basic operation for moving data to
|
||||
// memory. Schematically pops a Ptr of the
|
||||
// stack, pops a value, moves the value into
|
||||
// the Ptr, and then pushes the value back.
|
||||
|
||||
Store, // Same as StoreRet but does not push the
|
||||
// value back. Not implemented.
|
||||
|
||||
StoreRet8, // Same as StoreRet except two ints are popped
|
||||
// from the stack and moved into the data.
|
||||
|
||||
IAdd, // Standard addition, operates on the two next
|
||||
// ints in the stack, and stores the result in
|
||||
// the stack.
|
||||
|
||||
ISub, // Subtraction. a-b, where a is pushed first.
|
||||
|
||||
IMul, // Multiplication
|
||||
|
||||
IDiv, // Division. The dividend must be pushed
|
||||
// first, the divisor second.
|
||||
UDiv, // uint
|
||||
|
||||
IDivRem, // Reminder division
|
||||
UDivRem, // uint
|
||||
|
||||
INeg, // Negate the next integer on the stack
|
||||
|
||||
FAdd, // Float arithmetic
|
||||
FSub,
|
||||
FMul,
|
||||
FDiv,
|
||||
FIDiv,
|
||||
FDivRem,
|
||||
FNeg,
|
||||
|
||||
LAdd, // Long arithmetic
|
||||
LSub,
|
||||
LMul,
|
||||
LDiv,
|
||||
ULDiv,
|
||||
LDivRem,
|
||||
ULDivRem,
|
||||
LNeg,
|
||||
|
||||
DAdd, // Double arithmetic
|
||||
DSub,
|
||||
DMul,
|
||||
DDiv,
|
||||
DIDiv,
|
||||
DDivRem,
|
||||
DNeg,
|
||||
|
||||
IsEqual, // Pop two ints, push true (1) if they are
|
||||
// equal or false (0) otherwise
|
||||
IsEqualMulti, // Takes an int parameter giving the value size
|
||||
|
||||
IsCaseEqual, // Same as IsEqual, but uses (unicode) case
|
||||
// insensitive match. Values are assumed to be
|
||||
// dchars.
|
||||
|
||||
CmpArray, // Compare two arrays. Only does exact
|
||||
// byte-for-byte matching.
|
||||
ICmpStr, // Does a case insensitive check of two
|
||||
// strings. Both these pop two array indices
|
||||
// of the stack and push a bool.
|
||||
|
||||
PreInc, // ++, pops an address, increases variable,
|
||||
// and pushes the value
|
||||
|
||||
PreDec, // -- (these might change)
|
||||
|
||||
PostInc, // ++ and -- that push the original value
|
||||
PostDec, // rather than the new one
|
||||
|
||||
PreInc8, PreDec8,
|
||||
PostInc8, PostDec8, // 64 bit versions
|
||||
|
||||
Not, // Inverse the bool on the stack
|
||||
|
||||
ILess, // Pops b, pops a, pushes true if a < b, false
|
||||
// otherwise. Works on signed ints.
|
||||
|
||||
ULess, // unsigned ints
|
||||
LLess, // long
|
||||
ULLess, // ulong
|
||||
FLess, // float
|
||||
DLess, // double
|
||||
|
||||
// All the Cast* instructions pops a variable of the given type of
|
||||
// the stack, and pushes back an equivalent variable of the new
|
||||
// type.
|
||||
|
||||
CastI2L, // int to long (signed)
|
||||
|
||||
CastI2F, // int to float
|
||||
CastU2F, // uint to float
|
||||
CastL2F, // long to float
|
||||
CastUL2F, // ulong to float
|
||||
CastD2F, // double to float
|
||||
|
||||
CastI2D, // int to double
|
||||
CastU2D, // uint to double
|
||||
CastL2D, // long to double
|
||||
CastUL2D, // ulong to double
|
||||
CastF2D, // float to double
|
||||
|
||||
CastI2S, // int to char[]. Takes one byte giving the
|
||||
// int type: 1=int,2=uint,3=long,4=ulong
|
||||
CastF2S, // float to char[]. Takes one byte giving the
|
||||
// float size (1 or 2).
|
||||
CastB2S, // bool to char[]
|
||||
CastO2S, // an object to char[]
|
||||
|
||||
// The "Cast Type Array to String" instructions below are used to
|
||||
// convert arrays of the form [1,2,3] and [[1,2,3],[4,5]] to a
|
||||
// string. They all take a byte as parameter, specifying the array
|
||||
// depth. They are NOT IMPLEMENTED YET!
|
||||
|
||||
CastIA2S, // Cast int arrays to char[]
|
||||
CastFA2S, // Cast float arrays to char[]
|
||||
CastBA2S, // Cast bool arrays to char[]
|
||||
CastCA2S, // Cast char arrays to char[]
|
||||
CastOA2S, // Cast object arrays to char[]
|
||||
|
||||
Upcast, // Implicitly cast an object to a parent class
|
||||
// type. Takes an int class index.
|
||||
|
||||
|
||||
FetchElem, // Get an element from an array. Pops the
|
||||
// index, then the array reference, then
|
||||
// pushes the value. The element size is
|
||||
// determined from the array.
|
||||
|
||||
GetArrLen, // Get the length of an array. Pops the array,
|
||||
// pushes the length.
|
||||
|
||||
MakeArray, // Takes an offset (int), the element size
|
||||
// (int) and the total raw length (number of
|
||||
// elements * elem size). Reads data from the
|
||||
// data segment at offset and creates an array
|
||||
// of the given length (times the element
|
||||
// size). Pushes the array index.
|
||||
|
||||
PopToArray, // Takes a raw length n (int) and an element
|
||||
// size (int). Creates an array from the last
|
||||
// n values on the stack and pops them off.
|
||||
// Pushes the new array index. The values are
|
||||
// copied into the new array, and are
|
||||
// independent of the stack.
|
||||
|
||||
NewArray, // Takes one int giving the array nesting
|
||||
// level (rank), one int giving the element
|
||||
// size (s) and s ints giving the initial
|
||||
// value. Pops the lengths (one int per rank
|
||||
// level) from the stack. Pushes a new array
|
||||
// of the given length. The lengths should
|
||||
// pushed in the same order they appear in a
|
||||
// new-expression, ie. new int[1][2] are
|
||||
// pushed as 1 then 2.
|
||||
|
||||
CopyArray, // Pops two array indices from the stack, and
|
||||
// copies the data from one to another. Pushes
|
||||
// back the array index of the
|
||||
// destination. The destination array is
|
||||
// popped first, then the source. The lengths
|
||||
// must match. If the arrays may overlap in
|
||||
// memory without unexpected effects.
|
||||
|
||||
DupArray, // Pops an array index of the stack, creates a
|
||||
// copy of the array, and pushes the index of
|
||||
// the new array.
|
||||
|
||||
MakeConstArray, // Pops an array index, creates a const
|
||||
// reference to the same data, pushes the
|
||||
// index.
|
||||
|
||||
IsConstArray, // Pops the array index, pushes bool
|
||||
// reflecting the const status
|
||||
|
||||
Slice, // Create a slice. Pops the second index, then
|
||||
// the first, then the array index. Pushes a
|
||||
// new array that is a slice of the original.
|
||||
|
||||
FillArray, // Fill an array. Pop an array index, then a
|
||||
// value (int). Sets all the elements in the
|
||||
// array to the value. Pushes the array index
|
||||
// back. Takes an int specifying the element
|
||||
// size.
|
||||
|
||||
CatArray, // Concatinate two arrays, on the stack.
|
||||
|
||||
CatLeft, // Concatinate an array with a left
|
||||
// element. The element is pushed first, so
|
||||
// the array index is first off the stack.
|
||||
CatRight, // Concatinate with right element. Array
|
||||
// pushed first, then element. Both these take
|
||||
// an element size (int).
|
||||
|
||||
ReverseArray, // Reverses an array. The index is on the
|
||||
// stack. The array is reversed in place, and
|
||||
// the index is left untouched on the stack.
|
||||
|
||||
CreateArrayIter, // Create array iterator. Expects the stack to
|
||||
// hold an index(int), a value(int) and an
|
||||
// array index, pushed in that order. Replaces
|
||||
// the array index with an iterator index,
|
||||
// sets the rest to reflect the first element
|
||||
// in iteration. Takes two byte parameters
|
||||
// that are either 0 or 1. If the first is
|
||||
// set, then the array is iterated in the
|
||||
// reverse order. If the second is set, then
|
||||
// the value is a reference, ie. changes to it
|
||||
// will be transfered back to the orignal
|
||||
// array. Pushes false if the array is empty,
|
||||
// true otherwise.
|
||||
|
||||
CreateClassIter, // Create a class iterator. Expects the
|
||||
// stack to hold a value (object index =
|
||||
// int). Takes one int parameter, which is the
|
||||
// class index. Pushes false if no objects
|
||||
// exist, true otherwise.
|
||||
|
||||
IterNext, // Iterate to the next element. Leaves the
|
||||
// iteration variables on the stack. Pushes
|
||||
// false if this was the last element, true
|
||||
// otherwise.
|
||||
|
||||
IterBreak, // Break off iteration. Does exactly the same
|
||||
// thing as IterNext would done if it had just
|
||||
// finished the last iteration step, except it
|
||||
// does not push anything onto the stack.
|
||||
|
||||
IterUpdate, // Update the original array or data from
|
||||
// reference variables in this
|
||||
// iteration. Called whenever a 'ref' variable
|
||||
// is changed, to make sure that the changes
|
||||
// take effect. Takes an int parameter that
|
||||
// gives the stack position (in the current
|
||||
// frame) of the iterator index.
|
||||
|
||||
GetStack, // Internal debugging function. Pushes the
|
||||
// current stack position on the stack. Might
|
||||
// be removed later.
|
||||
|
||||
MultiByte, // This instruction consists of a second byte
|
||||
// or an extra int. Reserved for future
|
||||
// use. This is intended for when / if the
|
||||
// number of instructions exceeds
|
||||
// 256. Instructions in this list with numbers
|
||||
// >= 256 will be coded in this way.
|
||||
|
||||
// Instructions appearing after the MultiByte mark might be coded
|
||||
// as multi-byte. They are handled in a more time-consuming
|
||||
// matter. You should only use this space for instructions that
|
||||
// are seldomly executed.
|
||||
|
||||
Error, // Throw an exception. Takes an error code
|
||||
// (byte) defined below. The library user will
|
||||
// later be able to choose whether this halts
|
||||
// execution entirely or just kills the
|
||||
// offending object.
|
||||
|
||||
Last
|
||||
}
|
||||
|
||||
// Make sure all single-byte instructions will in fact fit in a single
|
||||
// byte.
|
||||
static assert(BC.MultiByte < 255);
|
||||
|
||||
// Make us aware when we break the byte barrier
|
||||
static assert(BC.Last < 255);
|
||||
|
||||
enum Err
|
||||
{
|
||||
None, // Should never happen
|
||||
NoReturn, // Function is missing a return statement
|
||||
}
|
||||
|
||||
// Used for coded pointers. The first byte in a coded pointer gives
|
||||
// the pointer type, and the remaining 24 bits gives an index whose
|
||||
// meaning is determined by the type. The pointers can be used both
|
||||
// for variables and for functions.
|
||||
enum PT
|
||||
{
|
||||
Null = 0, // Null pointer. The index must also be zero.
|
||||
|
||||
// Variable pointers
|
||||
|
||||
Stack = 1, // Index is relative to function stack
|
||||
// frame. Used for local variables.
|
||||
|
||||
DataOffs = 2, // This class, this object. Index is data
|
||||
// segment offset.
|
||||
|
||||
DataOffsCls = 4, // Variable is in this object, but in another
|
||||
// class. The class MUST be a parent class of the
|
||||
// current object. A class index follows this
|
||||
// pointer on the stack.
|
||||
|
||||
FarDataOffs = 5, // Another class, another object. The index is a
|
||||
// data offset. Pop the class index off the
|
||||
// stack, and then object index.
|
||||
|
||||
ArrayIndex = 30, // Pointer to an array element. The array and
|
||||
// the index are pushed on the stack, the
|
||||
// pointer index is zero.
|
||||
}
|
||||
|
||||
char[] errorString(ubyte er)
|
||||
{
|
||||
if(er < errorToString.length)
|
||||
return errorToString[er];
|
||||
return "Unknown error code";
|
||||
}
|
||||
|
||||
union _CodePtr
|
||||
{
|
||||
// How the pointer is coded
|
||||
align(1) struct
|
||||
{
|
||||
ubyte type;
|
||||
int val24;
|
||||
}
|
||||
|
||||
// The end result is stored in val32
|
||||
align(1) struct
|
||||
{
|
||||
int val32;
|
||||
ubyte remains;
|
||||
}
|
||||
}
|
||||
static assert(_CodePtr.sizeof == 5);
|
||||
|
||||
// Encode a "pointer". Pointers are two shorts encoded into an
|
||||
// int. The first byte is the pointer type, the remaining 24 bits
|
||||
// gives the index.
|
||||
int codePtr(PT type, int index)
|
||||
{
|
||||
assert(index >= -(1<<23) && index < (1<<24),
|
||||
"index out of range for 24 bit value");
|
||||
assert(type != 0 || index == 0,
|
||||
"null pointers must have index == 0");
|
||||
assert(type == PT.Stack || index >= 0,
|
||||
"only PT.Stack can have a negative index");
|
||||
|
||||
|
||||
_CodePtr t;
|
||||
t.type = type;
|
||||
t.val24 = index;
|
||||
|
||||
assert(t.remains == 0);
|
||||
|
||||
return t.val32;
|
||||
}
|
||||
|
||||
void decodePtr(int ptr, out PT type, out int index)
|
||||
{
|
||||
_CodePtr t;
|
||||
t.val32 = ptr;
|
||||
|
||||
type = cast(PT) t.type;
|
||||
index = t.val24;
|
||||
|
||||
assert(type != 0 || index == 0,
|
||||
"null pointers must have index == 0");
|
||||
}
|
||||
|
||||
// Getting the name of an enum should be much easier than creating
|
||||
// braindead constructions like this. Although this is still much
|
||||
// better than the C++ equivalent. I'm just happy I did it through a
|
||||
// script instead of typing it all by hand.
|
||||
|
||||
// These kind of braindead constructions will luckily be completely
|
||||
// unnecessary in Monster script, We will not only will have .name
|
||||
// property on enums, but make it easy to assign other values (like
|
||||
// numbers and descriptions) to them as well.
|
||||
char[][] errorToString =
|
||||
[
|
||||
Err.None: "No error!",
|
||||
Err.NoReturn: "Function ended without returning a value"
|
||||
];
|
||||
|
||||
char[][] bcToString =
|
||||
[
|
||||
BC.Exit: "Exit",
|
||||
BC.Call: "Call",
|
||||
BC.CallFar: "CallFar",
|
||||
BC.CallIdle: "CallIdle",
|
||||
BC.Return: "Return",
|
||||
BC.ReturnVal: "ReturnVal",
|
||||
BC.ReturnValN: "ReturnValN",
|
||||
BC.State: "State",
|
||||
BC.Halt: "Halt",
|
||||
BC.New: "New",
|
||||
BC.Jump: "Jump",
|
||||
BC.JumpZ: "JumpZ",
|
||||
BC.JumpNZ: "JumpNZ",
|
||||
BC.PushData: "PushData",
|
||||
BC.PushLocal: "PushLocal",
|
||||
BC.PushClassVar: "PushClassVar",
|
||||
BC.PushParentVar: "PushParentVar",
|
||||
BC.PushFarClassVar: "PushFarClassVar",
|
||||
BC.PushFarClassMulti: "PushFarClassMulti",
|
||||
BC.PushThis: "PushThis",
|
||||
BC.Push8: "Push8",
|
||||
BC.PushLocal8: "PushLocal8",
|
||||
BC.PushClassVar8: "PushClassVar8",
|
||||
BC.PushFarClassVar8: "PushFarClassVar8",
|
||||
BC.Pop: "Pop",
|
||||
BC.PopN: "PopN",
|
||||
BC.Dup: "Dup",
|
||||
BC.StoreRet: "StoreRet",
|
||||
BC.Store: "Store",
|
||||
BC.StoreRet8: "StoreRet8",
|
||||
BC.FetchElem: "FetchElem",
|
||||
BC.GetArrLen: "GetArrLen",
|
||||
BC.IMul: "IMul",
|
||||
BC.IAdd: "IAdd",
|
||||
BC.ISub: "ISub",
|
||||
BC.IDiv: "IDiv",
|
||||
BC.IDivRem: "IDivRem",
|
||||
BC.UDiv: "UDiv",
|
||||
BC.UDivRem: "UDivRem",
|
||||
BC.INeg: "INeg",
|
||||
BC.LMul: "LMul",
|
||||
BC.LAdd: "LAdd",
|
||||
BC.LSub: "LSub",
|
||||
BC.LDiv: "LDiv",
|
||||
BC.LDivRem: "LDivRem",
|
||||
BC.ULDiv: "ULDiv",
|
||||
BC.ULDivRem: "ULDivRem",
|
||||
BC.LNeg: "LNeg",
|
||||
BC.DMul: "DMul",
|
||||
BC.DAdd: "DAdd",
|
||||
BC.DSub: "DSub",
|
||||
BC.DDiv: "DDiv",
|
||||
BC.DIDiv: "DIDiv",
|
||||
BC.DDivRem: "DDivRem",
|
||||
BC.DNeg: "DNeg",
|
||||
BC.FAdd: "FAdd",
|
||||
BC.FSub: "FSub",
|
||||
BC.FMul: "FMul",
|
||||
BC.FDiv: "FDiv",
|
||||
BC.FIDiv: "FIDiv",
|
||||
BC.FDivRem: "FDivRem",
|
||||
BC.FNeg: "FNeg",
|
||||
BC.IsEqual: "IsEqual",
|
||||
BC.IsEqualMulti: "IsEqualMulti",
|
||||
BC.IsCaseEqual: "IsCaseEqual",
|
||||
BC.CmpArray: "CmpArray",
|
||||
BC.ICmpStr: "ICmpStr",
|
||||
BC.PreInc: "PreInc",
|
||||
BC.PreDec: "PreDec",
|
||||
BC.PostInc: "PostInc",
|
||||
BC.PostDec: "PostDec",
|
||||
BC.PreInc8: "PreInc8",
|
||||
BC.PreDec8: "PreDec8",
|
||||
BC.PostInc8: "PostInc8",
|
||||
BC.PostDec8: "PostDec8",
|
||||
BC.Not: "Not",
|
||||
BC.ILess: "ILess",
|
||||
BC.ULess: "ULess",
|
||||
BC.LLess: "LLess",
|
||||
BC.ULLess: "ULLess",
|
||||
BC.FLess: "FLess",
|
||||
BC.DLess: "DLess",
|
||||
BC.CastI2L: "CastI2L",
|
||||
BC.CastI2F: "CastI2F",
|
||||
BC.CastU2F: "CastU2F",
|
||||
BC.CastL2F: "CastL2F",
|
||||
BC.CastUL2F: "CastUL2F",
|
||||
BC.CastD2F: "CastD2F",
|
||||
BC.CastI2D: "CastI2D",
|
||||
BC.CastU2D: "CastU2D",
|
||||
BC.CastL2D: "CastL2D",
|
||||
BC.CastUL2D: "CastUL2D",
|
||||
BC.CastF2D: "CastF2D",
|
||||
BC.CastI2S: "CastI2S",
|
||||
BC.CastF2S: "CastF2S",
|
||||
BC.CastB2S: "CastB2S",
|
||||
BC.CastO2S: "CastO2S",
|
||||
BC.CastIA2S: "CastIA2S",
|
||||
BC.CastFA2S: "CastFA2S",
|
||||
BC.CastBA2S: "CastBA2S",
|
||||
BC.CastCA2S: "CastCA2S",
|
||||
BC.CastOA2S: "CastOA2S",
|
||||
BC.Upcast: "Upcast",
|
||||
BC.MakeArray: "MakeArray",
|
||||
BC.PopToArray: "PopToArray",
|
||||
BC.NewArray: "NewArray",
|
||||
BC.CopyArray: "CopyArray",
|
||||
BC.DupArray: "DupArray",
|
||||
BC.MakeConstArray: "MakeConstArray",
|
||||
BC.IsConstArray: "IsConstArray",
|
||||
BC.Slice: "Slice",
|
||||
BC.FillArray: "FillArray",
|
||||
BC.CatArray: "CatArray",
|
||||
BC.CatLeft: "CatLeft",
|
||||
BC.CatRight: "CatRight",
|
||||
BC.CreateArrayIter: "CreateArrayIter",
|
||||
BC.IterNext: "IterNext",
|
||||
BC.IterBreak: "IterBreak",
|
||||
BC.IterUpdate: "IterUpdate",
|
||||
BC.CreateClassIter: "CreateClassIter",
|
||||
BC.GetStack: "GetStack",
|
||||
BC.MultiByte: "MultiByte",
|
||||
BC.Error: "Error",
|
||||
];
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,470 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (functions.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.compiler.functions;
|
||||
|
||||
enum FuncType
|
||||
{
|
||||
Normal, // Normal function, defined in script code
|
||||
Native, // Unassigned native function
|
||||
NativeDFunc, // Native D function
|
||||
NativeDDel, // Native D delegate
|
||||
NativeCFunc, // Native C function
|
||||
Abstract, // Abstract, does not have a body
|
||||
Idle, // Idle function, can only be called in state code
|
||||
}
|
||||
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.assembler;
|
||||
import monster.compiler.bytecode;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.variables;
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.linespec;
|
||||
import monster.compiler.statement;
|
||||
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.idlefunction;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.error;
|
||||
import monster.vm.fstack;
|
||||
|
||||
import monster.minibos.stdio;
|
||||
|
||||
// One problem with these split compiler / vm classes is that we
|
||||
// likely end up with data (or at least pointers) we don't need, and a
|
||||
// messy interface. The problem with splitting is that we duplicate
|
||||
// code and definitions. One solution is to let the VM class be a
|
||||
// separate class (should be in vm/), but containing all we need in
|
||||
// the VM (like the code, list of parameters, etc.) The point of this
|
||||
// class (which we can rename FunctionCompiler, and leave in this
|
||||
// file) is to create, build and nurture the Function it creates. The
|
||||
// Function can be a struct, really, but I'll look into that. Flipping
|
||||
// function structs off a region and pointing to them is easy and
|
||||
// efficient, but creating classes isn't much worse. It depends if we
|
||||
// need to inherit from them, really.
|
||||
|
||||
// Used for native functions
|
||||
alias void delegate() dg_callback;
|
||||
typedef void function() fn_callback;
|
||||
typedef extern(C) void function() c_callback;
|
||||
|
||||
struct Function
|
||||
{
|
||||
Type type; // Return type
|
||||
FuncType ftype; // Function type
|
||||
Token name;
|
||||
Variable* params[]; // List of parameters
|
||||
MonsterClass owner;
|
||||
int index; // Unique function identifier within its class
|
||||
|
||||
int paramSize;
|
||||
int imprint; // Stack imprint of this function. Equals
|
||||
// (type.getSize() - paramSize)
|
||||
|
||||
union
|
||||
{
|
||||
ubyte[] bcode; // Final compiled code (normal functions)
|
||||
dg_callback natFunc_dg; // Various types of native functions
|
||||
fn_callback natFunc_fn;
|
||||
c_callback natFunc_c;
|
||||
IdleFunction idleFunc; // Idle function callback
|
||||
}
|
||||
LineSpec[] lines; // Line specifications for byte code
|
||||
|
||||
bool isNormal() { return ftype == FuncType.Normal; }
|
||||
bool isNative()
|
||||
{
|
||||
return
|
||||
ftype == FuncType.Native || ftype == FuncType.NativeDFunc ||
|
||||
ftype == FuncType.NativeDDel || ftype == FuncType.NativeCFunc;
|
||||
}
|
||||
bool isAbstract() { return ftype == FuncType.Abstract; }
|
||||
bool isIdle() { return ftype == FuncType.Idle; }
|
||||
|
||||
// True if the last parameter is a vararg parameter, meaning that
|
||||
// this is a function that takes a variable number of arguments.
|
||||
bool isVararg() { return params.length && params[$-1].isVararg; }
|
||||
|
||||
// It would be cool to have a template version of call that took and
|
||||
// returned the correct parameters, and could even check
|
||||
// them. However, doing generic type inference is either very
|
||||
// dangerous or would involve complicated checks (think basing an
|
||||
// ulong or a a byte to float parameters.) The best thing to do is
|
||||
// to handle types at the binding stage, allowing things like
|
||||
// bind!(int,float,int)("myfunc", myfunc) - does type checking for
|
||||
// you. A c++ version could be much more difficult to handle, and
|
||||
// might rely more on manual coding.
|
||||
|
||||
// Used to find the current virtual replacement for this
|
||||
// function. The result will depend on the objects real class, and
|
||||
// possibly on the object state. Functions might also be overridden
|
||||
// explicitly.
|
||||
Function *findVirtual(MonsterObject *obj)
|
||||
{
|
||||
assert(0, "not implemented");
|
||||
//return obj.upcast(owner).getVirtual(index);
|
||||
}
|
||||
|
||||
// Call the function virtually for the given object
|
||||
void vcall(MonsterObject *obj)
|
||||
{
|
||||
assert(0, "not implemented");
|
||||
//obj = obj.upcast(owner);
|
||||
//obj.getVirtual(index).call(obj);
|
||||
}
|
||||
|
||||
// This is used to call the given function from native code. Note
|
||||
// that this is used internally for native functions, but not for
|
||||
// any other type. Idle functions can NOT be called directly from
|
||||
// native code.
|
||||
void call(MonsterObject *obj)
|
||||
{
|
||||
// Cast the object to the correct type for this function.
|
||||
obj = obj.upcast(owner);
|
||||
|
||||
// Push the function on the stack
|
||||
fstack.push(this, obj);
|
||||
|
||||
switch(ftype)
|
||||
{
|
||||
case FuncType.NativeDDel:
|
||||
natFunc_dg();
|
||||
break;
|
||||
case FuncType.NativeDFunc:
|
||||
natFunc_fn();
|
||||
break;
|
||||
case FuncType.NativeCFunc:
|
||||
natFunc_c();
|
||||
break;
|
||||
case FuncType.Normal:
|
||||
obj.thread.execute();
|
||||
break;
|
||||
case FuncType.Native:
|
||||
fail("Called unimplemented native function " ~ toString);
|
||||
case FuncType.Idle:
|
||||
fail("Cannot call idle function " ~ toString ~ " from native code");
|
||||
case FuncType.Abstract:
|
||||
fail("Called unimplemented abstract function " ~ toString);
|
||||
default:
|
||||
assert(0, "unknown FuncType for " ~ toString);
|
||||
}
|
||||
|
||||
// Remove ourselves from the function stack
|
||||
fstack.pop();
|
||||
}
|
||||
|
||||
// Returns the function name, on the form Class.func()
|
||||
char[] toString()
|
||||
{ return owner.name.str ~ "." ~ name.str ~ "()"; }
|
||||
}
|
||||
|
||||
// Responsible for parsing, analysing and compiling functions.
|
||||
class FuncDeclaration : Statement
|
||||
{
|
||||
CodeBlock code;
|
||||
VarDeclaration[] paramList;
|
||||
FuncScope sc; // Scope used internally in the function body
|
||||
|
||||
// The persistant function definition. This data will be passed to
|
||||
// the VM when the compiler is done working.
|
||||
Function *fn;
|
||||
|
||||
// Parse keywords allowed to be used on functions
|
||||
private void parseKeywords(ref TokenArray toks)
|
||||
{
|
||||
Floc loc;
|
||||
|
||||
// Get the old state
|
||||
bool isNative = fn.isNative;
|
||||
bool isAbstract = fn.isAbstract;
|
||||
bool isIdle = fn.isIdle;
|
||||
|
||||
while(1)
|
||||
{
|
||||
if(isNext(toks, TT.Native, loc))
|
||||
{
|
||||
if(isNative)
|
||||
fail("Multiple token 'native' in function declaration",
|
||||
loc);
|
||||
isNative = true;
|
||||
continue;
|
||||
}
|
||||
if(isNext(toks, TT.Abstract, loc))
|
||||
{
|
||||
if(isNative)
|
||||
fail("Multiple token 'abstract' in function declaration",
|
||||
loc);
|
||||
isAbstract = true;
|
||||
continue;
|
||||
}
|
||||
if(isNext(toks, TT.Idle, loc))
|
||||
{
|
||||
if(isIdle)
|
||||
fail("Multiple token 'idle' in function declaration",
|
||||
loc);
|
||||
isIdle = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check that only one of the keywords are used
|
||||
if( (isAbstract && isNative) ||
|
||||
(isAbstract && isIdle) ||
|
||||
(isNative && isIdle) )
|
||||
fail("Only one of the keywords native, idle, abstract can be used on one function", loc);
|
||||
|
||||
// Set the new state
|
||||
if(isNative) fn.ftype = FuncType.Native;
|
||||
else if(isAbstract) fn.ftype = FuncType.Abstract;
|
||||
else if(isIdle) fn.ftype = FuncType.Idle;
|
||||
else assert(fn.isNormal);
|
||||
}
|
||||
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
// Create a Function struct. Will change later.
|
||||
fn = new Function;
|
||||
|
||||
// Default function type is normal
|
||||
fn.ftype = FuncType.Normal;
|
||||
|
||||
// Parse keyword list
|
||||
parseKeywords(toks);
|
||||
|
||||
// Is this a function without type?
|
||||
if(isFuncDec(toks))
|
||||
// If so, set the type to void
|
||||
fn.type = BasicType.getVoid;
|
||||
else
|
||||
// Otherwise, parse it
|
||||
fn.type = Type.identify(toks);
|
||||
|
||||
// Parse any other keywords
|
||||
parseKeywords(toks);
|
||||
|
||||
fn.name = next(toks);
|
||||
loc = fn.name.loc;
|
||||
if(fn.name.type != TT.Identifier)
|
||||
fail("Token '" ~ fn.name.str ~ "' cannot be used as a function name",
|
||||
loc);
|
||||
|
||||
|
||||
if(!isNext(toks, TT.LeftParen))
|
||||
fail("Function expected parameter list", toks);
|
||||
|
||||
// Parameters?
|
||||
if(!isNext(toks, TT.RightParen))
|
||||
{
|
||||
auto vd = new VarDeclaration();
|
||||
vd.parse(toks);
|
||||
paramList ~= vd;
|
||||
|
||||
// Other parameters
|
||||
while(isNext(toks, TT.Comma))
|
||||
{
|
||||
vd = new VarDeclaration();
|
||||
vd.parse(toks);
|
||||
paramList ~= vd;
|
||||
}
|
||||
|
||||
// Vararg-parameter?
|
||||
if(isNext(toks, TT.DDDot))
|
||||
paramList[$-1].var.isVararg = true;
|
||||
|
||||
if(!isNext(toks, TT.RightParen))
|
||||
fail("Expected end of parameter list", toks);
|
||||
}
|
||||
|
||||
if(fn.isAbstract || fn.isNative || fn.isIdle)
|
||||
{
|
||||
// Check that the function declaration ends with a ; rather
|
||||
// than a code block.
|
||||
if(!isNext(toks, TT.Semicolon))
|
||||
{
|
||||
if(fn.isAbstract)
|
||||
fail("Abstract function declaration expected ;", toks);
|
||||
else if(fn.isNative)
|
||||
fail("Native function declaration expected ;", toks);
|
||||
else if(fn.isIdle)
|
||||
fail("Idle function declaration expected ;", toks);
|
||||
else assert(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
code = new CodeBlock;
|
||||
code.parse(toks);
|
||||
}
|
||||
}
|
||||
|
||||
// Can the given tokens be parsed as the main function declaration?
|
||||
static bool isFuncDec(TokenArray toks)
|
||||
{
|
||||
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
|
||||
}
|
||||
|
||||
static bool canParse(TokenArray toks)
|
||||
{
|
||||
// Is the next token an allowed keyword?
|
||||
bool isKeyword(ref TokenArray toks)
|
||||
{
|
||||
return
|
||||
isNext(toks, TT.Native) ||
|
||||
isNext(toks, TT.Abstract) ||
|
||||
isNext(toks, TT.Idle);
|
||||
}
|
||||
|
||||
// Remove keywords
|
||||
while(isKeyword(toks)) {}
|
||||
|
||||
// We allow the declaration to have no type (which implies type
|
||||
// void)
|
||||
if(isFuncDec(toks)) return true;
|
||||
|
||||
// The next token(s) must be the type
|
||||
if(!Type.canParseRem(toks)) return false;
|
||||
|
||||
// There might be more keywords
|
||||
while(isKeyword(toks)) {}
|
||||
|
||||
// Finally we must have the function declaration at the end
|
||||
return isFuncDec(toks);
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
char[] res = "Function declaration: ";
|
||||
assert(fn.type !is null);
|
||||
|
||||
res ~= fn.type.toString();
|
||||
|
||||
res ~= " " ~ fn.name.str ~ "(";
|
||||
if(paramList.length)
|
||||
{
|
||||
if(paramList.length > 1)
|
||||
foreach(par; paramList[0..paramList.length-1])
|
||||
res ~= par.toString ~ ", ";
|
||||
|
||||
res ~= paramList[$-1].toString;
|
||||
}
|
||||
|
||||
res ~= ")\n";
|
||||
if(code !is null) res ~= code.toString();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Resolve the function definition (return type and parameter
|
||||
// types). The rest is handed by resolveBody()
|
||||
void resolve(Scope last)
|
||||
{
|
||||
fn.type.resolve(last);
|
||||
|
||||
// Create a local scope for this function
|
||||
sc = new FuncScope(last, fn);
|
||||
|
||||
// Calculate total size of parameters. This value is also used
|
||||
// in compile() and by external classes, so we store it.
|
||||
fn.paramSize = 0;
|
||||
foreach(vd; paramList)
|
||||
fn.paramSize += vd.var.type.getSize();
|
||||
|
||||
// Set the owner class.
|
||||
fn.owner = sc.getClass();
|
||||
|
||||
// Parameters are given negative numbers according to their
|
||||
// position backwards from the stack pointer, the last being
|
||||
// -1.
|
||||
int pos = -fn.paramSize;
|
||||
|
||||
// Set up the function variable list
|
||||
// TODO: Do fancy memory management
|
||||
fn.params.length = paramList.length;
|
||||
|
||||
// Add function parameters to scope.
|
||||
foreach(i, dec; paramList)
|
||||
{
|
||||
if(dec.var.type.isArray())
|
||||
dec.allowConst = true;
|
||||
|
||||
dec.resolve(sc, pos);
|
||||
|
||||
pos += dec.var.type.getSize();
|
||||
|
||||
fn.params[i] = dec.var;
|
||||
}
|
||||
|
||||
// Vararg functions must have the last parameter as an array.
|
||||
if(fn.isVararg)
|
||||
{
|
||||
assert(paramList.length > 0);
|
||||
auto dc = paramList[$-1];
|
||||
if(!dc.var.type.isArray)
|
||||
fail("Vararg argument must be an array type, not " ~
|
||||
dc.var.type.toString, dc.var.name.loc);
|
||||
}
|
||||
|
||||
assert(pos == 0, "Variable positions didn't add up");
|
||||
}
|
||||
|
||||
// Resolve the interior of the function
|
||||
void resolveBody()
|
||||
{
|
||||
// Validate all types (make sure there are no dangling forward
|
||||
// references)
|
||||
fn.type.validate();
|
||||
foreach(p; fn.params)
|
||||
p.type.validate();
|
||||
|
||||
if(code !is null)
|
||||
code.resolve(sc);
|
||||
}
|
||||
|
||||
void compile()
|
||||
{
|
||||
if(fn.isAbstract || fn.isNative || fn.isIdle)
|
||||
{
|
||||
// No body to compile
|
||||
return;
|
||||
}
|
||||
|
||||
tasm.newFunc();
|
||||
code.compile();
|
||||
|
||||
tasm.setLine(code.endLine.line);
|
||||
|
||||
if(fn.type.isVoid)
|
||||
// Remove parameters from the stack at the end of the function
|
||||
tasm.exit(fn.paramSize);
|
||||
else
|
||||
// Functions with return types must have a return statement
|
||||
// and should never reach the end of the function. Fail if we
|
||||
// do.
|
||||
tasm.error(Err.NoReturn);
|
||||
|
||||
// Assemble the finished function
|
||||
fn.bcode = tasm.assemble(fn.lines);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (linespec.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 provides a simple system for converting positions in a
|
||||
code segment (of compiled byte code) into a line number in source
|
||||
code.
|
||||
*/
|
||||
|
||||
module monster.compiler.linespec;
|
||||
|
||||
import monster.vm.error;
|
||||
|
||||
// A line specification. These are usually found in a list, one for
|
||||
// each buffer of code. All instructions after position 'pos' belong
|
||||
// to line 'line', unless a new LineSpec comes after it that covers
|
||||
// that position.
|
||||
struct LineSpec
|
||||
{
|
||||
int pos; // Offset of instruction
|
||||
int line; // Line number for instructions after this offset
|
||||
}
|
||||
|
||||
// Find the line belonging to a given position. This does not need to
|
||||
// be fast, it is only used for error messages and the like.
|
||||
int findLine(LineSpec[] list, int pos)
|
||||
{
|
||||
int lastpos = -1;
|
||||
int lastline = -1;
|
||||
|
||||
assert(pos >= 0);
|
||||
|
||||
// The first entry must represent pos = 0
|
||||
if(list.length && list[0].pos != 0)
|
||||
fail("Invalid line list: first offset not zero");
|
||||
|
||||
foreach(ls; list)
|
||||
{
|
||||
if(ls.pos <= lastpos)
|
||||
fail("Invalid line list: decreasing offset");
|
||||
|
||||
// Have we searched past pos?
|
||||
if(ls.pos > pos)
|
||||
// If so, the last entry was the correct one
|
||||
return lastline;
|
||||
|
||||
lastpos = ls.pos;
|
||||
lastline = ls.line;
|
||||
}
|
||||
|
||||
// We never searched past our position, that means the last entry is
|
||||
// the most correct.
|
||||
return lastline;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,356 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (properties.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.compiler.properties;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.assembler;
|
||||
|
||||
import monster.minibos.stdio;
|
||||
|
||||
/* This module contains special scopes for builtin types. These are
|
||||
used to resolve type properties like .length for arrays and .min
|
||||
and .max for ints, etc.
|
||||
*/
|
||||
|
||||
// TODO: This is nice, but could be nicer. I would like most of these
|
||||
// stored as values rather than functions, and have a general
|
||||
// mechanism for converting values into data (a converted version of
|
||||
// pushInit in Type) used for all types except the ones requiring a
|
||||
// function. I guess I could make versions of insert(s) that takes
|
||||
// floats, ints etc as parameters, and double checks it against the
|
||||
// type. The main reason for storing values is that most of them can
|
||||
// then be optimized away as compile time constants. This is not a
|
||||
// priority.
|
||||
|
||||
class NumericProperties(T) : SimplePropertyScope
|
||||
{
|
||||
this()
|
||||
{
|
||||
super(T.stringof ~ "Properties");
|
||||
|
||||
// Argh, this fails of course because we're trying to push a
|
||||
// long value as an int. We can use a static if here to check
|
||||
// the size, and use different instructions. Eg push8 that takes
|
||||
// long and double.
|
||||
|
||||
// Static properties of all numeric types
|
||||
static if(T.sizeof == 4)
|
||||
{
|
||||
inserts("min", T.stringof, { tasm.push(T.min); });
|
||||
inserts("max", T.stringof, { tasm.push(T.max); });
|
||||
}
|
||||
else static if(T.sizeof == 8)
|
||||
{
|
||||
inserts("min", T.stringof, { tasm.push8(T.min); });
|
||||
inserts("max", T.stringof, { tasm.push8(T.max); });
|
||||
}
|
||||
else static assert(0);
|
||||
|
||||
nextProp = GenericProperties.singleton;
|
||||
}
|
||||
}
|
||||
|
||||
class IntProperties : NumericProperties!(int)
|
||||
{ static IntProperties singleton; }
|
||||
|
||||
class UintProperties : NumericProperties!(uint)
|
||||
{ static UintProperties singleton; }
|
||||
|
||||
class LongProperties : NumericProperties!(long)
|
||||
{ static LongProperties singleton; }
|
||||
|
||||
class UlongProperties : NumericProperties!(ulong)
|
||||
{ static UlongProperties singleton; }
|
||||
|
||||
class FloatingProperties(T) : NumericProperties!(T)
|
||||
{
|
||||
this()
|
||||
{
|
||||
char[] tp = T.stringof;
|
||||
|
||||
// Additional static properties of floating point numbers
|
||||
static if(T.sizeof == 4)
|
||||
{
|
||||
inserts("infinity", tp, { tasm.push(T.infinity); });
|
||||
inserts("inf", tp, { tasm.push(T.infinity); });
|
||||
inserts("nan", tp, { tasm.push(T.nan); });
|
||||
inserts("epsilon", tp, { tasm.push(T.epsilon); });
|
||||
}
|
||||
else static if(T.sizeof == 8)
|
||||
{
|
||||
inserts("infinity", tp, { tasm.push8(T.infinity); });
|
||||
inserts("inf", tp, { tasm.push8(T.infinity); });
|
||||
inserts("nan", tp, { tasm.push8(T.nan); });
|
||||
inserts("epsilon", tp, { tasm.push8(T.epsilon); });
|
||||
}
|
||||
else static assert(0);
|
||||
|
||||
inserts("dig", "int", { tasm.push(T.dig); });
|
||||
inserts("max_10_exp", "int", { tasm.push(T.max_10_exp); });
|
||||
inserts("max_exp", "int", { tasm.push(T.max_exp); });
|
||||
inserts("min_10_exp", "int", { tasm.push(T.min_10_exp); });
|
||||
inserts("min_exp", "int", { tasm.push(T.min_exp); });
|
||||
|
||||
// Number of bits in mantissa. D calls it mant_dig, but
|
||||
// mant_bits is more natural. Let us allow both.
|
||||
inserts("mant_bits", "int", { tasm.push(T.mant_dig); });
|
||||
inserts("mant_dig", "int", { tasm.push(T.mant_dig); });
|
||||
|
||||
// Lets add in number of bits in the exponent as well
|
||||
inserts("exp_bits", "int", { tasm.push(8*T.sizeof-T.mant_dig); });
|
||||
}
|
||||
}
|
||||
|
||||
class FloatProperties : FloatingProperties!(float)
|
||||
{ static FloatProperties singleton; }
|
||||
|
||||
class DoubleProperties : FloatingProperties!(double)
|
||||
{ static DoubleProperties singleton; }
|
||||
|
||||
// Handles .length, .dup, etc for arrays
|
||||
class ArrayProperties: SimplePropertyScope
|
||||
{
|
||||
static ArrayProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("ArrayProperties");
|
||||
|
||||
insert("length", "int",
|
||||
{ tasm.getArrayLength(); },
|
||||
{ assert(0, "cannot set length yet"); });
|
||||
insert("dup", "owner", { tasm.dupArray(); });
|
||||
insert("reverse", "owner", { tasm.reverseArray(); });
|
||||
insert("sort", "owner", { assert(0, "sort not implemented"); });
|
||||
insert("const", "owner", { tasm.makeArrayConst(); });
|
||||
insert("isConst", "bool", { tasm.isArrayConst(); });
|
||||
|
||||
nextProp = GenericProperties.singleton;
|
||||
}
|
||||
}
|
||||
|
||||
class ClassProperties : SimplePropertyScope
|
||||
{
|
||||
static ClassProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("ClassProperties");
|
||||
|
||||
// For testing purposes. Makes 'singleton' an alias for the
|
||||
// first variable in the data segment. This might actually not
|
||||
// be far from how the end result would work - the singleton
|
||||
// would just be a hidden variable, but in the variable list
|
||||
// belonging to the class. We don't have to handle it using the
|
||||
// property system at all, really, we only need to allow the
|
||||
// special name 'singleton' as a variable.
|
||||
/*
|
||||
insert("singleton", "int",
|
||||
{ tasm.pushClass(0, 1); },
|
||||
{ tasm.pushClassAddr(0); tasm.movRet(); });
|
||||
*/
|
||||
|
||||
// We should move handling of states here. This will mean
|
||||
// removing StateStatement and making states a propert type. We
|
||||
// can't leave statestatement in as a special syntax for setting
|
||||
// types, because the member syntax obj.state = state.label;
|
||||
// would still have to be handled somehow. However, even if this
|
||||
// is more work, it has some additional benefits of allowing
|
||||
// states to be used in other expressions, eg state ==
|
||||
// SomeState. And we should still be able to optimize it into
|
||||
// one instruction.
|
||||
|
||||
// One downside now is that we are currently using static
|
||||
// properties. If we are going to use non-static properties and
|
||||
// allow both member and non-member access, we have to
|
||||
// differentiate between near and far properties too. Think more
|
||||
// about it.
|
||||
//insert("state", "int", { tasm.push(6); });
|
||||
|
||||
nextProp = GenericProperties.singleton;
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically handles properties like init and sizeof that are valid
|
||||
// for all types.
|
||||
class GenericProperties : SimplePropertyScope
|
||||
{
|
||||
static GenericProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("GenericProperties");
|
||||
|
||||
inserts("init", "owner", {assert(0);});
|
||||
inserts("sizeof", "int", {assert(0);});
|
||||
inserts("bitsof", "int", {assert(0);});
|
||||
}
|
||||
|
||||
// Overwrite the above actions
|
||||
void getValue(char[] name, Type oType)
|
||||
{
|
||||
if(oType.isMeta) oType = oType.getBase();
|
||||
|
||||
if(name == "sizeof") tasm.push(oType.getSize);
|
||||
else if(name == "bitsof") tasm.push(oType.getSize*32);
|
||||
else if(name == "init") oType.pushInit();
|
||||
else assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a base class that simplifies definition of property
|
||||
scopes. You can simply call insert and inserts (for static
|
||||
properties) in the constructor. An example:
|
||||
|
||||
inserts("max", "int", { tasm.push(int.max); });
|
||||
|
||||
This inserts the property "max", of type "int", and pushes the
|
||||
value int.max whenever it is invoked. Since it is a static
|
||||
property, the left hand side (eg an int value) is never
|
||||
evaluated. If the type is "" or "owner", then the property type
|
||||
will be the same as the owner type.
|
||||
*/
|
||||
|
||||
abstract class SimplePropertyScope : PropertyScope
|
||||
{
|
||||
this(char[] n) { super(n); }
|
||||
|
||||
private SP[char[]] propList;
|
||||
|
||||
// Convert a typename to a type
|
||||
private Type getType(char[] tp)
|
||||
{
|
||||
if(tp == "" || tp == "owner")
|
||||
return null;
|
||||
|
||||
return BasicType.get(tp);
|
||||
}
|
||||
|
||||
// Insert properties into the list
|
||||
void insert(char[] name, Type tp, Action push, Action pop = null)
|
||||
{
|
||||
assert(!hasProperty(name));
|
||||
propList[name] = SP(tp, false, push, pop);
|
||||
}
|
||||
|
||||
void insert(char[] name, char[] tp, Action push, Action pop = null)
|
||||
{ insert(name, getType(tp), push, pop); }
|
||||
|
||||
// Insert static properties. TODO: These should take values rather
|
||||
// than code. It should be possible to retireve this value at
|
||||
// compile-time.
|
||||
void inserts(char[] name, Type tp, Action push)
|
||||
{
|
||||
assert(!hasProperty(name));
|
||||
propList[name] = SP(tp, true, push, null);
|
||||
}
|
||||
|
||||
void inserts(char[] name, char[] tp, Action push)
|
||||
{ inserts(name, getType(tp), push); }
|
||||
|
||||
override:
|
||||
|
||||
// Return the stored type. If it is null, return the owner type
|
||||
// instead.
|
||||
Type getPropType(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
Type tp = propList[name].type;
|
||||
|
||||
// No stored type? We have to copy the owner.
|
||||
if(tp is null)
|
||||
{
|
||||
// The owner type might be a meta-type (eg. int.init). Pretend
|
||||
// it is the base type instead.
|
||||
if(oType.isMeta())
|
||||
tp = oType.getBase();
|
||||
else
|
||||
tp = oType;
|
||||
}
|
||||
|
||||
return tp;
|
||||
}
|
||||
|
||||
void getValue(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
propList[name].push();
|
||||
}
|
||||
|
||||
void setValue(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
propList[name].pop();
|
||||
}
|
||||
|
||||
bool hasProperty(char[] name)
|
||||
{ return (name in propList) != null; }
|
||||
|
||||
bool isStatic(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
return propList[name].isStatic;
|
||||
}
|
||||
|
||||
bool isLValue(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
return propList[name].isLValue();
|
||||
}
|
||||
}
|
||||
|
||||
alias void delegate() Action;
|
||||
struct SP
|
||||
{
|
||||
Action push, pop;
|
||||
Type type;
|
||||
bool isStatic;
|
||||
|
||||
static SP opCall(Type tp, bool stat, Action push, Action pop)
|
||||
{
|
||||
SP s;
|
||||
s.push = push;
|
||||
s.pop = pop;
|
||||
s.type = tp;
|
||||
s.isStatic = stat;
|
||||
assert(!stat || pop == null);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool isLValue() { return pop != null; }
|
||||
}
|
||||
|
||||
void initProperties()
|
||||
{
|
||||
GenericProperties.singleton = new GenericProperties;
|
||||
ArrayProperties.singleton = new ArrayProperties;
|
||||
IntProperties.singleton = new IntProperties;
|
||||
UintProperties.singleton = new UintProperties;
|
||||
LongProperties.singleton = new LongProperties;
|
||||
UlongProperties.singleton = new UlongProperties;
|
||||
FloatProperties.singleton = new FloatProperties;
|
||||
DoubleProperties.singleton = new DoubleProperties;
|
||||
ClassProperties.singleton = new ClassProperties;
|
||||
}
|
@ -0,0 +1,972 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (scopes.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.compiler.scopes;
|
||||
|
||||
import monster.minibos.stdio;
|
||||
import monster.minibos.string;
|
||||
|
||||
import monster.util.aa;
|
||||
|
||||
import monster.compiler.statement;
|
||||
import monster.compiler.expression;
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.properties;
|
||||
import monster.compiler.functions;
|
||||
import monster.compiler.states;
|
||||
import monster.compiler.variables;
|
||||
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.error;
|
||||
|
||||
// The global scope
|
||||
PackageScope global;
|
||||
|
||||
void initScope()
|
||||
{
|
||||
global = new PackageScope(null, "global");
|
||||
}
|
||||
|
||||
// TODO: Write here which of these should be kept around at runtime,
|
||||
// and which of them can be discarded after compilation.
|
||||
|
||||
abstract class Scope
|
||||
{
|
||||
protected:
|
||||
// The parent scope. For function scopes, this is the scope of the
|
||||
// class it belongs to. For code blocks, loops etc, it is the scope
|
||||
// of the code block outside this one. For classes, this points to
|
||||
// the scope of the parent class, if any, or to the package or
|
||||
// global scope.
|
||||
Scope parent;
|
||||
|
||||
// Properties assigned to this scope (if any).
|
||||
PropertyScope nextProp;
|
||||
|
||||
// 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.
|
||||
void clearId(Token name)
|
||||
{
|
||||
assert(!isRoot());
|
||||
parent.clearId(name);
|
||||
}
|
||||
|
||||
// Made protected since it is so easy to confuse with isStateCode(),
|
||||
// and we never actually need it anywhere outside this file.
|
||||
bool isState() { return false; }
|
||||
|
||||
private:
|
||||
// Name of this scope. All scopes must have a name, but for some
|
||||
// types it is set automatically (like a code block scope.) It is
|
||||
// mostly used for debugging.
|
||||
char[] scopeName;
|
||||
|
||||
public:
|
||||
|
||||
this(Scope last, char[] name)
|
||||
{
|
||||
scopeName = name;
|
||||
parent = last;
|
||||
|
||||
assert(last !is this, "scope cannot be it's own parent");
|
||||
|
||||
assert(name != "");
|
||||
}
|
||||
|
||||
// Is this the root scope?
|
||||
final bool isRoot()
|
||||
{
|
||||
if(parent !is null) return false;
|
||||
assert(allowRoot(), toString() ~ " cannot be a root scope");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is THIS scope of this particular kind?
|
||||
bool isFunc() { return false; }
|
||||
bool isCode() { return false; }
|
||||
bool isLoop() { return false; }
|
||||
bool isClass() { return false; }
|
||||
bool isArray() { return false; }
|
||||
bool isPackage() { return false; }
|
||||
bool isProperty() { return false; }
|
||||
|
||||
// Is this scope allowed to be a root scope (without parent?)
|
||||
bool allowRoot() { return false; }
|
||||
|
||||
// Get a property from this scope
|
||||
void getProperty(Token name, Type ownerType, ref Property p)
|
||||
{
|
||||
assert(0);
|
||||
}
|
||||
|
||||
// Get the function definition belonging to this scope.
|
||||
Function *getFunction()
|
||||
{
|
||||
assert(!isRoot(), "getFunction called on a root scope");
|
||||
return parent.getFunction();
|
||||
}
|
||||
|
||||
// Get the class
|
||||
MonsterClass getClass()
|
||||
{
|
||||
assert(!isRoot(), "getClass called on a root scope");
|
||||
return parent.getClass();
|
||||
}
|
||||
|
||||
State* getState()
|
||||
{
|
||||
assert(!isRoot(), "getState called on a root scope");
|
||||
return parent.getState();
|
||||
}
|
||||
|
||||
Expression getArray()
|
||||
{
|
||||
assert(!isRoot(), "getArray called on wrong scope type");
|
||||
return parent.getArray();
|
||||
}
|
||||
|
||||
int getLoopStack()
|
||||
{
|
||||
assert(!isRoot(), "getLoopStack called on wrong scope type");
|
||||
return parent.getLoopStack();
|
||||
}
|
||||
|
||||
// Get the break or continue label for the given named loop, or the
|
||||
// innermost loop if name is empty or omitted. Returns null if the
|
||||
// label was not found. Can only be called within loops.
|
||||
LabelStatement getBreak(char[] name = "") { return null; }
|
||||
LabelStatement getContinue(char[] name = "") { return null; }
|
||||
|
||||
// Does this scope have a property with the given name?
|
||||
bool hasProperty(Token name)
|
||||
{
|
||||
assert(!isProperty);
|
||||
return false;
|
||||
}
|
||||
|
||||
final bool findProperty(Token name, Type ownerType, ref Property result)
|
||||
{
|
||||
if(hasProperty(name))
|
||||
{
|
||||
getProperty(name, ownerType, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(nextProp !is null)
|
||||
return nextProp.findProperty(name, ownerType, result);
|
||||
|
||||
// No property in this scope. Check the parent.
|
||||
if(!isRoot) return parent.findProperty(name, ownerType, result);
|
||||
|
||||
// No parent, property not found.
|
||||
return false;
|
||||
}
|
||||
|
||||
Variable* findVar(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findVar(name);
|
||||
}
|
||||
|
||||
State* findState(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findState(name);
|
||||
}
|
||||
|
||||
Function* findFunc(char[] name)
|
||||
{
|
||||
if(isRoot()) return null;
|
||||
return parent.findFunc(name);
|
||||
}
|
||||
|
||||
void insertLabel(StateLabel *lb)
|
||||
{
|
||||
assert(!isRoot);
|
||||
parent.insertLabel(lb);
|
||||
}
|
||||
|
||||
void insertVar(Variable* dec) { assert(0); }
|
||||
|
||||
// Used for summing up stack level. Redeclared in StackScope.
|
||||
int getTotLocals() { return 0; }
|
||||
int getLocals() { assert(0); }
|
||||
|
||||
// These must be overridden in their respective scopes
|
||||
int addNewDataVar(int varSize) { assert(0); }
|
||||
int addNewLocalVar(int varSize) { assert(0); }
|
||||
|
||||
final:
|
||||
|
||||
bool isStateCode()
|
||||
{ return isInState() && !isInFunc(); }
|
||||
|
||||
// Is this scope OR one of the parent scopes of the given kind?
|
||||
bool isInFunc()
|
||||
{
|
||||
if(isFunc()) return true;
|
||||
if(isRoot()) return false;
|
||||
return parent.isInFunc();
|
||||
}
|
||||
bool isInState()
|
||||
{
|
||||
if(isState()) return true;
|
||||
if(isRoot()) return false;
|
||||
return parent.isInState();
|
||||
}
|
||||
bool isInLoop()
|
||||
{
|
||||
if(isLoop()) return true;
|
||||
if(isRoot()) return false;
|
||||
return parent.isInLoop();
|
||||
}
|
||||
bool isInClass()
|
||||
{
|
||||
if(isClass()) return true;
|
||||
if(isRoot()) return false;
|
||||
return parent.isInClass();
|
||||
}
|
||||
bool isInPackage()
|
||||
{
|
||||
if(isPackage()) return true;
|
||||
if(isRoot()) return false;
|
||||
return parent.isInPackage();
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
if(parent is null) return scopeName;
|
||||
else return parent.toString() ~ "." ~ scopeName;
|
||||
}
|
||||
}
|
||||
|
||||
final class StateScope : Scope
|
||||
{
|
||||
private:
|
||||
State* st;
|
||||
|
||||
public:
|
||||
|
||||
this(Scope last, State* s)
|
||||
{
|
||||
st = s;
|
||||
super(last, st.name.str);
|
||||
}
|
||||
|
||||
override:
|
||||
void clearId(Token name)
|
||||
{
|
||||
assert(name.str != "");
|
||||
|
||||
// Check against labels. We are not allowed to shadow labels at
|
||||
// any point.
|
||||
StateLabel *lb;
|
||||
if(st.labels.inList(name.str, lb))
|
||||
fail(format("Identifier '%s' conflicts with label on line %s", name.str,
|
||||
lb.name.loc), name.loc);
|
||||
|
||||
super.clearId(name);
|
||||
}
|
||||
|
||||
// Insert a label, check for name collisions.
|
||||
void insertLabel(StateLabel *lb)
|
||||
{
|
||||
// Check for name collisions
|
||||
clearId(lb.name);
|
||||
|
||||
st.labels[lb.name.str] = lb;
|
||||
}
|
||||
|
||||
State* getState() { return st; }
|
||||
|
||||
bool isState() { return true; }
|
||||
}
|
||||
|
||||
// A package scope is a scope that can contain classes.
|
||||
final class PackageScope : Scope
|
||||
{
|
||||
// List of classes in this package. This is case insensitive, so we
|
||||
// can look up file names too.
|
||||
HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes;
|
||||
|
||||
// Lookup by integer index. TODO: This should be in a global scope
|
||||
// rather than per package. We can think about that when we
|
||||
// implement packages.
|
||||
HashTable!(CIndex, MonsterClass) indexList;
|
||||
|
||||
// Forward references. Refers to unloaded and non-existing classes.
|
||||
HashTable!(char[], CIndex) forwards;
|
||||
|
||||
// Unique global index to give the next class. TODO: Ditto
|
||||
CIndex next = 1;
|
||||
|
||||
this(Scope last, char[] name)
|
||||
{
|
||||
super(last, name);
|
||||
|
||||
assert(last is null);
|
||||
}
|
||||
|
||||
bool isPackage() { return true; }
|
||||
bool allowRoot() { return true; }
|
||||
|
||||
// Insert a new class into the scope. The class is given a unique
|
||||
// global index. If the class was previously forward referenced, the
|
||||
// forward is replaced and the previously assigned forward index is
|
||||
// used. A class can only be inserted once.
|
||||
void insertClass(MonsterClass cls)
|
||||
{
|
||||
assert(cls !is null);
|
||||
assert(cls.name.str != "");
|
||||
|
||||
// Are we already in the list?
|
||||
MonsterClass c2;
|
||||
if(global.ciInList(cls.name.str, c2))
|
||||
{
|
||||
// That's not allowed. Determine what error message to give.
|
||||
|
||||
if(c2.name.str == cls.name.str)
|
||||
// Exact name collision
|
||||
fail("Cannot load class " ~ cls.name.str ~
|
||||
" because it is already loaded.");
|
||||
|
||||
// Case insensitive match
|
||||
fail("Cannot load class " ~ cls.name.str ~ " because "
|
||||
~ c2.name.str ~
|
||||
" already exists (class names cannot differ only by case.)");
|
||||
}
|
||||
|
||||
// Check that no other identifier with this name exists
|
||||
clearId(cls.name);
|
||||
|
||||
// We're clear. Find an index to use. If the class was forward
|
||||
// referenced, then an index has already been assigned.
|
||||
CIndex ci;
|
||||
|
||||
if(forwards.inList(cls.name.str, ci))
|
||||
{
|
||||
// ci is set, remove the entry from the forwards hashmap
|
||||
assert(ci != 0);
|
||||
forwards.remove(cls.name.str);
|
||||
}
|
||||
else
|
||||
// Get a new index
|
||||
ci = next++;
|
||||
|
||||
assert(!indexList.inList(ci));
|
||||
|
||||
// Assign the index and insert class into both lists
|
||||
cls.gIndex = ci;
|
||||
classes[cls.name.str] = cls;
|
||||
indexList[ci] = cls;
|
||||
|
||||
assert(indexList.inList(ci));
|
||||
}
|
||||
|
||||
// Case insensitive lookups. Used for comparing with file names,
|
||||
// before the actual class is loaded.
|
||||
bool ciInList(char[] name)
|
||||
{ return classes.inList(name); }
|
||||
bool ciInList(char[] name, ref MonsterClass cb)
|
||||
{ return classes.inList(name, cb); }
|
||||
|
||||
// Case sensitive versions. If a class is found that does not match
|
||||
// in case, it is an error.
|
||||
bool csInList(char[] name, ref MonsterClass cb)
|
||||
{
|
||||
return ciInList(name, cb) && cb.name.str == name;
|
||||
//fail(format("Class name mismatch: wanted %s but found %s",
|
||||
// name, cb.name.str));
|
||||
}
|
||||
bool csInList(char[] name)
|
||||
{
|
||||
MonsterClass mc;
|
||||
return csInList(name, mc);
|
||||
}
|
||||
|
||||
// Get the class. It must exist and the case must match. getClass
|
||||
// will set up the class scope if this is not already done.
|
||||
MonsterClass getClass(char[] name)
|
||||
{
|
||||
MonsterClass mc;
|
||||
if(!csInList(name, mc))
|
||||
{
|
||||
char[] msg = "Class '" ~ name ~ "' not found.";
|
||||
if(ciInList(name, mc))
|
||||
msg ~= " (Perhaps you meant " ~ mc.name.str ~ "?)";
|
||||
fail(msg);
|
||||
}
|
||||
mc.requireScope();
|
||||
return mc;
|
||||
}
|
||||
|
||||
MonsterClass getClass(CIndex ind)
|
||||
{
|
||||
MonsterClass mc;
|
||||
if(!indexList.inList(ind, mc))
|
||||
fail("Invalid class index encountered");
|
||||
mc.requireScope();
|
||||
return mc;
|
||||
}
|
||||
|
||||
override void clearId(Token name)
|
||||
{
|
||||
assert(name.str != "");
|
||||
|
||||
// Type names can never be overwritten, so we check findClass
|
||||
// and the built-in types. We might move the builtin type check
|
||||
// to a "global" scope at some point.
|
||||
if(BasicType.isBasic(name.str) || csInList(name.str))
|
||||
fail("Identifier '"~ name.str~ "' is a type and cannot be redeclared",
|
||||
name.loc);
|
||||
|
||||
assert(isRoot());
|
||||
}
|
||||
|
||||
// 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.
|
||||
private 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(MonsterClass.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'xs
|
||||
// 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.
|
||||
abstract class VarScope : Scope
|
||||
{
|
||||
private:
|
||||
HashTable!(char[], Variable*) variables;
|
||||
|
||||
public:
|
||||
|
||||
this(Scope last, char[] name)
|
||||
{ super(last, name); }
|
||||
|
||||
override:
|
||||
|
||||
// Find a variable, returns the declaration.
|
||||
Variable* findVar(char[] name)
|
||||
{
|
||||
Variable* vd;
|
||||
|
||||
if(variables.inList(name, vd))
|
||||
return vd;
|
||||
|
||||
return super.findVar(name);
|
||||
}
|
||||
|
||||
void clearId(Token name)
|
||||
{
|
||||
assert(name.str != "");
|
||||
|
||||
Variable *vd;
|
||||
if(variables.inList(name.str, vd))
|
||||
fail(format("Identifier '%s' already declared on line %s (as a variable)",
|
||||
name.str, vd.name.loc),
|
||||
name.loc);
|
||||
|
||||
super.clearId(name);
|
||||
}
|
||||
|
||||
// Insert a variable, checks if it already exists.
|
||||
void insertVar(Variable* dec)
|
||||
{
|
||||
assert(!isStateCode, "called insertVar in state code");
|
||||
|
||||
// Check for name collisions.
|
||||
clearId(dec.name);
|
||||
|
||||
variables[dec.name.str] = dec;
|
||||
}
|
||||
}
|
||||
|
||||
// A class scope. In addition to variables, they can contain states
|
||||
// and functions, and they keep track of the data segment size.
|
||||
final class ClassScope : VarScope
|
||||
{
|
||||
private:
|
||||
// The class information for this class.
|
||||
MonsterClass cls;
|
||||
|
||||
HashTable!(char[], State*) states;
|
||||
HashTable!(char[], Function*) functions;
|
||||
|
||||
int dataSize; // Data segment size for this class
|
||||
|
||||
public:
|
||||
|
||||
this(Scope last, MonsterClass cl)
|
||||
{
|
||||
cls = cl;
|
||||
super(last, cls.name.str);
|
||||
|
||||
// Connect a class property scope with this scope.
|
||||
nextProp = ClassProperties.singleton;
|
||||
}
|
||||
|
||||
bool isClass() { return true; }
|
||||
MonsterClass getClass() { return cls; }
|
||||
|
||||
// Add a variable to the data segment, returns the offset.
|
||||
int addNewDataVar(int varSize)
|
||||
{
|
||||
int tmp = dataSize;
|
||||
|
||||
dataSize += varSize;
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
override void clearId(Token name)
|
||||
{
|
||||
assert(name.str != "");
|
||||
|
||||
Function* fd;
|
||||
State* sd;
|
||||
|
||||
if(functions.inList(name.str, fd))
|
||||
{
|
||||
fail(format("Identifier '%s' already declared on line %s (as a function)",
|
||||
name.str, fd.name.loc),
|
||||
name.loc);
|
||||
}
|
||||
|
||||
if(states.inList(name.str, sd))
|
||||
fail(format("Identifier '%s' already declared on line %s (as a state)",
|
||||
name.str, sd.name.loc),
|
||||
name.loc);
|
||||
|
||||
// Let VarScope handle variables and parent scopes
|
||||
super.clearId(name);
|
||||
}
|
||||
|
||||
// Get total data segment size
|
||||
int getDataSize() { return dataSize; }
|
||||
|
||||
// Insert a state
|
||||
void insertState(State* st)
|
||||
{
|
||||
clearId(st.name);
|
||||
|
||||
st.index = states.length;
|
||||
states[st.name.str] = st;
|
||||
}
|
||||
|
||||
// Insert a function.
|
||||
void insertFunc(Function* fd)
|
||||
{
|
||||
// TODO: First check if we are legally overriding an existing
|
||||
// function. If not, we are creating a new identifier and must
|
||||
// call clearId.
|
||||
clearId(fd.name);
|
||||
|
||||
fd.index = functions.length;
|
||||
|
||||
// Store the function definition
|
||||
functions[fd.name.str] = fd;
|
||||
}
|
||||
|
||||
State* findState(char[] name)
|
||||
{
|
||||
State* st;
|
||||
|
||||
if(states.inList(name, st))
|
||||
return st;
|
||||
|
||||
assert(!isRoot());
|
||||
return parent.findState(name);
|
||||
}
|
||||
|
||||
Function* findFunc(char[] name)
|
||||
{
|
||||
Function* fd;
|
||||
|
||||
if(functions.inList(name, fd))
|
||||
return fd;
|
||||
|
||||
assert(!isRoot());
|
||||
return parent.findFunc(name);
|
||||
}
|
||||
}
|
||||
|
||||
// A scope that keeps track of the stack
|
||||
abstract class StackScope : VarScope
|
||||
{
|
||||
private:
|
||||
int locals; // The number of local variables declared in this
|
||||
// scope. These must be pop'ed when the scope exits.
|
||||
int sumLocals;// Current position of the stack relative to the stack
|
||||
// pointer. This is set to zero at the start of the
|
||||
// function, and increased for each local variable
|
||||
// that is added. The difference between sumLocals and
|
||||
// locals is that sumLocals counts ALL local variables
|
||||
// declared in the current function (above the current
|
||||
// point), while locals only counts THIS scope.
|
||||
|
||||
int expStack; // Expression stack. Only eeps track of intra-
|
||||
// expression stack values, and must end up at zero
|
||||
// after each statement.
|
||||
|
||||
public:
|
||||
this(Scope last, char[] name)
|
||||
{
|
||||
super(last, name);
|
||||
|
||||
// Local variable position is inherited
|
||||
assert(!isRoot());
|
||||
sumLocals = parent.getTotLocals;
|
||||
}
|
||||
|
||||
// Allocate a local variable on the stack, and return the offset.
|
||||
// The parameter gives the size of the requested variable in ints (4
|
||||
// bytes.)
|
||||
int addNewLocalVar(int varSize)
|
||||
{
|
||||
assert(expStack == 0);
|
||||
|
||||
locals += varSize;
|
||||
|
||||
int tmp = sumLocals;
|
||||
sumLocals += varSize;
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void push(int i) { expStack += i; }
|
||||
void push(Type t) { push(t.getSize); }
|
||||
void pop(int i) { expStack -= i; }
|
||||
void pop(Type t) { pop(t.getSize); }
|
||||
|
||||
// Get the number of local variables in the current scope. In
|
||||
// reality it gives the number of ints. A variable 8 bytes long will
|
||||
// count as two variables.
|
||||
int getLocals() { return locals; }
|
||||
|
||||
// Get the total number of local variables for this function. Used
|
||||
// in return statements and by other jumps that might span several
|
||||
// blocks (break and continue.)
|
||||
int getTotLocals() { return sumLocals; }
|
||||
|
||||
// Get instra-expression stack
|
||||
int getExpStack() { return expStack; }
|
||||
|
||||
// Get total stack position, including expression stack values. This
|
||||
// is used by the array lenght symbol $ to find the array index.
|
||||
int getPos() { return getTotLocals() + getExpStack(); }
|
||||
}
|
||||
|
||||
class FuncScope : StackScope
|
||||
{
|
||||
// Function definition, for function scopes
|
||||
private:
|
||||
Function *fnc;
|
||||
|
||||
public:
|
||||
this(Scope last, Function *fd)
|
||||
{
|
||||
super(last, fd.name.str);
|
||||
fnc = fd;
|
||||
}
|
||||
|
||||
override:
|
||||
void insertLabel(StateLabel *lb)
|
||||
{ assert(0, "cannot insert labels in function scopes"); }
|
||||
|
||||
bool isFunc() { return true; }
|
||||
Function *getFunction() { return fnc; }
|
||||
}
|
||||
|
||||
class CodeScope : StackScope
|
||||
{
|
||||
this(Scope last, CodeBlock cb)
|
||||
{
|
||||
char[] name = "codeblock";
|
||||
if(cb.isState) name = "stateblock";
|
||||
|
||||
super(last, name);
|
||||
}
|
||||
|
||||
this(Scope last, char[] name) { super(last, name) ;}
|
||||
|
||||
bool isCode() { return true; }
|
||||
|
||||
LabelStatement getBreak(char[] name = "") { return parent.getBreak(name); }
|
||||
LabelStatement getContinue(char[] name = "") { return parent.getContinue(name); }
|
||||
}
|
||||
|
||||
// Experimental! Used to recompute the array expression for $. NOT a
|
||||
// permanent solution.
|
||||
class ArrayScope : StackScope
|
||||
{
|
||||
private Expression expArray;
|
||||
|
||||
this(Scope last, Expression arr)
|
||||
{
|
||||
super(last, "arrayscope");
|
||||
expArray = arr;
|
||||
}
|
||||
|
||||
bool isArray() { return true; }
|
||||
Expression getArray() { return expArray; }
|
||||
}
|
||||
|
||||
// Base class for scopes that have properties. The instances of this
|
||||
// scope are defined in properties.d
|
||||
abstract class PropertyScope : Scope
|
||||
{
|
||||
this(char[] n) { super(null, n); }
|
||||
|
||||
// Override these in base classes.
|
||||
|
||||
Type getPropType(char[] name, Type oType);
|
||||
void getValue(char[] name, Type oType);
|
||||
bool hasProperty(char[] name);
|
||||
bool isStatic(char[] name, Type oType);
|
||||
|
||||
// Most properties are read-only, but override these for exceptions.
|
||||
bool isLValue(char[] name, Type oType) { return false; }
|
||||
void setValue(char[] name, Type oType) { assert(0); }
|
||||
|
||||
override:
|
||||
final:
|
||||
bool isProperty() { return true; }
|
||||
|
||||
// No need for collision checks
|
||||
void clearId(Token name)
|
||||
{ assert(isRoot()); }
|
||||
|
||||
bool allowRoot() { return true; }
|
||||
|
||||
void getProperty(Token name, Type ownerType, ref Property p)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
|
||||
p.scp = this;
|
||||
p.name = name.str;
|
||||
p.oType = ownerType;
|
||||
}
|
||||
|
||||
// Check if we have a given property.
|
||||
bool hasProperty(Token name) { return hasProperty(name.str); }
|
||||
}
|
||||
|
||||
// A reference to a property, used in VariableExpr.
|
||||
struct Property
|
||||
{
|
||||
PropertyScope scp;
|
||||
char[] name; // Name of the property
|
||||
Type oType; // Type of the owner
|
||||
|
||||
// Get the type of this property
|
||||
Type getType() { return scp.getPropType(name, oType); }
|
||||
|
||||
// Push the value (of type getType) onto the stack. Assumes the
|
||||
// owner (of type oType) has already been pushed onto the stack,
|
||||
// unless the property is static.
|
||||
void getValue() { scp.getValue(name, oType); }
|
||||
|
||||
// Pops a value (of type getType) off the stack and sets the
|
||||
// property. Can only be called for lvalues.
|
||||
void setValue() { assert(isLValue); scp.setValue(name, oType); }
|
||||
|
||||
// Can we write to this property?
|
||||
bool isLValue() { return scp.isLValue(name, oType); }
|
||||
|
||||
// Is this property static? If it is, we do not have to push the
|
||||
// owner onto the stack before using the property.
|
||||
bool isStatic() { return scp.isStatic(name, oType); }
|
||||
}
|
||||
|
||||
// Scope inside of loops. Handles break and continue, loop labels, and
|
||||
// some stack stuff.
|
||||
class LoopScope : CodeScope
|
||||
{
|
||||
private:
|
||||
LabelStatement breakLabel, continueLabel;
|
||||
Token loopName; // Loop label name.
|
||||
int loopStack = -1; // Stack position of the array index. Only used in
|
||||
// foreach loops.
|
||||
bool isForeach; // Redundant variable used for integrity checking only
|
||||
|
||||
// Set the label and set up a nice name
|
||||
this(Scope last, char[] name, Token label)
|
||||
{
|
||||
// Include the label name in the scope name
|
||||
if(label.str != "")
|
||||
name ~= ":" ~ label.str;
|
||||
|
||||
super(last, name);
|
||||
|
||||
if(label.str != "")
|
||||
{
|
||||
// This loop has a label, so set it up
|
||||
clearId(label);
|
||||
loopName = label;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
this(Scope last, ForStatement fs)
|
||||
{ this(last, "for-loop", fs.labelName); }
|
||||
this(Scope last, ForeachStatement fs)
|
||||
{
|
||||
this(last, "foreach-loop", fs.labelName);
|
||||
isForeach = true;
|
||||
}
|
||||
this(Scope last, DoWhileStatement fs)
|
||||
{
|
||||
this(last, "do-while-loop", fs.labelName);
|
||||
stackPoint();
|
||||
}
|
||||
this(Scope last, WhileStatement fs)
|
||||
{
|
||||
this(last, "while-loop", fs.labelName);
|
||||
stackPoint();
|
||||
}
|
||||
|
||||
bool isLoop() { return true; }
|
||||
|
||||
// Called when the stack is set up to the level of the loop
|
||||
// interior, after loop variables and such are declared. This
|
||||
// function should only be called for for-loops and foreach-loops,
|
||||
// and will make sure that loop variables are not popped by break
|
||||
// and continue statements.
|
||||
void stackPoint()
|
||||
{
|
||||
assert(breakLabel is null && continueLabel is null && loopStack == -1,
|
||||
"do not call stackPoint multiple times");
|
||||
|
||||
if(isForeach) loopStack = getTotLocals() - 1;
|
||||
breakLabel = new LabelStatement(getTotLocals());
|
||||
continueLabel = new LabelStatement(getTotLocals());
|
||||
}
|
||||
|
||||
override void clearId(Token name)
|
||||
{
|
||||
assert(name.str != "");
|
||||
|
||||
// Check for loop labels as well
|
||||
if(loopName.str == name.str)
|
||||
fail(format("Identifier %s is a loop label on line %s and cannot be redefined.",
|
||||
name.str, loopName.loc),
|
||||
name.loc);
|
||||
|
||||
super.clearId(name);
|
||||
}
|
||||
|
||||
// Get the break or continue label for the given named loop, or the
|
||||
// innermost loop if name is empty or omitted.
|
||||
LabelStatement getBreak(char[] name = "")
|
||||
{
|
||||
if(name == "" || name == loopName.str)
|
||||
return breakLabel;
|
||||
|
||||
return parent.getBreak(name);
|
||||
}
|
||||
|
||||
LabelStatement getContinue(char[] name = "")
|
||||
{
|
||||
if(name == "" || name == loopName.str)
|
||||
return continueLabel;
|
||||
|
||||
return parent.getContinue(name);
|
||||
}
|
||||
|
||||
int getLoopStack()
|
||||
{
|
||||
assert(loopStack != -1 && isForeach,
|
||||
"getLoopStack called for non-foreach scope");
|
||||
return loopStack;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,247 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (states.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.compiler.states;
|
||||
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.assembler;
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.linespec;
|
||||
import monster.compiler.statement;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.error;
|
||||
|
||||
import monster.util.aa;
|
||||
|
||||
import monster.minibos.stdio;
|
||||
|
||||
struct State
|
||||
{
|
||||
Token name;
|
||||
int index;
|
||||
|
||||
// Labels in this scope.
|
||||
HashTable!(char[], StateLabel*) labels;
|
||||
StateLabel* labelList[];
|
||||
|
||||
StateScope sc; // Scope for this state
|
||||
|
||||
// State declaration - used to resolve forward references. Should
|
||||
// not be kept around when compilation is finished.
|
||||
StateDeclaration stateDec;
|
||||
|
||||
ubyte[] bcode;
|
||||
LineSpec[] lines;
|
||||
|
||||
StateLabel* findLabel(char[] name)
|
||||
{
|
||||
StateLabel *lb;
|
||||
if(labels.inList(name, lb))
|
||||
return lb;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look up a label in this state, or register a forward reference if
|
||||
// the state hasn't been resolved yet.
|
||||
void registerGoto(char[] label, LabelUser lu)
|
||||
{
|
||||
StateLabel *sl;
|
||||
|
||||
assert(lu !is null);
|
||||
|
||||
if( labels.inList(label, sl) )
|
||||
lu.setLabel(sl.ls);
|
||||
else
|
||||
{
|
||||
if(stateDec is null)
|
||||
{
|
||||
// The state has been resolved, and the label was not
|
||||
// found. Let lu handle the error message.
|
||||
lu.setLabel(null);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
with(stateDec)
|
||||
{
|
||||
// The state is not resolved yet, so create a forward
|
||||
// reference to this label.
|
||||
Forward *fw;
|
||||
|
||||
// Get the pointer to the Forward struct in the AA, or a
|
||||
// new one if none existed.
|
||||
forwards.insertEdit(label, fw);
|
||||
|
||||
// Add the reference to the list
|
||||
fw.lus ~= lu;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StateLabel
|
||||
{
|
||||
Token name;
|
||||
uint offs;
|
||||
uint index; // Index used to represent this label in byte code
|
||||
LabelStatement ls; // TODO: Remove this later?
|
||||
}
|
||||
|
||||
// Simple struct used for representing a label and its state in one
|
||||
// value.
|
||||
struct StateLabelPair
|
||||
{
|
||||
State *state;
|
||||
StateLabel *label;
|
||||
}
|
||||
|
||||
// Handles declaration of states at the class scope. Uses a code block
|
||||
// for state contents.
|
||||
class StateDeclaration : Statement
|
||||
{
|
||||
State *st;
|
||||
|
||||
CodeBlock code;
|
||||
|
||||
static struct Forward
|
||||
{ LabelUser lus[]; }
|
||||
|
||||
HashTable!(char[], Forward) forwards;
|
||||
|
||||
static bool canParse(TokenArray toks)
|
||||
{
|
||||
return isNext(toks, TT.State);
|
||||
}
|
||||
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
st = new State;
|
||||
st.stateDec = this;
|
||||
|
||||
if(!isNext(toks, TT.State))
|
||||
assert(0, "Internal error in StateDeclaration");
|
||||
|
||||
if(!isNext(toks, TT.Identifier, st.name))
|
||||
fail("Expected state name identifier", toks);
|
||||
|
||||
// Create a code block, and tell it (the parameter) that it is a
|
||||
// state block.
|
||||
code = new CodeBlock(true);
|
||||
code.parse(toks);
|
||||
}
|
||||
|
||||
// Resolve the state. Besides resolving the code we have to resolve
|
||||
// any forward references to labels within the state afterwards.
|
||||
void resolve(Scope last)
|
||||
{
|
||||
assert(st !is null);
|
||||
// Create a state scope. The scope will help enforce special
|
||||
// rules, such as allowing idle functions and disallowing
|
||||
// variable declarations.
|
||||
st.sc = new StateScope(last, st);
|
||||
|
||||
// Resolve the interior of the code block
|
||||
assert(code !is null);
|
||||
code.resolve(st.sc);
|
||||
|
||||
// Go through the forward list and resolve everything
|
||||
foreach(char[] label, Forward fd; forwards)
|
||||
{
|
||||
StateLabel *sl;
|
||||
LabelStatement ls;
|
||||
if(st.labels.inList(label, sl))
|
||||
{
|
||||
assert(sl !is null);
|
||||
ls = sl.ls;
|
||||
}
|
||||
else
|
||||
ls = null; // Give a null to setLabel and let it handle
|
||||
// the error message.
|
||||
|
||||
// Loop through the label users
|
||||
foreach(LabelUser lu; fd.lus)
|
||||
lu.setLabel(ls);
|
||||
|
||||
// setLabel should have thrown an error at this point
|
||||
assert(ls !is null);
|
||||
}
|
||||
|
||||
// Clear the forwards list
|
||||
forwards.reset();
|
||||
|
||||
// At this point the State no longer needs to refer to us. Set
|
||||
// the stateDec reference to null. This is also a signal to
|
||||
// State.registerGoto that the state has been resolved, and no
|
||||
// further forward references will be accepted.
|
||||
st.stateDec = null;
|
||||
|
||||
// After the code has been resolved, all labels should now be
|
||||
// registered in the 'labels' list. We must assign a number to
|
||||
// each label for later reference. We also set up the labelList
|
||||
// which can be used to look up the labels directly.
|
||||
int cnt = 0;
|
||||
st.labelList.length = st.labels.length;
|
||||
foreach(char[] name, StateLabel *sl; st.labels)
|
||||
{
|
||||
assert(sl !is null);
|
||||
assert(name == sl.name.str, "label name mismatch");
|
||||
sl.index = cnt++;
|
||||
st.labelList[sl.index] = sl;
|
||||
}
|
||||
}
|
||||
|
||||
// Compile it as a function.
|
||||
void compile()
|
||||
{
|
||||
// No forward references must be inserted after the state has
|
||||
// been resolved.
|
||||
assert(forwards.length == 0);
|
||||
|
||||
tasm.newFunc();
|
||||
code.compile();
|
||||
|
||||
// Table used to fetch the offset for the labels in this state.
|
||||
uint offsets[];
|
||||
|
||||
offsets.length = st.labels.length;
|
||||
|
||||
// Assemble the code and get the offsets
|
||||
st.bcode = tasm.assemble(st.lines, offsets);
|
||||
|
||||
// Store the offsets in the label statements themselves, for
|
||||
// later use
|
||||
int cnt = 0;
|
||||
foreach(StateLabel* ls; st.labels)
|
||||
{
|
||||
ls.offs = offsets[ls.index];
|
||||
}
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
return
|
||||
"State declaration: " ~
|
||||
st.name.str ~ "\n" ~
|
||||
code.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,634 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (tokenizer.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.compiler.tokenizer;
|
||||
|
||||
import monster.minibos.string;
|
||||
import monster.minibos.stream;
|
||||
import monster.minibos.stdio;
|
||||
|
||||
import monster.util.string : begins;
|
||||
|
||||
import monster.vm.error;
|
||||
|
||||
alias Token[] TokenArray;
|
||||
|
||||
// Check if a character is alpha-numerical or an underscore
|
||||
bool validIdentChar(char c)
|
||||
{
|
||||
if(validFirstIdentChar(c) || numericalChar(c))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Same as above, except numbers are not allowed as the first
|
||||
// character. Will extend to support UTF8 later.
|
||||
bool validFirstIdentChar(char c)
|
||||
{
|
||||
if((c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c == '_') ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool numericalChar(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
enum TT
|
||||
{
|
||||
// Syntax characters
|
||||
Semicolon, DDDot, DDot,
|
||||
LeftParen, RightParen,
|
||||
LeftCurl, RightCurl,
|
||||
LeftSquare, RightSquare,
|
||||
Dot, Comma, Colon,
|
||||
|
||||
// Array length symbol
|
||||
Dollar,
|
||||
|
||||
// Conditional expressions
|
||||
IsEqual, NotEqual,
|
||||
IsCaseEqual, IsCaseEqual2,
|
||||
NotCaseEqual, NotCaseEqual2,
|
||||
Less, More,
|
||||
LessEq, MoreEq,
|
||||
And, Or, Not,
|
||||
|
||||
// Assignment operators.
|
||||
Equals, PlusEq, MinusEq, MultEq, DivEq, RemEq, IDivEq,
|
||||
CatEq,
|
||||
|
||||
// Pre- and postfix increment and decrement operators ++ --
|
||||
PlusPlus, MinusMinus,
|
||||
|
||||
// Arithmetic operators
|
||||
Plus, Minus, Mult, Div, Rem, IDiv,
|
||||
Cat,
|
||||
|
||||
// Keywords. Note that we use Class as a separator below, so it
|
||||
// must be first in this list. All operator tokens must occur
|
||||
// before Class, and all keywords must come after Class.
|
||||
Class,
|
||||
For,
|
||||
If,
|
||||
Else,
|
||||
Foreach,
|
||||
ForeachRev,
|
||||
Do,
|
||||
While,
|
||||
Until,
|
||||
Typeof,
|
||||
Return,
|
||||
Continue,
|
||||
Break,
|
||||
Switch,
|
||||
Select,
|
||||
State,
|
||||
Singleton,
|
||||
This, New, Static, Const, Out, Ref, Abstract, Idle,
|
||||
Public, Private, Protected, True, False, Native, Null,
|
||||
Goto, Halt, Auto, Var, In,
|
||||
|
||||
Last, // Tokens after this do not have a specific string
|
||||
// associated with them.
|
||||
|
||||
StringLiteral, // "something"
|
||||
NumberLiteral, // Anything that starts with a number
|
||||
CharLiteral, // 'a'
|
||||
Identifier, // user-named identifier
|
||||
EOF // end of file
|
||||
}
|
||||
|
||||
|
||||
struct Token
|
||||
{
|
||||
TT type;
|
||||
char[] str;
|
||||
Floc loc;
|
||||
|
||||
char[] toString() { return str; }
|
||||
}
|
||||
|
||||
// Used to look up keywords.
|
||||
TT keywordLookup[char[]];
|
||||
|
||||
void initTokenizer()
|
||||
{
|
||||
// Insert the keywords into the lookup table
|
||||
for(TT t = TT.Class; t < TT.Last; t++)
|
||||
{
|
||||
char[] tok = tokenList[t];
|
||||
assert(tok != "");
|
||||
assert((tok in keywordLookup) == null);
|
||||
keywordLookup[tok] = t;
|
||||
}
|
||||
}
|
||||
|
||||
// Index table of all the tokens
|
||||
const char[][] tokenList =
|
||||
[
|
||||
TT.Semicolon : ";",
|
||||
TT.DDDot : "...",
|
||||
TT.DDot : "..",
|
||||
TT.LeftParen : "(",
|
||||
TT.RightParen : ")",
|
||||
TT.LeftCurl : "{",
|
||||
TT.RightCurl : "}",
|
||||
TT.LeftSquare : "[",
|
||||
TT.RightSquare : "]",
|
||||
TT.Dot : ".",
|
||||
TT.Comma : ",",
|
||||
TT.Colon : ":",
|
||||
|
||||
TT.Dollar : "$",
|
||||
|
||||
TT.IsEqual : "==",
|
||||
TT.NotEqual : "!=",
|
||||
TT.IsCaseEqual : "=i=",
|
||||
TT.IsCaseEqual2 : "=I=",
|
||||
TT.NotCaseEqual : "!=i=",
|
||||
TT.NotCaseEqual2 : "!=I=",
|
||||
TT.Less : "<",
|
||||
TT.More : ">",
|
||||
TT.LessEq : "<=",
|
||||
TT.MoreEq : ">=",
|
||||
TT.And : "&&",
|
||||
TT.Or : "||",
|
||||
TT.Not : "!",
|
||||
|
||||
TT.Equals : "=",
|
||||
TT.PlusEq : "+=",
|
||||
TT.MinusEq : "-=",
|
||||
TT.MultEq : "*=",
|
||||
TT.DivEq : "/=",
|
||||
TT.RemEq : "%%=",
|
||||
TT.IDivEq : "\\=",
|
||||
TT.CatEq : "~=",
|
||||
|
||||
TT.PlusPlus : "++",
|
||||
TT.MinusMinus : "--",
|
||||
TT.Cat : "~",
|
||||
|
||||
TT.Plus : "+",
|
||||
TT.Minus : "-",
|
||||
TT.Mult : "*",
|
||||
TT.Div : "/",
|
||||
TT.Rem : "%%",
|
||||
TT.IDiv : "\\",
|
||||
|
||||
TT.Class : "class",
|
||||
TT.Return : "return",
|
||||
TT.For : "for",
|
||||
TT.This : "this",
|
||||
TT.New : "new",
|
||||
TT.If : "if",
|
||||
TT.Else : "else",
|
||||
TT.Foreach : "foreach",
|
||||
TT.ForeachRev : "foreach_reverse",
|
||||
TT.Do : "do",
|
||||
TT.While : "while",
|
||||
TT.Until : "until",
|
||||
TT.Continue : "continue",
|
||||
TT.Break : "break",
|
||||
TT.Switch : "switch",
|
||||
TT.Select : "select",
|
||||
TT.State : "state",
|
||||
TT.Typeof : "typeof",
|
||||
TT.Singleton : "singleton",
|
||||
TT.Static : "static",
|
||||
TT.Const : "const",
|
||||
TT.Abstract : "abstract",
|
||||
TT.Idle : "idle",
|
||||
TT.Out : "out",
|
||||
TT.Ref : "ref",
|
||||
TT.Public : "public",
|
||||
TT.Private : "private",
|
||||
TT.Protected : "protected",
|
||||
TT.True : "true",
|
||||
TT.False : "false",
|
||||
TT.Native : "native",
|
||||
TT.Null : "null",
|
||||
TT.Goto : "goto",
|
||||
TT.Halt : "halt",
|
||||
TT.Auto : "auto",
|
||||
TT.Var : "var",
|
||||
TT.In : "in",
|
||||
];
|
||||
|
||||
class StreamTokenizer
|
||||
{
|
||||
private:
|
||||
// Line buffer. Don't worry, this is perfectly safe. It is used by
|
||||
// Stream.readLine, which uses the buffer if it fits and creates a
|
||||
// new one if it doesn't. It is only here to optimize memory usage
|
||||
// (avoid creating a new buffer for each line), and lines longer
|
||||
// than 300 characters will work without problems.
|
||||
char[300] buffer;
|
||||
char[] line; // The rest of the current line
|
||||
Stream inf;
|
||||
uint lineNum;
|
||||
char[] fname;
|
||||
|
||||
// Make a token of given type with given string, and remove it from
|
||||
// the input line.
|
||||
Token retToken(TT type, char[] str)
|
||||
{
|
||||
Token t;
|
||||
t.type = type;
|
||||
t.str = str;
|
||||
t.loc.fname = fname;
|
||||
t.loc.line = lineNum;
|
||||
|
||||
// Special case for =I= and !=I=. Treat them the same as =i= and
|
||||
// !=i=.
|
||||
if(type == TT.IsCaseEqual2) t.type = TT.IsCaseEqual;
|
||||
if(type == TT.NotCaseEqual2) t.type = TT.NotCaseEqual;
|
||||
|
||||
// Remove the string from 'line', along with any following witespace
|
||||
remWord(str);
|
||||
return t;
|
||||
}
|
||||
|
||||
// Removes 'str' from the beginning of 'line', or from
|
||||
// line[leadIn..$] if leadIn != 0.
|
||||
void remWord(char[] str, int leadIn = 0)
|
||||
{
|
||||
assert(line.length >= leadIn);
|
||||
line = line[leadIn..$];
|
||||
|
||||
assert(line.begins(str));
|
||||
line = line[str.length..$].stripl();
|
||||
}
|
||||
|
||||
Token eofToken()
|
||||
{
|
||||
Token t;
|
||||
t.str = "<end of file>";
|
||||
t.type = TT.EOF;
|
||||
t.loc.line = lineNum;
|
||||
t.loc.fname = fname;
|
||||
return t;
|
||||
}
|
||||
|
||||
public:
|
||||
final:
|
||||
this(char[] fname, Stream inf, int bom)
|
||||
{
|
||||
assert(inf !is null);
|
||||
|
||||
// The BOM (byte order mark) defines the byte order (little
|
||||
// endian or big endian) and the encoding (utf8, utf16 or
|
||||
// utf32).
|
||||
switch(bom)
|
||||
{
|
||||
case -1:
|
||||
// Files without a BOM are interpreted as UTF8
|
||||
case BOM.UTF8:
|
||||
// UTF8 is the default
|
||||
break;
|
||||
|
||||
case BOM.UTF16LE:
|
||||
case BOM.UTF16BE:
|
||||
case BOM.UTF32LE:
|
||||
case BOM.UTF32BE:
|
||||
fail("UTF16 and UTF32 files are not supported yet");
|
||||
default:
|
||||
fail("Unknown BOM value!");
|
||||
}
|
||||
|
||||
this.inf = inf;
|
||||
this.fname = fname;
|
||||
}
|
||||
|
||||
~this() { if(inf !is null) delete inf; }
|
||||
|
||||
void fail(char[] msg)
|
||||
{
|
||||
throw new MonsterException(format("%s:%s: %s", fname, lineNum, msg));
|
||||
}
|
||||
|
||||
Token getNext()
|
||||
{
|
||||
// Various parsing modes
|
||||
enum
|
||||
{
|
||||
Normal, // Normal mode
|
||||
Block, // Block comment
|
||||
Nest // Nested block comment
|
||||
}
|
||||
int mode = Normal;
|
||||
int nests = 0; // Nest level
|
||||
|
||||
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);
|
||||
|
||||
if(mode == Block)
|
||||
{
|
||||
int index = line.find("*/");
|
||||
|
||||
// If we find a '*/', the comment is done
|
||||
if(index != -1)
|
||||
{
|
||||
mode = Normal;
|
||||
|
||||
// Cut it the comment from the input
|
||||
remWord("*/", index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Comment not ended on this line, try the next
|
||||
line = null;
|
||||
}
|
||||
|
||||
// Start over
|
||||
goto restart;
|
||||
}
|
||||
|
||||
if(mode == Nest)
|
||||
{
|
||||
// Check for nested /+ and +/ in here, but go to restart if
|
||||
// none is found (meaning the comment continues on the next
|
||||
// line), or reset mode and go to restart if nest level ever
|
||||
// gets to 0.
|
||||
|
||||
do
|
||||
{
|
||||
int incInd = -1;
|
||||
int decInd = -1;
|
||||
// Find the first matching '/+' or '+/
|
||||
foreach(int i, char c; line[0..$-1])
|
||||
{
|
||||
if(c == '/' && line[i+1] == '+')
|
||||
{
|
||||
incInd = i;
|
||||
break;
|
||||
}
|
||||
else if(c == '+' && line[i+1] == '/')
|
||||
{
|
||||
decInd = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a nest level when '/+' is found
|
||||
if(incInd != -1)
|
||||
{
|
||||
remWord("/+", incInd);
|
||||
nests++;
|
||||
continue; // Search more in this line
|
||||
}
|
||||
|
||||
// Remove a nest level when '+/' is found
|
||||
if(decInd != -1)
|
||||
{
|
||||
// Remove the +/ from input
|
||||
remWord("+/", decInd);
|
||||
|
||||
nests--; // Remove a level
|
||||
assert(nests >= 0);
|
||||
|
||||
// Are we done? If so, return to normal mode.
|
||||
if(nests == 0)
|
||||
{
|
||||
mode = Normal;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nothing found on this line, try the next
|
||||
line = null;
|
||||
break;
|
||||
}
|
||||
while(line.length >= 2);
|
||||
|
||||
goto restart;
|
||||
}
|
||||
|
||||
// Comment - start next line
|
||||
if(line.begins("//"))
|
||||
{
|
||||
line = null;
|
||||
goto restart;
|
||||
}
|
||||
|
||||
// Block comment
|
||||
if(line.begins("/*"))
|
||||
{
|
||||
mode = Block;
|
||||
line = line[2..$];
|
||||
goto restart;
|
||||
}
|
||||
|
||||
// Nested comment
|
||||
if(line.begins("/+"))
|
||||
{
|
||||
mode = Nest;
|
||||
line = line[2..$];
|
||||
nests++;
|
||||
goto restart;
|
||||
}
|
||||
|
||||
if(line.begins("*/")) fail("Unexpected end of block comment");
|
||||
if(line.begins("+/")) fail("Unexpected end of nested comment");
|
||||
|
||||
// String literals (multi-line literals not implemented yet)
|
||||
if(line.begins("\""))
|
||||
{
|
||||
int len = 1;
|
||||
bool found = false;
|
||||
foreach(char ch; line[1..$])
|
||||
{
|
||||
len++;
|
||||
// No support for escape sequences as of now
|
||||
if(ch == '"')
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) fail("Unterminated string literal '" ~line~"'");
|
||||
return retToken(TT.StringLiteral, line[0..len].dup);
|
||||
}
|
||||
|
||||
// Character literals (not parsed yet, so escape sequences like
|
||||
// '\n', '\'', or unicode stuff won't work.)
|
||||
if(line[0] == '\'')
|
||||
{
|
||||
if(line.length < 2 || line[2] != '\'')
|
||||
fail("Malformed character literal " ~line);
|
||||
return retToken(TT.CharLiteral, line[0..3].dup);
|
||||
}
|
||||
|
||||
// Numerical literals - if it starts with a number, we accept
|
||||
// it, until it is interupted by an unacceptible character. We
|
||||
// also accept numbers on the form .NUM. We do not try to parse
|
||||
// the number here.
|
||||
if(numericalChar(line[0]) ||
|
||||
// Cover the .num case
|
||||
( line.length >= 2 && line[0] == '.' &&
|
||||
numericalChar(line[1]) ))
|
||||
{
|
||||
// Treat the rest as we would an identifier - the actual
|
||||
// interpretation will be done later. We allow non-numerical
|
||||
// tokens in the literal, such as 0x0a or 1_000_000. We must
|
||||
// also explicitly allow '.' dots. A number literal can end
|
||||
// with a percentage sign '%'.
|
||||
int len = 1;
|
||||
bool lastDot = false; // Was the last char a '.'?
|
||||
bool lastPer = false; // Was it a '%'?
|
||||
foreach(char ch; line[1..$])
|
||||
{
|
||||
if(ch == '.')
|
||||
{
|
||||
// We accept "." but not "..", as this might be an
|
||||
// operator.
|
||||
if(lastDot)
|
||||
{
|
||||
len--; // Remove the last dot and exit.
|
||||
break;
|
||||
}
|
||||
lastDot = true;
|
||||
}
|
||||
else if(ch == '%')
|
||||
{
|
||||
// Ditto for percentage signs. We allow '%' but not
|
||||
// '%%'
|
||||
if(lastPer)
|
||||
{
|
||||
len--;
|
||||
break;
|
||||
}
|
||||
lastPer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!validIdentChar(ch)) break;
|
||||
lastDot = false;
|
||||
lastPer = false;
|
||||
}
|
||||
|
||||
// This was a valid character, count it
|
||||
len++;
|
||||
}
|
||||
return retToken(TT.NumberLiteral, line[0..len].dup);
|
||||
}
|
||||
|
||||
// Check for identifiers
|
||||
if(validFirstIdentChar(line[0]))
|
||||
{
|
||||
// It's an identifier or name, find the length
|
||||
int len = 1;
|
||||
foreach(char ch; line[1..$])
|
||||
{
|
||||
if(!validIdentChar(ch)) break;
|
||||
len++;
|
||||
}
|
||||
|
||||
char[] id = line[0..len];
|
||||
|
||||
// We only allow certain identifiers to begin with __, as
|
||||
// these are reserved for internal use.
|
||||
if(id.begins("__"))
|
||||
if(id != "__STACK__")
|
||||
fail("Identifier " ~ id ~ " is not allowed to begin with __");
|
||||
|
||||
// Check if this is a keyword
|
||||
if(id in keywordLookup)
|
||||
{
|
||||
TT t = keywordLookup[id];
|
||||
assert(t >= TT.Class && t < TT.Last,
|
||||
"Found " ~ id ~ " as a keyword, but with wrong type!");
|
||||
return retToken(t, tokenList[t]);
|
||||
}
|
||||
|
||||
// Not a keyword? Then it's an identifier
|
||||
return retToken(TT.Identifier, id.dup);
|
||||
}
|
||||
|
||||
// Check for operators and syntax characters. We browse through
|
||||
// the entire list, and select the longest match that fits (so
|
||||
// we don't risk matching "+" to "+=", for example.)
|
||||
TT match;
|
||||
int mlen = 0;
|
||||
foreach(int i, char[] tok; tokenList[0..TT.Class])
|
||||
if(line.begins(tok) && tok.length >= mlen)
|
||||
{
|
||||
assert(tok.length > mlen, "Two matching tokens of the same length");
|
||||
mlen = tok.length;
|
||||
match = cast(TT) i;
|
||||
}
|
||||
|
||||
if(mlen) return retToken(match, tokenList[match]);
|
||||
|
||||
// Invalid token
|
||||
fail("Invalid token " ~ line);
|
||||
}
|
||||
|
||||
// Require a specific token
|
||||
bool isToken(TT tok)
|
||||
{
|
||||
Token tt = getNext();
|
||||
return tt.type == tok;
|
||||
}
|
||||
|
||||
bool notToken(TT tok) { return !isToken(tok); }
|
||||
}
|
||||
|
||||
// Read the entire file into an array of tokens. This includes the EOF
|
||||
// token at the end.
|
||||
TokenArray tokenizeStream(char[] fname, Stream stream, int bom)
|
||||
{
|
||||
TokenArray tokenArray;
|
||||
|
||||
StreamTokenizer tok = new StreamTokenizer(fname, stream, bom);
|
||||
Token tt;
|
||||
do
|
||||
{
|
||||
tt = tok.getNext();
|
||||
tokenArray ~= tt;
|
||||
}
|
||||
while(tt.type != TT.EOF)
|
||||
delete tok;
|
||||
|
||||
return tokenArray;
|
||||
}
|
@ -0,0 +1,952 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (types.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.compiler.types;
|
||||
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.expression;
|
||||
import monster.compiler.assembler;
|
||||
import monster.compiler.properties;
|
||||
import monster.compiler.block;
|
||||
import monster.compiler.functions;
|
||||
import monster.compiler.variables;
|
||||
import monster.compiler.states;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.error;
|
||||
|
||||
import monster.minibos.stdio;
|
||||
import monster.minibos.string;
|
||||
|
||||
/*
|
||||
List of all type classes:
|
||||
|
||||
Type (abstract)
|
||||
InternalType (abstract)
|
||||
NullType (null expression)
|
||||
BasicType (covers int, char, bool, float, void)
|
||||
ObjectType
|
||||
ArrayType
|
||||
MetaType
|
||||
|
||||
*/
|
||||
class TypeException : Exception
|
||||
{
|
||||
Type type1, type2;
|
||||
|
||||
this(Type t1, Type t2)
|
||||
{
|
||||
type1 = t1;
|
||||
type2 = t2;
|
||||
super("Unhandled TypeException on types " ~ type1.toString ~ " and " ~
|
||||
type2.toString ~ ".");
|
||||
}
|
||||
}
|
||||
|
||||
// A class that represents a type. The Type class is abstract, and the
|
||||
// different types are actually handled by various subclasses of Type
|
||||
// (see below.) The static function identify() is used to figure out
|
||||
// exactly which subclass to use.
|
||||
abstract class Type : Block
|
||||
{
|
||||
// Can the given tokens possibly be parsed as a type? This is not
|
||||
// meant as an extensive test to differentiate between types and
|
||||
// non-types, it is more like a short-cut to get the type tokens out
|
||||
// of the way. It should only be called from places where you
|
||||
// require a type (and only in canParse(), since it removes the
|
||||
// tokens.)
|
||||
static bool canParseRem(ref TokenArray toks)
|
||||
{
|
||||
// TODO: Parse typeof(exp) here. We have to do a hack here, as
|
||||
// we have no hope of parsing every expression in here. Instead
|
||||
// we require the first ( and then remove every token until the
|
||||
// matching ), allowing matching () pairs on the inside.
|
||||
|
||||
if(!isNext(toks, TT.Identifier)) return false;
|
||||
|
||||
while(isNext(toks,TT.LeftSquare))
|
||||
if(!isNext(toks,TT.RightSquare))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse a type specifieer and return the correct class to handle it
|
||||
// (fully parsed). Currently valid type formats are
|
||||
//
|
||||
// identifier - either a basic type (int, bool, etc) or a class name
|
||||
// identifier[] - array
|
||||
// identifier[][]... - arrays can be multi-dimensional
|
||||
//
|
||||
// static buffers, ie. int[10] are not allowed as types. The only
|
||||
// place a type is allowed to take array expressions is after a new,
|
||||
// eg. int[] i = new int[10], in that case you should set the second
|
||||
// parameter to true. The expression array is stored in exps.
|
||||
static Type identify(ref TokenArray toks, bool takeExpr, ref ExprArray exps)
|
||||
{
|
||||
assert(toks.length != 0);
|
||||
|
||||
// Model this after Exp.idSub or Code.identify
|
||||
Type t = null;
|
||||
|
||||
// Find what kind of type this is and create an instance of the
|
||||
// corresponding class.
|
||||
if(BasicType.canParse(toks)) t = new BasicType();
|
||||
else if(ObjectType.canParse(toks)) t = new ObjectType();
|
||||
//else if(TypeofType.canParse(toks)) t = new TypeofType();
|
||||
else fail("Cannot parse " ~ toks[0].str ~ " as a type", toks[0].loc);
|
||||
|
||||
// Parse the actual tokens with our new and shiny object.
|
||||
t.parse(toks);
|
||||
|
||||
// Add arrays here afterwards by wrapping the previous type in
|
||||
// an ArrayType.
|
||||
exps = VarDeclaration.getArray(toks, takeExpr);
|
||||
|
||||
// Despite looking strange, this code is correct.
|
||||
foreach(e; exps)
|
||||
t = new ArrayType(t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
// Short version of the above, when expressions are not allowed.
|
||||
static Type identify(ref TokenArray toks)
|
||||
{
|
||||
ExprArray exp;
|
||||
return identify(toks, false, exp);
|
||||
}
|
||||
|
||||
// The complete type name including specifiers, eg. "int[]".
|
||||
char[] name;
|
||||
|
||||
// Used for easy checking
|
||||
bool isInt() { return false; }
|
||||
bool isUint() { return false; }
|
||||
bool isLong() { return false; }
|
||||
bool isUlong() { return false; }
|
||||
bool isChar() { return false; }
|
||||
bool isBool() { return false; }
|
||||
bool isFloat() { return false; }
|
||||
bool isDouble() { return false; }
|
||||
bool isVoid() { return false; }
|
||||
|
||||
bool isString() { return false; }
|
||||
|
||||
bool isObject() { return false; }
|
||||
bool isArray() { return arrays() != 0; }
|
||||
|
||||
bool isIntegral() { return isInt || isUint || isLong || isUlong; }
|
||||
bool isFloating() { return isFloat || isDouble; }
|
||||
|
||||
// Numerical types allow arithmetic operators
|
||||
bool isNumerical() { return isIntegral() || isFloating(); }
|
||||
|
||||
// Is this a meta-type? A meta-type is the type of expressions like
|
||||
// 'int' and 'ClassName' - they themselves describe a type.
|
||||
// Meta-types always have a base type accessible through getBase(),
|
||||
// and a member scope similar to variables of the type itself.
|
||||
bool isMeta() { return false; }
|
||||
|
||||
// Is this a legal type for variables? If this is false, you cannot
|
||||
// create variables of this type, neither directly or indirectly
|
||||
// (through automatic type inference.) This isn't currently used
|
||||
// anywhere, but we will need it later when we implement automatic
|
||||
// types.
|
||||
bool isLegal() { return true; }
|
||||
|
||||
// Get base type (used for arrays and meta-types)
|
||||
Type getBase() { assert(0, "Type " ~ toString() ~ " has no base type"); }
|
||||
|
||||
// Return the array dimension of this type. Eg. int[][] has two
|
||||
// dimensions, int has zero.
|
||||
int arrays() { return 0; }
|
||||
|
||||
// Return the number of ints needed to store one variable of this
|
||||
// type.
|
||||
int getSize();
|
||||
|
||||
// Get the scope for resolving members of this type. Examples:
|
||||
// myObject.func() -> getMemberScope called for the type of myObject,
|
||||
// returns the class scope, where func is resolved.
|
||||
// array.length -> getMemberScope called for ArrayType, returns a
|
||||
// special scope that contains the 'length' property
|
||||
Scope getMemberScope()
|
||||
{
|
||||
// Returning null means this type does not have any members.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate that this type actually exists. This is used to make
|
||||
// sure that all forward references are resolved.
|
||||
void validate() {}
|
||||
|
||||
// Check if this type is equivalent to the given D type
|
||||
final bool isDType(TypeInfo ti)
|
||||
{
|
||||
char[] name = ti.toString;
|
||||
name = name[name.rfind('.')+1..$];
|
||||
|
||||
switch(name)
|
||||
{
|
||||
case "int": return isInt();
|
||||
case "uint": return isUint();
|
||||
case "long": return isLong();
|
||||
case "ulong": return isUlong();
|
||||
case "float": return isFloat();
|
||||
case "double": return isDouble();
|
||||
case "bool": return isBool();
|
||||
case "dchar": return isChar();
|
||||
case "AIndex": return isArray();
|
||||
case "MIndex": return isObject();
|
||||
default:
|
||||
assert(0, "illegal type in isDType(): " ~ name);
|
||||
}
|
||||
}
|
||||
|
||||
// Used by defaultInit as a shortcut for converting a variable to an
|
||||
// int array.
|
||||
static int[] makeData(T)(T val)
|
||||
{
|
||||
int data[];
|
||||
if(T.sizeof == 4) data.length = 1;
|
||||
else if(T.sizeof == 8) data.length = 2;
|
||||
else assert(0, "Unsupported type size");
|
||||
|
||||
*(cast(T*)data.ptr) = val;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Get the default initializer for this type. The assembler deals
|
||||
// with data in terms of ints (4 byte chunks), so we return the data
|
||||
// as an int[]. The default initializer should be an illegal value
|
||||
// when possible (null pointers, nan, etc) to catch mistakenly
|
||||
// uninitialized variables as quickly as possible. This will usually
|
||||
// be the same init value as in D.
|
||||
int[] defaultInit();
|
||||
|
||||
// Generate assembler code that pushes the default initializer on
|
||||
// the stack. TODO: This should become a general function in the
|
||||
// assembler to push any int[]. Pass it a type so it can check the
|
||||
// size automatically as well.
|
||||
final void pushInit()
|
||||
{
|
||||
int[] def = defaultInit();
|
||||
assert(def.length == getSize, "default initializer is not the correct size");
|
||||
setLine();
|
||||
|
||||
tasm.pushArray(def);
|
||||
}
|
||||
|
||||
// Compare types
|
||||
final int opEquals(Type t)
|
||||
{
|
||||
if(toString != t.toString) return 0;
|
||||
|
||||
// TODO: In our current system, if the name and array dimension
|
||||
// match, we must be the same type. In the future we might have
|
||||
// to add more tests here however. For example, two structs of
|
||||
// the same name might be defined in different classes. The best
|
||||
// solution is perhaps to always make sure the type name is
|
||||
// unique.
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
final char[] toString() { return name; }
|
||||
|
||||
// Cast the expression orig to this type. Uses canCastTo to
|
||||
// determine if a cast is possible.
|
||||
final void typeCast(ref Expression orig)
|
||||
{
|
||||
if(orig.type == this) return;
|
||||
|
||||
// Replace the expression with a CastExpression. This acts as a
|
||||
// wrapper that puts in the conversion code after expression is
|
||||
// evaluated.
|
||||
if(orig.type.canCastTo(this))
|
||||
orig = new CastExpression(orig, this);
|
||||
else
|
||||
throw new TypeException(this, orig.type);
|
||||
}
|
||||
|
||||
// Do compile-time type casting. Gets orig.evalCTime() and returns
|
||||
// the converted result.
|
||||
final int[] typeCastCTime(Expression orig)
|
||||
{
|
||||
int[] res = orig.evalCTime();
|
||||
|
||||
assert(res.length == orig.type.getSize);
|
||||
|
||||
if(orig.type.canCastTo(this))
|
||||
res = orig.type.doCastCTime(res, this);
|
||||
else
|
||||
throw new TypeException(this, orig.type);
|
||||
|
||||
assert(res.length == getSize);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Can this type be cast to the parameter type? This function is not
|
||||
// required to handle cases where the types are the same.
|
||||
bool canCastTo(Type to)
|
||||
{
|
||||
return false; // By default we can't cast anything
|
||||
}
|
||||
|
||||
// Returns true if the cast can be performed at compile time. This
|
||||
// is usually true.
|
||||
bool canCastCTime(Type to)
|
||||
{ return canCastTo(to); }
|
||||
|
||||
// Returns true if the types are equal or if canCastTo returns true.
|
||||
final bool canCastOrEqual(Type to)
|
||||
{
|
||||
return to == this || canCastTo(to);
|
||||
}
|
||||
|
||||
// Do the cast in the assembler. Rename to compileCastTo, perhaps.
|
||||
void evalCastTo(Type to)
|
||||
{
|
||||
assert(0, "evalCastTo not implemented for type " ~ toString);
|
||||
}
|
||||
|
||||
// Do the cast in the compiler.
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
{
|
||||
assert(0, "doCastCTime not implemented for type " ~ toString);
|
||||
}
|
||||
|
||||
/* Cast two expressions to their common type, if any. Throw a
|
||||
TypeException exception if not possible. This exception should be
|
||||
caught elsewhere to give a more useful error message. Examples of
|
||||
possible outcomes:
|
||||
|
||||
int, int -> does nothing
|
||||
float, int -> converts the second paramter to float
|
||||
int, float -> converts the first to float
|
||||
bool, int -> throws an error
|
||||
|
||||
For various integer types, special rules apply. These are (for
|
||||
the time being):
|
||||
|
||||
ulong, any -> ulong
|
||||
long, any 32bit -> long
|
||||
int, uint -> uint
|
||||
|
||||
Similar types (eg. uint, uint) will never convert types.
|
||||
*/
|
||||
static void castCommon(ref Expression e1, ref Expression e2)
|
||||
{
|
||||
Type t1 = e1.type;
|
||||
Type t2 = e2.type;
|
||||
|
||||
if(t1 == t2) return;
|
||||
|
||||
Type common;
|
||||
|
||||
// Apply integral promotion rules first. TODO: Apply polysemous
|
||||
// rules later.
|
||||
if(t1.isIntegral && t2.isIntegral)
|
||||
{
|
||||
// ulong dominates all other types
|
||||
if(t1.isUlong) common = t1;
|
||||
else if(t2.isUlong) common = t2;
|
||||
|
||||
// long dominates over 32-bit values
|
||||
else if(t1.isLong) common = t1;
|
||||
else if(t2.isLong) common = t2;
|
||||
|
||||
// unsigned dominates over signed
|
||||
else if(t1.isUint) common = t1;
|
||||
else if(t2.isUint) common = t2;
|
||||
else
|
||||
{
|
||||
assert(t1.isInt && t2.isInt, "unknown integral type encountered");
|
||||
assert(0, "should never get here");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find the common type
|
||||
if(t1.canCastTo(t2)) common = t2; else
|
||||
if(t2.canCastTo(t1)) common = t1;
|
||||
else throw new TypeException(t1, t2);
|
||||
}
|
||||
|
||||
// Wrap the expressions in CastExpression blocks if necessary.
|
||||
common.typeCast(e1);
|
||||
common.typeCast(e2);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal types are types that are used internally by the compiler,
|
||||
// eg. for type conversion or for special syntax. They can not be used
|
||||
// for variables, neither directly nor indirectly.
|
||||
abstract class InternalType : Type
|
||||
{
|
||||
final:
|
||||
bool isLegal() { return false; }
|
||||
int[] defaultInit() {assert(0);}
|
||||
int getSize() { return 0; }
|
||||
void parse(ref TokenArray toks) {assert(0);}
|
||||
void resolve(Scope sc) {assert(0);}
|
||||
}
|
||||
|
||||
// Handles the 'null' literal. This type is only used for
|
||||
// conversions. You cannot declare variables of the nulltype.
|
||||
class NullType : InternalType
|
||||
{
|
||||
this() { name = "null-type"; }
|
||||
|
||||
bool canCastTo(Type to)
|
||||
{
|
||||
return to.isArray || to.isObject;
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
{
|
||||
assert(to.getSize == 1);
|
||||
|
||||
// The value of null is always zero. There is no value on the
|
||||
// stack to convert, so just push it.
|
||||
tasm.push(0);
|
||||
}
|
||||
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
{
|
||||
assert(to.getSize == 1);
|
||||
assert(data.length == 0);
|
||||
return [0];
|
||||
}
|
||||
}
|
||||
|
||||
// Handles all the built-in types. These are: int, uint, long, ulong,
|
||||
// float, double, bool, char and the "void" type, which is represented
|
||||
// by an empty string. The void type is only allowed in special cases
|
||||
// (currently only in function return types), and is not parsed
|
||||
// normally through identify()/parse(). Instead it is created directly
|
||||
// with the constructor.
|
||||
class BasicType : Type
|
||||
{
|
||||
this() {}
|
||||
|
||||
// Create the given type directly without parsing. Use an empty
|
||||
// string "" for the void type. This is the only way a void type can
|
||||
// be created.
|
||||
this(char[] tn)
|
||||
{
|
||||
if(!isBasic(tn))
|
||||
fail("BasicType does not support type " ~ tn);
|
||||
|
||||
name = tn;
|
||||
|
||||
// Cache the class to save some overhead
|
||||
store[tn] = this;
|
||||
}
|
||||
|
||||
private static BasicType[char[]] store;
|
||||
|
||||
// Get a basic type of the given name. This will not allocate a new
|
||||
// instance if another instance already exists.
|
||||
static BasicType get(char[] tn)
|
||||
{
|
||||
if(tn in store) return store[tn];
|
||||
|
||||
return new BasicType(tn);
|
||||
}
|
||||
|
||||
// Shortcuts
|
||||
static BasicType getVoid() { return get(""); }
|
||||
static BasicType getInt() { return get("int"); }
|
||||
static BasicType getUint() { return get("uint"); }
|
||||
static BasicType getLong() { return get("long"); }
|
||||
static BasicType getUlong() { return get("ulong"); }
|
||||
static BasicType getFloat() { return get("float"); }
|
||||
static BasicType getDouble() { return get("double"); }
|
||||
static BasicType getChar() { return get("char"); }
|
||||
static BasicType getBool() { return get("bool"); }
|
||||
|
||||
static bool isBasic(char[] tn)
|
||||
{
|
||||
return (tn == "" || tn == "int" || tn == "float" || tn == "char" ||
|
||||
tn == "bool" || tn == "uint" || tn == "long" ||
|
||||
tn == "ulong" || tn == "double");
|
||||
}
|
||||
|
||||
static bool canParse(TokenArray toks)
|
||||
{
|
||||
Token t;
|
||||
if(!isNext(toks, TT.Identifier, t)) return false;
|
||||
|
||||
return isBasic(t.str);
|
||||
}
|
||||
|
||||
override:
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
Token t;
|
||||
|
||||
if(!isNext(toks, TT.Identifier, t) || !isBasic(t.str))
|
||||
assert(0, "Internal error in BasicType.parse()");
|
||||
|
||||
// Get the name and the line from the token
|
||||
name = t.str;
|
||||
loc = t.loc;
|
||||
}
|
||||
|
||||
bool isInt() { return name == "int"; }
|
||||
bool isUint() { return name == "uint"; }
|
||||
bool isLong() { return name == "long"; }
|
||||
bool isUlong() { return name == "ulong"; }
|
||||
bool isChar() { return name == "char"; }
|
||||
bool isBool() { return name == "bool"; }
|
||||
bool isFloat() { return name == "float"; }
|
||||
bool isDouble() { return name == "double"; }
|
||||
bool isVoid() { return name == ""; }
|
||||
|
||||
Scope getMemberScope()
|
||||
{
|
||||
if(isInt) return IntProperties.singleton;
|
||||
if(isUint) return UintProperties.singleton;
|
||||
if(isLong) return LongProperties.singleton;
|
||||
if(isUlong) return UlongProperties.singleton;
|
||||
if(isFloat) return FloatProperties.singleton;
|
||||
if(isDouble) return DoubleProperties.singleton;
|
||||
|
||||
if(isChar || isBool)
|
||||
return GenericProperties.singleton;
|
||||
|
||||
assert(isVoid);
|
||||
return null;
|
||||
}
|
||||
|
||||
// List the implisit conversions that are possible
|
||||
bool canCastTo(Type to)
|
||||
{
|
||||
// We can convert between all integral types
|
||||
if(to.isIntegral) return isIntegral;
|
||||
|
||||
// All numerical types can be converted to floating point
|
||||
if(to.isFloating) return isNumerical;
|
||||
|
||||
// These types can be converted to strings.
|
||||
if(to.isString)
|
||||
return isNumerical || isChar || isBool;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool canCastCTime(Type to)
|
||||
{
|
||||
// We haven't implemented compile time string casting yet
|
||||
if(to.isString) return false;
|
||||
return canCastTo(to);
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
{
|
||||
assert(this != to);
|
||||
assert(!isVoid);
|
||||
|
||||
int fromSize = getSize();
|
||||
int toSize = to.getSize();
|
||||
bool fromSign = isInt || isLong || isFloat || isBool;
|
||||
|
||||
if(to.isInt || to.isUint)
|
||||
{
|
||||
assert(isIntegral);
|
||||
if(isLong || isUlong)
|
||||
tasm.castLongToInt();
|
||||
}
|
||||
else if(to.isLong || to.isUlong)
|
||||
{
|
||||
if(isInt || isUint) tasm.castIntToLong(fromSign);
|
||||
else assert(isUlong || isLong);
|
||||
}
|
||||
else if(to.isFloat || to.isDouble)
|
||||
{
|
||||
if(isIntegral)
|
||||
tasm.castIntToFloat(this, to);
|
||||
else if(isFloating)
|
||||
tasm.castFloatToFloat(fromSize, toSize);
|
||||
else assert(0);
|
||||
}
|
||||
else if(to.isString)
|
||||
{
|
||||
assert(!isVoid);
|
||||
|
||||
if(isIntegral)
|
||||
tasm.castIntToString(this);
|
||||
else if(isFloating)
|
||||
tasm.castFloatToString(this);
|
||||
else if(isBool) tasm.castBoolToString();
|
||||
|
||||
// Create an array from one element on the stack
|
||||
else if(isChar) tasm.popToArray(1, 1);
|
||||
else assert(0, name ~ " not done yet");
|
||||
}
|
||||
else
|
||||
fail("Conversion " ~ toString ~ " to " ~ to.toString ~
|
||||
" not implemented.");
|
||||
}
|
||||
|
||||
int[] doCastCTime(int[] data, Type to)
|
||||
{
|
||||
assert(this != to);
|
||||
assert(!isVoid);
|
||||
|
||||
int fromSize = getSize();
|
||||
int toSize = to.getSize();
|
||||
bool fromSign = isInt || isLong || isFloat || isBool;
|
||||
|
||||
assert(data.length == fromSize);
|
||||
|
||||
if(to.isInt || to.isUint)
|
||||
{
|
||||
assert(isIntegral);
|
||||
data = data[0..1];
|
||||
}
|
||||
else if(to.isLong || to.isUlong)
|
||||
{
|
||||
if(isInt || isUint)
|
||||
{
|
||||
if(fromSign && to.isLong && data[0] < 0) data ~= -1;
|
||||
else data ~= 0;
|
||||
}
|
||||
else assert(isUlong || isLong);
|
||||
}
|
||||
else if(to.isFloat)
|
||||
{
|
||||
assert(isNumerical);
|
||||
|
||||
float *fptr = cast(float*)data.ptr;
|
||||
|
||||
if(isInt) *fptr = data[0];
|
||||
else if(isUint) *fptr = cast(uint)data[0];
|
||||
else if(isLong) *fptr = *(cast(long*)data.ptr);
|
||||
else if(isUlong) *fptr = *(cast(ulong*)data.ptr);
|
||||
else if(isDouble) *fptr = *(cast(double*)data.ptr);
|
||||
else assert(0);
|
||||
data = data[0..1];
|
||||
}
|
||||
else if(to.isDouble)
|
||||
{
|
||||
assert(isNumerical);
|
||||
|
||||
if(data.length < 2) data.length = 2;
|
||||
double *fptr = cast(double*)data.ptr;
|
||||
|
||||
if(isInt) *fptr = data[0];
|
||||
else if(isUint) *fptr = cast(uint)data[0];
|
||||
else if(isLong) *fptr = *(cast(long*)data.ptr);
|
||||
else if(isUlong) *fptr = *(cast(ulong*)data.ptr);
|
||||
else if(isFloat) *fptr = *(cast(float*)data.ptr);
|
||||
else assert(0);
|
||||
data = data[0..2];
|
||||
}
|
||||
else
|
||||
fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~
|
||||
" not implemented.");
|
||||
|
||||
assert(data.length == toSize);
|
||||
return data;
|
||||
}
|
||||
|
||||
int getSize()
|
||||
{
|
||||
if( isInt || isUint || isFloat || isChar || isBool )
|
||||
return 1;
|
||||
|
||||
if( isLong || isUlong || isDouble )
|
||||
return 2;
|
||||
|
||||
assert(0, "getSize() does not handle type '" ~ name ~ "'");
|
||||
}
|
||||
|
||||
void resolve(Scope sc)
|
||||
{
|
||||
// Check that our given name is indeed valid
|
||||
assert(isBasic(name));
|
||||
}
|
||||
|
||||
int[] defaultInit()
|
||||
{
|
||||
int data[];
|
||||
|
||||
// Ints default to 0, bools to false
|
||||
if(isInt || isUint || isBool) data = makeData!(int)(0);
|
||||
// Ditto for double-size ints
|
||||
else if(isLong || isUlong) data = makeData!(long)(0);
|
||||
// Chars default to an illegal utf-32 value
|
||||
else if(isChar) data = makeData!(int)(0x0000FFFF);
|
||||
// Floats default to not a number
|
||||
else if(isFloat) data = makeData!(float)(float.nan);
|
||||
else if(isDouble) data = makeData!(double)(double.nan);
|
||||
else
|
||||
assert(0, "Type '" ~ name ~ "' has no default initializer");
|
||||
|
||||
assert(data.length == getSize, "size mismatch in defaultInit");
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// Represents a normal class name. The reason this is called
|
||||
// "ObjectType" is because an actual variable (of this type) points to
|
||||
// an object, not to a class. The meta-type ClassType should be used
|
||||
// for variables that point to classes.
|
||||
class ObjectType : Type
|
||||
{
|
||||
final:
|
||||
private:
|
||||
// Class that we represent (set by resolve()). Variables of this
|
||||
// type may point to objects of this class, or to objects of
|
||||
// subclasses. We only use an index, since classes might be forward
|
||||
// referenced.
|
||||
CIndex clsIndex;
|
||||
|
||||
public:
|
||||
|
||||
this() {}
|
||||
this(MonsterClass mc)
|
||||
{
|
||||
assert(mc !is null);
|
||||
name = mc.name.str;
|
||||
loc = mc.name.loc;
|
||||
clsIndex = mc.gIndex;
|
||||
}
|
||||
|
||||
static bool canParse(TokenArray toks)
|
||||
{
|
||||
if(!isNext(toks, TT.Identifier)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
MonsterClass getClass()
|
||||
{
|
||||
assert(clsIndex != 0);
|
||||
|
||||
if(!global.isLoaded(clsIndex))
|
||||
fail("Cannot use " ~ name ~
|
||||
": class not found or forward reference", loc);
|
||||
|
||||
return global.getClass(clsIndex);
|
||||
}
|
||||
|
||||
override:
|
||||
// getClass does all the error checking we need
|
||||
void validate() { getClass(); }
|
||||
|
||||
int getSize() { return 1; }
|
||||
bool isObject() { return true; }
|
||||
int[] defaultInit() { return makeData!(int)(0); }
|
||||
|
||||
bool canCastTo(Type type)
|
||||
{
|
||||
assert(clsIndex != 0);
|
||||
|
||||
if(type.isString) return true;
|
||||
|
||||
if(type.isObject)
|
||||
{
|
||||
auto ot = cast(ObjectType)type;
|
||||
assert(ot !is null);
|
||||
|
||||
MonsterClass us = getClass();
|
||||
MonsterClass other = ot.getClass();
|
||||
|
||||
assert(us != other);
|
||||
|
||||
// We can only upcast
|
||||
return other.parentOf(us);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
{
|
||||
assert(clsIndex != 0);
|
||||
assert(canCastTo(to));
|
||||
|
||||
if(to.isObject)
|
||||
{
|
||||
auto tt = cast(ObjectType)to;
|
||||
assert(tt !is null);
|
||||
assert(clsIndex !is tt.clsIndex);
|
||||
int cnum = tt.clsIndex;
|
||||
|
||||
// We have not decided what index to pass here yet. Think
|
||||
// more about it when we implement full polymorphism.
|
||||
assert(0, "not implemented");
|
||||
tasm.upcast(cnum);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(to.isString);
|
||||
tasm.castObjToString();
|
||||
}
|
||||
|
||||
// Members of objects are resolved in the class scope.
|
||||
Scope getMemberScope()
|
||||
{
|
||||
return getClass().sc;
|
||||
}
|
||||
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
Token t;
|
||||
|
||||
if(!isNext(toks, TT.Identifier, t))
|
||||
assert(0, "Internal error in ObjectType.parse()");
|
||||
|
||||
// Get the name and the line from the token
|
||||
name = t.str;
|
||||
loc = t.loc;
|
||||
}
|
||||
|
||||
// This is called when the type is defined, and it can forward
|
||||
// reference classes that do not exist yet. The classes must exist
|
||||
// by the time getClass() is called though, usually when class body
|
||||
// (not just the header) is being resolved.
|
||||
void resolve(Scope sc)
|
||||
{
|
||||
clsIndex = global.getForwardIndex(name);
|
||||
assert(clsIndex != 0);
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayType : Type
|
||||
{
|
||||
// What type is this an array of?
|
||||
Type base;
|
||||
|
||||
this(Type btype)
|
||||
{
|
||||
base = btype;
|
||||
name = base.name ~ "[]";
|
||||
loc = base.loc;
|
||||
}
|
||||
|
||||
override:
|
||||
void validate() { assert(base !is null); base.validate(); }
|
||||
int arrays() { return base.arrays() + 1; }
|
||||
int getSize() { return 1; }
|
||||
int[] defaultInit() { return makeData!(int)(0); }
|
||||
Type getBase() { return base; }
|
||||
|
||||
Scope getMemberScope()
|
||||
{
|
||||
return ArrayProperties.singleton;
|
||||
}
|
||||
|
||||
// We are a string (char[]) if the base type is char.
|
||||
bool isString()
|
||||
{
|
||||
return base.isChar();
|
||||
}
|
||||
|
||||
void parse(ref TokenArray toks)
|
||||
{ assert(0, "array types aren't parsed"); }
|
||||
|
||||
void resolve(Scope sc)
|
||||
{
|
||||
base.resolve(sc);
|
||||
}
|
||||
}
|
||||
|
||||
// This type is given to type-names that are used as variables
|
||||
// (ie. they are gramatically parsed by VariableExpr.) It's only used
|
||||
// for expressions like int.max and p == int. Only basic types are
|
||||
// supported. Classes are handled in a separate type.
|
||||
class MetaType : InternalType
|
||||
{
|
||||
private:
|
||||
static MetaType[char[]] store;
|
||||
|
||||
BasicType base;
|
||||
|
||||
public:
|
||||
|
||||
// Get a basic type of the given name. This will not allocate a new
|
||||
// instance if another instance already exists.
|
||||
static MetaType get(char[] tn)
|
||||
{
|
||||
if(tn in store) return store[tn];
|
||||
|
||||
return new MetaType(tn);
|
||||
}
|
||||
|
||||
this(char[] baseType)
|
||||
{
|
||||
base = BasicType.get(baseType);
|
||||
name = base.toString;
|
||||
loc = base.loc;
|
||||
|
||||
store[name] = this;
|
||||
}
|
||||
|
||||
// Return the scope belonging to the base type. This makes int.max
|
||||
// work just like i.max.
|
||||
Scope getMemberScope() { return base.getMemberScope(); }
|
||||
|
||||
Type getBase() { return base; }
|
||||
bool isMeta() { return true; }
|
||||
|
||||
bool canCastTo(Type type)
|
||||
{
|
||||
return false;// type.isString;
|
||||
}
|
||||
|
||||
void evalCastTo(Type to)
|
||||
{
|
||||
assert(to.isString);
|
||||
// TODO: Fix static strings soon
|
||||
assert(0, "not supported yet");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Types we might add later:
|
||||
|
||||
ClassType - on the form 'class MyClass', variable may refer to
|
||||
MyClass or to any subclass of MyClass.
|
||||
|
||||
ListType - lists on the form { a; b; c } or similar
|
||||
|
||||
AAType - associative array (hash map)
|
||||
|
||||
TableType - something similar to Lua tables
|
||||
|
||||
StructType - structs
|
||||
|
||||
EnumType - enums and flags
|
||||
|
||||
FunctionType - pointer to a function with a given header. Since all
|
||||
Monster functions are object members (methods), all
|
||||
function pointers are in effect delegates.
|
||||
|
||||
*/
|
@ -0,0 +1,311 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (variables.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.compiler.variables;
|
||||
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.expression;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.block;
|
||||
|
||||
import monster.minibos.string;
|
||||
import monster.minibos.stdio;
|
||||
import monster.vm.error;
|
||||
|
||||
enum VarType
|
||||
{
|
||||
Class,
|
||||
Param,
|
||||
Local,
|
||||
}
|
||||
|
||||
struct Variable
|
||||
{
|
||||
Type type;
|
||||
VarType vtype;
|
||||
Token name;
|
||||
|
||||
VarScope sc; // Scope that owns this variable
|
||||
|
||||
int number; // Index used in bytecode to reference this variable
|
||||
|
||||
bool isRef; // Is this a reference variable?
|
||||
bool isConst; // Used for function parameters
|
||||
bool isVararg; // A vararg function parameter
|
||||
}
|
||||
|
||||
// Variable declaration. Handles local and class variables, function
|
||||
// parameters and loop variables.
|
||||
class VarDeclaration : Block
|
||||
{
|
||||
Variable *var;
|
||||
|
||||
// Initializer expression, if any. Eg:
|
||||
// int i = 3; => init = 3.
|
||||
Expression init;
|
||||
|
||||
bool allowRef; // Allows reference variable.
|
||||
bool allowNoType; // Allow no type to be specified (used in foreach)
|
||||
bool allowConst; // Allows const.
|
||||
|
||||
this() {}
|
||||
|
||||
// Used when the type is already given, and we only need to read the
|
||||
// name and what follows. This is used for multiple declarations,
|
||||
// ie. int i=1, j=2; and will possibly also be used in other places
|
||||
// later.
|
||||
this(Type type)
|
||||
{
|
||||
assert(var is null);
|
||||
var = new Variable;
|
||||
|
||||
var.type = type;
|
||||
}
|
||||
|
||||
// Parse keywords allowed on variables
|
||||
private void parseKeywords(ref TokenArray toks)
|
||||
{
|
||||
Floc loc;
|
||||
while(1)
|
||||
{
|
||||
if(isNext(toks, TT.Ref, loc))
|
||||
{
|
||||
if(var.isRef)
|
||||
fail("Multiple token 'ref' in variable declaration",
|
||||
loc);
|
||||
if(!allowRef)
|
||||
fail("You cannot use 'ref' variables here", loc);
|
||||
var.isRef = true;
|
||||
continue;
|
||||
}
|
||||
if(isNext(toks, TT.Const, loc))
|
||||
{
|
||||
if(var.isConst)
|
||||
fail("Multiple token 'const' in variable declaration",
|
||||
loc);
|
||||
var.isConst = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a series of array specifiers, ie.
|
||||
// []
|
||||
// [][]...
|
||||
// [expr1][expr2]....
|
||||
// If takeExpr = false then the last form is not allowed
|
||||
static ExprArray getArray(ref TokenArray toks, bool takeExpr = false)
|
||||
{
|
||||
// Arrays?
|
||||
ExprArray arrayArgs;
|
||||
while(isNext(toks,TT.LeftSquare))
|
||||
{
|
||||
Expression expr = null;
|
||||
|
||||
// Is there an expression inside the brackets?
|
||||
if(!isNext(toks, TT.RightSquare))
|
||||
{
|
||||
Floc loc = getLoc(toks);
|
||||
|
||||
expr = Expression.identify(toks);
|
||||
|
||||
if(!takeExpr)
|
||||
fail("Array expression [" ~ expr.toString ~
|
||||
"] not allowed here", loc);
|
||||
|
||||
if(!isNext(toks, TT.RightSquare))
|
||||
fail("Expected matching ]", toks);
|
||||
}
|
||||
|
||||
// Insert the expression (or a null if the brackets were
|
||||
// empty)
|
||||
arrayArgs ~= expr;
|
||||
}
|
||||
return arrayArgs;
|
||||
}
|
||||
|
||||
// Get the total number of array dimensions. Eg. int[] j[][]; has a
|
||||
// total of three dimensions. This is now handled entirely by the
|
||||
// Type class so this function is here for backwards compatability.
|
||||
int arrays()
|
||||
{
|
||||
return var.type.arrays;
|
||||
}
|
||||
|
||||
// This is slightly messy. But what I'm trying to do IS slightly
|
||||
// messy.
|
||||
static bool hasType(TokenArray toks)
|
||||
{
|
||||
// Remove the type, if any
|
||||
if(!Type.canParseRem(toks)) return false;
|
||||
|
||||
// Skip any keywords
|
||||
while(1)
|
||||
{
|
||||
if(isNext(toks, TT.Ref)) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
// There must be a variable identifier at the end
|
||||
return isNext(toks, TT.Identifier);
|
||||
}
|
||||
|
||||
override void parse(ref TokenArray toks)
|
||||
{
|
||||
if(var is null)
|
||||
{
|
||||
var = new Variable;
|
||||
|
||||
// Keywords may come before or after the type
|
||||
parseKeywords(toks);
|
||||
|
||||
// Parse the type, if any.
|
||||
if(!allowNoType || hasType(toks))
|
||||
{
|
||||
var.type = Type.identify(toks);
|
||||
|
||||
parseKeywords(toks);
|
||||
}
|
||||
}
|
||||
// The type was already set externally.
|
||||
else
|
||||
{
|
||||
assert(var.type !is null);
|
||||
|
||||
// allowNoType is not used in these cases
|
||||
assert(allowNoType == false);
|
||||
}
|
||||
|
||||
if(!isNext(toks, TT.Identifier, var.name))
|
||||
fail("Variable name must be identifier", toks);
|
||||
|
||||
loc = var.name.loc;
|
||||
|
||||
// Look for arrays after the variable name.
|
||||
ExprArray arrayArgs = getArray(toks);
|
||||
|
||||
/* We must append these arrays to the type, in the reverse
|
||||
order. Eg.
|
||||
|
||||
int[1][2] i[4][3];
|
||||
|
||||
is the same as
|
||||
|
||||
int[1][2][3][4] i;
|
||||
|
||||
(also the same as int i[4][3][2][1];)
|
||||
|
||||
Since we don't take expressions here yet the order is
|
||||
irrelevant, but let's set it up right.
|
||||
*/
|
||||
foreach_reverse(e; arrayArgs)
|
||||
{
|
||||
assert(e is null);
|
||||
var.type = new ArrayType(var.type);
|
||||
}
|
||||
|
||||
// Does the variable have an initializer?
|
||||
if(isNext(toks, TT.Equals))
|
||||
init = Expression.identify(toks);
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
char[] res = var.type.toString() ~ " " ~ var.name.str;
|
||||
if(init !is null)
|
||||
res ~= " = " ~ init.toString;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Special version used for explicitly numbering function
|
||||
// parameters. Only called from FuncDeclaration.resolve()
|
||||
void resolve(Scope sc, int num)
|
||||
{
|
||||
assert(num<0, "VarDec.resolve was given a positive num: " ~ .toString(num));
|
||||
var.number = num;
|
||||
resolve(sc);
|
||||
}
|
||||
|
||||
// Calls resolve() for all sub-expressions
|
||||
override void resolve(Scope sc)
|
||||
{
|
||||
var.type.resolve(sc);
|
||||
|
||||
if(!allowConst && var.isConst)
|
||||
fail("'const' is not allowed here", loc);
|
||||
|
||||
// Store the scope in the var struct for later referral.
|
||||
var.sc = cast(VarScope)sc;
|
||||
assert(var.sc !is null, "variables can only be declared in VarScopes");
|
||||
|
||||
if(var.number == 0)
|
||||
{
|
||||
// If 'number' has not been set at this point (ie. we are
|
||||
// not a function parameter), we must get it from the scope.
|
||||
if(sc.isClass())
|
||||
// Class variable. Get a position in the data segment.
|
||||
var.number = sc.addNewDataVar(var.type.getSize());
|
||||
else
|
||||
// We're a local variable. Ask the scope what number we
|
||||
// should have.
|
||||
var.number = sc.addNewLocalVar(var.type.getSize());
|
||||
}
|
||||
else assert(sc.isFunc());
|
||||
|
||||
if(init !is null)
|
||||
{
|
||||
init.resolve(sc);
|
||||
|
||||
// Convert type, if necessary.
|
||||
try var.type.typeCast(init);
|
||||
catch(TypeException)
|
||||
fail(format("Cannot initialize %s of type %s with %s of type %s",
|
||||
var.name.str, var.type,
|
||||
init, init.type), loc);
|
||||
assert(init.type == var.type);
|
||||
}
|
||||
|
||||
// Insert ourselves into the scope.
|
||||
sc.insertVar(var);
|
||||
}
|
||||
|
||||
// Executed for local variables upon declaration. Push the variable
|
||||
// on the stack.
|
||||
void compile()
|
||||
{
|
||||
// Validate the type
|
||||
var.type.validate();
|
||||
|
||||
setLine();
|
||||
|
||||
if(init !is null)
|
||||
// Push the initializer
|
||||
init.eval();
|
||||
else
|
||||
// Default initializer
|
||||
var.type.pushInit();
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
for a in $(find -iname \*.d); do
|
||||
cat "$a" | sed s/monster.minibos./std./g > "$a"_new
|
||||
mv "$a"_new "$a"
|
||||
done
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (monster.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.monster;
|
||||
|
||||
public
|
||||
{
|
||||
// These should contain all you need for normal usage.
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.stack;
|
||||
import monster.vm.vm;
|
||||
import monster.vm.scheduler;
|
||||
import monster.vm.idlefunction;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.params;
|
||||
import monster.vm.error;
|
||||
}
|
||||
|
||||
private import monster.compiler.tokenizer;
|
||||
private import monster.compiler.properties;
|
||||
private import monster.compiler.scopes;
|
||||
|
||||
version(LittleEndian) {}
|
||||
else static assert(0, "This library does not yet support big endian systems.");
|
||||
|
||||
static this()
|
||||
{
|
||||
// Initialize compiler constructs
|
||||
initTokenizer();
|
||||
initProperties();
|
||||
initScope();
|
||||
|
||||
// Initialize VM
|
||||
scheduler.init();
|
||||
stack.init();
|
||||
arrays.initialize();
|
||||
}
|
@ -0,0 +1,363 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (arrays.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.vm.arrays;
|
||||
|
||||
import monster.vm.stack;
|
||||
import monster.util.freelist;
|
||||
import monster.util.flags;
|
||||
import monster.vm.error;
|
||||
|
||||
import monster.minibos.string;
|
||||
import monster.minibos.uni;
|
||||
import monster.minibos.stdio;
|
||||
|
||||
// An index to an array. Array indices may be 0, unlike object indices
|
||||
// which span from 1 and upwards, and has 0 as the illegal 'null'
|
||||
// reference. A null array will always refer to an empty array,
|
||||
// because we insert an empty array at the first slot in the index
|
||||
// list.
|
||||
typedef int AIndex;
|
||||
|
||||
// Not all of these are used yet.
|
||||
enum AFlags : int
|
||||
{
|
||||
None = 0x00,
|
||||
Alive = 0x01, // This reference is not deleted
|
||||
Const = 0x02, // Constant data
|
||||
CanCollect = 0x04, // Can be colleted by the GC
|
||||
RefCounted = 0x08, // Is reference counted
|
||||
Marked = 0x10, // Was marked in the last GC sweep
|
||||
Null = 0x20, // Is the null array
|
||||
}
|
||||
|
||||
struct ArrayRef
|
||||
{
|
||||
union
|
||||
{
|
||||
int[] iarr;
|
||||
float[] farr;
|
||||
dchar[] carr;
|
||||
AIndex[] aarr;
|
||||
}
|
||||
Flags!(AFlags) flags;
|
||||
|
||||
uint elemSize; // Size of each element (in ints)
|
||||
|
||||
AIndex getIndex()
|
||||
{
|
||||
return cast(AIndex)( Arrays.ArrayList.getIndex(this) );
|
||||
}
|
||||
|
||||
// Array length, in terms of its element size
|
||||
uint length()
|
||||
{
|
||||
if(isNull) return 0;
|
||||
assert(elemSize != 0, "elemSize not set");
|
||||
assert(iarr.length % elemSize == 0, "array length not divisible by element size");
|
||||
return iarr.length / elemSize;
|
||||
}
|
||||
|
||||
bool isAlive() { return flags.has(AFlags.Alive); }
|
||||
bool isConst() { return flags.has(AFlags.Const); }
|
||||
bool isNull() { return flags.has(AFlags.Null); }
|
||||
}
|
||||
|
||||
Arrays arrays;
|
||||
|
||||
struct Arrays
|
||||
{
|
||||
alias FreeList!(ArrayRef) ArrayList;
|
||||
|
||||
private:
|
||||
ArrayList arrList;
|
||||
|
||||
// Get a new array reference
|
||||
ArrayRef *createArray()
|
||||
{
|
||||
ArrayRef *ar = arrList.getNew();
|
||||
|
||||
assert(!ar.isAlive);
|
||||
|
||||
// Set the "alive" flag
|
||||
ar.flags.set(AFlags.Alive);
|
||||
|
||||
assert(!ar.isNull);
|
||||
|
||||
return ar;
|
||||
}
|
||||
|
||||
// Put a reference back into the freelist
|
||||
void destroyArray(ArrayRef *ar)
|
||||
{
|
||||
assert(ar.isAlive);
|
||||
assert(!ar.isNull);
|
||||
assert(!ar.isConst);
|
||||
ar.flags.unset(AFlags.Alive);
|
||||
arrList.remove(ar);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// Set up this struct
|
||||
void initialize()
|
||||
{
|
||||
// Make sure index zero is valid and is an empty array. Set
|
||||
// more flags later.
|
||||
auto ar = createArray();
|
||||
ar.iarr = null;
|
||||
ar.flags.set(AFlags.Null);
|
||||
|
||||
assert(ar.getIndex == 0);
|
||||
}
|
||||
|
||||
// Get the reference to the empty array
|
||||
ArrayRef *getZero()
|
||||
{
|
||||
return getRef(cast(AIndex)0);
|
||||
}
|
||||
|
||||
ArrayRef *createT(T)(T[] data)
|
||||
{
|
||||
static if(T.sizeof == 4) return create(cast(int[])data, 1);
|
||||
else static if(T.sizeof == 8) return create(cast(int[])data, 2);
|
||||
else static assert(0);
|
||||
}
|
||||
|
||||
alias createT!(int) create;
|
||||
alias createT!(uint) create;
|
||||
alias createT!(long) create;
|
||||
alias createT!(ulong) create;
|
||||
alias createT!(float) create;
|
||||
alias createT!(double) create;
|
||||
alias createT!(dchar) create;
|
||||
alias createT!(AIndex) create;
|
||||
|
||||
// Generic element size
|
||||
ArrayRef *create(int[] data, int size)
|
||||
{
|
||||
assert(size > 0);
|
||||
|
||||
if(data.length == 0) return getZero();
|
||||
|
||||
ArrayRef *ar = createArray();
|
||||
ar.iarr = data;
|
||||
ar.elemSize = size;
|
||||
|
||||
if(data.length % size != 0)
|
||||
fail("Array length not divisible by element size");
|
||||
|
||||
return ar;
|
||||
}
|
||||
|
||||
ArrayRef *createConst(int[] data, int elem)
|
||||
{
|
||||
ArrayRef *arf = create(data, elem);
|
||||
arf.flags.set(AFlags.Const);
|
||||
return arf;
|
||||
}
|
||||
|
||||
ArrayRef *getRef(AIndex index)
|
||||
{
|
||||
if(index < 0 || index >= getTotalArrays())
|
||||
fail("Invalid array reference: " ~ toString(cast(int)index));
|
||||
|
||||
ArrayRef *arr = ArrayList.getNode(index);
|
||||
|
||||
if(!arr.isAlive)
|
||||
fail("Dead array reference: " ~ toString(cast(int)index));
|
||||
|
||||
assert(arr.getIndex() == index);
|
||||
|
||||
if(index == 0) assert(arr.iarr.length == 0);
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
// Get the number of array references in use
|
||||
int getArrays()
|
||||
{
|
||||
return arrList.length();
|
||||
}
|
||||
|
||||
// Get the total number of Array references ever allocated for the
|
||||
// free list.
|
||||
int getTotalArrays()
|
||||
{
|
||||
return ArrayList.totLength();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a multi-dimensional array of rank 'rank' and innermost data
|
||||
// initialized to 'initval'. The array lengths are popped of the
|
||||
// script stack.
|
||||
void createMultiDimArray(int rank, int init[])
|
||||
{
|
||||
if(rank <= 0 || rank >= 30)
|
||||
fail("Invalid array nesting number " ~ toString(rank));
|
||||
|
||||
assert(init.length > 0);
|
||||
|
||||
int[30] lenbuf;
|
||||
int[] lens = lenbuf[0..rank];
|
||||
int[] data; // All the elements + overhead data
|
||||
ulong totElem = 1; // Total number of elements. Set to 1 and
|
||||
// multiplied with the length later.
|
||||
ulong totSize = 0; // Total size of data to allocate
|
||||
|
||||
int[] currSlice; // Current slice of the data, used by getNext.
|
||||
|
||||
// Get the next 'count' ints of data, in the form of a newly created
|
||||
// ArrayRef.
|
||||
ArrayRef *getNext(int count, int elemSize=1)
|
||||
{
|
||||
assert(count <= currSlice.length);
|
||||
|
||||
int[] res = currSlice[0..count];
|
||||
|
||||
currSlice = currSlice[count..$];
|
||||
|
||||
return arrays.create(res, elemSize);
|
||||
}
|
||||
|
||||
// Get the lengths, and calculate how much data we need. The first
|
||||
// length is the outermost wrapper, the last is the number of
|
||||
// actual elements in the innermost array wrapper.
|
||||
foreach(int i, ref int len; lens)
|
||||
{
|
||||
len = stack.popInt();
|
||||
|
||||
// Do some sanity check on the length. The upper bound set here
|
||||
// is pretty arbitrary, we might enlarge it later.
|
||||
if(len <= 0 || len > 0x100000)
|
||||
fail("Invalid array length " ~ toString(len));
|
||||
|
||||
// We could allow 0-length arrays here, but there's not much
|
||||
// point really.
|
||||
|
||||
// Calculate in the element size in the last element
|
||||
if(i == lens.length-1) len *= init.length;
|
||||
|
||||
// The total data is the cumulative value of totElem through all
|
||||
// iterations. For example, if we have a k*m*n array, we must
|
||||
// have k outer arrays, indexing a total of k*m subarrays,
|
||||
// indexing a total of k*m*n elements. The total data size,
|
||||
// assuming element sizes have been figured in, is
|
||||
// k + k*m + k*m*n.
|
||||
totElem *= len;
|
||||
totSize += totElem;
|
||||
}
|
||||
|
||||
// Allocate all the elements + overhead (data for the lookup arrays)
|
||||
if(totSize)
|
||||
{
|
||||
assert(totElem >= 0 && totElem <= totSize);
|
||||
|
||||
// Let's slap a 10 meg sanity check on the total data size
|
||||
if(totSize > 10*1024*1024)
|
||||
fail("Total array size is too large: " ~ toString(totSize));
|
||||
|
||||
data.length = totSize;
|
||||
|
||||
// Set currSlice to point to the entire data
|
||||
currSlice = data;
|
||||
}
|
||||
|
||||
// Set up inner arrays recursively. This can be optimized heavily
|
||||
// later (removing recursion, moving if-tests out of loops, avoiding
|
||||
// double initialization, and so on.)
|
||||
void setupArray(int lenIndex, ArrayRef *arr)
|
||||
{
|
||||
// Length of arrays at this level
|
||||
int len = lens[lenIndex];
|
||||
|
||||
// Loop through the previous level and create the arrays of this level
|
||||
foreach(ref AIndex ind; arr.aarr)
|
||||
{
|
||||
ArrayRef *narr;
|
||||
if(lenIndex == rank-1)
|
||||
// Remember to set the element size on the inner level
|
||||
narr = getNext(len, init.length);
|
||||
else
|
||||
narr = getNext(len);
|
||||
|
||||
// Store the index or this array in the previous level
|
||||
ind = narr.getIndex();
|
||||
|
||||
// Is this the innermost level?
|
||||
if(lenIndex == rank-1)
|
||||
{
|
||||
// If so, this is an array of elements. Initialize them.
|
||||
if(init.length == 1) narr.iarr[] = init[0];
|
||||
else if(init.length == 2) (cast(long[])narr.iarr)[] = *(cast(long*)init.ptr);
|
||||
else
|
||||
for(int i=0; i<lens[0]; i+=init.length)
|
||||
arr.iarr[i..i+init.length] = init[];
|
||||
}
|
||||
else
|
||||
// If not, set up the indices in this array
|
||||
setupArray(lenIndex+1, narr);
|
||||
}
|
||||
}
|
||||
|
||||
if(rank > 1)
|
||||
{
|
||||
// Create outer array and push it
|
||||
ArrayRef *arr = getNext(lens[0]);
|
||||
stack.pushArray(arr);
|
||||
|
||||
// Recursively set up the sub-arrays
|
||||
setupArray(1, arr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create outer array and push it. Element size has already been
|
||||
// multiplied into the length.
|
||||
ArrayRef *arr = getNext(lens[0], init.length);
|
||||
stack.pushArray(arr);
|
||||
|
||||
// There is only one array level, so this IS the inner
|
||||
// array. Initialize the elements. Optimize for element sizes 1
|
||||
// and 2
|
||||
if(init.length == 1) arr.iarr[] = init[0];
|
||||
else if(init.length == 2) (cast(long[])arr.iarr)[] = *(cast(long*)init.ptr);
|
||||
else
|
||||
for(int i=0; i<lens[0]; i+=init.length)
|
||||
arr.iarr[i..i+init.length] = init[];
|
||||
}
|
||||
|
||||
// Make sure we used all the data!
|
||||
assert(currSlice.length == 0);
|
||||
}
|
||||
|
||||
// There's no phobos function that does unicode case insensitive
|
||||
// string comparison, so let's make one ourselves. This can probably
|
||||
// be optimized. toUniLower is in prinicple an expensive operation,
|
||||
// but not so much if we assume most characters are ascii.
|
||||
bool isUniCaseEqual(dchar[] a, dchar[] b)
|
||||
{
|
||||
if(a.length != b.length) return false;
|
||||
foreach(int i, dchar ch; a)
|
||||
if(ch != b[i] && toUniLower(ch) != toUniLower(b[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (codestream.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.vm.codestream;
|
||||
|
||||
import monster.minibos.string;
|
||||
import monster.minibos.stdio;
|
||||
import monster.vm.error;
|
||||
import monster.compiler.linespec;
|
||||
|
||||
// CodeStream is a simple utility structure for reading data
|
||||
// sequentially. It holds a piece of byte compiled code, and keeps
|
||||
// track of the position within the code.
|
||||
struct CodeStream
|
||||
{
|
||||
private:
|
||||
ubyte[] data;
|
||||
int len;
|
||||
ubyte *pos;
|
||||
|
||||
// Position of the last instruction
|
||||
ubyte *cmdPos;
|
||||
|
||||
// Used to convert position to the corresponding source code line,
|
||||
// for error messages.
|
||||
LineSpec[] lines;
|
||||
|
||||
// Size of debug output
|
||||
const int preView = 50;
|
||||
const int perLine = 16;
|
||||
|
||||
public:
|
||||
|
||||
void setData(ubyte[] data,
|
||||
LineSpec[] lines)
|
||||
{
|
||||
this.data = data;
|
||||
this.lines = lines;
|
||||
len = data.length;
|
||||
pos = data.ptr;
|
||||
}
|
||||
|
||||
// Called when the end of the stream was unexpectedly encountered
|
||||
void eos(char[] func)
|
||||
{
|
||||
char[] res = format("Premature end of input:\nCodeStream.%s() missing %s byte(s)\n",
|
||||
func, -len);
|
||||
|
||||
res ~= debugString();
|
||||
|
||||
fail(res);
|
||||
}
|
||||
|
||||
char[] debugString()
|
||||
{
|
||||
int start = data.length - preView;
|
||||
if(start < 0) start = 0;
|
||||
|
||||
char[] res = format("\nLast %s bytes of byte code:\n", data.length-start);
|
||||
foreach(int i, ubyte val; data[start..$])
|
||||
{
|
||||
if(i%perLine == 0)
|
||||
res ~= format("\n 0x%-4x: ", i+start);
|
||||
res ~= format("%-4x", val);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void debugPrint()
|
||||
{
|
||||
writefln(debugString());
|
||||
}
|
||||
|
||||
// Jump to given position
|
||||
void jump(int newPos)
|
||||
{
|
||||
if(newPos<0 || newPos>=data.length)
|
||||
fail("Jump out of range");
|
||||
len = data.length - newPos;
|
||||
pos = &data[newPos];
|
||||
}
|
||||
|
||||
// Get the current position
|
||||
int getPos()
|
||||
{
|
||||
return pos-data.ptr;
|
||||
}
|
||||
|
||||
// Get the current line
|
||||
int getLine()
|
||||
{
|
||||
// call shared.linespec.findLine
|
||||
return findLine(lines, cmdPos-data.ptr);
|
||||
}
|
||||
|
||||
ubyte get()
|
||||
{
|
||||
if(len--) return *(pos++);
|
||||
eos("get");
|
||||
}
|
||||
|
||||
// Used for getting an instruction. It stores the offset which can
|
||||
// be used to infer the line number later.
|
||||
ubyte getCmd()
|
||||
{
|
||||
cmdPos = pos;
|
||||
return get();
|
||||
}
|
||||
|
||||
int getInt()
|
||||
{
|
||||
len -= 4;
|
||||
if(len < 0) eos("getInt");
|
||||
int i = *(cast(int*)pos);
|
||||
pos+=4;
|
||||
return i;
|
||||
}
|
||||
|
||||
// Get a slice of the 'size' next ints
|
||||
int[] getIntArray(uint size)
|
||||
{
|
||||
size *=4; // Convert size to bytes
|
||||
len -= size;
|
||||
if(len < 0) eos("getArray");
|
||||
int[] res = cast(int[])pos[0..size];
|
||||
pos += size;
|
||||
return res;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (error.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.vm.error;
|
||||
|
||||
import monster.compiler.tokenizer;
|
||||
version(Tango) import tango.core.Exception;
|
||||
import monster.minibos.string;
|
||||
|
||||
class MonsterException : Exception
|
||||
{
|
||||
this(char[] msg) { super(/*"MonsterException: " ~*/ msg); }
|
||||
}
|
||||
|
||||
// Source file location
|
||||
struct Floc
|
||||
{
|
||||
int line = -1;
|
||||
char[] fname;
|
||||
|
||||
char[] toString() { return format("%s:%s", fname, line); }
|
||||
}
|
||||
|
||||
void fail(char[] msg, Floc loc)
|
||||
{
|
||||
fail(msg, loc.fname, loc.line);
|
||||
}
|
||||
|
||||
void fail(char[] msg, char[] fname, int line)
|
||||
{
|
||||
if(line != -1)
|
||||
fail(format("%s:%s: %s", fname, line, msg));
|
||||
else
|
||||
fail(msg);
|
||||
}
|
||||
|
||||
void fail(char[] msg)
|
||||
{
|
||||
throw new MonsterException(msg);
|
||||
}
|
||||
|
||||
void fail(char[] msg, TokenArray toks)
|
||||
{
|
||||
if(toks.length)
|
||||
fail(msg ~ ", found " ~ toks[0].str, toks[0].loc);
|
||||
else
|
||||
fail(msg ~ ", found end of file");
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (fstack.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.vm.fstack;
|
||||
|
||||
import monster.vm.codestream;
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.stack;
|
||||
import monster.vm.error;
|
||||
import monster.compiler.states;
|
||||
import monster.compiler.functions;
|
||||
|
||||
// "friendly" parameter and stack handling.
|
||||
enum SPType
|
||||
{
|
||||
Function, // A function (script or native)
|
||||
State, // State code
|
||||
NConst, // Native constructor
|
||||
|
||||
// The idle function callbacks are split because they handle the
|
||||
// stack differently.
|
||||
Idle_Initiate, // IdleFunction.initiate()
|
||||
Idle_Reentry, // IdleFunction.reentry()
|
||||
Idle_Abort, // IdleFunction.abort()
|
||||
Idle_Check // IdleFunction.hasFinished()
|
||||
}
|
||||
|
||||
// One entry in the function stack
|
||||
struct StackPoint
|
||||
{
|
||||
CodeStream code; // The byte code handler
|
||||
|
||||
union
|
||||
{
|
||||
Function *func; // What function we are in (if any)
|
||||
State *state; // What state the function belongs to (if any)
|
||||
}
|
||||
|
||||
SPType ftype;
|
||||
|
||||
MonsterObject *obj; // "this"-pointer for the function
|
||||
|
||||
int afterStack; // Where the stack should be when this function
|
||||
// returns
|
||||
int *frame; // Stack frame, stored when entering the function
|
||||
}
|
||||
|
||||
FunctionStack fstack;
|
||||
|
||||
// 30 is somewhat small, but suitable for debugging.
|
||||
StackPoint fslist[30];
|
||||
|
||||
struct FunctionStack
|
||||
{
|
||||
// The current entry
|
||||
StackPoint *cur = null;
|
||||
|
||||
// Index of next entry
|
||||
int next = 0;
|
||||
|
||||
// Consistancy checks
|
||||
invariant()
|
||||
{
|
||||
assert(next >= 0);
|
||||
if(next > 0)
|
||||
{
|
||||
assert(cur !is null);
|
||||
if(cur.ftype == SPType.State)
|
||||
assert(next == 1);
|
||||
}
|
||||
else assert(cur is null);
|
||||
}
|
||||
|
||||
// Is the function stack empty?
|
||||
bool isEmpty() { return next == 0; }
|
||||
|
||||
// Are we currently running state code?
|
||||
bool isStateCode() { return next == 1 && cur.ftype == SPType.State; }
|
||||
|
||||
// Sets up the next stack point and assigns the given object
|
||||
private void push(MonsterObject *obj)
|
||||
{
|
||||
if(next >= fslist.length)
|
||||
fail("Function stack overflow - infinite recursion?");
|
||||
|
||||
cur = &fslist[next++];
|
||||
cur.obj = obj;
|
||||
|
||||
cur.frame = stack.setFrame();
|
||||
}
|
||||
|
||||
// Set the stack point up as a function
|
||||
void push(Function *func, MonsterObject *obj)
|
||||
{
|
||||
push(obj);
|
||||
cur.ftype = SPType.Function;
|
||||
cur.func = func;
|
||||
|
||||
// Point the code stream to the byte code, if any.
|
||||
if(func.isNormal)
|
||||
cur.code.setData(func.bcode, func.lines);
|
||||
|
||||
assert(!func.isIdle, "don't use fstack.push() on idle functions");
|
||||
}
|
||||
|
||||
// Set the stack point up as a state
|
||||
void push(State *st, MonsterObject *obj)
|
||||
{
|
||||
push(obj);
|
||||
cur.ftype = SPType.State;
|
||||
cur.state = st;
|
||||
|
||||
// Set up the byte code
|
||||
cur.code.setData(st.bcode, st.lines);
|
||||
}
|
||||
|
||||
// Native constructor
|
||||
void pushNConst(MonsterObject *obj)
|
||||
{
|
||||
push(obj);
|
||||
cur.ftype = SPType.NConst;
|
||||
}
|
||||
|
||||
private void pushIdleCommon(Function *fn, MonsterObject *obj, SPType tp)
|
||||
{
|
||||
push(obj);
|
||||
cur.func = fn;
|
||||
assert(fn.isIdle, fn.name.str ~ "() is not an idle function");
|
||||
cur.ftype = tp;
|
||||
}
|
||||
|
||||
// These are used for the various idle callbacks
|
||||
void pushIdleInit(Function *fn, MonsterObject *obj)
|
||||
{ pushIdleCommon(fn, obj, SPType.Idle_Initiate); }
|
||||
|
||||
void pushIdleReentry(Function *fn, MonsterObject *obj)
|
||||
{ pushIdleCommon(fn, obj, SPType.Idle_Reentry); }
|
||||
|
||||
void pushIdleAbort(Function *fn, MonsterObject *obj)
|
||||
{ pushIdleCommon(fn, obj, SPType.Idle_Abort); }
|
||||
|
||||
void pushIdleCheck(Function *fn, MonsterObject *obj)
|
||||
{ pushIdleCommon(fn, obj, SPType.Idle_Check); }
|
||||
|
||||
// Pops one entry of the stack. Checks that the stack level has been
|
||||
// returned to the correct position.
|
||||
void pop()
|
||||
{
|
||||
if(next == 0)
|
||||
fail("Function stack underflow");
|
||||
|
||||
stack.setFrame(cur.frame);
|
||||
|
||||
if(--next > 0) cur--;
|
||||
else cur = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (idlefunction.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.vm.idlefunction;
|
||||
|
||||
import monster.vm.mobject;
|
||||
|
||||
// A callback class for idle functions. A child object of this class
|
||||
// is what you "bind" to idle functions (rather than just a delegate,
|
||||
// like for native functions.) Note that instances are not bound to
|
||||
// specific script objects; one idle function instance may be called
|
||||
// for many objects simultaneously. Any data specific to the monster
|
||||
// object (such as parameters) must be stored elsewhere, usually
|
||||
// through the 'extra' pointer in MonsterObject.
|
||||
abstract class IdleFunction
|
||||
{
|
||||
// This is called immediately after the idle function is "called"
|
||||
// from M script. It has to handle function parameters (remove them
|
||||
// from the stack), but otherwise does not have to do
|
||||
// anything. Return true if the scheduler should put this idle
|
||||
// function into the condition list, which is usually a good
|
||||
// idea. For functions which never "return", or event driven idle
|
||||
// functions (which handle their own scheduling), we should return
|
||||
// false.
|
||||
bool initiate(MonsterObject*) { return true; }
|
||||
|
||||
// This is called whenever the idle function is about to "return" to
|
||||
// state code. It has to push the return value, if any, but
|
||||
// otherwise it can be empty. Note that if the idle function is
|
||||
// aborted (eg. state is changed), this function is never called,
|
||||
// and abort() is called instead.
|
||||
void reentry(MonsterObject*) {}
|
||||
|
||||
// Called whenever an idle function is aborted, for example by a
|
||||
// state change. No action is usually required.
|
||||
void abort(MonsterObject*) {}
|
||||
|
||||
// The condition that determines if this function has finished. This
|
||||
// is the main method by which the scheduler determines when to
|
||||
// reenter M state code. For example, for an idle function
|
||||
// waitSoundFinish(), this would return false if the sound is still
|
||||
// playing, and true if the sound has finished. If you want a purely
|
||||
// event-driven idle function (rather than polling each frame), you
|
||||
// should return false in initiate and instead reschedule the object
|
||||
// manually when the event occurs. (A nice interface for this has
|
||||
// not been created yet, though.)
|
||||
abstract bool hasFinished(MonsterObject*);
|
||||
}
|
@ -0,0 +1,279 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (iterators.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.vm.iterators;
|
||||
|
||||
import monster.util.freelist;
|
||||
import monster.vm.error;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.mobject;
|
||||
import monster.util.flags;
|
||||
|
||||
import monster.minibos.string;
|
||||
import monster.minibos.stdio;
|
||||
|
||||
// An iterator index.
|
||||
typedef int IIndex;
|
||||
|
||||
// Flags for iterator structs
|
||||
enum IFlags
|
||||
{
|
||||
None = 0x00,
|
||||
Alive = 0x01, // This reference is not deleted
|
||||
}
|
||||
|
||||
struct IteratorRef
|
||||
{
|
||||
Flags!(IFlags) flags;
|
||||
|
||||
ArrayRef *array;
|
||||
int index; // TODO: Might not be necessary to keep a local copy of this
|
||||
int indexMul; // Index multiplied with element size
|
||||
int elemSize;
|
||||
int *sindex; // Index on the stack
|
||||
int[] sval; // Value on the stack
|
||||
|
||||
bool isReverse, isRef;
|
||||
bool isClass;
|
||||
|
||||
MonsterObject *mo;
|
||||
|
||||
// Array iterators
|
||||
bool firstArray(bool irev, bool iref, int *stk)
|
||||
{
|
||||
isRef = iref;
|
||||
isReverse = irev;
|
||||
isClass = false;
|
||||
|
||||
// Replace the array index on the stack
|
||||
AIndex ai = cast(AIndex)*stk;
|
||||
*stk = cast(int) getIndex();
|
||||
|
||||
// Fetch the array
|
||||
array = arrays.getRef(ai);
|
||||
|
||||
// Cannot use reference values on const arrays
|
||||
if(array.isConst && isRef)
|
||||
// TODO: Try to give line and file in all messages
|
||||
fail("Cannot use 'ref' values with constant arrays");
|
||||
|
||||
// Skip the loop if it's empty
|
||||
if(array.iarr.length == 0) return false;
|
||||
|
||||
assert(array.elemSize > 0);
|
||||
elemSize = array.elemSize;
|
||||
|
||||
// Point to the stack index and value
|
||||
stk -= elemSize;
|
||||
sval = stk[0..elemSize];
|
||||
stk--;
|
||||
sindex = stk;
|
||||
|
||||
// Set up the first element
|
||||
if(isReverse) index = array.length-1;
|
||||
else index = 0;
|
||||
|
||||
indexMul = index * elemSize;
|
||||
|
||||
*sindex = index;
|
||||
sval[] = array.iarr[indexMul..indexMul+elemSize];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Class iterators
|
||||
bool firstClass(MonsterClass mc, int[] stk)
|
||||
{
|
||||
assert(stk.length == 2);
|
||||
isClass = true;
|
||||
|
||||
// Set the iterator index on the stack
|
||||
stk[1] = cast(int) getIndex();
|
||||
|
||||
mo = mc.getFirst();
|
||||
|
||||
// Are there any objects?
|
||||
if(mo == null) return false;
|
||||
|
||||
sindex = &stk[0];
|
||||
*sindex = cast(int)mo.getIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
void storeRef()
|
||||
{
|
||||
assert(!isClass);
|
||||
|
||||
if(isRef)
|
||||
array.iarr[indexMul..indexMul+elemSize] = sval[];
|
||||
else
|
||||
fail("Array iterator update called on non-ref parameter");
|
||||
}
|
||||
|
||||
bool next()
|
||||
{
|
||||
// Handle class iterations seperately
|
||||
if(isClass)
|
||||
{
|
||||
mo = mo.getNext();
|
||||
if(mo == null) return false;
|
||||
|
||||
*sindex = cast(int)mo.getIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isReverse)
|
||||
{
|
||||
index--;
|
||||
indexMul -= elemSize;
|
||||
if(index == -1) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
indexMul += elemSize;
|
||||
if(index*elemSize == array.iarr.length) return false;
|
||||
}
|
||||
|
||||
assert(indexMul < array.iarr.length);
|
||||
assert(index >= 0 && index < array.length);
|
||||
assert(indexMul == index*elemSize);
|
||||
|
||||
*sindex = index;
|
||||
sval[] = array.iarr[indexMul..indexMul+elemSize];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IIndex getIndex()
|
||||
{
|
||||
return cast(IIndex)( Iterators.IterList.getIndex(this) );
|
||||
}
|
||||
|
||||
bool isAlive() { return flags.has(IFlags.Alive); }
|
||||
}
|
||||
|
||||
Iterators iterators;
|
||||
|
||||
struct Iterators
|
||||
{
|
||||
alias FreeList!(IteratorRef) IterList;
|
||||
|
||||
private:
|
||||
IterList iterList;
|
||||
|
||||
// Get a new iterator reference
|
||||
IteratorRef *createIterator()
|
||||
{
|
||||
IteratorRef *it = iterList.getNew();
|
||||
|
||||
assert(!it.isAlive);
|
||||
|
||||
// Set the "alive" flag
|
||||
it.flags.set(IFlags.Alive);
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
// Put a reference back into the freelist
|
||||
void destroyIterator(IteratorRef *it)
|
||||
{
|
||||
assert(it.isAlive);
|
||||
it.flags.unset(IFlags.Alive);
|
||||
iterList.remove(it);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool firstArray(bool irev, bool iref, int *stk)
|
||||
{
|
||||
IteratorRef *it = createIterator();
|
||||
bool res = it.firstArray(irev,iref,stk);
|
||||
|
||||
// Kill the iterator reference if we are done iterating
|
||||
if(!res) destroyIterator(it);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool firstClass(MonsterClass mc, int[] stk)
|
||||
{
|
||||
IteratorRef *it = createIterator();
|
||||
bool res = it.firstClass(mc,stk);
|
||||
|
||||
// Kill the iterator reference if we are done iterating
|
||||
if(!res) destroyIterator(it);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool next(IIndex ind)
|
||||
{
|
||||
IteratorRef *it = getRef(ind);
|
||||
bool res = it.next();
|
||||
|
||||
// Kill the iterator reference if this was the last iteration
|
||||
if(!res) destroyIterator(it);
|
||||
return res;
|
||||
}
|
||||
|
||||
void stop(IIndex ind)
|
||||
{
|
||||
IteratorRef *it = getRef(ind);
|
||||
destroyIterator(it);
|
||||
}
|
||||
|
||||
void update(IIndex ind)
|
||||
{
|
||||
IteratorRef *it = getRef(ind);
|
||||
it.storeRef();
|
||||
}
|
||||
|
||||
IteratorRef *getRef(IIndex index)
|
||||
{
|
||||
if(index < 0 || index >= getTotalIterators())
|
||||
fail("Invalid iterator reference: " ~ toString(cast(int)index));
|
||||
|
||||
IteratorRef *itr = IterList.getNode(index);
|
||||
|
||||
if(!itr.isAlive)
|
||||
fail("Dead iterator reference: " ~ toString(cast(int)index));
|
||||
|
||||
assert(itr.getIndex() == index);
|
||||
|
||||
return itr;
|
||||
}
|
||||
|
||||
// Get the number of iterator references in use
|
||||
int getIterators()
|
||||
{
|
||||
return iterList.length();
|
||||
}
|
||||
|
||||
// Get the total number of Iterator references ever allocated for the
|
||||
// free list.
|
||||
int getTotalIterators()
|
||||
{
|
||||
return IterList.totLength();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,376 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (mobject.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.vm.mobject;
|
||||
|
||||
import monster.vm.vm;
|
||||
import monster.vm.error;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.arrays;
|
||||
|
||||
import monster.compiler.states;
|
||||
import monster.compiler.variables;
|
||||
import monster.compiler.scopes;
|
||||
|
||||
import monster.minibos.string;
|
||||
import monster.minibos.stdio;
|
||||
import monster.minibos.utf;
|
||||
|
||||
// An index to a monster object.
|
||||
typedef int MIndex;
|
||||
|
||||
struct MonsterObject
|
||||
{
|
||||
/*******************************************************
|
||||
* *
|
||||
* Public variables *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
MonsterClass cls;
|
||||
|
||||
// Extra data. This allows you to assign additional data to an
|
||||
// object. We might refine this concept a little later.
|
||||
void *extra;
|
||||
|
||||
// The thread. Each object has its own thread, but not every
|
||||
// MonsterObject has its own unique thread. For derived classes, we
|
||||
// allocate a MonsterObject for each parent class, but only one
|
||||
// thread for the object entire object.
|
||||
CodeThread *thread;
|
||||
|
||||
// Object data segment
|
||||
int[] data;
|
||||
|
||||
// Static data segment. Do not write to this.
|
||||
int[] sdata;
|
||||
|
||||
// Parent object tree. This reflects the equivalent 'tree' table in
|
||||
// the MonsterClass.
|
||||
MonsterObject* tree[];
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Functions for object handling *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Get the next object in the objects list - used to iterate through
|
||||
// objects of one class
|
||||
MonsterObject *getNext()
|
||||
{
|
||||
// TODO: This syntax is rather hackish, and bug-prone if we
|
||||
// suddenly change the list structure.
|
||||
return cast(MonsterObject*)
|
||||
( cast(MonsterClass.ObjectList.TList.Iterator)this ).getNext();
|
||||
}
|
||||
|
||||
// Get the index of this object
|
||||
MIndex getIndex()
|
||||
{
|
||||
return cast(MIndex)( MonsterClass.ObjectList.getIndex(this)+1 );
|
||||
}
|
||||
|
||||
// Delete this object. Do not use the object after calling this
|
||||
// function.
|
||||
void deleteSelf()
|
||||
{
|
||||
cls.deleteObject(this);
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Casting / polymorphism functions *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Cast this object to the given class, if possible. Both upcasts
|
||||
// and downcasts are allowed.
|
||||
MonsterObject *Cast(MonsterClass toClass)
|
||||
{ return doCast(toClass, thread.topObj.tree); }
|
||||
|
||||
// Upcast this object to the given class. Upcasting means that
|
||||
// toClass must be the class of this object, or one of its parent
|
||||
// classes.
|
||||
MonsterObject *upcast(MonsterClass toClass)
|
||||
{
|
||||
assert(toClass !is null);
|
||||
return doCast(toClass, tree);
|
||||
}
|
||||
// Special version used from bytecode. The index is the global class
|
||||
// index.
|
||||
MonsterObject *upcastIndex(int index)
|
||||
{
|
||||
// Convert the global class index to the tree index. TODO: Later
|
||||
// on we should pass this index directly, but that is just
|
||||
// optimization.
|
||||
index = global.getClass(cast(CIndex)index).treeIndex;
|
||||
|
||||
assert(index < tree.length, "cannot upcast class " ~ cls.getName ~
|
||||
" to index " ~ format(index));
|
||||
return tree[index];
|
||||
}
|
||||
|
||||
// Is this object part of a linked inheritance chain?
|
||||
bool isBaseObject() {return !isTopObject(); }
|
||||
|
||||
// Is this object the topmost object in the inheritance chain?
|
||||
bool isTopObject() { return thread.topObj is this; }
|
||||
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Member variable getters / setters *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Template versions first
|
||||
T* getPtr(T)(char[] name)
|
||||
{
|
||||
// Find the variable
|
||||
Variable *vb = cls.findVariable(name);
|
||||
assert(vb !is null);
|
||||
|
||||
// Check the type
|
||||
if(!vb.type.isDType(typeid(T)))
|
||||
{
|
||||
char[] request;
|
||||
static if(is(T == dchar)) request = "char"; else
|
||||
static if(is(T == AIndex)) request = "array"; else
|
||||
static if(is(T == MIndex)) request = "object"; else
|
||||
request = typeid(T).toString();
|
||||
|
||||
fail(format("Requested variable %s is not the right type (wanted %s, found %s)",
|
||||
name, request, vb.type.toString()));
|
||||
}
|
||||
|
||||
// Cast the object to the right kind
|
||||
assert(vb.sc.isClass(), "variable must be a class variable");
|
||||
MonsterClass mc = vb.sc.getClass();
|
||||
assert(mc !is null);
|
||||
MonsterObject *obj = upcast(mc);
|
||||
|
||||
// Return the pointer
|
||||
return cast(T*) obj.getDataInt(vb.number);
|
||||
}
|
||||
T getType(T)(char[] name)
|
||||
{ return *getPtr!(T)(name); }
|
||||
void setType(T)(char[] name, T t)
|
||||
{ *getPtr!(T)(name) = t; }
|
||||
|
||||
alias getPtr!(int) getIntPtr;
|
||||
alias getPtr!(uint) getUintPtr;
|
||||
alias getPtr!(long) getLongPtr;
|
||||
alias getPtr!(ulong) getUlongPtr;
|
||||
alias getPtr!(bool) getBoolPtr;
|
||||
alias getPtr!(float) getFloatPtr;
|
||||
alias getPtr!(double) getDoublePtr;
|
||||
alias getPtr!(dchar) getCharPtr;
|
||||
alias getPtr!(AIndex) getAIndexPtr;
|
||||
alias getPtr!(MIndex) getMIndexPtr;
|
||||
|
||||
alias getType!(int) getInt;
|
||||
alias getType!(uint) getUint;
|
||||
alias getType!(long) getLong;
|
||||
alias getType!(ulong) getUlong;
|
||||
alias getType!(bool) getBool;
|
||||
alias getType!(float) getFloat;
|
||||
alias getType!(double) getDouble;
|
||||
alias getType!(dchar) getChar;
|
||||
alias getType!(AIndex) getAIndex;
|
||||
alias getType!(MIndex) getMIndex;
|
||||
|
||||
alias setType!(int) setInt;
|
||||
alias setType!(uint) setUint;
|
||||
alias setType!(long) setLong;
|
||||
alias setType!(ulong) setUlong;
|
||||
alias setType!(bool) setBool;
|
||||
alias setType!(float) setFloat;
|
||||
alias setType!(double) setDouble;
|
||||
alias setType!(dchar) setChar;
|
||||
alias setType!(AIndex) setAIndex;
|
||||
alias setType!(MIndex) setMIndex;
|
||||
|
||||
MonsterObject *getObject(char[] name)
|
||||
{ return getMObject(getMIndex(name)); }
|
||||
void setObject(char[] name, MonsterObject *obj)
|
||||
{ setMIndex(name, obj.getIndex()); }
|
||||
|
||||
// Array stuff
|
||||
ArrayRef* getArray(char[] name)
|
||||
{ return arrays.getRef(getAIndex(name)); }
|
||||
void setArray(char[] name, ArrayRef *r)
|
||||
{ setAIndex(name,r.getIndex()); }
|
||||
|
||||
char[] getString8(char[] name)
|
||||
{ return toUTF8(getArray(name).carr); }
|
||||
void setString8(char[] name, char[] str)
|
||||
{ setArray(name, arrays.create(toUTF32(str))); }
|
||||
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Lower level member data functions *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Get an int from the data segment
|
||||
int *getDataInt(int pos)
|
||||
{
|
||||
if(pos < 0 || pos>=data.length)
|
||||
fail("MonsterObject: data pointer out of range: " ~ toString(pos));
|
||||
return &data[pos];
|
||||
}
|
||||
|
||||
// Get a long (two ints) from the data segment
|
||||
long *getDataLong(int pos)
|
||||
{
|
||||
if(pos < 0 || pos+1>=data.length)
|
||||
fail("MonsterObject: data pointer out of range: " ~ toString(pos));
|
||||
return cast(long*)&data[pos];
|
||||
}
|
||||
|
||||
// Get an array from the data segment
|
||||
int[] getDataArray(int pos, int len)
|
||||
{
|
||||
if(pos < 0 || len < 0 || (pos+len) > data.length)
|
||||
fail("MonsterObject: data array out of range: pos=" ~ toString(pos) ~
|
||||
", len=" ~toString(len));
|
||||
return data[pos..pos+len];
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Calling functions and setting states *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Call a named function. The function is executed immediately, and
|
||||
// call() returns when the function is finished. The function is
|
||||
// called virtually, so any sub-class function that overrides it in
|
||||
// this object will take precedence.
|
||||
void call(char[] name)
|
||||
{
|
||||
cls.findFunction(name).call(this);
|
||||
}
|
||||
|
||||
// Call a function non-virtually. In other words, ignore
|
||||
// derived objects.
|
||||
void nvcall(char[] name)
|
||||
{
|
||||
assert(0, "not implemented");
|
||||
}
|
||||
|
||||
// Set the current state of the object. If called from within state
|
||||
// code, we have to return all the way back to the state code level
|
||||
// before the new state is scheduled. If called when the object is
|
||||
// idle (not actively running state code), the state is scheduled
|
||||
// now, and the idle function is aborted. New state code does not
|
||||
// start running until the next frame.
|
||||
void setState(State *st, StateLabel *lb = null)
|
||||
{
|
||||
assert(st !is null || lb is null,
|
||||
"If state is null, label must also be null");
|
||||
thread.setState(st, lb);
|
||||
}
|
||||
|
||||
// Named version of the above function. An empty string sets the
|
||||
// state to -1 (the empty state.) If no label is given (or given as
|
||||
// ""), this is equivalent to the script command state=name; If a
|
||||
// label is given, it is equivalent to state = name.label;
|
||||
void setState(char[] name, char[] label = "")
|
||||
{
|
||||
if(label == "")
|
||||
{
|
||||
if(name == "") thread.setState(null,null);
|
||||
else setState(cls.findState(name));
|
||||
return;
|
||||
}
|
||||
|
||||
assert(name != "", "The empty state cannot contain the label " ~ label);
|
||||
|
||||
auto stl = cls.findState(name, label);
|
||||
setState(stl.state, stl.label);
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Private functions *
|
||||
* *
|
||||
*******************************************************/
|
||||
private:
|
||||
|
||||
MonsterObject *doCast(MonsterClass toClass, MonsterObject* ptree[])
|
||||
{
|
||||
assert(toClass !is null);
|
||||
|
||||
if(toClass is cls) return this;
|
||||
|
||||
// TODO: At some point, a class will have several possible tree
|
||||
// indices. We will loop through the list and try them all.
|
||||
int index = toClass.treeIndex;
|
||||
MonsterObject *mo = null;
|
||||
|
||||
if(index < ptree.length)
|
||||
mo = ptree[index];
|
||||
|
||||
assert(mo !is this);
|
||||
|
||||
// It's only a match if the classes match
|
||||
if(mo.cls !is toClass) mo = null;
|
||||
|
||||
// If no match was found, then the cast failed.
|
||||
if(mo is null)
|
||||
fail("object of class " ~ cls.name.str ~
|
||||
" cannot be cast to " ~ toClass.name.str);
|
||||
|
||||
return mo;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert an index to an object pointer
|
||||
MonsterObject *getMObject(MIndex index)
|
||||
{
|
||||
if(index == 0)
|
||||
fail("Null object reference encountered");
|
||||
|
||||
if(index < 0 || index > getTotalObjects())
|
||||
fail("Invalid object reference");
|
||||
|
||||
MonsterObject *obj = MonsterClass.ObjectList.getNode(index-1);
|
||||
|
||||
if(obj.thread == null)
|
||||
fail("Dead object reference (index " ~ toString(cast(int)index) ~ ")");
|
||||
|
||||
assert(obj.getIndex() == index);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Get the total number of MonsterObjects ever allocated for the free
|
||||
// list. Does NOT correspond to the number of objects in use.
|
||||
int getTotalObjects()
|
||||
{
|
||||
return MonsterClass.ObjectList.totLength();
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (params.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.vm.params;
|
||||
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.fstack;
|
||||
|
||||
/* This module offers a "friendly" interface for dealing with
|
||||
parameters and return values on the stack. It is meant to be an
|
||||
alternative to manipulating the stack directly when writing native
|
||||
functions.
|
||||
|
||||
NOT FINISHED!
|
||||
*/
|
||||
|
||||
Params params;
|
||||
|
||||
struct Params
|
||||
{
|
||||
static:
|
||||
|
||||
// Get the current object (the 'this' reference for the current
|
||||
// function)
|
||||
MonsterObject *obj()
|
||||
{
|
||||
assert(fstack.cur !is null);
|
||||
assert(fstack.cur.obj !is null);
|
||||
return fstack.cur.obj;
|
||||
}
|
||||
}
|
@ -0,0 +1,332 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (scheduler.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.vm.scheduler;
|
||||
|
||||
import monster.compiler.functions;
|
||||
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.idlefunction;
|
||||
import monster.vm.error;
|
||||
import monster.vm.fstack;
|
||||
|
||||
import monster.util.freelist;
|
||||
import monster.minibos.string;
|
||||
|
||||
// Enable minor safety checks - can be removed from release code.
|
||||
debug=safecheck;
|
||||
|
||||
// Are we currently looping through the wait list?
|
||||
debug(safecheck) bool waitLoop;
|
||||
|
||||
// The various types of code a scheduled node will call
|
||||
enum CallType
|
||||
{
|
||||
None, // Not used and should never be set
|
||||
Idle, // The return of an idle function (starts state code)
|
||||
State // The beginning of a state (also starts state code)
|
||||
}
|
||||
|
||||
// The scheduler singleton
|
||||
Scheduler scheduler;
|
||||
|
||||
// Represents a code point for the scheduler to jump back to. Points
|
||||
// to an object (which owns a coe thread object) and a position. TODO:
|
||||
// Function calls must refer to some index, if the state changes we
|
||||
// must call the correct function.
|
||||
struct ScheduleStruct
|
||||
{
|
||||
CallType type;
|
||||
Function *idle;
|
||||
MonsterObject *obj;
|
||||
ListManager *list;
|
||||
int retPos; // Return position for idle functions.
|
||||
|
||||
// Unschedule this node from the runlist or waitlist it belongs
|
||||
// to. Any idle function connected to this node is aborted.
|
||||
void cancel()
|
||||
{
|
||||
debug(safecheck) auto node = obj.thread.scheduleNode;
|
||||
if(idle !is null)
|
||||
{
|
||||
fstack.pushIdleAbort(idle, obj);
|
||||
idle.idleFunc.abort(obj);
|
||||
fstack.pop();
|
||||
}
|
||||
// Make sure the scheduleNode is the same before and after calling
|
||||
// abort().
|
||||
debug(safecheck)
|
||||
assert(node == obj.thread.scheduleNode,
|
||||
"abort() can not reschedule object or change state");
|
||||
remove();
|
||||
}
|
||||
|
||||
// Remove this node from the list it belongs to.
|
||||
void remove()
|
||||
{
|
||||
debug(safecheck) assert(!waitLoop, "remove() called from hasFinished()");
|
||||
type = CallType.None;
|
||||
list.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
alias FreeList!(ScheduleStruct) ScheduleFreeList;
|
||||
alias ScheduleStruct* CallNode;
|
||||
|
||||
// Get the next node in a freelist
|
||||
static CallNode getNext(CallNode cn)
|
||||
{
|
||||
// Simple hack. The ScheduleStruct (pointed at by the CallNode) is
|
||||
// the first part of, and therefore in the same location as, the
|
||||
// iterator struct for the FreeList. It's therefore ok to cast the
|
||||
// pointer, as long as we never change the iterator struct layout.
|
||||
return cast(CallNode)
|
||||
( cast(ScheduleFreeList.TList.Iterator)cn ).getNext();
|
||||
}
|
||||
|
||||
// A wrapper around a freelist. This struct takes care of some
|
||||
// additional pointers in CodeThread and in ScheduleStruct.
|
||||
struct ListManager
|
||||
{
|
||||
ScheduleFreeList list;
|
||||
|
||||
// Create a new node in this list. This means scheduling the given
|
||||
// object in one of the lists.
|
||||
CallNode newNode(MonsterObject *obj, CallType type,
|
||||
Function *idle, int retPos)
|
||||
{
|
||||
assert(obj.thread.scheduleNode == null,
|
||||
"CodeThread cannot have two schedule nodes.");
|
||||
|
||||
CallNode cn = list.getNew();
|
||||
cn.obj = obj;
|
||||
cn.type = type;
|
||||
cn.idle = idle;
|
||||
cn.list = this;
|
||||
cn.retPos = retPos;
|
||||
obj.thread.scheduleNode = cn;
|
||||
return cn;
|
||||
}
|
||||
|
||||
// Remove a node from this run list (put it back into the freelist.)
|
||||
void remove(MonsterObject *obj)
|
||||
{
|
||||
CallNode node = obj.thread.scheduleNode;
|
||||
node.list = null;
|
||||
obj.thread.scheduleNode = null;
|
||||
list.remove(node);
|
||||
}
|
||||
|
||||
CallNode moveTo(ListManager *to, CallNode node)
|
||||
{
|
||||
node.list = to;
|
||||
return list.moveTo(to.list, node);
|
||||
}
|
||||
}
|
||||
|
||||
struct Scheduler
|
||||
{
|
||||
// The acutal lists. We use pointers to access the run lists, since
|
||||
// we want to swap them easily.
|
||||
ListManager run1, run2, wait;
|
||||
|
||||
// The run lists for this and the next round.
|
||||
ListManager* runNext, run;
|
||||
|
||||
void init()
|
||||
{
|
||||
// Assign the run list pointers
|
||||
run = &run1;
|
||||
runNext = &run2;
|
||||
}
|
||||
|
||||
// Statistics:
|
||||
|
||||
// Number of elements in the waiting list
|
||||
int numWait() { return wait.list.length; }
|
||||
|
||||
// Number of elements scheduled to run the next frame
|
||||
int numRun() { return runNext.list.length; }
|
||||
|
||||
// Total number of objects scheduled or waiting
|
||||
int numTotal()
|
||||
{
|
||||
assert(run.list.length == 0); // The 'run' list is always empty
|
||||
// between frames.
|
||||
return numRun() + numWait();
|
||||
}
|
||||
|
||||
// "Call" an idle function. We must notify the idle function that it
|
||||
// has been called. We also have the responsibility of rescheduling
|
||||
// the given code thread at the right moment.
|
||||
void callIdle(MonsterObject *obj, Function *idle,
|
||||
int pos)
|
||||
{
|
||||
//writefln("Idle function '%s' called", lf.name);
|
||||
debug(safecheck) assert(!waitLoop, "callIdle() called from hasFinished()");
|
||||
assert(obj.thread.scheduleNode == null);
|
||||
|
||||
// Make sure the object and the function are set up correctly
|
||||
assert(obj.cls is idle.owner);
|
||||
assert(idle.isIdle);
|
||||
assert(idle.idleFunc !is null);
|
||||
|
||||
// Notify the idle function
|
||||
fstack.pushIdleInit(idle, obj);
|
||||
if(idle.idleFunc.initiate(obj))
|
||||
{
|
||||
// The idle function wants to be scheduled
|
||||
|
||||
// Make sure initiate() didn't change anything it shouldn't
|
||||
// have.
|
||||
assert(obj.thread.scheduleNode == null,
|
||||
"initiate() cannot reschedule object or change state");
|
||||
|
||||
// Schedule it to be checked next round.
|
||||
wait.newNode(obj, CallType.Idle, idle, pos);
|
||||
}
|
||||
|
||||
fstack.pop();
|
||||
}
|
||||
|
||||
// Schedule the given state code to run the next frame
|
||||
void scheduleState(MonsterObject *obj, int offs)
|
||||
{
|
||||
runNext.newNode(obj, CallType.State, null, offs);
|
||||
}
|
||||
|
||||
// Do a complete frame. TODO: Make a distinction between a round and
|
||||
// a frame later. We could for example do several rounds per frame,
|
||||
// measured by some criterion of how much time we want to spend on
|
||||
// script code or whether there are any pending items in the run
|
||||
// list. We could do several runs of the run-list (to handle state
|
||||
// changes etc) but only one run on the condition list (actually
|
||||
// that is a good idea.) We also do not have to execute everything in
|
||||
// the run list if it is long (otoh, allowing a build-up is not
|
||||
// good.) But all this falls in the "optimization" category.
|
||||
void doFrame()
|
||||
{
|
||||
//writefln("Beginning of frame");
|
||||
|
||||
// Turn on some safety features
|
||||
debug(safecheck) waitLoop = true;
|
||||
|
||||
// Go through the condition list for this round.
|
||||
CallNode cn = wait.list.getHead();
|
||||
CallNode next;
|
||||
while(cn != null)
|
||||
{
|
||||
// Get the next node here, since the current node might move
|
||||
// somewhere else during this iteration, and then getNext will
|
||||
// point to another list.
|
||||
next = getNext(cn);
|
||||
|
||||
assert(cn.obj.thread.scheduleNode == cn);
|
||||
|
||||
// This is an idle function and it is finished. Note that
|
||||
// hasFinished() is NOT allowed to change the wait list in any
|
||||
// way, ie to change object states or interact with the
|
||||
// scheduler. In fact, hasFinished() should do as little as
|
||||
// possible.
|
||||
if(cn.type == CallType.Idle)
|
||||
{
|
||||
fstack.pushIdleCheck(cn.idle, cn.obj);
|
||||
if(cn.idle.idleFunc.hasFinished(cn.obj))
|
||||
// Schedule the code to start running again this round. We
|
||||
// move it from the wait list to the run list.
|
||||
wait.moveTo(runNext,cn);
|
||||
|
||||
fstack.pop();
|
||||
}
|
||||
// Set the next item
|
||||
cn = next;
|
||||
}
|
||||
|
||||
debug(safecheck) waitLoop = false;
|
||||
|
||||
|
||||
/*
|
||||
if(wait.list.length)
|
||||
writefln(" There are idle functions lurking in the shadows");
|
||||
*/
|
||||
|
||||
//writefln("Condition phase complete, beginning execution phase.");
|
||||
|
||||
// Swap the runlist for the next frame with the current one. All
|
||||
// code that is scheduled after this point is executed the next
|
||||
// frame.
|
||||
auto tmp = runNext;
|
||||
runNext = run;
|
||||
run = tmp;
|
||||
|
||||
// Now execute the run list for this frame. Note that items might
|
||||
// be removed from the run list as we go (eg. if a scheduled
|
||||
// object has it's state changed) but this is handled. New nodes
|
||||
// might also be scheduled, but these are added to the runNext
|
||||
// list.
|
||||
|
||||
// First element
|
||||
cn = run.list.getHead();
|
||||
while(cn != null)
|
||||
{
|
||||
// Remove the current item from the list before starting.
|
||||
MonsterObject *obj = cn.obj;
|
||||
auto code = obj.thread;
|
||||
assert(code.scheduleNode == cn);
|
||||
run.remove(obj);
|
||||
|
||||
// Now execute the item
|
||||
if(cn.type == CallType.Idle)
|
||||
{
|
||||
// Tell the idle function that we we are reentering
|
||||
fstack.pushIdleReentry(cn.idle, obj);
|
||||
cn.idle.idleFunc.reentry(obj);
|
||||
fstack.pop();
|
||||
|
||||
assert(code.scheduleNode == null,
|
||||
"reentry() cannot reschedule object or change state");
|
||||
|
||||
// Return to the code point
|
||||
code.callState(cn.retPos);
|
||||
}
|
||||
else if(cn.type == CallType.State)
|
||||
// Code is scheduled after a state change. We must jump to
|
||||
// the right offset.
|
||||
code.callState(cn.retPos);
|
||||
|
||||
else assert(0, "Unhandled return type");
|
||||
|
||||
// The function stack should now be at zero
|
||||
assert(fstack.isEmpty());
|
||||
|
||||
// Get the next item.
|
||||
cn = run.list.getHead();
|
||||
}
|
||||
|
||||
// Check that we cleared the run list
|
||||
assert(run.list.length == 0);
|
||||
|
||||
//writefln("End of frame\n");
|
||||
}
|
||||
}
|
@ -0,0 +1,407 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (stack.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.vm.stack;
|
||||
|
||||
import monster.minibos.string;
|
||||
import monster.minibos.stdio;
|
||||
import monster.minibos.utf;
|
||||
|
||||
import monster.compiler.scopes;
|
||||
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.error;
|
||||
|
||||
// The stack. One nice attribue of our cooperative multithreading
|
||||
// scheme is that we only need one single stack frame. All functions
|
||||
// and code is required to "finish up" before returning or giving
|
||||
// control to other code. When we switch to "real" threads later, we
|
||||
// will need one stack per system thread, but many virtual threads
|
||||
// will still share each stack.
|
||||
CodeStack stack;
|
||||
|
||||
// A simple stack frame. All data are in chunks of 4 bytes
|
||||
struct CodeStack
|
||||
{
|
||||
private:
|
||||
int[] data;
|
||||
|
||||
int left, total;
|
||||
int *pos; // Current position
|
||||
|
||||
// Frame pointer, used for accessing local variables and parameters
|
||||
int *frame;
|
||||
int fleft; // A security measure to make sure we don't access
|
||||
// variables outside the stack
|
||||
|
||||
public:
|
||||
void init()
|
||||
{
|
||||
// 100 is just a random number - it should probably be quite a bit
|
||||
// larger (but NOT dynamic, since we want to catch run-away code.)
|
||||
const int size = 100;
|
||||
|
||||
data.length = size;
|
||||
left = size;
|
||||
total = size;
|
||||
pos = data.ptr;
|
||||
frame = null;
|
||||
}
|
||||
|
||||
// Get the current position index. Used mostly for debugging and
|
||||
// error checking.
|
||||
int getPos()
|
||||
{
|
||||
return total-left;
|
||||
}
|
||||
|
||||
// Sets the current position as the 'frame pointer', and return
|
||||
// previous value.
|
||||
int *setFrame()
|
||||
{
|
||||
auto old = frame;
|
||||
frame = pos;
|
||||
fleft = left;
|
||||
//writefln("setFrame(): new=%s, old=%s", frame, old);
|
||||
return old;
|
||||
}
|
||||
|
||||
// Sets the given frame pointer
|
||||
void setFrame(int *frm)
|
||||
{
|
||||
//writefln("setFrame(%s)", frm);
|
||||
frame = frm;
|
||||
if(frm is null)
|
||||
{
|
||||
fleft = 0;
|
||||
return;
|
||||
}
|
||||
fleft = left + (frm-pos);
|
||||
assert(fleft >= 0 && fleft <= total);
|
||||
}
|
||||
|
||||
// Reset the stack level to zero. Should only be called when the
|
||||
// frame pointer is already zero (we use it in state code only.)
|
||||
void reset()
|
||||
{
|
||||
left = total;
|
||||
pos = data.ptr;
|
||||
|
||||
assert(fleft == left);
|
||||
}
|
||||
|
||||
void pushInt(int i)
|
||||
{
|
||||
left--;
|
||||
if(left<0) overflow("pushInt");
|
||||
*pos = i;
|
||||
pos++;
|
||||
}
|
||||
|
||||
void pushLong(long i)
|
||||
{
|
||||
left -= 2;
|
||||
if(left<0) overflow("pushLong");
|
||||
*(cast(long*)pos) = i;
|
||||
pos+=2;
|
||||
}
|
||||
|
||||
int popInt()
|
||||
{
|
||||
left++;
|
||||
if(left>total) overflow("popInt");
|
||||
pos--;
|
||||
return *pos;
|
||||
}
|
||||
|
||||
long popLong()
|
||||
{
|
||||
left+=2;
|
||||
if(left>total) overflow("popLong");
|
||||
pos-=2;
|
||||
return *(cast(long*)pos);
|
||||
}
|
||||
|
||||
// Get the pointer to an int at the given position backwards from
|
||||
// the current stack pointer. 0 means the first int, ie. the one we
|
||||
// would get if we called popInt. 1 is the next, etc
|
||||
int *getInt(int ptr)
|
||||
{
|
||||
ptr++;
|
||||
if(ptr < 1 || ptr > (total-left) )
|
||||
fail("CodeStack.getInt() pointer out of range");
|
||||
return pos-ptr;
|
||||
}
|
||||
|
||||
// Get the array _beginning_ at ptr
|
||||
int[] getInts(int ptr, int len)
|
||||
{
|
||||
assert(len > 0 && ptr >= len-1);
|
||||
if(left+len-ptr>total) overflow("getInts");
|
||||
return getInt(ptr)[0..len];
|
||||
}
|
||||
|
||||
// Pops the next len ints off the stack and returns them as an
|
||||
// array. The array is ordered as the values were pushed, not as
|
||||
// they would have been popped (ie. this function is like popping
|
||||
// one big value of the stack.) The array is a direct slice of the
|
||||
// stack, so don't store it or use it after pushing other values.
|
||||
int[] popInts(int len)
|
||||
{
|
||||
assert(len > 0);
|
||||
int[] r = getInts(len-1, len);
|
||||
pop(len);
|
||||
return r;
|
||||
}
|
||||
|
||||
void pushInts(int[] arr)
|
||||
{
|
||||
left -= arr.length;
|
||||
if(left<0) overflow("pushInts");
|
||||
pos[0..arr.length] = arr[];
|
||||
pos+=arr.length;
|
||||
}
|
||||
|
||||
// Get an int a given position from frame pointer. Can be negative
|
||||
// or positive. 0 means the int at the pointer, -1 is the one before
|
||||
// and 1 the one after, etc.
|
||||
int *getFrameInt(int ptr)
|
||||
{
|
||||
assert(frame !is null);
|
||||
assert(frame <= pos);
|
||||
if(ptr < (fleft-total) || ptr >= fleft)
|
||||
fail("CodeStack.getFrameInt() pointer out of range");
|
||||
return frame+ptr;
|
||||
}
|
||||
|
||||
// Pushing and poping objects of the stack - will actually push/pop
|
||||
// their index.
|
||||
void pushObject(MonsterObject *mo)
|
||||
{ pushInt(mo.getIndex); }
|
||||
|
||||
MonsterObject *popObject()
|
||||
{ return getMObject(cast(MIndex)popInt()); }
|
||||
|
||||
// Push an object, and make sure it is cast to the right type
|
||||
void pushCast(MonsterObject *obj, MonsterClass cls)
|
||||
{ pushObject(obj.Cast(cls)); }
|
||||
|
||||
void pushCast(MonsterObject *obj, char[] name)
|
||||
{ pushCast(obj, global.getClass(name)); }
|
||||
|
||||
// Push arrays of objects. TODO: These do memory allocation, and I'm
|
||||
// not sure that belongs here. I will look into it later.
|
||||
void pushObjects(MonsterObject *objs[])
|
||||
{
|
||||
int[] indices;
|
||||
indices.length = objs.length;
|
||||
|
||||
foreach(i, mo; objs)
|
||||
indices[i] = mo.getIndex();
|
||||
|
||||
pushIArray(indices);
|
||||
}
|
||||
|
||||
MonsterObject*[] popObjects()
|
||||
{
|
||||
MIndex[] indices = cast(MIndex[]) popIArray();
|
||||
MonsterObject* objs[];
|
||||
|
||||
objs.length = indices.length;
|
||||
foreach(i, ind; indices)
|
||||
objs[i] = getMObject(ind);
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
// Push and pop array references.
|
||||
void pushArray(ArrayRef *ar)
|
||||
{ pushInt(ar.getIndex); }
|
||||
ArrayRef *popArray()
|
||||
{ return arrays.getRef(cast(AIndex)popInt()); }
|
||||
ArrayRef *getArray(int i)
|
||||
{ return arrays.getRef(cast(AIndex)*getInt(i)); }
|
||||
|
||||
// More easy versions. Note that pushArray() will create a new array
|
||||
// reference each time it is called! Only use it if this is what you
|
||||
// want.
|
||||
void pushCArray(dchar[] str) { pushArray(arrays.create(str)); }
|
||||
void pushIArray(int[] str) { pushArray(arrays.create(str)); }
|
||||
void pushUArray(uint[] str) { pushArray(arrays.create(str)); }
|
||||
void pushLArray(long[] str){ pushArray(arrays.create(str)); }
|
||||
void pushULArray(ulong[] str) { pushArray(arrays.create(str)); }
|
||||
void pushFArray(float[] str) { pushArray(arrays.create(str)); }
|
||||
void pushDArray(double[] str) { pushArray(arrays.create(str)); }
|
||||
void pushAArray(AIndex[] str) { pushArray(arrays.create(str)); }
|
||||
|
||||
alias pushCArray pushArray, pushString;
|
||||
alias pushIArray pushArray;
|
||||
alias pushFArray pushArray;
|
||||
alias pushAArray pushArray;
|
||||
alias pushString8 pushArray, pushString;
|
||||
|
||||
dchar[] popCArray() { return popArray().carr; }
|
||||
int[] popIArray() { return popArray().iarr; }
|
||||
float[] popFArray() { return popArray().farr; }
|
||||
AIndex[] popAArray() { return popArray().aarr; }
|
||||
alias popCArray popString;
|
||||
|
||||
void pushString8(char[] str)
|
||||
{ pushArray(toUTF32(str)); }
|
||||
char[] popString8()
|
||||
{ return toUTF8(popString()); }
|
||||
|
||||
// For multibyte arrays
|
||||
void pushArray(int[] str, int size)
|
||||
{ pushArray(arrays.create(str, size)); }
|
||||
|
||||
|
||||
// Various convenient conversion templates. These will be inlined,
|
||||
// so don't worry :) The *4() functions are for types that are 4
|
||||
// bytes long. These are mostly intended for use in native
|
||||
// functions, so there is no equivalent of getFrameInt and similar
|
||||
// functions.
|
||||
void push4(T)(T var)
|
||||
{
|
||||
static assert(T.sizeof == 4);
|
||||
pushInt(*(cast(int*)&var));
|
||||
}
|
||||
T pop4(T)()
|
||||
{
|
||||
static assert(T.sizeof == 4);
|
||||
int i = popInt();
|
||||
return *(cast(T*)&i);
|
||||
}
|
||||
T* get4(T)(int ptr) { return cast(T*)getInt(ptr); }
|
||||
|
||||
// 64 bit version
|
||||
void push8(T)(T var)
|
||||
{
|
||||
static assert(T.sizeof == 8);
|
||||
pushLong(*(cast(long*)&var));
|
||||
}
|
||||
T pop8(T)()
|
||||
{
|
||||
static assert(T.sizeof == 8);
|
||||
long l = popLong();
|
||||
return *(cast(T*)&l);
|
||||
}
|
||||
|
||||
// Bools are 1 byte in D
|
||||
void pushBool(bool b)
|
||||
{
|
||||
if(b) pushInt(1);
|
||||
else pushInt(0);
|
||||
}
|
||||
bool popBool() { return popInt() != 0; }
|
||||
alias get4!(bool) getBool;
|
||||
|
||||
// Template conversions
|
||||
|
||||
alias push4!(MIndex) pushIndex;
|
||||
alias pop4!(MIndex) popIndex;
|
||||
alias get4!(MIndex) getIndex;
|
||||
|
||||
alias push4!(AIndex) pushAIndex;
|
||||
alias pop4!(AIndex) popAIndex;
|
||||
alias get4!(AIndex) getAIndex;
|
||||
|
||||
alias push4!(uint) pushUint;
|
||||
alias pop4!(uint) popUint;
|
||||
alias get4!(uint) getUint;
|
||||
|
||||
alias get4!(long) getLong;
|
||||
|
||||
alias push8!(ulong) pushUlong;
|
||||
alias pop8!(ulong) popUlong;
|
||||
alias get4!(ulong) getUlong;
|
||||
|
||||
alias push4!(float) pushFloat;
|
||||
alias pop4!(float) popFloat;
|
||||
alias get4!(float) getFloat;
|
||||
|
||||
alias push8!(double) pushDouble;
|
||||
alias pop8!(double) popDouble;
|
||||
alias get4!(double) getDouble;
|
||||
|
||||
alias push4!(dchar) pushChar;
|
||||
alias pop4!(dchar) popChar;
|
||||
alias get4!(dchar) getChar;
|
||||
|
||||
// Pop off and ignore a given amount of values
|
||||
void pop(int num)
|
||||
{
|
||||
left += num;
|
||||
if(left>total) overflow("pop1");
|
||||
pos -= num;
|
||||
}
|
||||
|
||||
// Pop off and ignore given values, but remember the top values
|
||||
void pop(uint num, uint keep)
|
||||
{
|
||||
assert(keep>0);
|
||||
assert(num>0);
|
||||
|
||||
left += num;
|
||||
|
||||
// We move the stack pointer back num values, but we access as far
|
||||
// back as num+keep values, so we need to check that we are still
|
||||
// within the stack.
|
||||
if((left+keep)>total) overflow("pop2");
|
||||
|
||||
int *from = pos-keep; // Where to get the 'keep' values from
|
||||
int *to = from-num; // Where they end up
|
||||
pos -= num; // Where the final stack pointer should be
|
||||
|
||||
assert(to < from);
|
||||
|
||||
// Copy the values
|
||||
for(; keep>0; keep--)
|
||||
*(to++) = *(from++);
|
||||
}
|
||||
|
||||
void debugPrint()
|
||||
{
|
||||
writefln("Stack:");
|
||||
foreach(int i, int val; data[0..total-left])
|
||||
writefln("%s: %s", i, val);
|
||||
writefln();
|
||||
}
|
||||
|
||||
void overflow(char[] func)
|
||||
{
|
||||
char[] res;
|
||||
if(left<0)
|
||||
res = format("Stack overflow by %s ints in CodeStack.%s()",
|
||||
-left, func);
|
||||
else if(left>total)
|
||||
res = format("Stack underflow by %s ints in CodeStack.%s()",
|
||||
left-total, func);
|
||||
else res = format("Internal error in CodeStack.%s(), left=%s, total=%s",
|
||||
func, left, total);
|
||||
fail(res);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue