- Updated to latest Monster trunk.

- Changed config and music manager to singletons


git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@77 ea6a568a-9f4f-0410-981a-c910a81bb256
pull/7/head
nkorslund 16 years ago
parent 196f50f848
commit 0d7b08cbae

@ -625,6 +625,14 @@ struct Assembler
// Push 'this', the current object reference
void pushThis() { cmd(BC.PushThis); }
// Push the singleton object of the given class index
void pushSingleton(int classIndex)
{
assert(classIndex >= 0);
cmd(BC.PushSingleton);
addi(classIndex);
}
private void pushPtr(PT pt, int index)
{
cmd(BC.PushData);
@ -728,7 +736,11 @@ struct Assembler
}
}
void mov(int s) { cmd2(BC.StoreRet, BC.StoreRet8, s); }
void mov(int s)
{
if(s < 3) cmd2(BC.StoreRet, BC.StoreRet8, s);
else cmdmult(BC.StoreRet, BC.StoreRetMult, s);
}
// Set object state to the given index
void setState(int st, int label, int cls)
@ -748,9 +760,8 @@ struct Assembler
void catArrayLeft(int s) { cmd(BC.CatLeft); addi(s); }
void catArrayRight(int s) { cmd(BC.CatRight); addi(s); }
// Get the length of an array. The parameter gives element size. For
// example, an array of six ints with an element size of 2 (eg. a
// double) has length 3.
// Get the length of an array. Converts the array index to an int
// (holding the length) on the stack.
void getArrayLength() { cmd(BC.GetArrLen); }
// Reverse an array in place

@ -76,6 +76,25 @@ abstract class Block
return true;
}
static void reqNext(ref TokenArray toks, TT type, out Token tok)
{
if(!isNext(toks, type, tok))
fail("Expected " ~ tokenList[type], toks);
}
static void reqNext(ref TokenArray toks, TT type, out Floc loc)
{
Token t;
reqNext(toks, type, t);
loc = t.loc;
}
static void reqNext(ref TokenArray toks, TT type)
{
Token t;
reqNext(toks, type, t);
}
// Sets the assembler debug line to the line belonging to this
// block.
final void setLine() { tasm.setLine(loc.line); }

@ -108,6 +108,9 @@ enum BC
PushThis, // Push the 'this' object reference
PushSingleton, // Push the singleton object. Takes the global
// class index (int) as parameter.
// The Push*8 instructions are not implemented yet as they are
// just optimizations of existing features. The names are reserved
// for future use.
@ -136,11 +139,14 @@ enum BC
// the Ptr, and then pushes the value back.
Store, // Same as StoreRet but does not push the
// value back. Not implemented.
// value back. Not implemented, but will later
// replace storeret completely.
StoreRet8, // Same as StoreRet except two ints are popped
// from the stack and moved into the data.
StoreRetMult, // Takes the size as an int parameter
IAdd, // Standard addition, operates on the two next
// ints in the stack, and stores the result in
// the stack.
@ -535,6 +541,7 @@ char[][] bcToString =
BC.PushFarClassVar: "PushFarClassVar",
BC.PushFarClassMulti: "PushFarClassMulti",
BC.PushThis: "PushThis",
BC.PushSingleton: "PushSingleton",
BC.Push8: "Push8",
BC.PushLocal8: "PushLocal8",
BC.PushClassVar8: "PushClassVar8",
@ -545,6 +552,7 @@ char[][] bcToString =
BC.StoreRet: "StoreRet",
BC.Store: "Store",
BC.StoreRet8: "StoreRet8",
BC.StoreRetMult: "StoreRetMult",
BC.FetchElem: "FetchElem",
BC.GetArrLen: "GetArrLen",
BC.IMul: "IMul",

@ -0,0 +1,67 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007, 2008 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (enums.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.enums;
import monster.compiler.scopes;
import monster.compiler.types;
import monster.compiler.statement;
import monster.compiler.tokenizer;
class EnumDeclaration : TypeDeclaration
{
static bool canParse(TokenArray toks)
{ return toks.isNext(TT.Enum); }
Token name;
EnumType type;
override:
void parse(ref TokenArray toks)
{
reqNext(toks, TT.Enum);
reqNext(toks, TT.Identifier, name);
reqNext(toks, TT.LeftCurl);
// Just skip everything until the matching }. This lets us
// define some enums and play around in the scripts, even if it
// doesn't actually work.
while(!isNext(toks, TT.RightCurl)) next(toks);
isNext(toks, TT.Semicolon);
}
void insertType(TFVScope last)
{
type = new EnumType(this);
// Insert ourselves into the parent scope
assert(last !is null);
last.insertEnum(this);
}
// Very little to resolve, really. It's purely a declarative
// statement.
void resolve(Scope last)
{}
}

@ -72,6 +72,7 @@ abstract class Expression : Block
// The rest are not allowed for members (eg. con.(a+b) is not
// allowed)
else if(NewExpression.canParse(toks)) b = new NewExpression;
else if(TypeofExpression.canParse(toks)) b = new TypeofExpression;
else if(LiteralExpr.canParse(toks)) b = new LiteralExpr;
else if(ArrayLiteralExpr.canParse(toks)) b = new ArrayLiteralExpr;
// Sub-expression (expr)
@ -450,6 +451,32 @@ abstract class Expression : Block
int[] evalCTime() { assert(0); return null; }
}
// Handles typeof(exp), returns an empty expression with the meta-type
// of exp.
class TypeofExpression : Expression
{
TypeofType tt;
static bool canParse(TokenArray toks)
{ return TypeofType.canParse(toks); }
void parse(ref TokenArray toks)
{
// Let TypeofType handle the details
tt = new TypeofType;
tt.parse(toks);
}
void resolve(Scope sc)
{
tt.resolve(sc);
type = tt.getBase().getMeta();
}
// Don't actually produce anything
void evalAsm() {}
}
// new-expressions, ie. (new Sometype[]).
class NewExpression : Expression
{
@ -471,9 +498,7 @@ class NewExpression : Expression
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.New, loc))
assert(0, "Internal error in NewExpression");
reqNext(toks, TT.New, loc);
type = Type.identify(toks, true, exArr);
}
@ -486,6 +511,9 @@ class NewExpression : Expression
{
type.resolve(sc);
if(type.isReplacer)
type = type.getBase();
if(type.isObject)
{
// We need to find the index associated with this class, and
@ -600,12 +628,11 @@ class ArrayLiteralExpr : Expression
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.LeftSquare, loc))
assert(0, "Internal error in ArrayLiteralExpr");
reqNext(toks, TT.LeftSquare, loc);
Floc loc2;
if(isNext(toks, TT.RightSquare, loc2))
fail("Array literal cannot be empty", loc2);
fail("Array literal cannot be empty. Use 'null' instead.", loc2);
// Read the first expression
params ~= Expression.identify(toks);
@ -614,8 +641,7 @@ class ArrayLiteralExpr : Expression
while(isNext(toks, TT.Comma))
params ~= Expression.identify(toks);
if(!isNext(toks, TT.RightSquare))
fail("Array literal expected closing ]", toks);
reqNext(toks, TT.RightSquare);
}
char[] toString()
@ -642,7 +668,7 @@ class ArrayLiteralExpr : Expression
// Set the type
Type base = params[0].type;
type = new ArrayType(base);
type = ArrayType.get(base);
foreach(ref par; params)
{
@ -656,8 +682,10 @@ class ArrayLiteralExpr : Expression
cls = sc.getClass();
/*
if(isCTime)
cls.reserveStatic(params.length * base.getSize);
*/
}
int[] evalCTime()
@ -682,9 +710,8 @@ class ArrayLiteralExpr : Expression
foreach(i, par; params)
data[i*elem..(i+1)*elem] = par.evalCTime();
// Insert the array into the static data, and get the array
// reference
arrind = cls.insertStatic(data, elem);
// Create the array and get the index
arrind = arrays.createConst(data, elem).getIndex;
// Delete the array, if necessary
if(data.length > LEN)
@ -877,7 +904,7 @@ class LiteralExpr : Expression
// Strings
if(value.type == TT.StringLiteral)
{
type = new ArrayType(BasicType.getChar);
type = ArrayType.getString;
// Check that we do indeed have '"'s at the ends of the
// string. Special cases which we allow later (like wysiwig
@ -887,7 +914,7 @@ class LiteralExpr : Expression
"Encountered invalid string literal token: " ~ value.str);
strVal = toUTF32(value.str[1..$-1]);
cls.reserveStatic(strVal.length);
//cls.reserveStatic(strVal.length);
return;
}
@ -914,9 +941,8 @@ class LiteralExpr : Expression
if(type.isString)
{
// Insert the array into the static data, and get the array
// reference
arrind = cls.insertStatic(cast(int[])strVal, 1);
// Create array index
arrind = arrays.createConst(cast(int[])strVal, 1).getIndex;
// Create an array from it
return (cast(int*)&arrind)[0..1];
}
@ -971,548 +997,6 @@ abstract class MemberExpression : Expression
}
}
// Represents a reference to a variable. Simply stores the token
// representing the identifier. Evaluation is handled by the variable
// declaration itself. This allows us to use this class for local and
// global variables as well as for properties, without handling each
// case separately. The special names (currently __STACK__) are
// handled internally.
class VariableExpr : MemberExpression
{
Token name;
Variable *var;
Property prop;
enum VType
{
None, // Should never be set
LocalVar, // Local variable
ThisVar, // Variable in this object and this class
ParentVar, // Variable in another class but this object
FarOtherVar, // Another class, another object
Property, // Property (like .length of arrays)
Special, // Special name (like __STACK__)
Type, // Typename
}
VType vtype;
int classIndex = -1; // Index of the class that owns this variable.
static bool canParse(TokenArray toks)
{
return
isNext(toks, TT.Identifier) ||
isNext(toks, TT.Singleton) ||
isNext(toks, TT.State) ||
isNext(toks, TT.Clone) ||
isNext(toks, TT.Const);
}
void parse(ref TokenArray toks)
{
name = next(toks);
loc = name.loc;
}
// Does this variable name refer to a type name rather than an
// actual variable?
bool isType()
{
return type.isMeta();
}
bool isProperty()
out(res)
{
if(res)
{
assert(prop.name != "");
assert(var is null);
assert(!isSpecial);
}
else
{
assert(prop.name == "");
}
}
body
{
return vtype == VType.Property;
}
bool isSpecial() { return vtype == VType.Special; }
override:
char[] toString() { return name.str; }
// Ask the variable if we can write to it.
bool isLValue()
{
// Specials are read only
if(isSpecial)
return false;
// Properties may or may not be changable
if(isProperty)
return prop.isLValue;
// Normal variables are always lvalues.
return true;
}
bool isStatic()
{
// Properties can be static
if(isProperty)
return prop.isStatic;
// Type names are always static (won't be true for type
// variables later, though.)
if(isType)
return true;
return false;
}
// TODO: isCTime - should be usable for static properties and members
void writeProperty()
{
assert(isProperty);
prop.setValue();
}
void resolve(Scope sc)
out
{
// Some sanity checks on the result
if(isProperty) assert(var is null);
if(var !is null)
{
assert(var.sc !is null);
assert(!isProperty);
}
assert(type !is null);
assert(vtype != VType.None);
}
body
{
if(isMember) // Are we called as a member?
{
// Look up the name in the scope belonging to the owner
assert(leftScope !is null);
// Check first if this is a variable
var = leftScope.findVar(name.str);
if(var !is null)
{
// We are a member variable
type = var.type;
// The object pointer is pushed on the stack. We must
// also provide the class index, so the variable is
// changed in the correct class (it could be a parent
// class of the given object.)
vtype = VType.FarOtherVar;
assert(var.sc.isClass);
classIndex = var.sc.getClass().getIndex();
return;
}
// Check for properties last
if(leftScope.findProperty(name, ownerType, prop))
{
// We are a property
vtype = VType.Property;
type = prop.getType;
return;
}
// No match
fail(name.str ~ " is not a member of " ~ ownerType.toString,
loc);
}
// Not a member
// Look for reserved names first.
if(name.str == "__STACK__")
{
vtype = VType.Special;
type = BasicType.getInt;
return;
}
if(name.type == TT.Const)
fail("Cannot use const as a variable", name.loc);
if(name.type == TT.Clone)
fail("Cannot use clone as a variable", name.loc);
// These are special cases that work both as properties
// (object.state) and as non-member variables (state=...) inside
// class functions / state code. Since we already handle them
// nicely as properties, treat them as properties.
if(name.type == TT.Singleton || name.type == TT.State)
{
if(!sc.isInClass)
fail(name.str ~ " can only be used in classes", name.loc);
if(!sc.findProperty(name, sc.getClass().objType, prop))
assert(0, "should have found property " ~ name.str ~
" in scope " ~ sc.toString);
vtype = VType.Property;
type = prop.getType;
return;
}
// Not a member, property or a special name. Look ourselves up
// in the local variable scope.
var = sc.findVar(name.str);
if(var !is null)
{
type = var.type;
assert(var.sc !is null);
// Class variable?
if(var.sc.isClass)
{
// Check if it's in THIS class, which is a common
// case. If so, we can use a simplified instruction that
// doesn't have to look up the class.
if(var.sc.getClass is sc.getClass)
vtype = VType.ThisVar;
else
{
// It's another class. For non-members this can only
// mean a parent class.
vtype = VType.ParentVar;
classIndex = var.sc.getClass().getIndex();
}
}
else
vtype = VType.LocalVar;
return;
}
// We are not a variable. Maybe we're a basic type?
if(BasicType.isBasic(name.str))
{
// Yes!
type = MetaType.get(name.str);
vtype = VType.Type;
return;
}
// No match at all
fail("Undefined identifier "~name.str, name.loc);
}
void evalAsm()
{
// If we are a type, just do nothing. If the type is used for
// anything, it will be typecast and all the interesting stuff
// will be handled by the type system.
if(isType) return;
setLine();
// Special name
if(isSpecial)
{
if(name.str == "__STACK__")
tasm.getStack();
else assert(0, "Unknown special name " ~ name.str);
return;
}
// Property
if(isProperty)
{
prop.getValue();
return;
}
// Normal variable
int s = type.getSize;
if(vtype == VType.LocalVar)
// This is a variable local to this function. The number gives
// the stack position.
tasm.pushLocal(var.number, s);
else if(vtype == VType.ThisVar)
// The var.number gives the offset into the data segment in
// this class
tasm.pushClass(var.number, s);
else if(vtype == VType.ParentVar)
// Variable in a parent but this object
tasm.pushParentVar(var.number, classIndex, s);
else if(vtype == VType.FarOtherVar)
// Push the value from a "FAR pointer". The class index should
// already have been pushed on the stack by DotOperator, we
// only push the index.
tasm.pushFarClass(var.number, classIndex, s);
else assert(0);
}
// Push the address of the variable rather than its value
void evalDest()
{
assert(!isType, "types can never be written to");
assert(isLValue());
assert(!isProperty);
setLine();
// No size information is needed for addresses.
if(vtype == VType.LocalVar)
tasm.pushLocalAddr(var.number);
else if(vtype == VType.ThisVar)
tasm.pushClassAddr(var.number);
else if(vtype == VType.ParentVar)
tasm.pushParentVarAddr(var.number, classIndex);
else if(vtype == VType.FarOtherVar)
tasm.pushFarClassAddr(var.number, classIndex);
else assert(0);
}
void postWrite()
{
assert(!isProperty);
assert(isLValue());
assert(var.sc !is null);
if(var.isRef)
// TODO: This assumes all ref variables are foreach values,
// which will probably not be true in the future.
tasm.iterateUpdate(var.sc.getLoopStack());
}
}
// Expression representing a function call
class FunctionCallExpr : MemberExpression
{
Token name;
ExprArray params;
Function* fd;
bool isVararg;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
}
// Read a parameter list (a,b,...)
static ExprArray getParams(ref TokenArray toks)
{
ExprArray res;
if(!isNext(toks, TT.LeftParen)) return res;
Expression exp;
// No parameters?
if(isNext(toks, TT.RightParen)) return res;
// Read the first parameter
res ~= Expression.identify(toks);
// Are there more?
while(isNext(toks, TT.Comma))
res ~= Expression.identify(toks);
if(!isNext(toks, TT.RightParen))
fail("Parameter list expected ')'", toks);
return res;
}
void parse(ref TokenArray toks)
{
name = next(toks);
loc = name.loc;
params = getParams(toks);
}
char[] toString()
{
char[] result = name.str ~ "(";
foreach(b; params)
result ~= b.toString ~" ";
return result ~ ")";
}
void resolve(Scope sc)
{
if(isMember) // Are we called as a member?
{
assert(leftScope !is null);
fd = leftScope.findFunc(name.str);
if(fd is null) fail(name.str ~ " is not a member function of "
~ leftScope.toString, loc);
}
else
{
assert(leftScope is null);
fd = sc.findFunc(name.str);
if(fd is null) fail("Undefined function "~name.str, name.loc);
}
// Is this an idle function?
if(fd.isIdle)
{
if(!sc.isStateCode)
fail("Idle functions can only be called from state code",
name.loc);
if(isMember)
fail("Idle functions cannot be called as members", name.loc);
}
type = fd.type;
assert(type !is null);
isVararg = fd.isVararg;
if(isVararg)
{
// The vararg parameter can match a variable number of
// arguments, including zero.
if(params.length < fd.params.length-1)
fail(format("%s() expected at least %s parameters, got %s",
name.str, fd.params.length-1, params.length),
name.loc);
}
else
// Non-vararg functions must match function parameter number
// exactly
if(params.length != fd.params.length)
fail(format("%s() expected %s parameters, got %s",
name.str, fd.params.length, params.length),
name.loc);
// Check parameter types
foreach(int i, par; fd.params)
{
// Handle varargs below
if(isVararg && i == fd.params.length-1)
break;
params[i].resolve(sc);
try par.type.typeCast(params[i]);
catch(TypeException)
fail(format("%s() expected parameter %s to be type %s, not type %s",
name.str, i+1, par.type.toString, params[i].typeString),
name.loc);
}
// Loop through remaining arguments
if(isVararg)
{
int start = fd.params.length-1;
assert(fd.params[start].type.isArray);
Type base = fd.params[start].type.getBase();
foreach(int i, ref par; params[start..$])
{
par.resolve(sc);
// If the first and last vararg parameter is of the
// array type itself, then we are sending an actual
// array. Treat it like a normal parameter.
if(i == 0 && start == params.length-1 &&
par.type == fd.params[start].type)
{
isVararg = false;
break;
}
// Otherwise, cast the type to the array base type.
try base.typeCast(par);
catch(TypeException)
fail(format("Cannot convert %s of type %s to %s", par.toString,
par.typeString, base.toString), par.loc);
}
}
}
// Used in cases where the parameters need to be evaluated
// separately from the function call. This is done by DotOperator,
// in cases like obj.func(expr); Here expr is evaluated first, then
// obj, and then finally the far function call. This is because the
// far function call needs to read 'obj' off the top of the stack.
void evalParams()
{
assert(pdone == false);
foreach(i, ex; params)
{
ex.eval();
// Convert 'const' parameters to actual constant references
if(i < fd.params.length-1) // Skip the last parameter (in
// case of vararg functions)
if(fd.params[i].isConst)
{
assert(fd.params[i].type.isArray);
tasm.makeArrayConst();
}
}
if(isVararg)
{
// Compute the length of the vararg array.
int len = params.length - fd.params.length + 1;
// If it contains no elements, push a null array reference
// (0 is always null).
if(len == 0) tasm.push(0);
else
// Converte the pushed values to an array index
tasm.popToArray(len, params[$-1].type.getSize());
}
// Handle the last parameter after everything has been
// pushed. This will work for both normal and vararg parameters.
if(fd.params.length != 0 && fd.params[$-1].isConst)
{
assert(fd.params[$-1].type.isArray);
tasm.makeArrayConst();
}
pdone = true;
}
bool pdone;
void evalAsm()
{
if(!pdone) evalParams();
setLine();
assert(fd.owner !is null);
if(isMember)
tasm.callFarFunc(fd.index, fd.owner.getIndex());
else if(fd.isIdle)
tasm.callIdle(fd.index, fd.owner.getIndex());
else
tasm.callFunc(fd.index, fd.owner.getIndex());
}
}
// Expression that handles conversion from one type to another. This
// is used for implisit casts (eg. when using ints and floats
// together) and in the future it will also parse explisit casts. It

@ -39,6 +39,7 @@ import monster.compiler.types;
import monster.compiler.assembler;
import monster.compiler.bytecode;
import monster.compiler.scopes;
import monster.compiler.expression;
import monster.compiler.variables;
import monster.compiler.tokenizer;
import monster.compiler.linespec;
@ -49,8 +50,11 @@ import monster.vm.idlefunction;
import monster.vm.mclass;
import monster.vm.error;
import monster.vm.fstack;
import monster.vm.vm;
import std.stdio;
import std.stream;
import std.string;
// 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
@ -81,7 +85,13 @@ struct Function
int paramSize;
int imprint; // Stack imprint of this function. Equals
// (type.getSize() - paramSize)
// (type.getSize() - paramSize) (NOT USED YET)
// Is this function final? (can not be overridden in child classes)
bool isFinal;
// What function we override (if any)
Function *overrides;
union
{
@ -117,32 +127,16 @@ struct Function
// 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)
{
assert(obj !is null);
// Cast the object to the correct type for this function.
obj = obj.upcast(owner);
obj = obj.Cast(owner);
// Push the function on the stack
fstack.push(this, obj);
@ -175,9 +169,84 @@ struct Function
fstack.pop();
}
// Call without an object. TODO: Only allowed for functions compiled
// without a class using compile() below, but in the future this
// will be replaced with a static function.
void call()
{
assert(owner is int_mc);
assert(owner !is null);
assert(int_mo !is null);
call(int_mo);
}
static Function opCall(char[] file, MonsterClass mc = null)
{
Function fn;
fn.compile(file, mc);
return fn;
}
// Compile the function script 'file' in the context of the class
// 'mc'. If no class is given, use an empty internal class.
void compile(char[] file, MonsterClass mc = null)
{
assert(name.str == "",
"Function " ~ name.str ~ " has already been set up");
// Check if the file exists
if(!vm.findFile(file))
fail("File not found: " ~ file);
// Set mc to an empty class if no class is given
if(mc is null)
{
if(int_mc is null)
{
assert(int_mo is null);
int_mc = new MonsterClass(MC.String, int_class);
int_mo = int_mc.createObject;
}
assert(int_mo !is null);
mc = int_mc;
}
// TODO: Maybe we should centralize this stuff somewhere.
// Especially since we have to reinvent loading from string or
// other custom streams, and so on. It's easier to put the tools
// for this here than to have them in MonsterClass. Fix this later
// when you see how things turn out.
auto bf = new BufferedFile(file);
auto ef = new EndianStream(bf);
int bom = ef.readBOM();
TokenArray tokens = tokenizeStream(file, ef, bom);
delete bf;
// Check if this is a class or a module file first
if(MonsterClass.canParse(tokens))
fail("Cannot run " ~ file ~ " - it is a class or module.");
auto fd = new FuncDeclaration;
// Parse and comile the function
fd.parseFile(tokens, this);
fd.resolve(mc.sc);
fd.resolveBody();
fd.compile();
assert(fd.fn == this);
delete fd;
}
// Returns the function name, on the form Class.func()
char[] toString()
{ return owner.name.str ~ "." ~ name.str ~ "()"; }
private:
static const char[] int_class = "class _Func_Internal_;";
static MonsterClass int_mc;
static MonsterObject *int_mo;
}
// Responsible for parsing, analysing and compiling functions.
@ -191,7 +260,15 @@ class FuncDeclaration : Statement
// the VM when the compiler is done working.
Function *fn;
// Parse keywords allowed to be used on functions
// Is the 'override' keyword present
bool isOverride;
// Is this a stand-alone script file (not really needed)
bool isFile;
// Parse keywords allowed to be used on functions. This (and its
// borthers elsewhere) is definitely ripe for some pruning /
// refactoring.
private void parseKeywords(ref TokenArray toks)
{
Floc loc;
@ -213,7 +290,7 @@ class FuncDeclaration : Statement
}
if(isNext(toks, TT.Abstract, loc))
{
if(isNative)
if(isAbstract)
fail("Multiple token 'abstract' in function declaration",
loc);
isAbstract = true;
@ -227,10 +304,26 @@ class FuncDeclaration : Statement
isIdle = true;
continue;
}
if(isNext(toks, TT.Override, loc))
{
if(isOverride)
fail("Multiple token 'override' in function declaration",
loc);
isOverride = true;
continue;
}
if(isNext(toks, TT.Final, loc))
{
if(fn.isFinal)
fail("Multiple token 'final' in function declaration",
loc);
fn.isFinal = true;
continue;
}
break;
}
// Check that only one of the keywords are used
// Check that only one of the type keywords are used
if( (isAbstract && isNative) ||
(isAbstract && isIdle) ||
(isNative && isIdle) )
@ -243,6 +336,43 @@ class FuncDeclaration : Statement
else assert(fn.isNormal);
}
void parseFile(ref TokenArray toks, Function *fnc)
{
isFile = true;
fn = fnc;
fn.ftype = FuncType.Normal;
// Is there an explicit function declaration?
if(isNext(toks, TT.Function))
{
TokenArray temp = toks;
reqNext(temp, TT.Identifier);
// 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);
// In any case, parse the rest of the declaration
parseParams(toks);
isNext(toks, TT.Semicolon);
}
else
{
// No type, no parameters
fn.type = BasicType.getVoid;
fn.name.str = "script-file";
}
code = new CodeBlock(false, true);
code.parse(toks);
}
void parse(ref TokenArray toks)
{
// Create a Function struct. Will change later.
@ -265,6 +395,33 @@ class FuncDeclaration : Statement
// Parse any other keywords
parseKeywords(toks);
parseParams(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);
}
}
// Parse function name and parameters
void parseParams(ref TokenArray toks)
{
fn.name = next(toks);
loc = fn.name.loc;
if(fn.name.type != TT.Identifier)
@ -297,27 +454,6 @@ class FuncDeclaration : Statement
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?
@ -334,6 +470,8 @@ class FuncDeclaration : Statement
return
isNext(toks, TT.Native) ||
isNext(toks, TT.Abstract) ||
isNext(toks, TT.Override) ||
isNext(toks, TT.Final) ||
isNext(toks, TT.Idle);
}
@ -380,8 +518,16 @@ class FuncDeclaration : Statement
// types). The rest is handed by resolveBody()
void resolve(Scope last)
{
assert(fn.type !is null);
fn.type.resolve(last);
if(fn.type.isVar)
fail("var not allowed here", fn.type.loc);
if(fn.type.isReplacer)
fn.type = fn.type.getBase();
// Create a local scope for this function
sc = new FuncScope(last, fn);
@ -389,9 +535,16 @@ class FuncDeclaration : Statement
// in compile() and by external classes, so we store it.
fn.paramSize = 0;
foreach(vd; paramList)
fn.paramSize += vd.var.type.getSize();
{
// Resolve the variable first, to make sure we get the rigth
// size
vd.resolveParam(sc);
assert(!vd.var.type.isReplacer);
fn.paramSize += vd.var.type.getSize();
}
// Set the owner class.
// Set the owner class
fn.owner = sc.getClass();
// Parameters are given negative numbers according to their
@ -403,18 +556,16 @@ class FuncDeclaration : Statement
// TODO: Do fancy memory management
fn.params.length = paramList.length;
// Add function parameters to scope.
// Set up function parameter numbers and insert them into the
// list.
foreach(i, dec; paramList)
{
if(dec.var.type.isArray())
dec.allowConst = true;
dec.resolve(sc, pos);
assert(pos < 0);
dec.setNumber(pos);
pos += dec.var.type.getSize();
fn.params[i] = dec.var;
}
assert(pos == 0);
// Vararg functions must have the last parameter as an array.
if(fn.isVararg)
@ -427,6 +578,56 @@ class FuncDeclaration : Statement
}
assert(pos == 0, "Variable positions didn't add up");
// Do we override a function?
if(fn.overrides)
{
Function *o = fn.overrides;
assert(fn.owner !is null);
assert(o.owner !is null,
"overrided function must be resolved before us");
if(fn.owner is o.owner)
fail(format("Function %s is already declared on line %s",
fn.name.str, o.name.loc.line), fn.name.loc);
// Check that the function we're overriding isn't final
if(o.isFinal)
fail("Cannot override final function " ~ o.toString, fn.name.loc);
// Check that signatures match
if(o.type != fn.type)
fail(format("Cannot override %s with different return type (%s -> %s)",
fn.name.str, o.type, fn.type), fn.name.loc);
bool parFail = false;
if(o.params.length != fn.params.length) parFail = true;
else
foreach(i,p; fn.params)
if(p.type != o.params[i].type ||
p.isVararg != o.params[i].isVararg)
{
parFail = true;
break;
}
if(parFail)
fail(format("Cannot override %s, parameter types do not match",
o.toString), fn.name.loc);
// There's no big technical reason why this shouldn't be
// possible, but we haven't tested it or thought about it
// yet.
if(o.isIdle || fn.isIdle)
fail("Cannot override idle functions", fn.name.loc);
}
else
{
// No overriding. Make sure the 'override' flag isn't set.
if(isOverride)
fail("function " ~ fn.name.str ~
" doesn't override anything", fn.name.loc);
}
}
// Resolve the interior of the function
@ -468,3 +669,216 @@ class FuncDeclaration : Statement
fn.bcode = tasm.assemble(fn.lines);
}
}
// Expression representing a function call
class FunctionCallExpr : MemberExpression
{
Token name;
ExprArray params;
Function* fd;
bool isVararg;
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen);
}
// Read a parameter list (a,b,...)
static ExprArray getParams(ref TokenArray toks)
{
ExprArray res;
if(!isNext(toks, TT.LeftParen)) return res;
Expression exp;
// No parameters?
if(isNext(toks, TT.RightParen)) return res;
// Read the first parameter
res ~= Expression.identify(toks);
// Are there more?
while(isNext(toks, TT.Comma))
res ~= Expression.identify(toks);
if(!isNext(toks, TT.RightParen))
fail("Parameter list expected ')'", toks);
return res;
}
void parse(ref TokenArray toks)
{
name = next(toks);
loc = name.loc;
params = getParams(toks);
}
char[] toString()
{
char[] result = name.str ~ "(";
foreach(b; params)
result ~= b.toString ~" ";
return result ~ ")";
}
void resolve(Scope sc)
{
if(isMember) // Are we called as a member?
{
assert(leftScope !is null);
fd = leftScope.findFunc(name.str);
if(fd is null) fail(name.str ~ " is not a member function of "
~ leftScope.toString, loc);
}
else
{
assert(leftScope is null);
fd = sc.findFunc(name.str);
if(fd is null) fail("Undefined function "~name.str, name.loc);
}
// Is this an idle function?
if(fd.isIdle)
{
if(!sc.isStateCode)
fail("Idle functions can only be called from state code",
name.loc);
if(isMember)
fail("Idle functions cannot be called as members", name.loc);
}
type = fd.type;
assert(type !is null);
isVararg = fd.isVararg;
if(isVararg)
{
// The vararg parameter can match a variable number of
// arguments, including zero.
if(params.length < fd.params.length-1)
fail(format("%s() expected at least %s parameters, got %s",
name.str, fd.params.length-1, params.length),
name.loc);
}
else
// Non-vararg functions must match function parameter number
// exactly
if(params.length != fd.params.length)
fail(format("%s() expected %s parameters, got %s",
name.str, fd.params.length, params.length),
name.loc);
// Check parameter types
foreach(int i, par; fd.params)
{
// Handle varargs below
if(isVararg && i == fd.params.length-1)
break;
params[i].resolve(sc);
try par.type.typeCast(params[i]);
catch(TypeException)
fail(format("%s() expected parameter %s to be type %s, not type %s",
name.str, i+1, par.type.toString, params[i].typeString),
name.loc);
}
// Loop through remaining arguments
if(isVararg)
{
int start = fd.params.length-1;
assert(fd.params[start].type.isArray);
Type base = fd.params[start].type.getBase();
foreach(int i, ref par; params[start..$])
{
par.resolve(sc);
// If the first and last vararg parameter is of the
// array type itself, then we are sending an actual
// array. Treat it like a normal parameter.
if(i == 0 && start == params.length-1 &&
par.type == fd.params[start].type)
{
isVararg = false;
break;
}
// Otherwise, cast the type to the array base type.
try base.typeCast(par);
catch(TypeException)
fail(format("Cannot convert %s of type %s to %s", par.toString,
par.typeString, base.toString), par.loc);
}
}
}
// Used in cases where the parameters need to be evaluated
// separately from the function call. This is done by DotOperator,
// in cases like obj.func(expr); Here expr is evaluated first, then
// obj, and then finally the far function call. This is because the
// far function call needs to read 'obj' off the top of the stack.
void evalParams()
{
assert(pdone == false);
foreach(i, ex; params)
{
ex.eval();
// Convert 'const' parameters to actual constant references
if(i < fd.params.length-1) // Skip the last parameter (in
// case of vararg functions)
if(fd.params[i].isConst)
{
assert(fd.params[i].type.isArray);
tasm.makeArrayConst();
}
}
if(isVararg)
{
// Compute the length of the vararg array.
int len = params.length - fd.params.length + 1;
// If it contains no elements, push a null array reference
// (0 is always null).
if(len == 0) tasm.push(0);
else
// Converte the pushed values to an array index
tasm.popToArray(len, params[$-1].type.getSize());
}
// Handle the last parameter after everything has been
// pushed. This will work for both normal and vararg parameters.
if(fd.params.length != 0 && fd.params[$-1].isConst)
{
assert(fd.params[$-1].type.isArray);
tasm.makeArrayConst();
}
pdone = true;
}
bool pdone;
void evalAsm()
{
if(!pdone) evalParams();
setLine();
assert(fd.owner !is null);
if(isMember)
tasm.callFarFunc(fd.index, fd.owner.getTreeIndex());
else if(fd.isIdle)
tasm.callIdle(fd.index, fd.owner.getIndex());
else
tasm.callFunc(fd.index, fd.owner.getTreeIndex());
}
}

@ -29,6 +29,7 @@ import monster.compiler.assembler;
import monster.compiler.tokenizer;
import monster.compiler.scopes;
import monster.compiler.types;
import monster.compiler.functions;
import monster.vm.error;
import monster.vm.arrays;
@ -584,8 +585,8 @@ class AssignOperator : BinaryOperator
// Cast the right side to the left type, if possible.
try type.typeCast(right);
catch(TypeException)
fail("Assignment " ~tokenList[opType] ~ " not allowed for types " ~
left.typeString() ~ " and " ~ right.typeString(), loc);
fail("Assignment " ~tokenList[opType] ~ ": cannot implicitly cast " ~
right.typeString() ~ " to " ~ left.typeString(), loc);
assert(left.type == right.type);
}

@ -151,6 +151,7 @@ class ArrayProperties: SimplePropertyScope
}
}
// TODO: Rename to ObjectProperties
class ClassProperties : SimplePropertyScope
{
static ClassProperties singleton;

@ -32,13 +32,17 @@ import monster.compiler.statement;
import monster.compiler.expression;
import monster.compiler.tokenizer;
import monster.compiler.types;
import monster.compiler.assembler;
import monster.compiler.properties;
import monster.compiler.functions;
import monster.compiler.states;
import monster.compiler.structs;
import monster.compiler.enums;
import monster.compiler.variables;
import monster.vm.mclass;
import monster.vm.error;
import monster.vm.vm;
// The global scope
PackageScope global;
@ -109,6 +113,7 @@ abstract class Scope
bool isLoop() { return false; }
bool isClass() { return false; }
bool isArray() { return false; }
bool isStruct() { return false; }
bool isPackage() { return false; }
bool isProperty() { return false; }
@ -202,6 +207,18 @@ abstract class Scope
return parent.findFunc(name);
}
StructType findStruct(char[] name)
{
if(isRoot()) return null;
return parent.findStruct(name);
}
EnumType findEnum(char[] name)
{
if(isRoot()) return null;
return parent.findEnum(name);
}
void insertLabel(StateLabel *lb)
{
assert(!isRoot);
@ -448,7 +465,7 @@ final class PackageScope : Scope
// 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 findParsed(char[] name)
{
MonsterClass result = null;
@ -459,7 +476,7 @@ final class PackageScope : Scope
{
// Class not loaded. Check if the file exists.
char[] fname = classToFile(name);
if(MonsterClass.findFile(fname))
if(vm.findFile(fname))
{
// File exists. Load it right away. If the class is
// already forward referenced, this will be taken care
@ -570,16 +587,207 @@ abstract class VarScope : Scope
}
}
// 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
// A scope that can contain functions and variables
class FVScope : VarScope
{
protected:
HashTable!(char[], Function*) functions;
public:
this(Scope last, char[] name)
{ super(last, name); }
// Insert a function.
void insertFunc(Function* fd)
{
if(isClass)
{
// Are we overriding a function?
auto old = findFunc(fd.name.str);
// If there is no existing function, call clearId
if(old is null)
clearId(fd.name);
else
// We're overriding. Let fd know, and let it handle the
// details when it resolves.
fd.overrides = old;
}
else
// Non-class functions can never override anything
clearId(fd.name);
fd.index = functions.length;
// Store the function definition
functions[fd.name.str] = fd;
}
override:
void clearId(Token name)
{
assert(name.str != "");
Function* fd;
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);
}
// Let VarScope handle variables
super.clearId(name);
}
Function* findFunc(char[] name)
{
Function* fd;
if(functions.inList(name, fd))
return fd;
assert(!isRoot());
return parent.findFunc(name);
}
}
// Can contain types, functions and variables. 'Types' means structs,
// enums and other user-generated sub-types (may also include classes
// later.)
// The TFV, FV and Var scopes might (probably will) be merged at some
// point. I'm just keeping them separate for clearity while the scope
// structure is being developed.
class TFVScope : FVScope
{
private:
HashTable!(char[], StructType) structs;
HashTable!(char[], EnumType) enums;
public:
this(Scope last, char[] name)
{ super(last, name); }
void insertStruct(StructDeclaration sd)
{
clearId(sd.name);
structs[sd.name.str] = sd.type;
}
void insertEnum(EnumDeclaration sd)
{
clearId(sd.name);
enums[sd.name.str] = sd.type;
}
override:
void clearId(Token name)
{
assert(name.str != "");
StructType fd;
EnumType ed;
if(structs.inList(name.str, fd))
{
fail(format("Identifier '%s' already declared on line %s (as a struct)",
name.str, fd.loc),
name.loc);
}
if(enums.inList(name.str, ed))
{
fail(format("Identifier '%s' already declared on line %s (as an enum)",
name.str, ed.loc),
name.loc);
}
// Let VarScope handle variables
super.clearId(name);
}
StructType findStruct(char[] name)
{
StructType fd;
if(structs.inList(name, fd))
return fd;
assert(!isRoot());
return parent.findStruct(name);
}
EnumType findEnum(char[] name)
{
EnumType ed;
if(enums.inList(name, ed))
return ed;
assert(!isRoot());
return parent.findEnum(name);
}
}
// Lookup scope for enums. For simplicity we use the property system
// to handle enum members.
final class EnumScope : SimplePropertyScope
{
this() { super("EnumScope"); }
int index; // Index in a global enum index list. Do whatever you do
// with global class indices here.
void setup()
{
/*
insert("name", ArrayType.getString(), { tasm.getEnumName(index); });
// Replace these with the actual fields of the enum
insert("value", type1, { tasm.getEnumValue(index, field); });
// Some of them are static
inserts("length", "int", { tasm.push(length); });
inserts("last", "owner", { tasm.push(last); });
inserts("first", "owner", { tasm.push(first); });
*/
}
}
// Scope for the interior of structs
final class StructScope : VarScope
{
int offset;
StructType str;
this(Scope last, StructType t)
{
str = t;
assert(str !is null);
super(last, t.name);
}
bool isStruct() { return true; }
int addNewLocalVar(int varSize)
{ return (offset+=varSize) - varSize; }
// Define it only here since we don't need it anywhere else.
Scope getParent() { return parent; }
}
// A class scope. In addition to variables, and functions, classes can
// contain states and they keep track of the data segment size.
final class ClassScope : TFVScope
{
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
@ -611,22 +819,14 @@ final class ClassScope : VarScope
{
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
// Let the parent handle everything else
super.clearId(name);
}
@ -642,20 +842,6 @@ final class ClassScope : VarScope
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;
@ -666,17 +852,6 @@ final class ClassScope : VarScope
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
@ -745,6 +920,7 @@ abstract class StackScope : VarScope
int getPos() { return getTotLocals() + getExpStack(); }
}
// Scope used for the inside of functions
class FuncScope : StackScope
{
// Function definition, for function scopes

@ -56,8 +56,7 @@ class ExprStatement : Statement
void parse(ref TokenArray toks)
{
exp = Expression.identify(toks);
if(!isNext(toks, TT.Semicolon, loc))
fail("Statement expected ;", toks);
reqNext(toks, TT.Semicolon, loc);
}
char[] toString() { return "ExprStatement: " ~ exp.toString(); }
@ -67,89 +66,6 @@ class ExprStatement : Statement
void compile() { exp.evalPop(); }
}
// A variable declaration that works as a statement. Supports multiple
// variable declarations, ie. int i, j; but they must be the same
// type, so int i, j[]; is not allowed.
class VarDeclStatement : Statement
{
VarDeclaration[] vars;
static bool canParse(TokenArray toks)
{
if(Type.canParseRem(toks) &&
isNext(toks, TT.Identifier))
return true;
return false;
}
void parse(ref TokenArray toks)
{
VarDeclaration varDec;
varDec = new VarDeclaration;
varDec.parse(toks);
vars ~= varDec;
loc = varDec.var.name.loc;
int arr = varDec.arrays();
// Are there more?
while(isNext(toks, TT.Comma))
{
// Read a variable, but with the same type as the last
varDec = new VarDeclaration(varDec.var.type);
varDec.parse(toks);
if(varDec.arrays() != arr)
fail("Multiple declarations must have same type",
varDec.var.name.loc);
vars ~= varDec;
}
if(!isNext(toks, TT.Semicolon))
fail("Declaration statement expected ;", toks);
}
char[] toString()
{
char[] res = "Variable declaration: ";
foreach(vd; vars) res ~= vd.toString ~" ";
return res;
}
// Special version used for function parameters, to set the number
// explicitly.
void resolve(Scope sc, int num)
{
assert(vars.length == 1);
vars[0].resolve(sc, num);
}
void resolve(Scope sc)
{
if(sc.isStateCode())
fail("Variable declarations not allowed in state code", loc);
// Add variables to the scope.
foreach(vd; vars)
vd.resolve(sc);
}
// Validate types
void validate()
{
assert(vars.length >= 1);
vars[0].var.type.validate();
}
// Insert local variable(s) on the stack.
void compile()
{
// Compile the variable declarations, they will push the right
// values to the stack.
foreach(vd; vars)
vd.compile();
}
}
// Destination for a goto statement, on the form 'label:'. This
// statement is actually a bit complicated, since it must handle jumps
// occuring both above and below the label. Seeing as the assembler
@ -226,10 +142,8 @@ class LabelStatement : Statement
{
// canParse has already checked the token types, all we need to
// do is to fetch the name.
if(!isNext(toks, TT.Identifier, lb.name))
assert(0, "Internal error");
if(!isNext(toks, TT.Colon))
assert(0, "Internal error");
reqNext(toks, TT.Identifier, lb.name);
reqNext(toks, TT.Colon);
loc = lb.name.loc;
}
@ -325,15 +239,13 @@ class GotoStatement : Statement, LabelUser
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.Goto, loc))
assert(0, "Internal error");
reqNext(toks, TT.Goto, loc);
// Read the label name
if(!isNext(toks, TT.Identifier, labelName))
fail("goto expected label identifier", toks);
if(!isNext(toks, TT.Semicolon))
fail("goto statement expected ;", toks);
reqNext(toks, TT.Semicolon);
}
void resolve(Scope sc)
@ -1299,7 +1211,9 @@ class IfStatement : Statement
// Resolve all parts
condition.resolve(sc);
if(!condition.type.isBool)
if(!condition.type.isBool &&
!condition.type.isArray &&
!condition.type.isObject)
fail("if condition " ~ condition.toString ~ " must be a bool, not "
~condition.type.toString, condition.loc);
@ -1314,6 +1228,17 @@ class IfStatement : Statement
// Push the conditionel expression
condition.eval();
// For arrays, get the length
if(condition.type.isArray)
tasm.getArrayLength();
// TODO: For objects, the null reference will automatically
// evaluate to false. However, we should make a "isValid"
// instruction, both to check if the object is dead (deleted)
// and to guard against any future changes to the object index
// type.
assert(condition.type.getSize == 1);
// Jump if the value is zero. Get a label reference.
int label = tasm.jumpz();
@ -1431,6 +1356,7 @@ class CodeBlock : Statement
Floc endLine; // Last line, used in error messages
bool isState; // True if this block belongs to a state
bool isFile; // True if this is a stand-alone file
private:
@ -1475,8 +1401,13 @@ class CodeBlock : Statement
public:
this(bool isState = false)
{ this.isState = isState; }
this(bool isState = false, bool isFile = false)
{
this.isState = isState;
this.isFile = isFile;
assert(!isFile || !isState);
}
static bool canParse(TokenArray toks)
{
@ -1485,25 +1416,41 @@ class CodeBlock : Statement
void parse(ref TokenArray toks)
{
if(toks.length == 0)
fail("Code block expected, got end of file");
Token strt;
if(!isFile)
{
if(toks.length == 0)
fail("Code block expected, got end of file");
Token strt = toks[0];
if(strt.type != TT.LeftCurl)
fail("Code block expected a {", toks);
strt = toks[0];
if(strt.type != TT.LeftCurl)
fail("Code block expected a {", toks);
loc = strt.loc;
loc = strt.loc;
toks = toks[1..toks.length];
toks = toks[1..toks.length];
}
// Are we parsing stuff that comes before the code? Only
// applicable to state blocks.
bool beforeCode = isState;
while(!isNext(toks, TT.RightCurl, endLine))
bool theEnd()
{
if(isFile)
return isNext(toks, TT.EOF, endLine);
else
return isNext(toks, TT.RightCurl, endLine);
}
while(!theEnd())
{
if(toks.length == 0)
fail(format("Unterminated code block (starting at line %s)", strt.loc));
{
if(!isFile)
fail(format("Unterminated code block (starting at line %s)", strt.loc));
else break;
}
if(beforeCode)
{
@ -1571,3 +1518,13 @@ class CodeBlock : Statement
tasm.pop(sc.getLocals);
}
}
// Used for declarations that create new types, like enum, struct,
// etc.
abstract class TypeDeclaration : Statement
{
override void compile() {}
// Insert the type into the scope. This is called before resolve().
void insertType(TFVScope sc);
}

@ -0,0 +1,167 @@
/*
Monster - an advanced game scripting language
Copyright (C) 2007, 2008 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (structs.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.structs;
import monster.compiler.types;
import monster.compiler.scopes;
import monster.compiler.variables;
import monster.compiler.functions;
import monster.compiler.tokenizer;
import monster.compiler.statement;
import monster.vm.error;
import std.stdio;
class StructDeclaration : TypeDeclaration
{
StructType type;
Token name;
private:
FuncDeclaration[] funcdecs;
VarDeclStatement[] vardecs;
// Identify what kind of block the given set of tokens represent,
// parse them, and store it in the appropriate list;
void store(ref TokenArray toks)
{
// canParse() is not ment as a complete syntax test, only to be
// enough to identify which Block parser to apply.
if(FuncDeclaration.canParse(toks))
{
auto fd = new FuncDeclaration;
funcdecs ~= fd;
fd.parse(toks);
}
else if(VarDeclStatement.canParse(toks))
{
auto vd = new VarDeclStatement;
vd.parse(toks);
vardecs ~= vd;
}
else
fail("Illegal type or declaration", toks);
}
public:
static bool canParse(TokenArray toks)
{ return toks.isNext(TT.Struct); }
override:
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.Struct, loc))
fail("Internal error in StructDeclaration");
if(!isNext(toks, TT.Identifier, name))
fail("Expected struct name", toks);
if(!isNext(toks, TT.LeftCurl))
fail("Struct expected {", toks);
// Parse the rest of the file
while(!isNext(toks, TT.RightCurl))
store(toks);
// Allow an optional semicolon
isNext(toks, TT.Semicolon);
}
void insertType(TFVScope last)
{
// Set up the struct type.
type = new StructType(this);
// Create a new scope
type.sc = new StructScope(last, type);
// Insert ourselves into the parent scope
assert(last !is null);
last.insertStruct(this);
}
void resolve(Scope last)
{
if(type.set)
return;
// Get the total number of variables.
uint tot = 0;
foreach(dec; vardecs)
tot += dec.vars.length;
// Must have at least one variable per declaration statement
assert(tot >= vardecs.length);
// Get one array containing all the variables
Variable*[] vars = new Variable*[tot];
int ind = 0;
foreach(st; vardecs)
foreach(dec; st.vars)
vars[ind++] = dec.var;
assert(ind == tot);
// Store the variables in the StructType
type.vars = vars;
// Mark the size as "set" now.
type.set = true;
// Resolve
foreach(dec; vardecs)
dec.resolve(type.sc);
// Calculate the struct size
type.size = 0;
foreach(t; vars)
type.size += t.type.getSize();
// Set up the init value
ind = 0;
int[] init = new int[type.getSize()];
foreach(st; vardecs)
foreach(dec; st.vars)
{
int si = dec.var.type.getSize();
init[ind..ind+si] = dec.getCTimeValue();
ind += si;
}
assert(ind == init.length);
type.defInit = init;
// Functions:
// Disallow anything but normal functions (we can fix static and
// native struct functions later.)
// Struct resolve only resolves header information, it doesn't
// resolve function bodies.
assert(funcdecs.length == 0, "struct functions not supported yet");
/*
foreach(dec; funcdecs)
type.sc.insertFunc(dec);
*/
}
}

@ -91,7 +91,7 @@ enum TT
// 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,
Class, Module,
For,
If,
Else,
@ -107,10 +107,11 @@ enum TT
Switch,
Select,
State,
Singleton, Clone,
Struct, Enum, Thread,
Singleton, Clone, Override, Final, Function,
This, New, Static, Const, Out, Ref, Abstract, Idle,
Public, Private, Protected, True, False, Native, Null,
Goto, Halt, Auto, Var, In,
Goto, Halt, Var, In,
Last, // Tokens after this do not have a specific string
// associated with them.
@ -184,7 +185,7 @@ const char[][] tokenList =
TT.MinusEq : "-=",
TT.MultEq : "*=",
TT.DivEq : "/=",
TT.RemEq : "%%=",
TT.RemEq : "%=",
TT.IDivEq : "\\=",
TT.CatEq : "~=",
@ -196,10 +197,11 @@ const char[][] tokenList =
TT.Minus : "-",
TT.Mult : "*",
TT.Div : "/",
TT.Rem : "%%",
TT.Rem : "%",
TT.IDiv : "\\",
TT.Class : "class",
TT.Module : "module",
TT.Return : "return",
TT.For : "for",
TT.This : "this",
@ -216,12 +218,18 @@ const char[][] tokenList =
TT.Switch : "switch",
TT.Select : "select",
TT.State : "state",
TT.Struct : "struct",
TT.Enum : "enum",
TT.Thread : "thread",
TT.Typeof : "typeof",
TT.Singleton : "singleton",
TT.Clone : "clone",
TT.Static : "static",
TT.Const : "const",
TT.Abstract : "abstract",
TT.Override : "override",
TT.Final : "final",
TT.Function : "function",
TT.Idle : "idle",
TT.Out : "out",
TT.Ref : "ref",
@ -234,7 +242,6 @@ const char[][] tokenList =
TT.Null : "null",
TT.Goto : "goto",
TT.Halt : "halt",
TT.Auto : "auto",
TT.Var : "var",
TT.In : "in",
];
@ -519,11 +526,13 @@ class StreamTokenizer
// 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 '%'.
// also explicitly allow '.' dots.
int len = 1;
bool lastDot = false; // Was the last char a '.'?
bool lastPer = false; // Was it a '%'?
// I've tried with percentage literals (10% = 0.10), but it
// conflicts with the remainder division operator (which
// shouldn't change), so I've disabled it for now.
//bool lastPer = false; // Was it a '%'?
foreach(char ch; line[1..$])
{
if(ch == '.')
@ -537,6 +546,7 @@ class StreamTokenizer
}
lastDot = true;
}
/*
else if(ch == '%')
{
// Ditto for percentage signs. We allow '%' but not
@ -548,11 +558,12 @@ class StreamTokenizer
}
lastPer = true;
}
*/
else
{
if(!validIdentChar(ch)) break;
lastDot = false;
lastPer = false;
//lastPer = false;
}
// This was a valid character, count it

@ -24,6 +24,7 @@
module monster.compiler.types;
import monster.compiler.tokenizer;
import monster.compiler.enums;
import monster.compiler.scopes;
import monster.compiler.expression;
import monster.compiler.assembler;
@ -32,6 +33,8 @@ import monster.compiler.block;
import monster.compiler.functions;
import monster.compiler.variables;
import monster.compiler.states;
import monster.compiler.structs;
import monster.vm.arrays;
import monster.vm.mclass;
import monster.vm.error;
@ -43,11 +46,18 @@ import std.string;
Type (abstract)
InternalType (abstract)
ReplacerType (abstract)
NullType (null expression)
BasicType (covers int, char, bool, float, void)
ObjectType
ArrayType
MetaType
StructType
EnumType
UserType (replacer for identifier-named types like structs and
classes)
TypeofType (replacer for typeof(x) when used as a type)
GenericType (var)
MetaType (type of type expressions, like writeln(int);)
*/
class TypeException : Exception
@ -77,10 +87,15 @@ abstract class Type : Block
// 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.Typeof))
{
reqNext(toks, TT.LeftParen);
Expression.identify(toks); // Possibly a bit wasteful...
reqNext(toks, TT.RightParen);
return true;
}
if(isNext(toks, TT.Var)) return true;
if(!isNext(toks, TT.Identifier)) return false;
@ -112,8 +127,9 @@ abstract class Type : Block
// 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 if(UserType.canParse(toks)) t = new UserType();
else if(GenericType.canParse(toks)) t = new GenericType();
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.
@ -123,6 +139,9 @@ abstract class Type : Block
// an ArrayType.
exps = VarDeclaration.getArray(toks, takeExpr);
if(t.isVar && exps.length)
fail("Cannot have arrays of var (yet)", t.loc);
// Despite looking strange, this code is correct.
foreach(e; exps)
t = new ArrayType(t);
@ -140,6 +159,14 @@ abstract class Type : Block
// The complete type name including specifiers, eg. "int[]".
char[] name;
MetaType meta;
final MetaType getMeta()
{
if(meta is null)
meta = new MetaType(this);
return meta;
}
// Used for easy checking
bool isInt() { return false; }
bool isUint() { return false; }
@ -151,10 +178,15 @@ abstract class Type : Block
bool isDouble() { return false; }
bool isVoid() { return false; }
bool isVar() { return false; }
bool isString() { return false; }
bool isObject() { return false; }
bool isArray() { return arrays() != 0; }
bool isObject() { return false; }
bool isStruct() { return false; }
bool isReplacer() { return false; }
bool isIntegral() { return isInt || isUint || isLong || isUlong; }
bool isFloating() { return isFloat || isDouble; }
@ -339,6 +371,9 @@ abstract class Type : Block
assert(0, "doCastCTime not implemented for type " ~ toString);
}
void parse(ref TokenArray toks) {assert(0, name);}
void resolve(Scope sc) {assert(0, name);}
/* Cast two expressions to their common type, if any. Throw a
TypeException exception if not possible. This exception should be
caught elsewhere to give a more useful error message. Examples of
@ -409,10 +444,8 @@ 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);}
int[] defaultInit() {assert(0, name);}
int getSize() {assert(0, name);}
}
// Handles the 'null' literal. This type is only used for
@ -500,7 +533,6 @@ class BasicType : Type
{
Token t;
if(!isNext(toks, TT.Identifier, t)) return false;
return isBasic(t.str);
}
@ -509,8 +541,8 @@ class BasicType : Type
{
Token t;
if(!isNext(toks, TT.Identifier, t) || !isBasic(t.str))
assert(0, "Internal error in BasicType.parse()");
reqNext(toks, TT.Identifier, t);
assert(isBasic(t.str));
// Get the name and the line from the token
name = t.str;
@ -730,7 +762,13 @@ class ObjectType : Type
public:
this() {}
this(Token t)
{
// Get the name and the line from the token
name = t.str;
loc = t.loc;
}
this(MonsterClass mc)
{
assert(mc !is null);
@ -739,12 +777,6 @@ class ObjectType : Type
clsIndex = mc.gIndex;
}
static bool canParse(TokenArray toks)
{
if(!isNext(toks, TT.Identifier)) return false;
return true;
}
MonsterClass getClass()
{
assert(clsIndex != 0);
@ -799,9 +831,6 @@ class ObjectType : Type
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;
}
@ -816,18 +845,6 @@ class ObjectType : Type
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
@ -844,6 +861,21 @@ class ArrayType : Type
// What type is this an array of?
Type base;
static ArrayType str;
static ArrayType getString()
{
if(str is null)
str = new ArrayType(BasicType.getChar);
return str;
}
static ArrayType get(Type base)
{
// TODO: We can cache stuff here later
return new ArrayType(base);
}
this(Type btype)
{
base = btype;
@ -869,81 +901,291 @@ class ArrayType : Type
return base.isChar();
}
void parse(ref TokenArray toks)
{ assert(0, "array types aren't parsed"); }
void resolve(Scope sc)
{
base.resolve(sc);
if(base.isReplacer())
base = base.getBase();
}
}
// 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
class EnumType : Type
{
// The scope contains the actual enum values
EnumScope sc;
this(EnumDeclaration ed)
{
name = ed.name.str;
loc = ed.name.loc;
}
int[] defaultInit() { return [0]; }
int getSize() { return 1; }
void resolve(Scope sc) {}
// can cast to int and to string, but not back
// Scope getMemberScope() { return sc; }
Scope getMemberScope()
{ return GenericProperties.singleton; }
}
class StructType : Type
{
private:
static MetaType[char[]] store;
StructDeclaration sd;
BasicType base;
void setup()
{
if(!set)
sd.resolve(sc.getParent);
else
{
// We might get here if this function is being called
// recursively from sd.resolve. This only happens when the
// struct contains itself somehow. In that case, size will
// not have been set yet.
if(size == -1)
fail("Struct " ~ name ~ " indirectly contains itself", loc);
}
assert(set);
assert(size != -1);
}
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)
int size = -1;
StructScope sc; // Scope of the struct interior
int[] defInit;
bool set; // Have vars and defInit been set?
// Member variables
Variable*[] vars;
this(StructDeclaration sdp)
{
if(tn in store) return store[tn];
sd = sdp;
name = sd.name.str;
loc = sd.name.loc;
}
return new MetaType(tn);
override:
// Calls validate for all member types
void validate()
{
foreach(v; vars)
v.type.validate();
}
this(char[] baseType)
// Resolves member
void resolve(Scope sc) {}
bool isStruct() { return true; }
int getSize()
{
base = BasicType.get(baseType);
name = base.toString;
loc = base.loc;
setup();
assert(size != -1);
return size;
}
store[name] = this;
int[] defaultInit()
{
setup();
assert(defInit.length == getSize);
return defInit;
}
// Return the scope belonging to the base type. This makes int.max
// work just like i.max.
Scope getMemberScope() { return base.getMemberScope(); }
Scope getMemberScope()
{ return GenericProperties.singleton; }
// Scope getMemberScope() { return sc; }
}
/*
// A type that mimics another after it is resolved. Used in cases
// where we can't know the real type until the resolve phase.
abstract class Doppelganger : Type
{
private:
bool resolved;
Type realType;
public:
override:
int getSize()
{
assert(resolved);
return realType.getSize();
}
}
*/
// A 'delayed lookup' type - can replace itself after resolve is
// called.
// OK - SCREW THIS. Make a doppelganger instead.
abstract class ReplacerType : InternalType
{
protected:
Type realType;
public:
override:
Type getBase() { assert(realType !is null); return realType; }
bool isReplacer() { return true; }
}
// Type names that consist of an identifier - classes, structs, enums,
// etc. We can't know what type it is until we resolve it.
class UserType : ReplacerType
{
private:
Token id;
public:
static bool canParse(TokenArray toks)
{
if(!isNext(toks, TT.Identifier)) return false;
return true;
}
void parse(ref TokenArray toks)
{
reqNext(toks, TT.Identifier, id);
// Get the name and the line from the token
name = id.str~"(replacer)";
loc = id.loc;
}
void resolve(Scope sc)
{
realType = sc.findStruct(id.str);
if(realType is null)
// Not a struct. Maybe an enum?
realType = sc.findEnum(id.str);
if(realType is null)
// Default to class name if nothing else is found. We can't
// really check this here since it might be a forward
// reference. These are handled later on.
realType = new ObjectType(id);
realType.resolve(sc);
assert(realType !is this);
assert(!realType.isReplacer);
}
}
class TypeofType : ReplacerType
{
static bool canParse(TokenArray toks)
{ return isNext(toks, TT.Typeof); }
Expression exp;
void parse(ref TokenArray toks)
{
reqNext(toks, TT.Typeof, loc);
reqNext(toks, TT.LeftParen);
exp = Expression.identify(toks);
reqNext(toks, TT.RightParen);
}
void resolve(Scope sc)
{
// Resolve the expression in the context of the scope
exp.resolve(sc);
if(exp.type.isMeta)
fail("Cannot use typeof on a meta type", exp.loc);
realType = exp.type;
}
}
// The 'var' type - generic type. Currently just works like 'auto' in
// D.
class GenericType : InternalType
{
this() { name = "var"; }
static bool canParse(TokenArray toks)
{ return isNext(toks, TT.Var); }
override:
bool isVar() { return true; }
void parse(ref TokenArray toks)
{
reqNext(toks, TT.Var, loc);
}
void resolve(Scope sc) {}
}
// I never meta type I didn't like!
class MetaType : InternalType
{
protected:
Type base;
AIndex ai;
public:
static Type getBasic(char[] basic)
{
return BasicType.get(basic).getMeta();
}
this(Type b)
{
base = b;
name = "(meta)"~base.toString;
ai = 0;
}
Type getBase() { return base; }
bool isMeta() { return true; }
bool canCastTo(Type type)
{
return false;// type.isString;
return type.isString;
}
void evalCastTo(Type to)
{
assert(to.isString);
// TODO: Fix static strings soon
assert(0, "not supported yet");
// Create an array index and store it for reuse later.
if(ai == 0)
{
auto arf = monster.vm.arrays.arrays.create(base.toString);
arf.flags.set(AFlags.Const);
ai = arf.getIndex();
}
assert(ai != 0);
tasm.push(cast(uint)ai);
}
// Return the scope belonging to the base type. This makes int.max
// work just like i.max. Differentiation between static and
// non-static members is handled in the expression resolves.
Scope getMemberScope() { return base.getMemberScope(); }
Type getBase() { return base; }
}
/*
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
TableType - a combination of lists, structures, hashmaps and
arrays. Loosely based on Lua tables, but not the same.
FunctionType - pointer to a function with a given header. Since all
Monster functions are object members (methods), all

@ -28,10 +28,14 @@ import monster.compiler.types;
import monster.compiler.tokenizer;
import monster.compiler.expression;
import monster.compiler.scopes;
import monster.compiler.statement;
import monster.compiler.block;
import monster.compiler.assembler;
import std.string;
import std.stdio;
import monster.vm.mclass;
import monster.vm.error;
enum VarType
@ -240,20 +244,85 @@ class VarDeclaration : Block
return res;
}
// Special version used for explicitly numbering function
// parameters. Only called from FuncDeclaration.resolve()
void resolve(Scope sc, int num)
bool isParam = false;
// Special version only called from FuncDeclaration.resolve()
void resolveParam(Scope sc)
{
assert(num<0, "VarDec.resolve was given a positive num: " ~ .toString(num));
var.number = num;
isParam = true;
resolve(sc);
}
// Sets var.number. Only for function parameters.
void setNumber(int num)
{
assert(num<0, "VarDec.setNumber was given a positive num: " ~ .toString(num));
assert(isParam);
var.number = num;
}
// Calls resolve() for all sub-expressions
override void resolve(Scope sc)
{
var.type.resolve(sc);
//writefln("Type is: %s", var.type);
if(var.type.isReplacer)
{
//writefln(" (we're here!)");
var.type = var.type.getBase();
}
//writefln(" now it is: %s", var.type);
// Allow 'const' for function array parameters
if(isParam && var.type.isArray())
allowConst = true;
// Handle initial value normally
if(init !is null)
{
init.resolve(sc);
// If 'var' is present, just copy the type of the init value
if(var.type.isVar)
var.type = init.type;
else
{
// Convert type, if necessary.
try var.type.typeCast(init);
catch(TypeException)
fail(format("Cannot initialize %s of type %s with %s of type %s",
var.name.str, var.type,
init, init.type), loc);
}
assert(init.type == var.type);
}
// If it's a struct, check that the variable is not part of
// itself.
if(var.type.isStruct)
{
auto st = cast(StructType)var.type;
if(st.sc is sc)
{
// We are inside ourselves
assert(sc.isStruct);
fail("Struct variables cannot be used inside the struct itself!",
loc);
}
}
// We can't have var at this point
if(var.type.isVar)
fail("cannot implicitly determine type", loc);
// Illegal types are illegal
if(!var.type.isLegal)
fail("Cannot create variables of type " ~ var.type.toString, loc);
if(!allowConst && var.isConst)
fail("'const' is not allowed here", loc);
@ -261,10 +330,10 @@ class VarDeclaration : Block
var.sc = cast(VarScope)sc;
assert(var.sc !is null, "variables can only be declared in VarScopes");
if(var.number == 0)
if(!isParam)
{
// If 'number' has not been set at this point (ie. we are
// not a function parameter), we must get it from the scope.
// If we are not a function parameter, we must get
// var.number from the scope.
if(sc.isClass())
// Class variable. Get a position in the data segment.
var.number = sc.addNewDataVar(var.type.getSize());
@ -275,23 +344,32 @@ class VarDeclaration : Block
}
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);
}
int[] getCTimeValue()
out(res)
{
assert(res.length == var.type.getSize, "Size mismatch");
}
body
{
// Does this variable have an initializer?
if(init !is null)
{
// And can it be evaluated at compile time?
if(!init.isCTime)
fail("Expression " ~ init.toString ~
" is not computable at compile time", init.loc);
return init.evalCTime();
}
else
// Use the default initializer.
return var.type.defaultInit();
}
// Executed for local variables upon declaration. Push the variable
// on the stack.
void compile()
@ -309,3 +387,441 @@ class VarDeclaration : Block
var.type.pushInit();
}
}
// Represents a reference to a variable. Simply stores the token
// representing the identifier. Evaluation is handled by the variable
// declaration itself. This allows us to use this class for local and
// global variables as well as for properties, without handling each
// case separately. The special names (currently __STACK__) are
// handled internally.
class VariableExpr : MemberExpression
{
Token name;
Variable *var;
Property prop;
enum VType
{
None, // Should never be set
LocalVar, // Local variable
ThisVar, // Variable in this object and this class
ParentVar, // Variable in another class but this object
FarOtherVar, // Another class, another object
Property, // Property (like .length of arrays)
Special, // Special name (like __STACK__)
Type, // Typename
}
VType vtype;
int classIndex = -1; // Index of the class that owns this variable.
CIndex singCls = -1; // Singleton class index
static bool canParse(TokenArray toks)
{
return
isNext(toks, TT.Identifier) ||
isNext(toks, TT.Singleton) ||
isNext(toks, TT.State) ||
isNext(toks, TT.Clone) ||
isNext(toks, TT.Const);
}
// Does this variable name refer to a type name rather than an
// actual variable?
bool isType()
{
return type.isMeta();
}
bool isProperty()
out(res)
{
if(res)
{
assert(prop.name != "");
assert(var is null);
assert(!isSpecial);
}
else
{
assert(prop.name == "");
}
}
body
{
return vtype == VType.Property;
}
bool isSpecial() { return vtype == VType.Special; }
override:
char[] toString() { return name.str; }
// Ask the variable if we can write to it.
bool isLValue()
{
// Specials are read only
if(isSpecial)
return false;
// Properties may or may not be changable
if(isProperty)
return prop.isLValue;
// Normal variables are always lvalues.
return true;
}
bool isStatic()
{
// Properties can be static
if(isProperty)
return prop.isStatic;
// Type names are always static. However, isType will return
// false for type names of eg. singletons, since these will not
// resolve to a meta type.
if(isType)
return true;
return false;
}
// TODO: isCTime - should be usable for static properties and members
void parse(ref TokenArray toks)
{
name = next(toks);
loc = name.loc;
}
void writeProperty()
{
assert(isProperty);
prop.setValue();
}
void resolve(Scope sc)
out
{
// Some sanity checks on the result
if(isProperty) assert(var is null);
if(var !is null)
{
assert(var.sc !is null);
assert(!isProperty);
}
assert(type !is null);
assert(vtype != VType.None);
}
body
{
if(isMember) // Are we called as a member?
{
// Look up the name in the scope belonging to the owner
assert(leftScope !is null);
// Check first if this is a variable
var = leftScope.findVar(name.str);
if(var !is null)
{
// We are a member variable
type = var.type;
// The object pointer is pushed on the stack. We must
// also provide the class index, so the variable is
// changed in the correct class (it could be a parent
// class of the given object.)
vtype = VType.FarOtherVar;
assert(var.sc.isClass);
classIndex = var.sc.getClass().getIndex();
return;
}
// Check for properties last
if(leftScope.findProperty(name, ownerType, prop))
{
// We are a property
vtype = VType.Property;
type = prop.getType;
return;
}
// No match
fail(name.str ~ " is not a member of " ~ ownerType.toString,
loc);
}
// Not a member
// Look for reserved names first.
if(name.str == "__STACK__")
{
vtype = VType.Special;
type = BasicType.getInt;
return;
}
if(name.type == TT.Const)
fail("Cannot use const as a variable", name.loc);
if(name.type == TT.Clone)
fail("Cannot use clone as a variable", name.loc);
// These are special cases that work both as properties
// (object.state) and as non-member variables (state=...) inside
// class functions / state code. Since we already handle them
// nicely as properties, treat them as properties.
if(name.type == TT.Singleton || name.type == TT.State)
{
if(!sc.isInClass)
fail(name.str ~ " can only be used in classes", name.loc);
if(!sc.findProperty(name, sc.getClass().objType, prop))
assert(0, "should have found property " ~ name.str ~
" in scope " ~ sc.toString);
vtype = VType.Property;
type = prop.getType;
return;
}
// Not a member, property or a special name. Look ourselves up
// in the local variable scope.
var = sc.findVar(name.str);
if(var !is null)
{
type = var.type;
assert(var.sc !is null);
// Class variable?
if(var.sc.isClass)
{
// Check if it's in THIS class, which is a common
// case. If so, we can use a simplified instruction that
// doesn't have to look up the class.
if(var.sc.getClass is sc.getClass)
vtype = VType.ThisVar;
else
{
// It's another class. For non-members this can only
// mean a parent class.
vtype = VType.ParentVar;
classIndex = var.sc.getClass().getIndex();
}
}
else
vtype = VType.LocalVar;
return;
}
// We are not a variable. Our last chance is a type name.
vtype = VType.Type;
if(BasicType.isBasic(name.str))
{
// Yes! Basic type.
type = MetaType.getBasic(name.str);
}
// Class name?
else if(auto mc = global.findParsed(name.str))
{
// This doesn't allow forward references.
mc.requireScope();
type = mc.classType;
// Singletons are treated differently - the class name can
// be used to access the singleton object
if(mc.isSingleton)
{
type = mc.objType;
singCls = mc.getIndex();
}
}
// Struct?
else if(auto tp = sc.findStruct(name.str))
{
type = tp.getMeta();
}
// Err, enum?
else if(auto tp = sc.findEnum(name.str))
{
type = tp.getMeta();
}
else
// No match at all
fail("Undefined identifier "~name.str, name.loc);
}
void evalAsm()
{
assert(!isType);
setLine();
// Special name
if(isSpecial)
{
if(name.str == "__STACK__")
tasm.getStack();
else assert(0, "Unknown special name " ~ name.str);
return;
}
// Property
if(isProperty)
{
prop.getValue();
return;
}
// Class singleton name
if(singCls != -1)
{
assert(type.isObject);
// Convert the class index into a object index at runtime
tasm.pushSingleton(singCls);
return;
}
// Normal variable
int s = type.getSize;
if(vtype == VType.LocalVar)
// This is a variable local to this function. The number gives
// the stack position.
tasm.pushLocal(var.number, s);
else if(vtype == VType.ThisVar)
// The var.number gives the offset into the data segment in
// this class
tasm.pushClass(var.number, s);
else if(vtype == VType.ParentVar)
// Variable in a parent but this object
tasm.pushParentVar(var.number, classIndex, s);
else if(vtype == VType.FarOtherVar)
// Push the value from a "FAR pointer". The class index should
// already have been pushed on the stack by DotOperator, we
// only push the index.
tasm.pushFarClass(var.number, classIndex, s);
else assert(0);
}
// Push the address of the variable rather than its value
void evalDest()
{
assert(!isType, "types can never be written to");
assert(isLValue());
assert(!isProperty);
setLine();
// No size information is needed for addresses.
if(vtype == VType.LocalVar)
tasm.pushLocalAddr(var.number);
else if(vtype == VType.ThisVar)
tasm.pushClassAddr(var.number);
else if(vtype == VType.ParentVar)
tasm.pushParentVarAddr(var.number, classIndex);
else if(vtype == VType.FarOtherVar)
tasm.pushFarClassAddr(var.number, classIndex);
else assert(0);
}
void postWrite()
{
assert(!isProperty);
assert(isLValue());
assert(var.sc !is null);
if(var.isRef)
// TODO: This assumes all ref variables are foreach values,
// which will probably not be true in the future.
tasm.iterateUpdate(var.sc.getLoopStack());
}
}
// A variable declaration that works as a statement. Supports multiple
// variable declarations, ie. int i, j; but they must be the same
// type, so int i, j[]; is not allowed.
class VarDeclStatement : Statement
{
VarDeclaration[] vars;
static bool canParse(TokenArray toks)
{
if(Type.canParseRem(toks) &&
isNext(toks, TT.Identifier))
return true;
return false;
}
void parse(ref TokenArray toks)
{
VarDeclaration varDec;
varDec = new VarDeclaration;
varDec.parse(toks);
vars ~= varDec;
loc = varDec.var.name.loc;
int arr = varDec.arrays();
// Are there more?
while(isNext(toks, TT.Comma))
{
// Read a variable, but with the same type as the last
varDec = new VarDeclaration(varDec.var.type);
varDec.parse(toks);
if(varDec.arrays() != arr)
fail("Multiple declarations must have same type",
varDec.var.name.loc);
vars ~= varDec;
}
if(!isNext(toks, TT.Semicolon))
fail("Declaration statement expected ;", toks);
}
char[] toString()
{
char[] res = "Variable declaration: ";
foreach(vd; vars) res ~= vd.toString ~" ";
return res;
}
void resolve(Scope sc)
{
if(sc.isStateCode())
fail("Variable declarations not allowed in state code", loc);
// Add variables to the scope.
foreach(vd; vars)
vd.resolve(sc);
}
// Validate types
void validate()
{
assert(vars.length >= 1);
vars[0].var.type.validate();
}
// Insert local variable(s) on the stack.
void compile()
{
// Compile the variable declarations, they will push the right
// values to the stack.
foreach(vd; vars)
vd.compile();
}
}

@ -25,6 +25,7 @@ module monster.vm.fstack;
import monster.vm.codestream;
import monster.vm.mobject;
import monster.vm.mclass;
import monster.vm.stack;
import monster.vm.error;
import monster.compiler.states;
@ -59,6 +60,7 @@ struct StackPoint
SPType ftype;
MonsterObject *obj; // "this"-pointer for the function
MonsterClass cls; // class owning the function
int afterStack; // Where the stack should be when this function
// returns
@ -109,12 +111,13 @@ struct FunctionStack
cur.frame = stack.setFrame();
}
// Set the stack point up as a function
// Set the stack point up as a function. Allows obj to be null.
void push(Function *func, MonsterObject *obj)
{
push(obj);
cur.ftype = SPType.Function;
cur.func = func;
cur.cls = func.owner;
// Point the code stream to the byte code, if any.
if(func.isNormal)
@ -130,6 +133,9 @@ struct FunctionStack
cur.ftype = SPType.State;
cur.state = st;
assert(obj !is null);
cur.cls = obj.cls;
// Set up the byte code
cur.code.setData(st.bcode, st.lines);
}
@ -137,12 +143,17 @@ struct FunctionStack
// Native constructor
void pushNConst(MonsterObject *obj)
{
assert(obj !is null);
push(obj);
cur.ftype = SPType.NConst;
}
private void pushIdleCommon(Function *fn, MonsterObject *obj, SPType tp)
{
// Not really needed - we will allow static idle functions later
// on.
assert(obj !is null);
push(obj);
cur.func = fn;
assert(fn.isIdle, fn.name.str ~ "() is not an idle function");

@ -30,15 +30,18 @@ import monster.compiler.tokenizer;
import monster.compiler.statement;
import monster.compiler.variables;
import monster.compiler.states;
import monster.compiler.structs;
import monster.compiler.block;
import monster.compiler.enums;
import monster.vm.vm;
import monster.vm.thread;
import monster.vm.codestream;
import monster.vm.scheduler;
import monster.vm.idlefunction;
import monster.vm.fstack;
import monster.vm.arrays;
import monster.vm.error;
import monster.vm.vm;
import monster.vm.mobject;
import monster.util.flags;
@ -63,6 +66,8 @@ typedef void *MClass; // Pointer to C++ equivalent of MonsterClass.
typedef int CIndex;
alias FreeList!(CodeThread) ThreadList;
// Parameter to the constructor. Decides how the class is created.
enum MC
{
@ -84,6 +89,9 @@ enum CFlags
Compiled = 0x08, // Class body has been compiled
InScope = 0x10, // We are currently inside the createScope
// function
Module = 0x20, // This is a module, not a class
Singleton = 0x40, // This is a singleton. Also set for modules.
Abstract = 0x80, // No objects can be created from this class
}
// The class that handles 'classes' in Monster.
@ -91,49 +99,11 @@ final class MonsterClass
{
/***********************************************
* *
* Static path functions *
* Static functions *
* *
***********************************************/
// TODO: These will probably be moved elsewhere.
// Path to search for script files. Extremely simple at the moment.
private static char[][] includes = [""];
static void addPath(char[] path)
{
// Make sure the path is slash terminated.
if(!path.ends("/") && !path.ends("\\"))
path ~= '/';
includes ~= path;
}
// Search for a file in the various paths. Returns true if found,
// false otherwise. Changes fname to point to the correct path.
static bool findFile(ref char[] fname)
{
// Check against our include paths. In the future we will replace
// this with a more flexible system, allowing virtual file systems,
// archive files, complete platform independence, improved error
// checking etc.
foreach(path; includes)
{
char[] res = path ~ fname;
if(exists(res))
{
fname = res;
return true;
}
}
return false;
}
/***********************************************
* *
* Static class functions *
* *
***********************************************/
// TODO: These should be moved to vm.vm
// Get a class with the given name. It must already be loaded.
static MonsterClass get(char[] name) { return global.getClass(name); }
@ -142,6 +112,13 @@ final class MonsterClass
// fail if the class cannot be found.
static MonsterClass find(char[] name) { return global.findClass(name); }
static bool canParse(TokenArray tokens)
{
return
Block.isNext(tokens, TT.Class) ||
Block.isNext(tokens, TT.Module);
}
final:
/*******************************************************
@ -150,17 +127,11 @@ final class MonsterClass
* *
*******************************************************/
alias FreeList!(CodeThread) ThreadList;
alias FreeList!(MonsterObject) ObjectList;
// TODO: Put as many of these as possible in the private
// section. Ie. move all of them and see what errors you get.
// Contains the entire class tree for this class, always with
// ourselves as the last entry. Any class in the list is always
// preceded by all the classes it inherits from.
MonsterClass tree[];
// Index within the parent tree. This might become a list at some
// point.
int treeIndex;
@ -182,6 +153,10 @@ final class MonsterClass
bool isResolved() { return flags.has(CFlags.Resolved); }
bool isCompiled() { return flags.has(CFlags.Compiled); }
bool isSingleton() { return flags.has(CFlags.Singleton); }
bool isModule() { return flags.has(CFlags.Module); }
bool isAbstract() { return flags.has(CFlags.Abstract); }
// Call whenever you require this function to have its scope in
// order. If the scope is missing, this will call createScope if
// possible, or fail if the class has not been loaded.
@ -201,12 +176,6 @@ final class MonsterClass
// already.
void requireCompile() { if(!isCompiled) compileBody(); }
// List of variables and functions declared in this class, ordered
// by index.
Function* functions[];
Variable* vars[];
State* states[];
/*******************************************************
* *
@ -237,8 +206,7 @@ final class MonsterClass
if(type == MC.String)
{
assert(name2 == "", "MC.String only takes one parameter");
loadString(name1);
loadString(name1, name2);
return;
}
@ -284,11 +252,12 @@ final class MonsterClass
void loadCI(char[] name1, char[] name2 = "", bool usePath=true)
{ doLoad(name1, name2, false, usePath); }
void loadString(char[] str)
void loadString(char[] str, char[] fname="")
{
assert(str != "");
auto ms = new MemoryStream(str);
loadStream(ms, "(string)");
if(fname == "") fname = "(string)";
loadStream(ms, fname);
}
// Load a script from a stream. The filename parameter is only used
@ -338,7 +307,20 @@ final class MonsterClass
return functions[index];
}
// Find a given callable function.
// Find a virtual function by index. ctree is the tree index of the
// class where the function is defined, findex is the intra-class
// function index.
Function *findVirtualFunc(int ctree, int findex)
{
requireScope();
assert(ctree >= 0 && ctree <= treeIndex);
assert(findex >= 0 && findex < virtuals[ctree].length);
assert(virtuals[ctree][findex] !is null);
return virtuals[ctree][findex];
}
// Find a given callable function, virtually.
Function *findFunction(char[] name)
{
requireScope();
@ -493,11 +475,23 @@ final class MonsterClass
MonsterObject* getFirst()
{ return objects.getHead(); }
// Get the singleton object
MonsterObject* getSing()
{
assert(isSingleton());
requireCompile();
assert(singObj !is null);
return singObj;
}
// Create a new object, and assign a thread to it.
MonsterObject* createObject()
{
requireCompile();
if(isAbstract)
fail("Cannot create objects from abstract class " ~ name.str);
// Create the thread
CodeThread *trd = threads.getNew();
@ -652,49 +646,11 @@ final class MonsterClass
// Get the global index of this class
CIndex getIndex() { requireScope(); return gIndex; }
int getTreeIndex() { requireScope(); return treeIndex; }
char[] getName() { assert(name.str != ""); return name.str; }
char[] toString() { return getName(); }
/*******************************************************
* *
* Private and lower-level members *
* *
*******************************************************/
void reserveStatic(int length)
{
assert(!isResolved);
assert(sdata.length == 0);
sdSize += length;
}
AIndex insertStatic(int[] array, int elemSize)
{
assert(isResolved);
// Allocate data, if it has not been done already.
if(sdata.length == 0 && sdSize != 0)
sdata.length = sdSize;
assert(array.length <= sdSize,
"Trying to allocate more than reserved size");
// How much will be left after inserting this array?
int newSize = sdSize - array.length;
assert(newSize >= 0);
int[] slice = sdata[$-sdSize..$-newSize];
sdSize = newSize;
// Copy the data
slice[] = array[];
ArrayRef *arf = arrays.createConst(slice, elemSize);
return arf.getIndex();
}
private:
@ -704,6 +660,26 @@ final class MonsterClass
* *
*******************************************************/
// Contains the entire class tree for this class, always with
// ourselves as the last entry. Any class in the list is always
// preceded by all the classes it inherits from.
MonsterClass tree[];
// List of variables and functions declared in this class, ordered
// by index.
Function* functions[];
Variable* vars[];
State* states[];
// Singleton object - used for singletons and modules only.
MonsterObject *singObj;
// Function table translation list. Same length as tree[]. For each
// class in the parent tree, this list holds a list equivalent to
// the functions[] list in that class. The difference is that all
// overrided functions have been replaced by their successors.
Function*[][] virtuals;
// The freelists used for allocation of objects and threads.
ObjectList objects;
ThreadList threads;
@ -711,9 +687,6 @@ final class MonsterClass
int[] data; // Contains the initial object data segment
int[] sdata; // Static data segment
uint sdSize; // Number of ints reserved for the static data, that
// have not yet been used.
// Size of the data segment
uint dataSize;
@ -724,13 +697,18 @@ final class MonsterClass
MonsterClass parents[];
Token parentNames[];
// Used at compile time
VarDeclStatement[] vardecs;
FuncDeclaration[] funcdecs;
StateDeclaration[] statedecs;
StructDeclaration[] structdecs;
EnumDeclaration[] enumdecs;
// Current stage of the loading process
MC loadType = MC.None;
// Native constructor type
// Native constructor type. Changed when the actual constructor is
// set.
FuncType constType = FuncType.Native;
union
{
@ -766,20 +744,7 @@ final class MonsterClass
int[] val;
totSize += size;
// Does this variable have an initializer?
if(vd.init !is null)
{
// And can it be evaluated at compile time?
if(!vd.init.isCTime)
fail("Expression " ~ vd.init.toString ~
" is not computable at compile time", vd.init.loc);
val = vd.init.evalCTime();
}
// Use the default initializer.
else val = vd.var.type.defaultInit();
assert(val.length == size, "Size mismatch");
val = vd.getCTimeValue();
data[vd.var.number..vd.var.number+size] = val[];
}
@ -953,7 +918,7 @@ final class MonsterClass
if(!checkFileName())
fail(format("Invalid class name %s (file %s)", cname, fname));
if(usePath && !findFile(fname))
if(usePath && !vm.findFile(fname))
fail("Cannot find script file " ~ fname);
// Create a temporary file stream and load it
@ -1036,6 +1001,18 @@ final class MonsterClass
sd.parse(toks);
statedecs ~= sd;
}
else if(StructDeclaration.canParse(toks))
{
auto sd = new StructDeclaration;
sd.parse(toks);
structdecs ~= sd;
}
else if(EnumDeclaration.canParse(toks))
{
auto sd = new EnumDeclaration;
sd.parse(toks);
enumdecs ~= sd;
}
else
fail("Illegal type or declaration", toks);
}
@ -1044,19 +1021,43 @@ final class MonsterClass
void parse(Stream str, char[] fname, int bom)
{
assert(!isParsed(), "parse() called on a parsed class " ~ name.str);
assert(str !is null);
TokenArray tokens = tokenizeStream(fname, str, bom);
alias Block.isNext isNext;
if(!isNext(tokens, TT.Class))
fail("File must begin with a valid class statement");
// TODO: Check for a list of keywords here. class, module,
// abstract, final. They can come in any order, but only certain
// combinations are legal. For example, class and module cannot
// both be present, and most other keywords only apply to
// classes. 'function' is not allowed at all, but should be
// checked for to make sure we're loading the right kind of
// file. If neither class nor module are found, that is also
// illegal in class files.
if(isNext(tokens, TT.Module))
{
flags.set(CFlags.Module);
flags.set(CFlags.Singleton);
}
else if(isNext(tokens, TT.Singleton))
flags.set(CFlags.Singleton);
else if(!isNext(tokens, TT.Class))
fail("File must begin with a class or module statement", tokens);
if(!isNext(tokens, TT.Identifier, name))
fail("Class statement expected identifier", tokens);
if(isModule)
{
assert(isSingleton);
fail("Modules are not implement yet.", name.loc);
}
if(isSingleton && isAbstract)
fail("Modules and singletons cannot be abstract", name.loc);
// Insert ourselves into the global scope. This will also
// resolve forward references to this class, if any.
global.insertClass(this);
@ -1064,6 +1065,9 @@ final class MonsterClass
// Get the parent classes, if any
if(isNext(tokens, TT.Colon))
{
if(isModule)
fail("Inheritance not allowed for modules.");
Token pName;
do
{
@ -1125,6 +1129,9 @@ final class MonsterClass
fail("Class " ~ name.str ~ " cannot inherit from itself",
name.loc);
if(mc.isModule)
fail("Cannot inherit from module " ~ mc.name.str);
// If a parent class is not a forward reference and still
// does not have a scope, it means that it is itself running
// this function. This can only happen if we are a parent of
@ -1165,10 +1172,22 @@ final class MonsterClass
// Set the type
objType = new ObjectType(this);
classType = objType.getMeta();
// Insert custom types first
foreach(dec; structdecs)
dec.insertType(sc);
foreach(dec; enumdecs)
dec.insertType(sc);
// Then resolve the headers.
foreach(dec; structdecs)
dec.resolve(sc);
foreach(dec; enumdecs)
dec.resolve(sc);
// Resolve variable declarations. They will insert themselves
// into the scope. TODO: Init values will have to be handled as
// part of the body later.
// into the scope.
foreach(dec; vardecs)
dec.resolve(sc);
@ -1195,6 +1214,60 @@ final class MonsterClass
foreach(st; statedecs)
states[st.st.index] = st.st;
// Now set up the virtual function table. It's elements
// correspond to the classes in tree[].
if(parents.length)
{
// This will get a lot trickier if we allow multiple inheritance
assert(parents.length == 1);
// Set up the virtuals list
auto pv = parents[0].virtuals;
virtuals.length = pv.length+1;
// We have to copy every single sublist, since we're not
// allowed to change our parent's data
foreach(i,l; pv)
virtuals[i] = l.dup;
// Add our own list
virtuals[$-1] = functions;
}
else
virtuals = [functions];
assert(virtuals.length == tree.length);
// Trace all our own functions back to their origin, and replace
// them. Since we've copied our parents list, and assume it is
// all set up, we only have to worry about our own
// functions. (For multiple inheritance this might be a bit more
// troublesome, but definitely doable.)
foreach(fn; functions)
{
auto o = fn.overrides;
// And we have to loop backwards through the overrides that
// o overrides as well.
while(o !is null)
{
// Find the owner class tree index of the function we're
// overriding
assert(o.owner !is this);
int clsInd = o.owner.treeIndex;
assert(clsInd < tree.length-1);
assert(tree[clsInd] == o.owner);
// Next, get the function index and replace the pointer
virtuals[clsInd][o.index] = fn;
// Get the function that o overrides too, and fix that
// one as well.
o = o.overrides;
}
}
// Set the data segment size and the total data size for all
// base classes.
dataSize = sc.getDataSize();
@ -1224,6 +1297,12 @@ final class MonsterClass
foreach(state; statedecs)
state.resolve(sc);
// TODO: Resolve struct functions
/*
foredach(stru; structdecs)
stru.resolveBody(sc);
*/
// Validate all variable types
foreach(var; vardecs)
var.validate();
@ -1248,6 +1327,13 @@ final class MonsterClass
data = getDataSegment();
flags.set(CFlags.Compiled);
// If it's a singleton, set up the object.
if(isSingleton)
{
assert(singObj is null);
singObj = createObject();
}
}
}

@ -23,7 +23,7 @@
module monster.vm.mobject;
import monster.vm.vm;
import monster.vm.thread;
import monster.vm.error;
import monster.vm.mclass;
import monster.vm.arrays;
@ -155,7 +155,7 @@ struct MonsterObject
* *
*******************************************************/
// Template versions first
// This is the work horse for all the set/get functions.
T* getPtr(T)(char[] name)
{
// Find the variable
@ -279,11 +279,11 @@ struct MonsterObject
// Call a named function. The function is executed immediately, and
// call() returns when the function is finished. The function is
// called virtually, so any sub-class function that overrides it in
// this object will take precedence.
// called virtually, so any child class function that overrides it
// will take precedence.
void call(char[] name)
{
cls.findFunction(name).call(this);
thread.topObj.cls.findFunction(name).call(this);
}
// Call a function non-virtually. In other words, ignore
@ -344,12 +344,14 @@ struct MonsterObject
MonsterObject *mo = null;
if(index < ptree.length)
mo = ptree[index];
{
mo = ptree[index];
assert(mo !is this);
assert(mo !is this);
// It's only a match if the classes match
if(mo.cls !is toClass) mo = null;
// 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)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -21,8 +21,7 @@
*/
// TODO: Should be a singleton
class Config : Object;
singleton Config : Object;
// Only some config options have been moved into Monster. Key bindings
// and other low-level settings are still handled in D.
@ -42,13 +41,13 @@ bool flipMouseY;
setMainVolume(float f)
{
mainVolume = f;
music().updateVolume();
Music.updateVolume();
}
setMusicVolume(float f)
{
musicVolume = f;
music().updateVolume();
Music.updateVolume();
}
setSfxVolume(float f)

@ -24,5 +24,12 @@
// Covers all alchemy apparatus - mortars, retorts, etc
class Apparatus : InventoryItem;
enum AppaType
{
MortarPestle = 0 : "Mortar and Pestle",
Albemic = 1,
Calcinator = 2
}
float quality;
int type;

@ -25,3 +25,17 @@
class Clothing : EnchantItem;
int type;
enum Type
{
Pants = 0,
Shoes = 1,
Shirt = 2,
Belt = 3,
Robe = 4,
RGlove = 5,
LGlove = 6,
Skirt = 7,
Ring = 8,
Amulet = 9
}

@ -24,7 +24,7 @@
// Contains all the game settings (GMST) variables of Morrowind,
// Tribunal and Bloodmoon. Based on "Morrowind Scripting for Dummies"
// (v9).
class GameSettings : Object;
singleton GameSettings : Object;
// Most of the comments are copied from MSfD. A bit of cleanup is
// still needed.

@ -34,16 +34,15 @@ import sound.music;
void initMonsterScripts()
{
// Add the script directories
MonsterClass.addPath("mscripts/");
MonsterClass.addPath("mscripts/gameobjects/");
MonsterClass.addPath("mscripts/sound/");
vm.addPath("mscripts/");
vm.addPath("mscripts/gameobjects/");
vm.addPath("mscripts/sound/");
// Make sure the Object class is loaded
auto mc = new MonsterClass("Object", "object.mn");
// Create the config object too (only needed here because Object
// refers to Config. This will change.)
config.mo = (new MonsterClass("Config")).createObject;
// Get the Config singleton object
config.mo = (new MonsterClass("Config")).getSing();
// Bind various functions
mc.bind("print", { print(); });
@ -52,10 +51,6 @@ void initMonsterScripts()
(stack.popInt,stack.popInt));});
mc.bind("sleep", new IdleSleep);
// Temporary hacks
mc.bind("config", { stack.pushObject(config.mo); });
mc.bind("music", { stack.pushObject(Music.controlM); });
// Load and run the test script
mc = new MonsterClass("Test");
mc.createObject().call("test");

@ -24,11 +24,6 @@
// This is the base class of all OpenMW Monster classes.
class Object;
// TODO: These are temporary hacks. A more elegant solution will be
// used once the language supports it.
native Config config();
native Music music();
// Sleeps a given amount of time
idle sleep(float seconds);

@ -72,7 +72,7 @@ pause()
resume()
{
if(!config().useMusic) return;
if(!Config.useMusic) return;
if(isPaused) playSound();
else next();
@ -94,7 +94,7 @@ stop()
play()
{
if(!config().useMusic) return;
if(!Config.useMusic) return;
if(index >= playlist.length)
return;
@ -112,7 +112,7 @@ play()
// Play the next song in the playlist
next()
{
if(!config().useMusic) return;
if(!Config.useMusic) return;
if(isPlaying)
stop();

@ -22,7 +22,7 @@
*/
// This class controls all the music.
class Music : Object;
singleton Music : Object;
// Create one jukebox for normal music, and one for battle music. This
// way we can pause / fade out one while the other resumes / fades in.
@ -98,7 +98,7 @@ setPlaylists(char[][] normal, char[][] battlelist)
// have been changed by the user.
updateVolume()
{
float v = config().calcMusicVolume();
float v = Config.calcMusicVolume();
jukebox.updateVolume(v);
battle.updateVolume(v);
}

@ -79,7 +79,7 @@ struct Music
jukeC.bindConst({new Jukebox(params.obj()); });
controlC = MonsterClass.get("Music");
controlM = controlC.createObject;
controlM = controlC.getSing();
controlM.call("setup");
}

Loading…
Cancel
Save