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