mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-21 07:53:53 +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
28 changed files with 3529 additions and 2230 deletions
|
@ -625,6 +625,14 @@ struct Assembler
|
||||||
// Push 'this', the current object reference
|
// Push 'this', the current object reference
|
||||||
void pushThis() { cmd(BC.PushThis); }
|
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)
|
private void pushPtr(PT pt, int index)
|
||||||
{
|
{
|
||||||
cmd(BC.PushData);
|
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
|
// Set object state to the given index
|
||||||
void setState(int st, int label, int cls)
|
void setState(int st, int label, int cls)
|
||||||
|
@ -748,9 +760,8 @@ struct Assembler
|
||||||
void catArrayLeft(int s) { cmd(BC.CatLeft); addi(s); }
|
void catArrayLeft(int s) { cmd(BC.CatLeft); addi(s); }
|
||||||
void catArrayRight(int s) { cmd(BC.CatRight); addi(s); }
|
void catArrayRight(int s) { cmd(BC.CatRight); addi(s); }
|
||||||
|
|
||||||
// Get the length of an array. The parameter gives element size. For
|
// Get the length of an array. Converts the array index to an int
|
||||||
// example, an array of six ints with an element size of 2 (eg. a
|
// (holding the length) on the stack.
|
||||||
// double) has length 3.
|
|
||||||
void getArrayLength() { cmd(BC.GetArrLen); }
|
void getArrayLength() { cmd(BC.GetArrLen); }
|
||||||
|
|
||||||
// Reverse an array in place
|
// Reverse an array in place
|
||||||
|
|
|
@ -76,6 +76,25 @@ abstract class Block
|
||||||
return true;
|
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
|
// Sets the assembler debug line to the line belonging to this
|
||||||
// block.
|
// block.
|
||||||
final void setLine() { tasm.setLine(loc.line); }
|
final void setLine() { tasm.setLine(loc.line); }
|
||||||
|
|
|
@ -108,6 +108,9 @@ enum BC
|
||||||
|
|
||||||
PushThis, // Push the 'this' object reference
|
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
|
// The Push*8 instructions are not implemented yet as they are
|
||||||
// just optimizations of existing features. The names are reserved
|
// just optimizations of existing features. The names are reserved
|
||||||
// for future use.
|
// for future use.
|
||||||
|
@ -136,11 +139,14 @@ enum BC
|
||||||
// the Ptr, and then pushes the value back.
|
// the Ptr, and then pushes the value back.
|
||||||
|
|
||||||
Store, // Same as StoreRet but does not push the
|
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
|
StoreRet8, // Same as StoreRet except two ints are popped
|
||||||
// from the stack and moved into the data.
|
// from the stack and moved into the data.
|
||||||
|
|
||||||
|
StoreRetMult, // Takes the size as an int parameter
|
||||||
|
|
||||||
IAdd, // Standard addition, operates on the two next
|
IAdd, // Standard addition, operates on the two next
|
||||||
// ints in the stack, and stores the result in
|
// ints in the stack, and stores the result in
|
||||||
// the stack.
|
// the stack.
|
||||||
|
@ -535,6 +541,7 @@ char[][] bcToString =
|
||||||
BC.PushFarClassVar: "PushFarClassVar",
|
BC.PushFarClassVar: "PushFarClassVar",
|
||||||
BC.PushFarClassMulti: "PushFarClassMulti",
|
BC.PushFarClassMulti: "PushFarClassMulti",
|
||||||
BC.PushThis: "PushThis",
|
BC.PushThis: "PushThis",
|
||||||
|
BC.PushSingleton: "PushSingleton",
|
||||||
BC.Push8: "Push8",
|
BC.Push8: "Push8",
|
||||||
BC.PushLocal8: "PushLocal8",
|
BC.PushLocal8: "PushLocal8",
|
||||||
BC.PushClassVar8: "PushClassVar8",
|
BC.PushClassVar8: "PushClassVar8",
|
||||||
|
@ -545,6 +552,7 @@ char[][] bcToString =
|
||||||
BC.StoreRet: "StoreRet",
|
BC.StoreRet: "StoreRet",
|
||||||
BC.Store: "Store",
|
BC.Store: "Store",
|
||||||
BC.StoreRet8: "StoreRet8",
|
BC.StoreRet8: "StoreRet8",
|
||||||
|
BC.StoreRetMult: "StoreRetMult",
|
||||||
BC.FetchElem: "FetchElem",
|
BC.FetchElem: "FetchElem",
|
||||||
BC.GetArrLen: "GetArrLen",
|
BC.GetArrLen: "GetArrLen",
|
||||||
BC.IMul: "IMul",
|
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
|
// The rest are not allowed for members (eg. con.(a+b) is not
|
||||||
// allowed)
|
// allowed)
|
||||||
else if(NewExpression.canParse(toks)) b = new NewExpression;
|
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(LiteralExpr.canParse(toks)) b = new LiteralExpr;
|
||||||
else if(ArrayLiteralExpr.canParse(toks)) b = new ArrayLiteralExpr;
|
else if(ArrayLiteralExpr.canParse(toks)) b = new ArrayLiteralExpr;
|
||||||
// Sub-expression (expr)
|
// Sub-expression (expr)
|
||||||
|
@ -450,6 +451,32 @@ abstract class Expression : Block
|
||||||
int[] evalCTime() { assert(0); return null; }
|
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[]).
|
// new-expressions, ie. (new Sometype[]).
|
||||||
class NewExpression : Expression
|
class NewExpression : Expression
|
||||||
{
|
{
|
||||||
|
@ -471,9 +498,7 @@ class NewExpression : Expression
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
if(!isNext(toks, TT.New, loc))
|
reqNext(toks, TT.New, loc);
|
||||||
assert(0, "Internal error in NewExpression");
|
|
||||||
|
|
||||||
type = Type.identify(toks, true, exArr);
|
type = Type.identify(toks, true, exArr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,6 +511,9 @@ class NewExpression : Expression
|
||||||
{
|
{
|
||||||
type.resolve(sc);
|
type.resolve(sc);
|
||||||
|
|
||||||
|
if(type.isReplacer)
|
||||||
|
type = type.getBase();
|
||||||
|
|
||||||
if(type.isObject)
|
if(type.isObject)
|
||||||
{
|
{
|
||||||
// We need to find the index associated with this class, and
|
// We need to find the index associated with this class, and
|
||||||
|
@ -600,12 +628,11 @@ class ArrayLiteralExpr : Expression
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
if(!isNext(toks, TT.LeftSquare, loc))
|
reqNext(toks, TT.LeftSquare, loc);
|
||||||
assert(0, "Internal error in ArrayLiteralExpr");
|
|
||||||
|
|
||||||
Floc loc2;
|
Floc loc2;
|
||||||
if(isNext(toks, TT.RightSquare, 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
|
// Read the first expression
|
||||||
params ~= Expression.identify(toks);
|
params ~= Expression.identify(toks);
|
||||||
|
@ -614,8 +641,7 @@ class ArrayLiteralExpr : Expression
|
||||||
while(isNext(toks, TT.Comma))
|
while(isNext(toks, TT.Comma))
|
||||||
params ~= Expression.identify(toks);
|
params ~= Expression.identify(toks);
|
||||||
|
|
||||||
if(!isNext(toks, TT.RightSquare))
|
reqNext(toks, TT.RightSquare);
|
||||||
fail("Array literal expected closing ]", toks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] toString()
|
char[] toString()
|
||||||
|
@ -642,7 +668,7 @@ class ArrayLiteralExpr : Expression
|
||||||
|
|
||||||
// Set the type
|
// Set the type
|
||||||
Type base = params[0].type;
|
Type base = params[0].type;
|
||||||
type = new ArrayType(base);
|
type = ArrayType.get(base);
|
||||||
|
|
||||||
foreach(ref par; params)
|
foreach(ref par; params)
|
||||||
{
|
{
|
||||||
|
@ -656,8 +682,10 @@ class ArrayLiteralExpr : Expression
|
||||||
|
|
||||||
cls = sc.getClass();
|
cls = sc.getClass();
|
||||||
|
|
||||||
|
/*
|
||||||
if(isCTime)
|
if(isCTime)
|
||||||
cls.reserveStatic(params.length * base.getSize);
|
cls.reserveStatic(params.length * base.getSize);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] evalCTime()
|
int[] evalCTime()
|
||||||
|
@ -682,9 +710,8 @@ class ArrayLiteralExpr : Expression
|
||||||
foreach(i, par; params)
|
foreach(i, par; params)
|
||||||
data[i*elem..(i+1)*elem] = par.evalCTime();
|
data[i*elem..(i+1)*elem] = par.evalCTime();
|
||||||
|
|
||||||
// Insert the array into the static data, and get the array
|
// Create the array and get the index
|
||||||
// reference
|
arrind = arrays.createConst(data, elem).getIndex;
|
||||||
arrind = cls.insertStatic(data, elem);
|
|
||||||
|
|
||||||
// Delete the array, if necessary
|
// Delete the array, if necessary
|
||||||
if(data.length > LEN)
|
if(data.length > LEN)
|
||||||
|
@ -877,7 +904,7 @@ class LiteralExpr : Expression
|
||||||
// Strings
|
// Strings
|
||||||
if(value.type == TT.StringLiteral)
|
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
|
// Check that we do indeed have '"'s at the ends of the
|
||||||
// string. Special cases which we allow later (like wysiwig
|
// string. Special cases which we allow later (like wysiwig
|
||||||
|
@ -887,7 +914,7 @@ class LiteralExpr : Expression
|
||||||
"Encountered invalid string literal token: " ~ value.str);
|
"Encountered invalid string literal token: " ~ value.str);
|
||||||
|
|
||||||
strVal = toUTF32(value.str[1..$-1]);
|
strVal = toUTF32(value.str[1..$-1]);
|
||||||
cls.reserveStatic(strVal.length);
|
//cls.reserveStatic(strVal.length);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -914,9 +941,8 @@ class LiteralExpr : Expression
|
||||||
|
|
||||||
if(type.isString)
|
if(type.isString)
|
||||||
{
|
{
|
||||||
// Insert the array into the static data, and get the array
|
// Create array index
|
||||||
// reference
|
arrind = arrays.createConst(cast(int[])strVal, 1).getIndex;
|
||||||
arrind = cls.insertStatic(cast(int[])strVal, 1);
|
|
||||||
// Create an array from it
|
// Create an array from it
|
||||||
return (cast(int*)&arrind)[0..1];
|
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
|
// Expression that handles conversion from one type to another. This
|
||||||
// is used for implisit casts (eg. when using ints and floats
|
// is used for implisit casts (eg. when using ints and floats
|
||||||
// together) and in the future it will also parse explisit casts. It
|
// 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.assembler;
|
||||||
import monster.compiler.bytecode;
|
import monster.compiler.bytecode;
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
|
import monster.compiler.expression;
|
||||||
import monster.compiler.variables;
|
import monster.compiler.variables;
|
||||||
import monster.compiler.tokenizer;
|
import monster.compiler.tokenizer;
|
||||||
import monster.compiler.linespec;
|
import monster.compiler.linespec;
|
||||||
|
@ -49,8 +50,11 @@ import monster.vm.idlefunction;
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.vm.fstack;
|
import monster.vm.fstack;
|
||||||
|
import monster.vm.vm;
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
import std.stream;
|
||||||
|
import std.string;
|
||||||
|
|
||||||
// One problem with these split compiler / vm classes is that we
|
// 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
|
// likely end up with data (or at least pointers) we don't need, and a
|
||||||
|
@ -81,7 +85,13 @@ struct Function
|
||||||
|
|
||||||
int paramSize;
|
int paramSize;
|
||||||
int imprint; // Stack imprint of this function. Equals
|
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
|
union
|
||||||
{
|
{
|
||||||
|
@ -117,32 +127,16 @@ struct Function
|
||||||
// you. A c++ version could be much more difficult to handle, and
|
// you. A c++ version could be much more difficult to handle, and
|
||||||
// might rely more on manual coding.
|
// 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
|
// This is used to call the given function from native code. Note
|
||||||
// that this is used internally for native functions, but not for
|
// that this is used internally for native functions, but not for
|
||||||
// any other type. Idle functions can NOT be called directly from
|
// any other type. Idle functions can NOT be called directly from
|
||||||
// native code.
|
// native code.
|
||||||
void call(MonsterObject *obj)
|
void call(MonsterObject *obj)
|
||||||
{
|
{
|
||||||
|
assert(obj !is null);
|
||||||
|
|
||||||
// Cast the object to the correct type for this function.
|
// Cast the object to the correct type for this function.
|
||||||
obj = obj.upcast(owner);
|
obj = obj.Cast(owner);
|
||||||
|
|
||||||
// Push the function on the stack
|
// Push the function on the stack
|
||||||
fstack.push(this, obj);
|
fstack.push(this, obj);
|
||||||
|
@ -175,9 +169,84 @@ struct Function
|
||||||
fstack.pop();
|
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()
|
// Returns the function name, on the form Class.func()
|
||||||
char[] toString()
|
char[] toString()
|
||||||
{ return owner.name.str ~ "." ~ name.str ~ "()"; }
|
{ 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.
|
// Responsible for parsing, analysing and compiling functions.
|
||||||
|
@ -191,7 +260,15 @@ class FuncDeclaration : Statement
|
||||||
// the VM when the compiler is done working.
|
// the VM when the compiler is done working.
|
||||||
Function *fn;
|
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)
|
private void parseKeywords(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
Floc loc;
|
Floc loc;
|
||||||
|
@ -213,7 +290,7 @@ class FuncDeclaration : Statement
|
||||||
}
|
}
|
||||||
if(isNext(toks, TT.Abstract, loc))
|
if(isNext(toks, TT.Abstract, loc))
|
||||||
{
|
{
|
||||||
if(isNative)
|
if(isAbstract)
|
||||||
fail("Multiple token 'abstract' in function declaration",
|
fail("Multiple token 'abstract' in function declaration",
|
||||||
loc);
|
loc);
|
||||||
isAbstract = true;
|
isAbstract = true;
|
||||||
|
@ -227,10 +304,26 @@ class FuncDeclaration : Statement
|
||||||
isIdle = true;
|
isIdle = true;
|
||||||
continue;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that only one of the keywords are used
|
// Check that only one of the type keywords are used
|
||||||
if( (isAbstract && isNative) ||
|
if( (isAbstract && isNative) ||
|
||||||
(isAbstract && isIdle) ||
|
(isAbstract && isIdle) ||
|
||||||
(isNative && isIdle) )
|
(isNative && isIdle) )
|
||||||
|
@ -243,6 +336,43 @@ class FuncDeclaration : Statement
|
||||||
else assert(fn.isNormal);
|
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)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
// Create a Function struct. Will change later.
|
// Create a Function struct. Will change later.
|
||||||
|
@ -265,6 +395,33 @@ class FuncDeclaration : Statement
|
||||||
// Parse any other keywords
|
// Parse any other keywords
|
||||||
parseKeywords(toks);
|
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);
|
fn.name = next(toks);
|
||||||
loc = fn.name.loc;
|
loc = fn.name.loc;
|
||||||
if(fn.name.type != TT.Identifier)
|
if(fn.name.type != TT.Identifier)
|
||||||
|
@ -297,27 +454,6 @@ class FuncDeclaration : Statement
|
||||||
if(!isNext(toks, TT.RightParen))
|
if(!isNext(toks, TT.RightParen))
|
||||||
fail("Expected end of parameter list", toks);
|
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?
|
// Can the given tokens be parsed as the main function declaration?
|
||||||
|
@ -334,6 +470,8 @@ class FuncDeclaration : Statement
|
||||||
return
|
return
|
||||||
isNext(toks, TT.Native) ||
|
isNext(toks, TT.Native) ||
|
||||||
isNext(toks, TT.Abstract) ||
|
isNext(toks, TT.Abstract) ||
|
||||||
|
isNext(toks, TT.Override) ||
|
||||||
|
isNext(toks, TT.Final) ||
|
||||||
isNext(toks, TT.Idle);
|
isNext(toks, TT.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,8 +518,16 @@ class FuncDeclaration : Statement
|
||||||
// types). The rest is handed by resolveBody()
|
// types). The rest is handed by resolveBody()
|
||||||
void resolve(Scope last)
|
void resolve(Scope last)
|
||||||
{
|
{
|
||||||
|
assert(fn.type !is null);
|
||||||
|
|
||||||
fn.type.resolve(last);
|
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
|
// Create a local scope for this function
|
||||||
sc = new FuncScope(last, fn);
|
sc = new FuncScope(last, fn);
|
||||||
|
|
||||||
|
@ -389,9 +535,16 @@ class FuncDeclaration : Statement
|
||||||
// in compile() and by external classes, so we store it.
|
// in compile() and by external classes, so we store it.
|
||||||
fn.paramSize = 0;
|
fn.paramSize = 0;
|
||||||
foreach(vd; paramList)
|
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();
|
fn.owner = sc.getClass();
|
||||||
|
|
||||||
// Parameters are given negative numbers according to their
|
// Parameters are given negative numbers according to their
|
||||||
|
@ -403,18 +556,16 @@ class FuncDeclaration : Statement
|
||||||
// TODO: Do fancy memory management
|
// TODO: Do fancy memory management
|
||||||
fn.params.length = paramList.length;
|
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)
|
foreach(i, dec; paramList)
|
||||||
{
|
{
|
||||||
if(dec.var.type.isArray())
|
assert(pos < 0);
|
||||||
dec.allowConst = true;
|
dec.setNumber(pos);
|
||||||
|
|
||||||
dec.resolve(sc, pos);
|
|
||||||
|
|
||||||
pos += dec.var.type.getSize();
|
pos += dec.var.type.getSize();
|
||||||
|
|
||||||
fn.params[i] = dec.var;
|
fn.params[i] = dec.var;
|
||||||
}
|
}
|
||||||
|
assert(pos == 0);
|
||||||
|
|
||||||
// Vararg functions must have the last parameter as an array.
|
// Vararg functions must have the last parameter as an array.
|
||||||
if(fn.isVararg)
|
if(fn.isVararg)
|
||||||
|
@ -427,6 +578,56 @@ class FuncDeclaration : Statement
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(pos == 0, "Variable positions didn't add up");
|
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
|
// Resolve the interior of the function
|
||||||
|
@ -468,3 +669,216 @@ class FuncDeclaration : Statement
|
||||||
fn.bcode = tasm.assemble(fn.lines);
|
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.tokenizer;
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
import monster.compiler.types;
|
import monster.compiler.types;
|
||||||
|
import monster.compiler.functions;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.vm.arrays;
|
import monster.vm.arrays;
|
||||||
|
|
||||||
|
@ -584,8 +585,8 @@ class AssignOperator : BinaryOperator
|
||||||
// Cast the right side to the left type, if possible.
|
// Cast the right side to the left type, if possible.
|
||||||
try type.typeCast(right);
|
try type.typeCast(right);
|
||||||
catch(TypeException)
|
catch(TypeException)
|
||||||
fail("Assignment " ~tokenList[opType] ~ " not allowed for types " ~
|
fail("Assignment " ~tokenList[opType] ~ ": cannot implicitly cast " ~
|
||||||
left.typeString() ~ " and " ~ right.typeString(), loc);
|
right.typeString() ~ " to " ~ left.typeString(), loc);
|
||||||
|
|
||||||
assert(left.type == right.type);
|
assert(left.type == right.type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,7 @@ class ArrayProperties: SimplePropertyScope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rename to ObjectProperties
|
||||||
class ClassProperties : SimplePropertyScope
|
class ClassProperties : SimplePropertyScope
|
||||||
{
|
{
|
||||||
static ClassProperties singleton;
|
static ClassProperties singleton;
|
||||||
|
|
|
@ -32,13 +32,17 @@ import monster.compiler.statement;
|
||||||
import monster.compiler.expression;
|
import monster.compiler.expression;
|
||||||
import monster.compiler.tokenizer;
|
import monster.compiler.tokenizer;
|
||||||
import monster.compiler.types;
|
import monster.compiler.types;
|
||||||
|
import monster.compiler.assembler;
|
||||||
import monster.compiler.properties;
|
import monster.compiler.properties;
|
||||||
import monster.compiler.functions;
|
import monster.compiler.functions;
|
||||||
import monster.compiler.states;
|
import monster.compiler.states;
|
||||||
|
import monster.compiler.structs;
|
||||||
|
import monster.compiler.enums;
|
||||||
import monster.compiler.variables;
|
import monster.compiler.variables;
|
||||||
|
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
|
import monster.vm.vm;
|
||||||
|
|
||||||
// The global scope
|
// The global scope
|
||||||
PackageScope global;
|
PackageScope global;
|
||||||
|
@ -109,6 +113,7 @@ abstract class Scope
|
||||||
bool isLoop() { return false; }
|
bool isLoop() { return false; }
|
||||||
bool isClass() { return false; }
|
bool isClass() { return false; }
|
||||||
bool isArray() { return false; }
|
bool isArray() { return false; }
|
||||||
|
bool isStruct() { return false; }
|
||||||
bool isPackage() { return false; }
|
bool isPackage() { return false; }
|
||||||
bool isProperty() { return false; }
|
bool isProperty() { return false; }
|
||||||
|
|
||||||
|
@ -202,6 +207,18 @@ abstract class Scope
|
||||||
return parent.findFunc(name);
|
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)
|
void insertLabel(StateLabel *lb)
|
||||||
{
|
{
|
||||||
assert(!isRoot);
|
assert(!isRoot);
|
||||||
|
@ -448,7 +465,7 @@ final class PackageScope : Scope
|
||||||
// Find a parsed class of the given name. Looks in the list of
|
// 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
|
// loaded classes and in the file system. Returns null if the class
|
||||||
// cannot be found.
|
// cannot be found.
|
||||||
private MonsterClass findParsed(char[] name)
|
MonsterClass findParsed(char[] name)
|
||||||
{
|
{
|
||||||
MonsterClass result = null;
|
MonsterClass result = null;
|
||||||
|
|
||||||
|
@ -459,7 +476,7 @@ final class PackageScope : Scope
|
||||||
{
|
{
|
||||||
// Class not loaded. Check if the file exists.
|
// Class not loaded. Check if the file exists.
|
||||||
char[] fname = classToFile(name);
|
char[] fname = classToFile(name);
|
||||||
if(MonsterClass.findFile(fname))
|
if(vm.findFile(fname))
|
||||||
{
|
{
|
||||||
// File exists. Load it right away. If the class is
|
// File exists. Load it right away. If the class is
|
||||||
// already forward referenced, this will be taken care
|
// 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
|
// A scope that can contain functions and variables
|
||||||
// and functions, and they keep track of the data segment size.
|
class FVScope : VarScope
|
||||||
final class ClassScope : 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:
|
private:
|
||||||
// The class information for this class.
|
// The class information for this class.
|
||||||
MonsterClass cls;
|
MonsterClass cls;
|
||||||
|
|
||||||
HashTable!(char[], State*) states;
|
HashTable!(char[], State*) states;
|
||||||
HashTable!(char[], Function*) functions;
|
|
||||||
|
|
||||||
int dataSize; // Data segment size for this class
|
int dataSize; // Data segment size for this class
|
||||||
|
|
||||||
|
@ -611,22 +819,14 @@ final class ClassScope : VarScope
|
||||||
{
|
{
|
||||||
assert(name.str != "");
|
assert(name.str != "");
|
||||||
|
|
||||||
Function* fd;
|
|
||||||
State* sd;
|
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))
|
if(states.inList(name.str, sd))
|
||||||
fail(format("Identifier '%s' already declared on line %s (as a state)",
|
fail(format("Identifier '%s' already declared on line %s (as a state)",
|
||||||
name.str, sd.name.loc),
|
name.str, sd.name.loc),
|
||||||
name.loc);
|
name.loc);
|
||||||
|
|
||||||
// Let VarScope handle variables and parent scopes
|
// Let the parent handle everything else
|
||||||
super.clearId(name);
|
super.clearId(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,20 +842,6 @@ final class ClassScope : VarScope
|
||||||
states[st.name.str] = st;
|
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* findState(char[] name)
|
||||||
{
|
{
|
||||||
State* st;
|
State* st;
|
||||||
|
@ -666,17 +852,6 @@ final class ClassScope : VarScope
|
||||||
assert(!isRoot());
|
assert(!isRoot());
|
||||||
return parent.findState(name);
|
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
|
// A scope that keeps track of the stack
|
||||||
|
@ -745,6 +920,7 @@ abstract class StackScope : VarScope
|
||||||
int getPos() { return getTotLocals() + getExpStack(); }
|
int getPos() { return getTotLocals() + getExpStack(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scope used for the inside of functions
|
||||||
class FuncScope : StackScope
|
class FuncScope : StackScope
|
||||||
{
|
{
|
||||||
// Function definition, for function scopes
|
// Function definition, for function scopes
|
||||||
|
|
|
@ -56,8 +56,7 @@ class ExprStatement : Statement
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
exp = Expression.identify(toks);
|
exp = Expression.identify(toks);
|
||||||
if(!isNext(toks, TT.Semicolon, loc))
|
reqNext(toks, TT.Semicolon, loc);
|
||||||
fail("Statement expected ;", toks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] toString() { return "ExprStatement: " ~ exp.toString(); }
|
char[] toString() { return "ExprStatement: " ~ exp.toString(); }
|
||||||
|
@ -67,89 +66,6 @@ class ExprStatement : Statement
|
||||||
void compile() { exp.evalPop(); }
|
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
|
// Destination for a goto statement, on the form 'label:'. This
|
||||||
// statement is actually a bit complicated, since it must handle jumps
|
// statement is actually a bit complicated, since it must handle jumps
|
||||||
// occuring both above and below the label. Seeing as the assembler
|
// 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
|
// canParse has already checked the token types, all we need to
|
||||||
// do is to fetch the name.
|
// do is to fetch the name.
|
||||||
if(!isNext(toks, TT.Identifier, lb.name))
|
reqNext(toks, TT.Identifier, lb.name);
|
||||||
assert(0, "Internal error");
|
reqNext(toks, TT.Colon);
|
||||||
if(!isNext(toks, TT.Colon))
|
|
||||||
assert(0, "Internal error");
|
|
||||||
|
|
||||||
loc = lb.name.loc;
|
loc = lb.name.loc;
|
||||||
}
|
}
|
||||||
|
@ -325,15 +239,13 @@ class GotoStatement : Statement, LabelUser
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
if(!isNext(toks, TT.Goto, loc))
|
reqNext(toks, TT.Goto, loc);
|
||||||
assert(0, "Internal error");
|
|
||||||
|
|
||||||
// Read the label name
|
// Read the label name
|
||||||
if(!isNext(toks, TT.Identifier, labelName))
|
if(!isNext(toks, TT.Identifier, labelName))
|
||||||
fail("goto expected label identifier", toks);
|
fail("goto expected label identifier", toks);
|
||||||
|
|
||||||
if(!isNext(toks, TT.Semicolon))
|
reqNext(toks, TT.Semicolon);
|
||||||
fail("goto statement expected ;", toks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void resolve(Scope sc)
|
void resolve(Scope sc)
|
||||||
|
@ -1299,7 +1211,9 @@ class IfStatement : Statement
|
||||||
// Resolve all parts
|
// Resolve all parts
|
||||||
condition.resolve(sc);
|
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 "
|
fail("if condition " ~ condition.toString ~ " must be a bool, not "
|
||||||
~condition.type.toString, condition.loc);
|
~condition.type.toString, condition.loc);
|
||||||
|
|
||||||
|
@ -1314,6 +1228,17 @@ class IfStatement : Statement
|
||||||
// Push the conditionel expression
|
// Push the conditionel expression
|
||||||
condition.eval();
|
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.
|
// Jump if the value is zero. Get a label reference.
|
||||||
int label = tasm.jumpz();
|
int label = tasm.jumpz();
|
||||||
|
|
||||||
|
@ -1431,6 +1356,7 @@ class CodeBlock : Statement
|
||||||
Floc endLine; // Last line, used in error messages
|
Floc endLine; // Last line, used in error messages
|
||||||
|
|
||||||
bool isState; // True if this block belongs to a state
|
bool isState; // True if this block belongs to a state
|
||||||
|
bool isFile; // True if this is a stand-alone file
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -1475,8 +1401,13 @@ class CodeBlock : Statement
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
this(bool isState = false)
|
this(bool isState = false, bool isFile = false)
|
||||||
{ this.isState = isState; }
|
{
|
||||||
|
this.isState = isState;
|
||||||
|
this.isFile = isFile;
|
||||||
|
|
||||||
|
assert(!isFile || !isState);
|
||||||
|
}
|
||||||
|
|
||||||
static bool canParse(TokenArray toks)
|
static bool canParse(TokenArray toks)
|
||||||
{
|
{
|
||||||
|
@ -1484,26 +1415,42 @@ class CodeBlock : Statement
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
void parse(ref TokenArray toks)
|
||||||
|
{
|
||||||
|
Token strt;
|
||||||
|
if(!isFile)
|
||||||
{
|
{
|
||||||
if(toks.length == 0)
|
if(toks.length == 0)
|
||||||
fail("Code block expected, got end of file");
|
fail("Code block expected, got end of file");
|
||||||
|
|
||||||
Token strt = toks[0];
|
strt = toks[0];
|
||||||
if(strt.type != TT.LeftCurl)
|
if(strt.type != TT.LeftCurl)
|
||||||
fail("Code block expected a {", toks);
|
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
|
// Are we parsing stuff that comes before the code? Only
|
||||||
// applicable to state blocks.
|
// applicable to state blocks.
|
||||||
bool beforeCode = isState;
|
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)
|
if(toks.length == 0)
|
||||||
|
{
|
||||||
|
if(!isFile)
|
||||||
fail(format("Unterminated code block (starting at line %s)", strt.loc));
|
fail(format("Unterminated code block (starting at line %s)", strt.loc));
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
if(beforeCode)
|
if(beforeCode)
|
||||||
{
|
{
|
||||||
|
@ -1571,3 +1518,13 @@ class CodeBlock : Statement
|
||||||
tasm.pop(sc.getLocals);
|
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
|
// Keywords. Note that we use Class as a separator below, so it
|
||||||
// must be first in this list. All operator tokens must occur
|
// must be first in this list. All operator tokens must occur
|
||||||
// before Class, and all keywords must come after Class.
|
// before Class, and all keywords must come after Class.
|
||||||
Class,
|
Class, Module,
|
||||||
For,
|
For,
|
||||||
If,
|
If,
|
||||||
Else,
|
Else,
|
||||||
|
@ -107,10 +107,11 @@ enum TT
|
||||||
Switch,
|
Switch,
|
||||||
Select,
|
Select,
|
||||||
State,
|
State,
|
||||||
Singleton, Clone,
|
Struct, Enum, Thread,
|
||||||
|
Singleton, Clone, Override, Final, Function,
|
||||||
This, New, Static, Const, Out, Ref, Abstract, Idle,
|
This, New, Static, Const, Out, Ref, Abstract, Idle,
|
||||||
Public, Private, Protected, True, False, Native, Null,
|
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
|
Last, // Tokens after this do not have a specific string
|
||||||
// associated with them.
|
// associated with them.
|
||||||
|
@ -184,7 +185,7 @@ const char[][] tokenList =
|
||||||
TT.MinusEq : "-=",
|
TT.MinusEq : "-=",
|
||||||
TT.MultEq : "*=",
|
TT.MultEq : "*=",
|
||||||
TT.DivEq : "/=",
|
TT.DivEq : "/=",
|
||||||
TT.RemEq : "%%=",
|
TT.RemEq : "%=",
|
||||||
TT.IDivEq : "\\=",
|
TT.IDivEq : "\\=",
|
||||||
TT.CatEq : "~=",
|
TT.CatEq : "~=",
|
||||||
|
|
||||||
|
@ -196,10 +197,11 @@ const char[][] tokenList =
|
||||||
TT.Minus : "-",
|
TT.Minus : "-",
|
||||||
TT.Mult : "*",
|
TT.Mult : "*",
|
||||||
TT.Div : "/",
|
TT.Div : "/",
|
||||||
TT.Rem : "%%",
|
TT.Rem : "%",
|
||||||
TT.IDiv : "\\",
|
TT.IDiv : "\\",
|
||||||
|
|
||||||
TT.Class : "class",
|
TT.Class : "class",
|
||||||
|
TT.Module : "module",
|
||||||
TT.Return : "return",
|
TT.Return : "return",
|
||||||
TT.For : "for",
|
TT.For : "for",
|
||||||
TT.This : "this",
|
TT.This : "this",
|
||||||
|
@ -216,12 +218,18 @@ const char[][] tokenList =
|
||||||
TT.Switch : "switch",
|
TT.Switch : "switch",
|
||||||
TT.Select : "select",
|
TT.Select : "select",
|
||||||
TT.State : "state",
|
TT.State : "state",
|
||||||
|
TT.Struct : "struct",
|
||||||
|
TT.Enum : "enum",
|
||||||
|
TT.Thread : "thread",
|
||||||
TT.Typeof : "typeof",
|
TT.Typeof : "typeof",
|
||||||
TT.Singleton : "singleton",
|
TT.Singleton : "singleton",
|
||||||
TT.Clone : "clone",
|
TT.Clone : "clone",
|
||||||
TT.Static : "static",
|
TT.Static : "static",
|
||||||
TT.Const : "const",
|
TT.Const : "const",
|
||||||
TT.Abstract : "abstract",
|
TT.Abstract : "abstract",
|
||||||
|
TT.Override : "override",
|
||||||
|
TT.Final : "final",
|
||||||
|
TT.Function : "function",
|
||||||
TT.Idle : "idle",
|
TT.Idle : "idle",
|
||||||
TT.Out : "out",
|
TT.Out : "out",
|
||||||
TT.Ref : "ref",
|
TT.Ref : "ref",
|
||||||
|
@ -234,7 +242,6 @@ const char[][] tokenList =
|
||||||
TT.Null : "null",
|
TT.Null : "null",
|
||||||
TT.Goto : "goto",
|
TT.Goto : "goto",
|
||||||
TT.Halt : "halt",
|
TT.Halt : "halt",
|
||||||
TT.Auto : "auto",
|
|
||||||
TT.Var : "var",
|
TT.Var : "var",
|
||||||
TT.In : "in",
|
TT.In : "in",
|
||||||
];
|
];
|
||||||
|
@ -519,11 +526,13 @@ class StreamTokenizer
|
||||||
// Treat the rest as we would an identifier - the actual
|
// Treat the rest as we would an identifier - the actual
|
||||||
// interpretation will be done later. We allow non-numerical
|
// interpretation will be done later. We allow non-numerical
|
||||||
// tokens in the literal, such as 0x0a or 1_000_000. We must
|
// tokens in the literal, such as 0x0a or 1_000_000. We must
|
||||||
// also explicitly allow '.' dots. A number literal can end
|
// also explicitly allow '.' dots.
|
||||||
// with a percentage sign '%'.
|
|
||||||
int len = 1;
|
int len = 1;
|
||||||
bool lastDot = false; // Was the last char a '.'?
|
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..$])
|
foreach(char ch; line[1..$])
|
||||||
{
|
{
|
||||||
if(ch == '.')
|
if(ch == '.')
|
||||||
|
@ -537,6 +546,7 @@ class StreamTokenizer
|
||||||
}
|
}
|
||||||
lastDot = true;
|
lastDot = true;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
else if(ch == '%')
|
else if(ch == '%')
|
||||||
{
|
{
|
||||||
// Ditto for percentage signs. We allow '%' but not
|
// Ditto for percentage signs. We allow '%' but not
|
||||||
|
@ -548,11 +558,12 @@ class StreamTokenizer
|
||||||
}
|
}
|
||||||
lastPer = true;
|
lastPer = true;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(!validIdentChar(ch)) break;
|
if(!validIdentChar(ch)) break;
|
||||||
lastDot = false;
|
lastDot = false;
|
||||||
lastPer = false;
|
//lastPer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This was a valid character, count it
|
// This was a valid character, count it
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
module monster.compiler.types;
|
module monster.compiler.types;
|
||||||
|
|
||||||
import monster.compiler.tokenizer;
|
import monster.compiler.tokenizer;
|
||||||
|
import monster.compiler.enums;
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
import monster.compiler.expression;
|
import monster.compiler.expression;
|
||||||
import monster.compiler.assembler;
|
import monster.compiler.assembler;
|
||||||
|
@ -32,6 +33,8 @@ import monster.compiler.block;
|
||||||
import monster.compiler.functions;
|
import monster.compiler.functions;
|
||||||
import monster.compiler.variables;
|
import monster.compiler.variables;
|
||||||
import monster.compiler.states;
|
import monster.compiler.states;
|
||||||
|
import monster.compiler.structs;
|
||||||
|
import monster.vm.arrays;
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
|
|
||||||
|
@ -43,11 +46,18 @@ import std.string;
|
||||||
|
|
||||||
Type (abstract)
|
Type (abstract)
|
||||||
InternalType (abstract)
|
InternalType (abstract)
|
||||||
|
ReplacerType (abstract)
|
||||||
NullType (null expression)
|
NullType (null expression)
|
||||||
BasicType (covers int, char, bool, float, void)
|
BasicType (covers int, char, bool, float, void)
|
||||||
ObjectType
|
ObjectType
|
||||||
ArrayType
|
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
|
class TypeException : Exception
|
||||||
|
@ -77,10 +87,15 @@ abstract class Type : Block
|
||||||
// tokens.)
|
// tokens.)
|
||||||
static bool canParseRem(ref TokenArray toks)
|
static bool canParseRem(ref TokenArray toks)
|
||||||
{
|
{
|
||||||
// TODO: Parse typeof(exp) here. We have to do a hack here, as
|
if(isNext(toks, TT.Typeof))
|
||||||
// we have no hope of parsing every expression in here. Instead
|
{
|
||||||
// we require the first ( and then remove every token until the
|
reqNext(toks, TT.LeftParen);
|
||||||
// matching ), allowing matching () pairs on the inside.
|
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;
|
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
|
// Find what kind of type this is and create an instance of the
|
||||||
// corresponding class.
|
// corresponding class.
|
||||||
if(BasicType.canParse(toks)) t = new BasicType();
|
if(BasicType.canParse(toks)) t = new BasicType();
|
||||||
else if(ObjectType.canParse(toks)) t = new ObjectType();
|
else if(UserType.canParse(toks)) t = new UserType();
|
||||||
//else if(TypeofType.canParse(toks)) t = new TypeofType();
|
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);
|
else fail("Cannot parse " ~ toks[0].str ~ " as a type", toks[0].loc);
|
||||||
|
|
||||||
// Parse the actual tokens with our new and shiny object.
|
// Parse the actual tokens with our new and shiny object.
|
||||||
|
@ -123,6 +139,9 @@ abstract class Type : Block
|
||||||
// an ArrayType.
|
// an ArrayType.
|
||||||
exps = VarDeclaration.getArray(toks, takeExpr);
|
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.
|
// Despite looking strange, this code is correct.
|
||||||
foreach(e; exps)
|
foreach(e; exps)
|
||||||
t = new ArrayType(t);
|
t = new ArrayType(t);
|
||||||
|
@ -140,6 +159,14 @@ abstract class Type : Block
|
||||||
// The complete type name including specifiers, eg. "int[]".
|
// The complete type name including specifiers, eg. "int[]".
|
||||||
char[] name;
|
char[] name;
|
||||||
|
|
||||||
|
MetaType meta;
|
||||||
|
final MetaType getMeta()
|
||||||
|
{
|
||||||
|
if(meta is null)
|
||||||
|
meta = new MetaType(this);
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
// Used for easy checking
|
// Used for easy checking
|
||||||
bool isInt() { return false; }
|
bool isInt() { return false; }
|
||||||
bool isUint() { return false; }
|
bool isUint() { return false; }
|
||||||
|
@ -151,10 +178,15 @@ abstract class Type : Block
|
||||||
bool isDouble() { return false; }
|
bool isDouble() { return false; }
|
||||||
bool isVoid() { return false; }
|
bool isVoid() { return false; }
|
||||||
|
|
||||||
|
bool isVar() { return false; }
|
||||||
|
|
||||||
bool isString() { return false; }
|
bool isString() { return false; }
|
||||||
|
|
||||||
bool isObject() { return false; }
|
|
||||||
bool isArray() { return arrays() != 0; }
|
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 isIntegral() { return isInt || isUint || isLong || isUlong; }
|
||||||
bool isFloating() { return isFloat || isDouble; }
|
bool isFloating() { return isFloat || isDouble; }
|
||||||
|
@ -339,6 +371,9 @@ abstract class Type : Block
|
||||||
assert(0, "doCastCTime not implemented for type " ~ toString);
|
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
|
/* Cast two expressions to their common type, if any. Throw a
|
||||||
TypeException exception if not possible. This exception should be
|
TypeException exception if not possible. This exception should be
|
||||||
caught elsewhere to give a more useful error message. Examples of
|
caught elsewhere to give a more useful error message. Examples of
|
||||||
|
@ -409,10 +444,8 @@ abstract class InternalType : Type
|
||||||
{
|
{
|
||||||
final:
|
final:
|
||||||
bool isLegal() { return false; }
|
bool isLegal() { return false; }
|
||||||
int[] defaultInit() {assert(0);}
|
int[] defaultInit() {assert(0, name);}
|
||||||
int getSize() { return 0; }
|
int getSize() {assert(0, name);}
|
||||||
void parse(ref TokenArray toks) {assert(0);}
|
|
||||||
void resolve(Scope sc) {assert(0);}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles the 'null' literal. This type is only used for
|
// Handles the 'null' literal. This type is only used for
|
||||||
|
@ -500,7 +533,6 @@ class BasicType : Type
|
||||||
{
|
{
|
||||||
Token t;
|
Token t;
|
||||||
if(!isNext(toks, TT.Identifier, t)) return false;
|
if(!isNext(toks, TT.Identifier, t)) return false;
|
||||||
|
|
||||||
return isBasic(t.str);
|
return isBasic(t.str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,8 +541,8 @@ class BasicType : Type
|
||||||
{
|
{
|
||||||
Token t;
|
Token t;
|
||||||
|
|
||||||
if(!isNext(toks, TT.Identifier, t) || !isBasic(t.str))
|
reqNext(toks, TT.Identifier, t);
|
||||||
assert(0, "Internal error in BasicType.parse()");
|
assert(isBasic(t.str));
|
||||||
|
|
||||||
// Get the name and the line from the token
|
// Get the name and the line from the token
|
||||||
name = t.str;
|
name = t.str;
|
||||||
|
@ -730,7 +762,13 @@ class ObjectType : Type
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
this() {}
|
this(Token t)
|
||||||
|
{
|
||||||
|
// Get the name and the line from the token
|
||||||
|
name = t.str;
|
||||||
|
loc = t.loc;
|
||||||
|
}
|
||||||
|
|
||||||
this(MonsterClass mc)
|
this(MonsterClass mc)
|
||||||
{
|
{
|
||||||
assert(mc !is null);
|
assert(mc !is null);
|
||||||
|
@ -739,12 +777,6 @@ class ObjectType : Type
|
||||||
clsIndex = mc.gIndex;
|
clsIndex = mc.gIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool canParse(TokenArray toks)
|
|
||||||
{
|
|
||||||
if(!isNext(toks, TT.Identifier)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
MonsterClass getClass()
|
MonsterClass getClass()
|
||||||
{
|
{
|
||||||
assert(clsIndex != 0);
|
assert(clsIndex != 0);
|
||||||
|
@ -799,9 +831,6 @@ class ObjectType : Type
|
||||||
assert(clsIndex !is tt.clsIndex);
|
assert(clsIndex !is tt.clsIndex);
|
||||||
int cnum = 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);
|
tasm.upcast(cnum);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -816,18 +845,6 @@ class ObjectType : Type
|
||||||
return getClass().sc;
|
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
|
// This is called when the type is defined, and it can forward
|
||||||
// reference classes that do not exist yet. The classes must exist
|
// reference classes that do not exist yet. The classes must exist
|
||||||
// by the time getClass() is called though, usually when class body
|
// 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?
|
// What type is this an array of?
|
||||||
Type base;
|
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)
|
this(Type btype)
|
||||||
{
|
{
|
||||||
base = btype;
|
base = btype;
|
||||||
|
@ -869,81 +901,291 @@ class ArrayType : Type
|
||||||
return base.isChar();
|
return base.isChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse(ref TokenArray toks)
|
|
||||||
{ assert(0, "array types aren't parsed"); }
|
|
||||||
|
|
||||||
void resolve(Scope sc)
|
void resolve(Scope sc)
|
||||||
{
|
{
|
||||||
base.resolve(sc);
|
base.resolve(sc);
|
||||||
|
|
||||||
|
if(base.isReplacer())
|
||||||
|
base = base.getBase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type is given to type-names that are used as variables
|
class EnumType : Type
|
||||||
// (ie. they are gramatically parsed by VariableExpr.) It's only used
|
{
|
||||||
// for expressions like int.max and p == int. Only basic types are
|
// The scope contains the actual enum values
|
||||||
// supported. Classes are handled in a separate type.
|
EnumScope sc;
|
||||||
class MetaType : InternalType
|
|
||||||
|
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:
|
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:
|
public:
|
||||||
|
|
||||||
// Get a basic type of the given name. This will not allocate a new
|
int size = -1;
|
||||||
// instance if another instance already exists.
|
StructScope sc; // Scope of the struct interior
|
||||||
static MetaType get(char[] tn)
|
int[] defInit;
|
||||||
{
|
bool set; // Have vars and defInit been set?
|
||||||
if(tn in store) return store[tn];
|
|
||||||
|
|
||||||
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);
|
foreach(v; vars)
|
||||||
name = base.toString;
|
v.type.validate();
|
||||||
loc = base.loc;
|
|
||||||
|
|
||||||
store[name] = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the scope belonging to the base type. This makes int.max
|
// Resolves member
|
||||||
// work just like i.max.
|
void resolve(Scope sc) {}
|
||||||
Scope getMemberScope() { return base.getMemberScope(); }
|
|
||||||
|
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 isMeta() { return true; }
|
||||||
|
|
||||||
bool canCastTo(Type type)
|
bool canCastTo(Type type)
|
||||||
{
|
{
|
||||||
return false;// type.isString;
|
return type.isString;
|
||||||
}
|
}
|
||||||
|
|
||||||
void evalCastTo(Type to)
|
void evalCastTo(Type to)
|
||||||
{
|
{
|
||||||
assert(to.isString);
|
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:
|
Types we might add later:
|
||||||
|
|
||||||
ClassType - on the form 'class MyClass', variable may refer to
|
TableType - a combination of lists, structures, hashmaps and
|
||||||
MyClass or to any subclass of MyClass.
|
arrays. Loosely based on Lua tables, but not the same.
|
||||||
|
|
||||||
ListType - lists on the form { a; b; c } or similar
|
|
||||||
|
|
||||||
AAType - associative array (hash map)
|
|
||||||
|
|
||||||
TableType - something similar to Lua tables
|
|
||||||
|
|
||||||
StructType - structs
|
|
||||||
|
|
||||||
EnumType - enums and flags
|
|
||||||
|
|
||||||
FunctionType - pointer to a function with a given header. Since all
|
FunctionType - pointer to a function with a given header. Since all
|
||||||
Monster functions are object members (methods), all
|
Monster functions are object members (methods), all
|
||||||
|
|
|
@ -28,10 +28,14 @@ import monster.compiler.types;
|
||||||
import monster.compiler.tokenizer;
|
import monster.compiler.tokenizer;
|
||||||
import monster.compiler.expression;
|
import monster.compiler.expression;
|
||||||
import monster.compiler.scopes;
|
import monster.compiler.scopes;
|
||||||
|
import monster.compiler.statement;
|
||||||
import monster.compiler.block;
|
import monster.compiler.block;
|
||||||
|
import monster.compiler.assembler;
|
||||||
|
|
||||||
import std.string;
|
import std.string;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
|
import monster.vm.mclass;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
|
|
||||||
enum VarType
|
enum VarType
|
||||||
|
@ -240,20 +244,85 @@ class VarDeclaration : Block
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special version used for explicitly numbering function
|
bool isParam = false;
|
||||||
// parameters. Only called from FuncDeclaration.resolve()
|
|
||||||
void resolve(Scope sc, int num)
|
// Special version only called from FuncDeclaration.resolve()
|
||||||
|
void resolveParam(Scope sc)
|
||||||
{
|
{
|
||||||
assert(num<0, "VarDec.resolve was given a positive num: " ~ .toString(num));
|
isParam = true;
|
||||||
var.number = num;
|
|
||||||
resolve(sc);
|
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
|
// Calls resolve() for all sub-expressions
|
||||||
override void resolve(Scope sc)
|
override void resolve(Scope sc)
|
||||||
{
|
{
|
||||||
var.type.resolve(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)
|
if(!allowConst && var.isConst)
|
||||||
fail("'const' is not allowed here", loc);
|
fail("'const' is not allowed here", loc);
|
||||||
|
|
||||||
|
@ -261,10 +330,10 @@ class VarDeclaration : Block
|
||||||
var.sc = cast(VarScope)sc;
|
var.sc = cast(VarScope)sc;
|
||||||
assert(var.sc !is null, "variables can only be declared in VarScopes");
|
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
|
// If we are not a function parameter, we must get
|
||||||
// not a function parameter), we must get it from the scope.
|
// var.number from the scope.
|
||||||
if(sc.isClass())
|
if(sc.isClass())
|
||||||
// Class variable. Get a position in the data segment.
|
// Class variable. Get a position in the data segment.
|
||||||
var.number = sc.addNewDataVar(var.type.getSize());
|
var.number = sc.addNewDataVar(var.type.getSize());
|
||||||
|
@ -275,23 +344,32 @@ class VarDeclaration : Block
|
||||||
}
|
}
|
||||||
else assert(sc.isFunc());
|
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.
|
// Insert ourselves into the scope.
|
||||||
sc.insertVar(var);
|
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
|
// Executed for local variables upon declaration. Push the variable
|
||||||
// on the stack.
|
// on the stack.
|
||||||
void compile()
|
void compile()
|
||||||
|
@ -309,3 +387,441 @@ class VarDeclaration : Block
|
||||||
var.type.pushInit();
|
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.codestream;
|
||||||
import monster.vm.mobject;
|
import monster.vm.mobject;
|
||||||
|
import monster.vm.mclass;
|
||||||
import monster.vm.stack;
|
import monster.vm.stack;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.compiler.states;
|
import monster.compiler.states;
|
||||||
|
@ -59,6 +60,7 @@ struct StackPoint
|
||||||
SPType ftype;
|
SPType ftype;
|
||||||
|
|
||||||
MonsterObject *obj; // "this"-pointer for the function
|
MonsterObject *obj; // "this"-pointer for the function
|
||||||
|
MonsterClass cls; // class owning the function
|
||||||
|
|
||||||
int afterStack; // Where the stack should be when this function
|
int afterStack; // Where the stack should be when this function
|
||||||
// returns
|
// returns
|
||||||
|
@ -109,12 +111,13 @@ struct FunctionStack
|
||||||
cur.frame = stack.setFrame();
|
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)
|
void push(Function *func, MonsterObject *obj)
|
||||||
{
|
{
|
||||||
push(obj);
|
push(obj);
|
||||||
cur.ftype = SPType.Function;
|
cur.ftype = SPType.Function;
|
||||||
cur.func = func;
|
cur.func = func;
|
||||||
|
cur.cls = func.owner;
|
||||||
|
|
||||||
// Point the code stream to the byte code, if any.
|
// Point the code stream to the byte code, if any.
|
||||||
if(func.isNormal)
|
if(func.isNormal)
|
||||||
|
@ -130,6 +133,9 @@ struct FunctionStack
|
||||||
cur.ftype = SPType.State;
|
cur.ftype = SPType.State;
|
||||||
cur.state = st;
|
cur.state = st;
|
||||||
|
|
||||||
|
assert(obj !is null);
|
||||||
|
cur.cls = obj.cls;
|
||||||
|
|
||||||
// Set up the byte code
|
// Set up the byte code
|
||||||
cur.code.setData(st.bcode, st.lines);
|
cur.code.setData(st.bcode, st.lines);
|
||||||
}
|
}
|
||||||
|
@ -137,12 +143,17 @@ struct FunctionStack
|
||||||
// Native constructor
|
// Native constructor
|
||||||
void pushNConst(MonsterObject *obj)
|
void pushNConst(MonsterObject *obj)
|
||||||
{
|
{
|
||||||
|
assert(obj !is null);
|
||||||
push(obj);
|
push(obj);
|
||||||
cur.ftype = SPType.NConst;
|
cur.ftype = SPType.NConst;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushIdleCommon(Function *fn, MonsterObject *obj, SPType tp)
|
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);
|
push(obj);
|
||||||
cur.func = fn;
|
cur.func = fn;
|
||||||
assert(fn.isIdle, fn.name.str ~ "() is not an idle function");
|
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.statement;
|
||||||
import monster.compiler.variables;
|
import monster.compiler.variables;
|
||||||
import monster.compiler.states;
|
import monster.compiler.states;
|
||||||
|
import monster.compiler.structs;
|
||||||
import monster.compiler.block;
|
import monster.compiler.block;
|
||||||
|
import monster.compiler.enums;
|
||||||
|
|
||||||
import monster.vm.vm;
|
import monster.vm.thread;
|
||||||
import monster.vm.codestream;
|
import monster.vm.codestream;
|
||||||
import monster.vm.scheduler;
|
import monster.vm.scheduler;
|
||||||
import monster.vm.idlefunction;
|
import monster.vm.idlefunction;
|
||||||
import monster.vm.fstack;
|
import monster.vm.fstack;
|
||||||
import monster.vm.arrays;
|
import monster.vm.arrays;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
|
import monster.vm.vm;
|
||||||
import monster.vm.mobject;
|
import monster.vm.mobject;
|
||||||
|
|
||||||
import monster.util.flags;
|
import monster.util.flags;
|
||||||
|
@ -63,6 +66,8 @@ typedef void *MClass; // Pointer to C++ equivalent of MonsterClass.
|
||||||
|
|
||||||
typedef int CIndex;
|
typedef int CIndex;
|
||||||
|
|
||||||
|
alias FreeList!(CodeThread) ThreadList;
|
||||||
|
|
||||||
// Parameter to the constructor. Decides how the class is created.
|
// Parameter to the constructor. Decides how the class is created.
|
||||||
enum MC
|
enum MC
|
||||||
{
|
{
|
||||||
|
@ -84,6 +89,9 @@ enum CFlags
|
||||||
Compiled = 0x08, // Class body has been compiled
|
Compiled = 0x08, // Class body has been compiled
|
||||||
InScope = 0x10, // We are currently inside the createScope
|
InScope = 0x10, // We are currently inside the createScope
|
||||||
// function
|
// 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.
|
// 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.
|
// TODO: These should be moved to vm.vm
|
||||||
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 *
|
|
||||||
* *
|
|
||||||
***********************************************/
|
|
||||||
|
|
||||||
// Get a class with the given name. It must already be loaded.
|
// Get a class with the given name. It must already be loaded.
|
||||||
static MonsterClass get(char[] name) { return global.getClass(name); }
|
static MonsterClass get(char[] name) { return global.getClass(name); }
|
||||||
|
@ -142,6 +112,13 @@ final class MonsterClass
|
||||||
// fail if the class cannot be found.
|
// fail if the class cannot be found.
|
||||||
static MonsterClass find(char[] name) { return global.findClass(name); }
|
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:
|
final:
|
||||||
|
|
||||||
/*******************************************************
|
/*******************************************************
|
||||||
|
@ -150,17 +127,11 @@ final class MonsterClass
|
||||||
* *
|
* *
|
||||||
*******************************************************/
|
*******************************************************/
|
||||||
|
|
||||||
alias FreeList!(CodeThread) ThreadList;
|
|
||||||
alias FreeList!(MonsterObject) ObjectList;
|
alias FreeList!(MonsterObject) ObjectList;
|
||||||
|
|
||||||
// TODO: Put as many of these as possible in the private
|
// TODO: Put as many of these as possible in the private
|
||||||
// section. Ie. move all of them and see what errors you get.
|
// 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
|
// Index within the parent tree. This might become a list at some
|
||||||
// point.
|
// point.
|
||||||
int treeIndex;
|
int treeIndex;
|
||||||
|
@ -182,6 +153,10 @@ final class MonsterClass
|
||||||
bool isResolved() { return flags.has(CFlags.Resolved); }
|
bool isResolved() { return flags.has(CFlags.Resolved); }
|
||||||
bool isCompiled() { return flags.has(CFlags.Compiled); }
|
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
|
// Call whenever you require this function to have its scope in
|
||||||
// order. If the scope is missing, this will call createScope if
|
// order. If the scope is missing, this will call createScope if
|
||||||
// possible, or fail if the class has not been loaded.
|
// possible, or fail if the class has not been loaded.
|
||||||
|
@ -201,12 +176,6 @@ final class MonsterClass
|
||||||
// already.
|
// already.
|
||||||
void requireCompile() { if(!isCompiled) compileBody(); }
|
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)
|
if(type == MC.String)
|
||||||
{
|
{
|
||||||
assert(name2 == "", "MC.String only takes one parameter");
|
loadString(name1, name2);
|
||||||
loadString(name1);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -284,11 +252,12 @@ final class MonsterClass
|
||||||
void loadCI(char[] name1, char[] name2 = "", bool usePath=true)
|
void loadCI(char[] name1, char[] name2 = "", bool usePath=true)
|
||||||
{ doLoad(name1, name2, false, usePath); }
|
{ doLoad(name1, name2, false, usePath); }
|
||||||
|
|
||||||
void loadString(char[] str)
|
void loadString(char[] str, char[] fname="")
|
||||||
{
|
{
|
||||||
assert(str != "");
|
assert(str != "");
|
||||||
auto ms = new MemoryStream(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
|
// Load a script from a stream. The filename parameter is only used
|
||||||
|
@ -338,7 +307,20 @@ final class MonsterClass
|
||||||
return functions[index];
|
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)
|
Function *findFunction(char[] name)
|
||||||
{
|
{
|
||||||
requireScope();
|
requireScope();
|
||||||
|
@ -493,11 +475,23 @@ final class MonsterClass
|
||||||
MonsterObject* getFirst()
|
MonsterObject* getFirst()
|
||||||
{ return objects.getHead(); }
|
{ 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.
|
// Create a new object, and assign a thread to it.
|
||||||
MonsterObject* createObject()
|
MonsterObject* createObject()
|
||||||
{
|
{
|
||||||
requireCompile();
|
requireCompile();
|
||||||
|
|
||||||
|
if(isAbstract)
|
||||||
|
fail("Cannot create objects from abstract class " ~ name.str);
|
||||||
|
|
||||||
// Create the thread
|
// Create the thread
|
||||||
CodeThread *trd = threads.getNew();
|
CodeThread *trd = threads.getNew();
|
||||||
|
|
||||||
|
@ -652,49 +646,11 @@ final class MonsterClass
|
||||||
|
|
||||||
// Get the global index of this class
|
// Get the global index of this class
|
||||||
CIndex getIndex() { requireScope(); return gIndex; }
|
CIndex getIndex() { requireScope(); return gIndex; }
|
||||||
|
int getTreeIndex() { requireScope(); return treeIndex; }
|
||||||
char[] getName() { assert(name.str != ""); return name.str; }
|
char[] getName() { assert(name.str != ""); return name.str; }
|
||||||
char[] toString() { return getName(); }
|
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:
|
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.
|
// The freelists used for allocation of objects and threads.
|
||||||
ObjectList objects;
|
ObjectList objects;
|
||||||
ThreadList threads;
|
ThreadList threads;
|
||||||
|
@ -711,9 +687,6 @@ final class MonsterClass
|
||||||
int[] data; // Contains the initial object data segment
|
int[] data; // Contains the initial object data segment
|
||||||
int[] sdata; // Static 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
|
// Size of the data segment
|
||||||
uint dataSize;
|
uint dataSize;
|
||||||
|
|
||||||
|
@ -724,13 +697,18 @@ final class MonsterClass
|
||||||
MonsterClass parents[];
|
MonsterClass parents[];
|
||||||
Token parentNames[];
|
Token parentNames[];
|
||||||
|
|
||||||
|
// Used at compile time
|
||||||
VarDeclStatement[] vardecs;
|
VarDeclStatement[] vardecs;
|
||||||
FuncDeclaration[] funcdecs;
|
FuncDeclaration[] funcdecs;
|
||||||
StateDeclaration[] statedecs;
|
StateDeclaration[] statedecs;
|
||||||
|
StructDeclaration[] structdecs;
|
||||||
|
EnumDeclaration[] enumdecs;
|
||||||
|
|
||||||
|
// Current stage of the loading process
|
||||||
MC loadType = MC.None;
|
MC loadType = MC.None;
|
||||||
|
|
||||||
// Native constructor type
|
// Native constructor type. Changed when the actual constructor is
|
||||||
|
// set.
|
||||||
FuncType constType = FuncType.Native;
|
FuncType constType = FuncType.Native;
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
|
@ -766,20 +744,7 @@ final class MonsterClass
|
||||||
int[] val;
|
int[] val;
|
||||||
totSize += size;
|
totSize += size;
|
||||||
|
|
||||||
// Does this variable have an initializer?
|
val = vd.getCTimeValue();
|
||||||
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");
|
|
||||||
|
|
||||||
data[vd.var.number..vd.var.number+size] = val[];
|
data[vd.var.number..vd.var.number+size] = val[];
|
||||||
}
|
}
|
||||||
|
@ -953,7 +918,7 @@ final class MonsterClass
|
||||||
if(!checkFileName())
|
if(!checkFileName())
|
||||||
fail(format("Invalid class name %s (file %s)", cname, fname));
|
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);
|
fail("Cannot find script file " ~ fname);
|
||||||
|
|
||||||
// Create a temporary file stream and load it
|
// Create a temporary file stream and load it
|
||||||
|
@ -1036,6 +1001,18 @@ final class MonsterClass
|
||||||
sd.parse(toks);
|
sd.parse(toks);
|
||||||
statedecs ~= sd;
|
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
|
else
|
||||||
fail("Illegal type or declaration", toks);
|
fail("Illegal type or declaration", toks);
|
||||||
}
|
}
|
||||||
|
@ -1044,19 +1021,43 @@ final class MonsterClass
|
||||||
void parse(Stream str, char[] fname, int bom)
|
void parse(Stream str, char[] fname, int bom)
|
||||||
{
|
{
|
||||||
assert(!isParsed(), "parse() called on a parsed class " ~ name.str);
|
assert(!isParsed(), "parse() called on a parsed class " ~ name.str);
|
||||||
|
|
||||||
assert(str !is null);
|
assert(str !is null);
|
||||||
|
|
||||||
TokenArray tokens = tokenizeStream(fname, str, bom);
|
TokenArray tokens = tokenizeStream(fname, str, bom);
|
||||||
|
|
||||||
alias Block.isNext isNext;
|
alias Block.isNext isNext;
|
||||||
|
|
||||||
if(!isNext(tokens, TT.Class))
|
// TODO: Check for a list of keywords here. class, module,
|
||||||
fail("File must begin with a valid class statement");
|
// 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))
|
if(!isNext(tokens, TT.Identifier, name))
|
||||||
fail("Class statement expected identifier", tokens);
|
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
|
// Insert ourselves into the global scope. This will also
|
||||||
// resolve forward references to this class, if any.
|
// resolve forward references to this class, if any.
|
||||||
global.insertClass(this);
|
global.insertClass(this);
|
||||||
|
@ -1064,6 +1065,9 @@ final class MonsterClass
|
||||||
// Get the parent classes, if any
|
// Get the parent classes, if any
|
||||||
if(isNext(tokens, TT.Colon))
|
if(isNext(tokens, TT.Colon))
|
||||||
{
|
{
|
||||||
|
if(isModule)
|
||||||
|
fail("Inheritance not allowed for modules.");
|
||||||
|
|
||||||
Token pName;
|
Token pName;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
@ -1125,6 +1129,9 @@ final class MonsterClass
|
||||||
fail("Class " ~ name.str ~ " cannot inherit from itself",
|
fail("Class " ~ name.str ~ " cannot inherit from itself",
|
||||||
name.loc);
|
name.loc);
|
||||||
|
|
||||||
|
if(mc.isModule)
|
||||||
|
fail("Cannot inherit from module " ~ mc.name.str);
|
||||||
|
|
||||||
// If a parent class is not a forward reference and still
|
// If a parent class is not a forward reference and still
|
||||||
// does not have a scope, it means that it is itself running
|
// does not have a scope, it means that it is itself running
|
||||||
// this function. This can only happen if we are a parent of
|
// this function. This can only happen if we are a parent of
|
||||||
|
@ -1165,10 +1172,22 @@ final class MonsterClass
|
||||||
|
|
||||||
// Set the type
|
// Set the type
|
||||||
objType = new ObjectType(this);
|
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
|
// Resolve variable declarations. They will insert themselves
|
||||||
// into the scope. TODO: Init values will have to be handled as
|
// into the scope.
|
||||||
// part of the body later.
|
|
||||||
foreach(dec; vardecs)
|
foreach(dec; vardecs)
|
||||||
dec.resolve(sc);
|
dec.resolve(sc);
|
||||||
|
|
||||||
|
@ -1195,6 +1214,60 @@ final class MonsterClass
|
||||||
foreach(st; statedecs)
|
foreach(st; statedecs)
|
||||||
states[st.st.index] = st.st;
|
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
|
// Set the data segment size and the total data size for all
|
||||||
// base classes.
|
// base classes.
|
||||||
dataSize = sc.getDataSize();
|
dataSize = sc.getDataSize();
|
||||||
|
@ -1224,6 +1297,12 @@ final class MonsterClass
|
||||||
foreach(state; statedecs)
|
foreach(state; statedecs)
|
||||||
state.resolve(sc);
|
state.resolve(sc);
|
||||||
|
|
||||||
|
// TODO: Resolve struct functions
|
||||||
|
/*
|
||||||
|
foredach(stru; structdecs)
|
||||||
|
stru.resolveBody(sc);
|
||||||
|
*/
|
||||||
|
|
||||||
// Validate all variable types
|
// Validate all variable types
|
||||||
foreach(var; vardecs)
|
foreach(var; vardecs)
|
||||||
var.validate();
|
var.validate();
|
||||||
|
@ -1248,6 +1327,13 @@ final class MonsterClass
|
||||||
data = getDataSegment();
|
data = getDataSegment();
|
||||||
|
|
||||||
flags.set(CFlags.Compiled);
|
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;
|
module monster.vm.mobject;
|
||||||
|
|
||||||
import monster.vm.vm;
|
import monster.vm.thread;
|
||||||
import monster.vm.error;
|
import monster.vm.error;
|
||||||
import monster.vm.mclass;
|
import monster.vm.mclass;
|
||||||
import monster.vm.arrays;
|
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)
|
T* getPtr(T)(char[] name)
|
||||||
{
|
{
|
||||||
// Find the variable
|
// Find the variable
|
||||||
|
@ -279,11 +279,11 @@ struct MonsterObject
|
||||||
|
|
||||||
// Call a named function. The function is executed immediately, and
|
// Call a named function. The function is executed immediately, and
|
||||||
// call() returns when the function is finished. The function is
|
// call() returns when the function is finished. The function is
|
||||||
// called virtually, so any sub-class function that overrides it in
|
// called virtually, so any child class function that overrides it
|
||||||
// this object will take precedence.
|
// will take precedence.
|
||||||
void call(char[] name)
|
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
|
// Call a function non-virtually. In other words, ignore
|
||||||
|
@ -344,12 +344,14 @@ struct MonsterObject
|
||||||
MonsterObject *mo = null;
|
MonsterObject *mo = null;
|
||||||
|
|
||||||
if(index < ptree.length)
|
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
|
// It's only a match if the classes match
|
||||||
if(mo.cls !is toClass) mo = null;
|
if(mo.cls !is toClass) mo = null;
|
||||||
|
}
|
||||||
|
|
||||||
// If no match was found, then the cast failed.
|
// If no match was found, then the cast failed.
|
||||||
if(mo is null)
|
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
1245
monster/vm/vm.d
1245
monster/vm/vm.d
File diff suppressed because it is too large
Load diff
|
@ -21,8 +21,7 @@
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: Should be a singleton
|
singleton Config : Object;
|
||||||
class Config : Object;
|
|
||||||
|
|
||||||
// Only some config options have been moved into Monster. Key bindings
|
// Only some config options have been moved into Monster. Key bindings
|
||||||
// and other low-level settings are still handled in D.
|
// and other low-level settings are still handled in D.
|
||||||
|
@ -42,13 +41,13 @@ bool flipMouseY;
|
||||||
setMainVolume(float f)
|
setMainVolume(float f)
|
||||||
{
|
{
|
||||||
mainVolume = f;
|
mainVolume = f;
|
||||||
music().updateVolume();
|
Music.updateVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
setMusicVolume(float f)
|
setMusicVolume(float f)
|
||||||
{
|
{
|
||||||
musicVolume = f;
|
musicVolume = f;
|
||||||
music().updateVolume();
|
Music.updateVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
setSfxVolume(float f)
|
setSfxVolume(float f)
|
||||||
|
|
|
@ -24,5 +24,12 @@
|
||||||
// Covers all alchemy apparatus - mortars, retorts, etc
|
// Covers all alchemy apparatus - mortars, retorts, etc
|
||||||
class Apparatus : InventoryItem;
|
class Apparatus : InventoryItem;
|
||||||
|
|
||||||
|
enum AppaType
|
||||||
|
{
|
||||||
|
MortarPestle = 0 : "Mortar and Pestle",
|
||||||
|
Albemic = 1,
|
||||||
|
Calcinator = 2
|
||||||
|
}
|
||||||
|
|
||||||
float quality;
|
float quality;
|
||||||
int type;
|
int type;
|
||||||
|
|
|
@ -25,3 +25,17 @@
|
||||||
class Clothing : EnchantItem;
|
class Clothing : EnchantItem;
|
||||||
|
|
||||||
int type;
|
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,
|
// Contains all the game settings (GMST) variables of Morrowind,
|
||||||
// Tribunal and Bloodmoon. Based on "Morrowind Scripting for Dummies"
|
// Tribunal and Bloodmoon. Based on "Morrowind Scripting for Dummies"
|
||||||
// (v9).
|
// (v9).
|
||||||
class GameSettings : Object;
|
singleton GameSettings : Object;
|
||||||
|
|
||||||
// Most of the comments are copied from MSfD. A bit of cleanup is
|
// Most of the comments are copied from MSfD. A bit of cleanup is
|
||||||
// still needed.
|
// still needed.
|
||||||
|
|
|
@ -34,16 +34,15 @@ import sound.music;
|
||||||
void initMonsterScripts()
|
void initMonsterScripts()
|
||||||
{
|
{
|
||||||
// Add the script directories
|
// Add the script directories
|
||||||
MonsterClass.addPath("mscripts/");
|
vm.addPath("mscripts/");
|
||||||
MonsterClass.addPath("mscripts/gameobjects/");
|
vm.addPath("mscripts/gameobjects/");
|
||||||
MonsterClass.addPath("mscripts/sound/");
|
vm.addPath("mscripts/sound/");
|
||||||
|
|
||||||
// Make sure the Object class is loaded
|
// Make sure the Object class is loaded
|
||||||
auto mc = new MonsterClass("Object", "object.mn");
|
auto mc = new MonsterClass("Object", "object.mn");
|
||||||
|
|
||||||
// Create the config object too (only needed here because Object
|
// Get the Config singleton object
|
||||||
// refers to Config. This will change.)
|
config.mo = (new MonsterClass("Config")).getSing();
|
||||||
config.mo = (new MonsterClass("Config")).createObject;
|
|
||||||
|
|
||||||
// Bind various functions
|
// Bind various functions
|
||||||
mc.bind("print", { print(); });
|
mc.bind("print", { print(); });
|
||||||
|
@ -52,10 +51,6 @@ void initMonsterScripts()
|
||||||
(stack.popInt,stack.popInt));});
|
(stack.popInt,stack.popInt));});
|
||||||
mc.bind("sleep", new IdleSleep);
|
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
|
// Load and run the test script
|
||||||
mc = new MonsterClass("Test");
|
mc = new MonsterClass("Test");
|
||||||
mc.createObject().call("test");
|
mc.createObject().call("test");
|
||||||
|
|
|
@ -24,11 +24,6 @@
|
||||||
// This is the base class of all OpenMW Monster classes.
|
// This is the base class of all OpenMW Monster classes.
|
||||||
class Object;
|
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
|
// Sleeps a given amount of time
|
||||||
idle sleep(float seconds);
|
idle sleep(float seconds);
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ pause()
|
||||||
|
|
||||||
resume()
|
resume()
|
||||||
{
|
{
|
||||||
if(!config().useMusic) return;
|
if(!Config.useMusic) return;
|
||||||
|
|
||||||
if(isPaused) playSound();
|
if(isPaused) playSound();
|
||||||
else next();
|
else next();
|
||||||
|
@ -94,7 +94,7 @@ stop()
|
||||||
|
|
||||||
play()
|
play()
|
||||||
{
|
{
|
||||||
if(!config().useMusic) return;
|
if(!Config.useMusic) return;
|
||||||
|
|
||||||
if(index >= playlist.length)
|
if(index >= playlist.length)
|
||||||
return;
|
return;
|
||||||
|
@ -112,7 +112,7 @@ play()
|
||||||
// Play the next song in the playlist
|
// Play the next song in the playlist
|
||||||
next()
|
next()
|
||||||
{
|
{
|
||||||
if(!config().useMusic) return;
|
if(!Config.useMusic) return;
|
||||||
|
|
||||||
if(isPlaying)
|
if(isPlaying)
|
||||||
stop();
|
stop();
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// This class controls all the music.
|
// This class controls all the music.
|
||||||
class Music : Object;
|
singleton Music : Object;
|
||||||
|
|
||||||
// Create one jukebox for normal music, and one for battle music. This
|
// 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.
|
// 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.
|
// have been changed by the user.
|
||||||
updateVolume()
|
updateVolume()
|
||||||
{
|
{
|
||||||
float v = config().calcMusicVolume();
|
float v = Config.calcMusicVolume();
|
||||||
jukebox.updateVolume(v);
|
jukebox.updateVolume(v);
|
||||||
battle.updateVolume(v);
|
battle.updateVolume(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ struct Music
|
||||||
jukeC.bindConst({new Jukebox(params.obj()); });
|
jukeC.bindConst({new Jukebox(params.obj()); });
|
||||||
|
|
||||||
controlC = MonsterClass.get("Music");
|
controlC = MonsterClass.get("Music");
|
||||||
controlM = controlC.createObject;
|
controlM = controlC.getSing();
|
||||||
controlM.call("setup");
|
controlM.call("setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue