mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-18 13:12:50 +00:00
- 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
This commit is contained in:
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",
|
||||
|
67
monster/compiler/enums.d
Normal file
67
monster/compiler/enums.d
Normal file
@ -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);
|
||||
|
||||
// Set the owner class.
|
||||
fn.paramSize += vd.var.type.getSize();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
167
monster/compiler/structs.d
Normal file
167
monster/compiler/structs.d
Normal file
@ -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)
|
||||
{
|
||||
if(tn in store) return store[tn];
|
||||
int size = -1;
|
||||
StructScope sc; // Scope of the struct interior
|
||||
int[] defInit;
|
||||
bool set; // Have vars and defInit been set?
|
||||
|
||||
return new MetaType(tn);
|
||||
// Member variables
|
||||
Variable*[] vars;
|
||||
|
||||
this(StructDeclaration sdp)
|
||||
{
|
||||
sd = sdp;
|
||||
name = sd.name.str;
|
||||
loc = sd.name.loc;
|
||||
}
|
||||
|
||||
this(char[] baseType)
|
||||
override:
|
||||
// Calls validate for all member types
|
||||
void validate()
|
||||
{
|
||||
base = BasicType.get(baseType);
|
||||
name = base.toString;
|
||||
loc = base.loc;
|
||||
|
||||
store[name] = this;
|
||||
foreach(v; vars)
|
||||
v.type.validate();
|
||||
}
|
||||
|
||||
// Return the scope belonging to the base type. This makes int.max
|
||||
// work just like i.max.
|
||||
Scope getMemberScope() { return base.getMemberScope(); }
|
||||
// Resolves member
|
||||
void resolve(Scope sc) {}
|
||||
|
||||
bool isStruct() { return true; }
|
||||
|
||||
int getSize()
|
||||
{
|
||||
setup();
|
||||
assert(size != -1);
|
||||
return size;
|
||||
}
|
||||
|
||||
int[] defaultInit()
|
||||
{
|
||||
setup();
|
||||
assert(defInit.length == getSize);
|
||||
return defInit;
|
||||
}
|
||||
|
||||
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)
|
||||
|
1269
monster/vm/thread.d
Normal file
1269
monster/vm/thread.d
Normal file
File diff suppressed because it is too large
Load Diff
1247
monster/vm/vm.d
1247
monster/vm/vm.d
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…
x
Reference in New Issue
Block a user