Adding in Monster

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@61 ea6a568a-9f4f-0410-981a-c910a81bb256
pull/7/head
nkorslund 16 years ago
parent 44f7b155f8
commit 3f1aeb3aef

@ -29,6 +29,7 @@ import std.stream;
import std.string;
import util.regions;
import util.utfconvert;
import monster.util.string;
import core.resource;

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();
}

@ -297,6 +297,19 @@ struct HashTable(Key, Value, Alloc = GCAlloc, Hash = DefHash,
return p.value;
}
// Gets the stored key associated with the given key. This seemingly
// useless function is handy for custom hashers that collapse
// several values into one, for example the case insensitive string
// hasher. getKey will return the key as it was originally inserted
// into the list. In the case of reference types (such as arrays) it
// can also be used for referencing the original data.
Key getKey(Key k)
{
Node *p = lookupKey(k);
if(!p) fail("Cannot get key '%s', not found", k);
return p.key;
}
// Insert a new value, replace it if it already exists. Returns v.
Value insert(Key k, Value v)
{

@ -34,18 +34,22 @@ struct Flags(T)
void unset(T t)
{ flags ^= flags & t; }
bool has(T t)
{ return (flags & t) == t; }
bool hasAny(T t)
{ return (flags & t) != 0; }
void set(T t, bool value)
{
if(value) set(t);
else unset(t);
assert(has(t) == value);
}
// Does it have all of the bits in the parameter set?
bool has(T t)
{ return (flags & t) == t; }
// Does it have any of the bits in the parameter set?
bool hasAny(T t)
{ return (flags & t) != 0; }
// For single-bit parameters, has() and hasAny() are identical.
}
unittest

@ -23,10 +23,10 @@
*/
module monster.util.string;
import std.string;
import std.utf;
// These functions check whether a string begins or ends with a
// certain substring.
bool begins(char[] str, char[] start)
{
if(str.length < start.length ||
@ -34,25 +34,6 @@ bool begins(char[] str, char[] start)
return true;
}
unittest
{
assert("heia".begins(""));
assert("heia".begins("h"));
assert("heia".begins("he"));
assert(!("heia".begins("H")));
assert(!("heia".begins("hE")));
assert("heia".begins("hei"));
assert("heia".begins("heia"));
assert(!("heia".begins("heia ")));
assert(!("heia".begins(" heia")));
assert(!("heia".begins("eia")));
assert(!("h".begins("ha")));
assert(!("h".begins("ah")));
assert(!("".begins("ah")));
assert("".begins(""));
}
bool ends(char[] str, char[] end)
{
if(str.length < end.length ||
@ -60,27 +41,7 @@ bool ends(char[] str, char[] end)
return true;
}
unittest
{
assert("heia".ends(""));
assert(!("heia".ends("h")));
assert("heia".ends("a"));
assert("heia".ends("ia"));
assert(!("heia".ends("A")));
assert(!("heia".ends("Ia")));
assert("heia".ends("eia"));
assert("heia".ends("heia"));
assert(!("heia".ends("heia ")));
assert(!("heia".ends(" heia")));
assert(!("heia".ends("hei")));
assert(!("h".ends("ha")));
assert(!("h".ends("ah")));
assert(!("".ends("ah")));
assert("".ends(""));
}
// Case insensitive version of begins()
// Case insensitive versions of begins and ends
bool iBegins(char[] str, char[] start)
{
if(str.length < start.length ||
@ -88,26 +49,6 @@ bool iBegins(char[] str, char[] start)
return true;
}
unittest
{
assert("heia".iBegins(""));
assert("heia".iBegins("H"));
assert("heia".iBegins("hE"));
assert("heia".iBegins("hei"));
assert("HeIa".iBegins("hei"));
assert("heia".iBegins("heia"));
assert("hEia".iBegins("heiA"));
assert(!("heia".iBegins("heia ")));
assert(!("heIa".iBegins("heia ")));
assert(!("heia".iBegins("eia")));
assert(!("h".iBegins("ha")));
assert(!("h".iBegins("ah")));
assert(!("".iBegins("ah")));
assert("".iBegins(""));
}
// Case insensitive version of begins()
bool iEnds(char[] str, char[] end)
{
if(str.length < end.length ||
@ -115,115 +56,6 @@ bool iEnds(char[] str, char[] end)
return true;
}
unittest
{
assert("heia".iEnds(""));
assert(!("heia".iEnds("h")));
assert("heia".iEnds("a"));
assert("heia".iEnds("ia"));
assert("heia".iEnds("A"));
assert("heia".iEnds("Ia"));
assert("heia".iEnds("EiA"));
assert("he ia".iEnds("HE IA"));
assert("heia".iEnds("eia"));
assert("heia".iEnds("heia"));
assert(!("heia".iEnds("heia ")));
assert(!("heia".iEnds(" heia")));
assert(!("heia".iEnds("hei")));
assert(!("h".iEnds("ha")));
assert(!("h".iEnds("ah")));
assert(!("".iEnds("ah")));
assert("".iEnds(""));
}
// A specialized version of std.utf.decode()
private bool fdecode(char[] s, inout size_t idx)
{
size_t len = s.length;
dchar V;
size_t i = idx;
char u = s[i];
if (u & 0x80)
{ uint n;
char u2;
/* The following encodings are valid, except for the 5 and 6 byte
* combinations:
* 0xxxxxxx
* 110xxxxx 10xxxxxx
* 1110xxxx 10xxxxxx 10xxxxxx
* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
for (n = 1; ; n++)
{
if (n > 4)
return false; // only do the first 4 of 6 encodings
if (((u << n) & 0x80) == 0)
{
if (n == 1)
return false;
break;
}
}
// Pick off (7 - n) significant bits of B from first byte of octet
V = cast(dchar)(u & ((1 << (7 - n)) - 1));
if (i + (n - 1) >= len)
return false; // off end of string
/* The following combinations are overlong, and illegal:
* 1100000x (10xxxxxx)
* 11100000 100xxxxx (10xxxxxx)
* 11110000 1000xxxx (10xxxxxx 10xxxxxx)
* 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx)
* 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx)
*/
u2 = s[i + 1];
if ((u & 0xFE) == 0xC0 ||
(u == 0xE0 && (u2 & 0xE0) == 0x80) ||
(u == 0xF0 && (u2 & 0xF0) == 0x80) ||
(u == 0xF8 && (u2 & 0xF8) == 0x80) ||
(u == 0xFC && (u2 & 0xFC) == 0x80))
return false; // overlong combination
for (uint j = 1; j != n; j++)
{
u = s[i + j];
if ((u & 0xC0) != 0x80)
return false; // trailing bytes are 10xxxxxx
V = (V << 6) | (u & 0x3F);
}
if (!isValidDchar(V))
return false;
i += n;
}
else
{
V = cast(dchar) u;
i++;
}
idx = i;
return true;
}
// Converts any string to valid UTF8 so it can be safely printed. It
// does not translate from other encodings but simply replaces invalid
// characters with 'replace'. Does everything in place.
char[] makeUTF8(char[] str, char replace = '?')
{
size_t idx = 0;
while(idx < str.length)
if(!fdecode(str, idx))
str[idx++] = replace;
return str;
}
char[] nextWord(ref char[] str, char delim = ' ')
{
int i = find(str, delim);
@ -246,60 +78,6 @@ char[] nextWord(ref char[] str, char delim = ' ')
return result;
}
unittest
{
char[] test = "bjarne betjent er betent";
assert(nextWord(test) == "bjarne");
assert(test == "betjent er betent");
assert(nextWord(test) == "betjent");
assert(nextWord(test) == "er");
assert(test == "betent");
assert(nextWord(test) == "betent");
assert(test == "");
assert(nextWord(test) == "");
test = ";;foo;bar;";
assert(nextWord(test,';') == "");
assert(nextWord(test,';') == "");
assert(nextWord(test,';') == "foo");
assert(nextWord(test,';') == "bar");
assert(nextWord(test,';') == "");
assert(nextWord(test,';') == "");
}
// An 'object oriented' interface to nextWord
class NextWord
{
char delim;
char[] str;
this(char[] str, char delim = ' ')
{
this.delim = delim;
this.str = str;
}
this(char delim = ' ')
{ this.delim = delim; }
char[] next()
{ return nextWord(str, delim); }
}
unittest
{
auto n = new NextWord(";;foo;bar;",';');
assert(n.next == "");
assert(n.next == "");
assert(n.next == "foo");
assert(n.next == "bar");
assert(n.next == "");
assert(n.next == "");
n.str = "a;bc";
assert(n.next == "a");
assert(n.next == "bc");
}
// Strip trailing zeros
char[] stripz(char [] s)
{
@ -310,21 +88,6 @@ char[] stripz(char [] s)
return s;
}
unittest
{
assert(stripz(" a b c ") == " a b c ");
char[8] str;
str[] = 0;
assert(stripz(str) == "");
str[2] = 'o';
assert(stripz(str) == "");
str[0] = 'f';
str[3] = 'd';
assert(stripz(str) == "f");
str[1] = 'o';
assert(stripz(str) == "food");
}
// Convert a long integer into a string using nice comma
// formatting. delim is the delimiter character, size is the number of
// digits in each group. See the unittest for examples.
@ -347,31 +110,3 @@ char[] comma(long i, char delim=',', int size = 3)
return res;
}
unittest
{
//_________________
// Inkas ___ \_
// were here / \ \
assert(comma(1) == "1");
assert(comma(12) == "12");
assert(comma(123) == "123");
assert(comma(1234) == "1,234");
assert(comma(12345) == "12,345");
assert(comma(123456) == "123,456");
assert(comma(1234567) == "1,234,567");
assert(comma(12345678) == "12,345,678");
// Negative values
assert(comma(-1) == "-1");
assert(comma(-12) == "-12");
assert(comma(-123) == "-123");
assert(comma(-1234) == "-1,234");
assert(comma(-12345) == "-12,345");
// Different delimiter
assert(comma(-888888888888,'-') == "-888-888-888-888");
// Different size
assert(comma(1111111111,'.',4) == "11.1111.1111");
}

@ -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

@ -25,7 +25,7 @@ module nif.extra;
import nif.record;
import nif.controlled;
import monster.util.string;
import util.utfconvert;
abstract class Extra : Record
{

Loading…
Cancel
Save