forked from teamnwah/openmw-tes3coop
1b01de4294
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@103 ea6a568a-9f4f-0410-981a-c910a81bb256
1798 lines
47 KiB
D
1798 lines
47 KiB
D
/*
|
|
Monster - an advanced game scripting language
|
|
Copyright (C) 2007-2009 Nicolay Korslund
|
|
Email: <korslund@gmail.com>
|
|
WWW: http://monster.snaptoad.com/
|
|
|
|
This file (types.d) is part of the Monster script language package.
|
|
|
|
Monster is distributed as free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License
|
|
version 3, as published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
version 3 along with this program. If not, see
|
|
http://www.gnu.org/licenses/ .
|
|
|
|
*/
|
|
|
|
module monster.compiler.types;
|
|
|
|
import monster.compiler.tokenizer;
|
|
import monster.compiler.enums;
|
|
import monster.compiler.scopes;
|
|
import monster.compiler.expression;
|
|
import monster.compiler.assembler;
|
|
import monster.compiler.properties;
|
|
import monster.compiler.block;
|
|
import monster.compiler.functions;
|
|
import monster.compiler.variables;
|
|
import monster.compiler.states;
|
|
import monster.compiler.structs;
|
|
import monster.vm.arrays;
|
|
import monster.vm.mclass;
|
|
import monster.vm.mobject;
|
|
import monster.vm.error;
|
|
|
|
import monster.options;
|
|
|
|
import std.stdio;
|
|
import std.utf;
|
|
import std.string;
|
|
|
|
/*
|
|
List of all type classes:
|
|
|
|
Type (abstract)
|
|
InternalType (abstract)
|
|
ReplacerType (abstract)
|
|
NullType (null expression)
|
|
BasicType (covers int, char, bool, float, void)
|
|
ObjectType
|
|
ArrayType
|
|
StructType
|
|
EnumType
|
|
FunctionType
|
|
PackageType
|
|
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);)
|
|
|
|
*/
|
|
|
|
// A class that represents a type. The Type class is abstract, and the
|
|
// different types are actually handled by various subclasses of Type
|
|
// (see below.) The static function identify() is used to figure out
|
|
// exactly which subclass to use.
|
|
abstract class Type : Block
|
|
{
|
|
// Can the given tokens possibly be parsed as a type? This is not
|
|
// meant as an extensive test to differentiate between types and
|
|
// non-types, it is more like a short-cut to get the type tokens out
|
|
// of the way. It should only be called from places where you
|
|
// require a type (and only in canParse(), since it removes the
|
|
// tokens.)
|
|
static bool canParseRem(ref TokenArray toks)
|
|
{
|
|
// We allow typeof(expression) as a type
|
|
if(isNext(toks, TT.Typeof))
|
|
{
|
|
reqNext(toks, TT.LeftParen);
|
|
Expression.identify(toks); // Possibly a bit wasteful...
|
|
reqNext(toks, TT.RightParen);
|
|
return true;
|
|
}
|
|
|
|
// The 'var' keyword can be used as a type
|
|
if(isNext(toks, TT.Var)) return true;
|
|
|
|
// Allow typename
|
|
if(!isNext(toks, TT.Identifier)) return false;
|
|
|
|
// Allow a.b.c
|
|
while(isNext(toks, TT.Dot))
|
|
if(!isNext(toks, TT.Identifier)) return false;
|
|
|
|
// Allow a[][]
|
|
while(isNext(toks,TT.LeftSquare))
|
|
if(!isNext(toks,TT.RightSquare))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Parse a type specifieer and return the correct class to handle it
|
|
// (fully parsed). Currently valid type formats are
|
|
//
|
|
// identifier - either a basic type (int, bool, etc) or a class name
|
|
// identifier[] - array
|
|
// identifier[][]... - arrays can be multi-dimensional
|
|
//
|
|
// static buffers, ie. int[10] are not allowed as types. The only
|
|
// place a type is allowed to take array expressions is after a new,
|
|
// eg. int[] i = new int[10], in that case you should set the second
|
|
// parameter to true. The expression array is stored in exps.
|
|
static Type identify(ref TokenArray toks, bool takeExpr, ref ExprArray exps)
|
|
{
|
|
assert(toks.length != 0);
|
|
|
|
// Model this after Exp.idSub or Code.identify
|
|
Type t = null;
|
|
|
|
// Find what kind of type this is and create an instance of the
|
|
// corresponding class. TODO: Lots of redundant objects created
|
|
// here.
|
|
if(BasicType.canParse(toks)) t = new BasicType();
|
|
else if(UserType.canParse(toks)) t = new UserType();
|
|
else if(GenericType.canParse(toks)) t = new GenericType();
|
|
else if(TypeofType.canParse(toks)) t = new TypeofType();
|
|
else fail("Cannot parse " ~ toks[0].str ~ " as a type", toks[0].loc);
|
|
|
|
// Parse the actual tokens with our new and shiny object.
|
|
t.parse(toks);
|
|
|
|
// Add arrays here afterwards by wrapping the previous type in
|
|
// an ArrayType.
|
|
exps = VarDeclaration.getArray(toks, takeExpr);
|
|
|
|
if(t.isVar && exps.length)
|
|
fail("Cannot have arrays of var (yet)", t.loc);
|
|
|
|
// Despite looking strange, this code is correct.
|
|
foreach(e; exps)
|
|
t = new ArrayType(t);
|
|
|
|
return t;
|
|
}
|
|
|
|
// Short version of the above, when expressions are not allowed.
|
|
static Type identify(ref TokenArray toks)
|
|
{
|
|
ExprArray exp;
|
|
return identify(toks, false, exp);
|
|
}
|
|
|
|
// Lookup table of all types
|
|
static Type[int] typeList;
|
|
int tIndex; // Index of this type
|
|
|
|
// The complete type name including specifiers, eg. "int[]".
|
|
char[] name;
|
|
|
|
MetaType meta;
|
|
final MetaType getMeta()
|
|
{
|
|
if(meta is null)
|
|
meta = new MetaType(this);
|
|
return meta;
|
|
}
|
|
|
|
this()
|
|
{
|
|
tIndex = typeList.length;
|
|
typeList[tIndex] = this;
|
|
}
|
|
|
|
// Used for easy checking
|
|
bool isInt() { return false; }
|
|
bool isUint() { return false; }
|
|
bool isLong() { return false; }
|
|
bool isUlong() { return false; }
|
|
bool isChar() { return false; }
|
|
bool isBool() { return false; }
|
|
bool isFloat() { return false; }
|
|
bool isDouble() { return false; }
|
|
bool isVoid() { return false; }
|
|
|
|
bool isVar() { return false; }
|
|
|
|
bool isString() { return false; }
|
|
|
|
bool isArray() { return arrays() != 0; }
|
|
bool isObject() { return false; }
|
|
bool isPackage() { return false; }
|
|
bool isStruct() { return false; }
|
|
bool isEnum() { return false; }
|
|
bool isFunc() { return false; }
|
|
|
|
bool isReplacer() { return false; }
|
|
|
|
bool isIntegral() { return isInt || isUint || isLong || isUlong; }
|
|
bool isFloating() { return isFloat || isDouble; }
|
|
|
|
// Numerical types allow arithmetic operators
|
|
bool isNumerical() { return isIntegral() || isFloating(); }
|
|
|
|
// Is this a meta-type? A meta-type is the type of expressions like
|
|
// 'int' and 'ClassName' - they themselves describe a type.
|
|
// Meta-types always have a base type accessible through getBase(),
|
|
// and a member scope similar to variables of the type itself.
|
|
bool isMeta() { return false; }
|
|
|
|
// Is this a legal type for variables? If this is false, you cannot
|
|
// create variables of this type, neither directly or indirectly
|
|
// (through automatic type inference.) This isn't currently used
|
|
// anywhere, but we will need it later when we implement automatic
|
|
// types.
|
|
bool isLegal() { return true; }
|
|
|
|
// Get base type (used for arrays and meta-types)
|
|
Type getBase() { assert(0, "Type " ~ toString() ~ " has no base type"); }
|
|
|
|
// Return the array dimension of this type. Eg. int[][] has two
|
|
// dimensions, int has zero.
|
|
int arrays() { return 0; }
|
|
|
|
// Return the number of ints needed to store one variable of this
|
|
// type.
|
|
int getSize();
|
|
|
|
// Get the scope for resolving members of this type. Examples:
|
|
// myObject.func() -> getMemberScope called for the type of myObject,
|
|
// returns the class scope, where func is resolved.
|
|
// array.length -> getMemberScope called for ArrayType, returns a
|
|
// special scope that contains the 'length' property
|
|
Scope getMemberScope()
|
|
{
|
|
// Returning null means this type does not have any members.
|
|
return null;
|
|
}
|
|
|
|
// Validate that this type actually exists. This is used to make
|
|
// sure that all forward references are resolved.
|
|
void validate(Floc loc) {}
|
|
|
|
// Check if this type is equivalent to the given D type
|
|
final bool isDType(TypeInfo ti)
|
|
{
|
|
char[] name = ti.toString;
|
|
name = name[name.rfind('.')+1..$];
|
|
|
|
switch(name)
|
|
{
|
|
case "int": return isInt();
|
|
case "uint": return isUint();
|
|
case "long": return isLong();
|
|
case "ulong": return isUlong();
|
|
case "float": return isFloat();
|
|
case "double": return isDouble();
|
|
case "bool": return isBool();
|
|
case "dchar": return isChar();
|
|
case "AIndex": return isArray();
|
|
case "MIndex": return isObject();
|
|
default:
|
|
assert(0, "illegal type in isDType(): " ~ name);
|
|
}
|
|
}
|
|
|
|
// Used by defaultInit as a shortcut for converting a variable to an
|
|
// int array.
|
|
static int[] makeData(T)(T val)
|
|
{
|
|
int data[];
|
|
if(T.sizeof == 4) data.length = 1;
|
|
else if(T.sizeof == 8) data.length = 2;
|
|
else assert(0, "Unsupported type size");
|
|
|
|
*(cast(T*)data.ptr) = val;
|
|
|
|
return data;
|
|
}
|
|
|
|
// Get the default initializer for this type. The assembler deals
|
|
// with data in terms of ints (4 byte chunks), so we return the data
|
|
// as an int[]. The default initializer should be an illegal value
|
|
// when possible (null pointers, nan, etc) to catch mistakenly
|
|
// uninitialized variables as quickly as possible. This will usually
|
|
// be the same init value as in D.
|
|
int[] defaultInit();
|
|
|
|
// Generate assembler code that pushes the default initializer on
|
|
// the stack. TODO: This should become a general function in the
|
|
// assembler to push any int[]. Pass it a type so it can check the
|
|
// size automatically as well.
|
|
final void pushInit()
|
|
{
|
|
int[] def = defaultInit();
|
|
assert(def.length == getSize, "default initializer is not the correct size");
|
|
setLine();
|
|
|
|
tasm.pushArray(def);
|
|
}
|
|
|
|
// Compare types
|
|
final int opEquals(Type t)
|
|
{
|
|
if(toString != t.toString) return 0;
|
|
|
|
// TODO: In our current system, if the name and array dimension
|
|
// match, we must be the same type. In the future we might have
|
|
// to add more tests here however. For example, two structs of
|
|
// the same name might be defined in different classes. The best
|
|
// solution is perhaps to always make sure the type name is
|
|
// unique.
|
|
|
|
return 1;
|
|
}
|
|
|
|
final char[] toString() { return name; }
|
|
|
|
// Cast the expression orig to this type. Uses canCastTo to
|
|
// determine if a cast is possible. The 'to' parameter is used in
|
|
// error messages to describe the destination.
|
|
final void typeCast(ref Expression orig, char[] to)
|
|
{
|
|
if(orig.type == this) return;
|
|
|
|
// Replace the expression with a CastExpression. This acts as a
|
|
// wrapper that puts in the conversion code after expression is
|
|
// evaluated.
|
|
if(orig.type.canCastTo(this))
|
|
orig = new CastExpression(orig, this);
|
|
else
|
|
fail(format("Cannot implicitly cast %s of type %s to %s of type %s",
|
|
orig.toString, orig.typeString,
|
|
to, this), orig.loc);
|
|
}
|
|
|
|
// Do explicit type casting. This allows for a wider range of type
|
|
// conversions.
|
|
final void typeCastExplicit(ref Expression orig)
|
|
{
|
|
if(orig.type == this) return;
|
|
|
|
if(orig.type.canCastToExplicit(this) || orig.type.canCastTo(this))
|
|
orig = new CastExpression(orig, this);
|
|
else
|
|
fail(format("Cannot cast %s of type %s to type %s",
|
|
orig.toString, orig.typeString,
|
|
this), orig.loc);
|
|
}
|
|
|
|
// Do compile-time type casting. Gets orig.evalCTime() and returns
|
|
// the converted result.
|
|
final int[] typeCastCTime(Expression orig, char[] to)
|
|
{
|
|
int[] res = orig.evalCTime();
|
|
|
|
assert(res.length == orig.type.getSize);
|
|
|
|
if(orig.type.canCastCTime(this))
|
|
res = orig.type.doCastCTime(res, this);
|
|
else
|
|
fail(format("Cannot cast %s of type %s to %s of type %s (at compile time)",
|
|
orig.toString, orig.typeString,
|
|
to, this), orig.loc);
|
|
|
|
assert(res.length == getSize);
|
|
|
|
return res;
|
|
}
|
|
|
|
// Can this type be cast to the parameter type? This function is not
|
|
// required to handle cases where the types are the same.
|
|
bool canCastTo(Type to)
|
|
{
|
|
return false; // By default we can't cast anything
|
|
}
|
|
|
|
// Can the type be explicitly cast? Is not required to handle cases
|
|
// where canCastTo is true.
|
|
bool canCastToExplicit(Type to)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Returns true if the cast can be performed at compile time. This
|
|
// is usually true if we can cast at runtime.
|
|
bool canCastCTime(Type to)
|
|
{ return canCastTo(to) || canCastToExplicit(to); }
|
|
|
|
// Returns true if the types are equal or if canCastTo returns true.
|
|
final bool canCastOrEqual(Type to)
|
|
{
|
|
return to == this || canCastTo(to);
|
|
}
|
|
|
|
// Do the cast in the assembler. Must handle both implicit and
|
|
// explicit casts.
|
|
void evalCastTo(Type to)
|
|
{
|
|
assert(0, "evalCastTo not implemented for type " ~ toString);
|
|
}
|
|
|
|
// Do the cast in the compiler. Must handle both implicit and
|
|
// explicit casts.
|
|
int[] doCastCTime(int[] data, Type to)
|
|
{
|
|
assert(0, "doCastCTime not implemented for type " ~ toString);
|
|
}
|
|
|
|
// Cast variable of this type to string
|
|
char[] valToString(int[] data)
|
|
{
|
|
assert(0, "valToString not implemented for " ~ toString);
|
|
}
|
|
|
|
final AIndex valToStringIndex(int[] data)
|
|
{
|
|
assert(data.length == getSize);
|
|
return monster.vm.arrays.arrays.create(valToString(data)).getIndex;
|
|
}
|
|
|
|
void parse(ref TokenArray toks) {assert(0, name);}
|
|
void resolve(Scope sc) {assert(0, name);}
|
|
|
|
/* Cast two expressions to their common type, if any. Fail if not
|
|
possible. Examples of possible outcomes:
|
|
|
|
int, int -> does nothing
|
|
float, int -> converts the second paramter to float
|
|
int, float -> converts the first to float
|
|
bool, int -> throws an error
|
|
|
|
For various integer types, special rules apply. These are (for
|
|
the time being):
|
|
|
|
ulong, any -> ulong
|
|
long, any 32bit -> long
|
|
int, uint -> uint
|
|
|
|
Similar types (eg. uint, uint) will never convert types.
|
|
*/
|
|
static void castCommon(ref Expression e1, ref Expression e2)
|
|
{
|
|
Type t1 = e1.type;
|
|
Type t2 = e2.type;
|
|
|
|
if(t1 == t2) return;
|
|
|
|
Type common;
|
|
|
|
// Apply integral promotion rules first. TODO: Apply polysemous
|
|
// rules later.
|
|
if(t1.isIntegral && t2.isIntegral)
|
|
{
|
|
// ulong dominates all other types
|
|
if(t1.isUlong) common = t1;
|
|
else if(t2.isUlong) common = t2;
|
|
|
|
// long dominates over 32-bit values
|
|
else if(t1.isLong) common = t1;
|
|
else if(t2.isLong) common = t2;
|
|
|
|
// unsigned dominates over signed
|
|
else if(t1.isUint) common = t1;
|
|
else if(t2.isUint) common = t2;
|
|
else
|
|
{
|
|
assert(t1.isInt && t2.isInt, "unknown integral type encountered");
|
|
assert(0, "should never get here");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Find the common type
|
|
if(t1.canCastTo(t2)) common = t2; else
|
|
if(t2.canCastTo(t1)) common = t1; else
|
|
fail(format("Cannot implicitly cast %s of type %s to %s of type %s, or vice versa.", e1, t1, e2, t2), e1.loc);
|
|
}
|
|
|
|
// Wrap the expressions in CastExpression blocks if necessary.
|
|
common.typeCast(e1, "");
|
|
common.typeCast(e2, "");
|
|
}
|
|
}
|
|
|
|
// Internal types are types that are used internally by the compiler,
|
|
// eg. for type conversion or for special syntax. They can not be used
|
|
// for variables or values, neither directly nor indirectly.
|
|
abstract class InternalType : Type
|
|
{
|
|
final:
|
|
bool isLegal() { return false; }
|
|
int[] defaultInit() {assert(0, name);}
|
|
int getSize() { return 0; }
|
|
}
|
|
|
|
// Handles package names, in expressions like Package.ClassName. It is
|
|
// only used for these expressions and never for anything else.
|
|
class PackageType : InternalType
|
|
{
|
|
PackageScope sc;
|
|
|
|
this(PackageScope _sc)
|
|
{
|
|
sc = _sc;
|
|
name = sc.toString() ~ " (package)";
|
|
}
|
|
|
|
override:
|
|
Scope getMemberScope()
|
|
{
|
|
assert(sc !is null);
|
|
return sc;
|
|
}
|
|
|
|
bool isPackage() { return true; }
|
|
}
|
|
|
|
// Handles the 'null' literal. This type is only used for
|
|
// conversions. You cannot declare variables of the nulltype.
|
|
class NullType : InternalType
|
|
{
|
|
this() { name = "null-type"; }
|
|
|
|
bool canCastTo(Type to)
|
|
{
|
|
return to.isArray || to.isObject || to.isEnum;
|
|
}
|
|
|
|
void evalCastTo(Type to)
|
|
{
|
|
assert(to.getSize == 1);
|
|
|
|
// The value of null is always zero. There is no value on the
|
|
// stack to convert, so just push it.
|
|
tasm.push(0);
|
|
}
|
|
|
|
int[] doCastCTime(int[] data, Type to)
|
|
{
|
|
assert(to.getSize == 1);
|
|
assert(data.length == 0);
|
|
return [0];
|
|
}
|
|
}
|
|
|
|
// Helper template for BasicType
|
|
abstract class TypeHolderBase
|
|
{
|
|
char[] getString(int[] data);
|
|
}
|
|
class TypeHolder(T) : TypeHolderBase
|
|
{
|
|
T toValue(int[] data)
|
|
{
|
|
static if(!is(T==bool))
|
|
assert(data.length*4 == T.sizeof);
|
|
return *(cast(T*)data.ptr);
|
|
}
|
|
|
|
char[] getString(int [] data)
|
|
{
|
|
static if(is(T == dchar))
|
|
return toUTF8([toValue(data)]);
|
|
else
|
|
return .toString(toValue(data));
|
|
}
|
|
}
|
|
|
|
// Handles all the built-in types. These are: int, uint, long, ulong,
|
|
// float, double, bool, char and the "void" type, which is represented
|
|
// by an empty string. The void type is only allowed in special cases
|
|
// (currently only in function return types), and is not parsed
|
|
// normally through identify()/parse(). Instead it is created directly
|
|
// with the constructor.
|
|
class BasicType : Type
|
|
{
|
|
this() {}
|
|
|
|
// Create the given type directly without parsing. Use an empty
|
|
// string "" for the void type. This is the only way a void type can
|
|
// be created.
|
|
this(char[] tn)
|
|
{
|
|
name = tn;
|
|
if(name == "") name = "(none)";
|
|
|
|
if(!isBasic(name))
|
|
fail("BasicType does not support type " ~ tn);
|
|
|
|
// Cache the class to save some overhead
|
|
store[tn] = this;
|
|
}
|
|
|
|
TypeHolderBase tph;
|
|
TypeHolderBase getHolder()
|
|
{
|
|
if(tph is null)
|
|
{
|
|
if(isInt) tph = new TypeHolder!(int);
|
|
if(isUint) tph = new TypeHolder!(uint);
|
|
if(isLong) tph = new TypeHolder!(long);
|
|
if(isUlong) tph = new TypeHolder!(ulong);
|
|
if(isFloat) tph = new TypeHolder!(float);
|
|
if(isDouble) tph = new TypeHolder!(double);
|
|
if(isChar) tph = new TypeHolder!(dchar);
|
|
if(isBool) tph = new TypeHolder!(bool);
|
|
}
|
|
assert(!isVoid);
|
|
|
|
return tph;
|
|
}
|
|
|
|
private static BasicType[char[]] store;
|
|
|
|
// Get a basic type of the given name. This will not allocate a new
|
|
// instance if another instance already exists.
|
|
static BasicType get(char[] tn)
|
|
{
|
|
if(tn in store) return store[tn];
|
|
|
|
// Automatically adds itself to the store
|
|
return new BasicType(tn);
|
|
}
|
|
|
|
// Shortcuts
|
|
static BasicType getVoid() { return get(""); }
|
|
static BasicType getInt() { return get("int"); }
|
|
static BasicType getUint() { return get("uint"); }
|
|
static BasicType getLong() { return get("long"); }
|
|
static BasicType getUlong() { return get("ulong"); }
|
|
static BasicType getFloat() { return get("float"); }
|
|
static BasicType getDouble() { return get("double"); }
|
|
static BasicType getChar() { return get("char"); }
|
|
static BasicType getBool() { return get("bool"); }
|
|
|
|
static bool isBasic(char[] tn)
|
|
{
|
|
return (tn == "(none)" || tn == "int" || tn == "float" || tn == "char" ||
|
|
tn == "bool" || tn == "uint" || tn == "long" ||
|
|
tn == "ulong" || tn == "double");
|
|
}
|
|
|
|
static bool canParse(TokenArray toks)
|
|
{
|
|
Token t;
|
|
if(!isNext(toks, TT.Identifier, t)) return false;
|
|
return isBasic(t.str);
|
|
}
|
|
|
|
override:
|
|
void parse(ref TokenArray toks)
|
|
{
|
|
Token t;
|
|
|
|
reqNext(toks, TT.Identifier, t);
|
|
assert(isBasic(t.str));
|
|
|
|
// Get the name and the line from the token
|
|
name = t.str;
|
|
loc = t.loc;
|
|
|
|
if(name == "") name = "(none)";
|
|
}
|
|
|
|
bool isInt() { return name == "int"; }
|
|
bool isUint() { return name == "uint"; }
|
|
bool isLong() { return name == "long"; }
|
|
bool isUlong() { return name == "ulong"; }
|
|
bool isChar() { return name == "char"; }
|
|
bool isBool() { return name == "bool"; }
|
|
bool isFloat() { return name == "float"; }
|
|
bool isDouble() { return name == "double"; }
|
|
bool isVoid() { return name == "(none)"; }
|
|
|
|
Scope getMemberScope()
|
|
{
|
|
if(isInt) return IntProperties.singleton;
|
|
if(isUint) return UintProperties.singleton;
|
|
if(isLong) return LongProperties.singleton;
|
|
if(isUlong) return UlongProperties.singleton;
|
|
if(isFloat) return FloatProperties.singleton;
|
|
if(isDouble) return DoubleProperties.singleton;
|
|
|
|
if(isChar || isBool)
|
|
return GenericProperties.singleton;
|
|
|
|
assert(isVoid);
|
|
return null;
|
|
}
|
|
|
|
// List the implisit conversions that are possible
|
|
bool canCastTo(Type to)
|
|
{
|
|
// If options.d allow it, we can implicitly convert back from
|
|
// floats to integral types.
|
|
static if(implicitTruncate)
|
|
{
|
|
if(isFloating && to.isIntegral)
|
|
return true;
|
|
}
|
|
|
|
// We can convert between all integral types
|
|
if(to.isIntegral) return isIntegral;
|
|
|
|
// All numerical types can be converted to floating point
|
|
if(to.isFloating) return isNumerical;
|
|
|
|
// These types can be converted to strings.
|
|
if(to.isString)
|
|
return isNumerical || isChar || isBool;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool canCastToExplicit(Type to)
|
|
{
|
|
if(isFloating && to.isIntegral)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void evalCastTo(Type to)
|
|
{
|
|
assert(this != to);
|
|
assert(!isVoid);
|
|
|
|
int fromSize = getSize();
|
|
int toSize = to.getSize();
|
|
bool fromSign = isInt || isLong || isFloat || isBool;
|
|
|
|
if(isFloating && to.isIntegral)
|
|
tasm.castFloatToInt(this, to);
|
|
else if(to.isInt || to.isUint)
|
|
{
|
|
assert(isIntegral);
|
|
if(isLong || isUlong)
|
|
tasm.castLongToInt();
|
|
}
|
|
else if(to.isLong || to.isUlong)
|
|
{
|
|
if(isInt || isUint) tasm.castIntToLong(fromSign);
|
|
else assert(isUlong || isLong);
|
|
}
|
|
else if(to.isFloat || to.isDouble)
|
|
{
|
|
if(isIntegral)
|
|
tasm.castIntToFloat(this, to);
|
|
else if(isFloating)
|
|
tasm.castFloatToFloat(fromSize, toSize);
|
|
else assert(0);
|
|
}
|
|
else if(to.isString)
|
|
{
|
|
assert(!isVoid);
|
|
|
|
// Create an array from one element on the stack
|
|
if(isChar) tasm.popToArray(1, 1);
|
|
else
|
|
tasm.castToString(tIndex);
|
|
}
|
|
else
|
|
fail("Conversion " ~ toString ~ " to " ~ to.toString ~
|
|
" not implemented.");
|
|
}
|
|
|
|
char[] valToString(int[] data)
|
|
{
|
|
assert(data.length == getSize);
|
|
assert(!isVoid);
|
|
|
|
return getHolder().getString(data);
|
|
}
|
|
|
|
int[] doCastCTime(int[] data, Type to)
|
|
{
|
|
assert(this != to);
|
|
assert(!isVoid);
|
|
assert(!to.isVoid);
|
|
|
|
if(to.isString)
|
|
return [valToStringIndex(data)];
|
|
|
|
int fromSize = getSize();
|
|
int toSize = to.getSize();
|
|
|
|
assert(data.length == fromSize);
|
|
bool fromSign = isInt || isLong || isFloat || isBool;
|
|
|
|
// Set up a new array to hold the result
|
|
int[] toData = new int[toSize];
|
|
|
|
if(isFloating && to.isIntegral)
|
|
{
|
|
int *iptr = cast(int*)toData.ptr;
|
|
uint *uptr = cast(uint*)toData.ptr;
|
|
long *lptr = cast(long*)toData.ptr;
|
|
ulong *ulptr = cast(ulong*)toData.ptr;
|
|
|
|
if(isFloat)
|
|
{
|
|
float f = *(cast(float*)data.ptr);
|
|
if(to.isInt) *iptr = cast(int) f;
|
|
else if(to.isUint) *uptr = cast(uint) f;
|
|
else if(to.isLong) *lptr = cast(long) f;
|
|
else if(to.isUlong) *ulptr = cast(ulong) f;
|
|
else assert(0);
|
|
}
|
|
else if(isDouble)
|
|
{
|
|
double f = *(cast(double*)data.ptr);
|
|
if(to.isInt) *iptr = cast(int) f;
|
|
else if(to.isUint) *uptr = cast(uint) f;
|
|
else if(to.isLong) *lptr = cast(long) f;
|
|
else if(to.isUlong) *ulptr = cast(ulong) f;
|
|
else assert(0);
|
|
}
|
|
else assert(0);
|
|
}
|
|
else if(to.isInt || to.isUint)
|
|
{
|
|
assert(isIntegral);
|
|
// Just pick out the least significant int
|
|
toData[] = data[0..1];
|
|
}
|
|
else if(to.isLong || to.isUlong)
|
|
{
|
|
if(isInt || isUint)
|
|
{
|
|
toData[0] = data[0];
|
|
if(fromSign && to.isLong && data[0] < 0) toData[1] = -1;
|
|
else toData[1] = 0;
|
|
}
|
|
else assert(isUlong || isLong);
|
|
}
|
|
else if(to.isFloat)
|
|
{
|
|
assert(isNumerical);
|
|
|
|
float *fptr = cast(float*)toData.ptr;
|
|
|
|
if(isInt) *fptr = data[0];
|
|
else if(isUint) *fptr = cast(uint)data[0];
|
|
else if(isLong) *fptr = *(cast(long*)data.ptr);
|
|
else if(isUlong) *fptr = *(cast(ulong*)data.ptr);
|
|
else if(isDouble) *fptr = *(cast(double*)data.ptr);
|
|
else assert(0);
|
|
}
|
|
else if(to.isDouble)
|
|
{
|
|
assert(isNumerical);
|
|
|
|
assert(toData.length == 2);
|
|
double *fptr = cast(double*)toData.ptr;
|
|
|
|
if(isInt) *fptr = data[0];
|
|
else if(isUint) *fptr = cast(uint)data[0];
|
|
else if(isLong) *fptr = *(cast(long*)data.ptr);
|
|
else if(isUlong) *fptr = *(cast(ulong*)data.ptr);
|
|
else if(isFloat) *fptr = *(cast(float*)data.ptr);
|
|
else assert(0);
|
|
}
|
|
else
|
|
fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~
|
|
" not implemented.");
|
|
|
|
assert(toData.length == toSize);
|
|
assert(toData.ptr !is data.ptr);
|
|
return toData;
|
|
}
|
|
|
|
int getSize()
|
|
{
|
|
if( isInt || isUint || isFloat || isChar || isBool )
|
|
return 1;
|
|
|
|
if( isLong || isUlong || isDouble )
|
|
return 2;
|
|
|
|
assert(0, "getSize() does not handle type '" ~ name ~ "'");
|
|
}
|
|
|
|
void resolve(Scope sc)
|
|
{
|
|
// Check that our given name is indeed valid
|
|
assert(isBasic(name));
|
|
}
|
|
|
|
int[] defaultInit()
|
|
{
|
|
int data[];
|
|
|
|
// Ints default to 0, bools to false
|
|
if(isInt || isUint || isBool) data = makeData!(int)(0);
|
|
// Ditto for double-size ints
|
|
else if(isLong || isUlong) data = makeData!(long)(0);
|
|
// Chars default to an illegal utf-32 value
|
|
else if(isChar) data = makeData!(int)(0x0000FFFF);
|
|
// Floats default to not a number
|
|
else if(isFloat) data = makeData!(float)(float.nan);
|
|
else if(isDouble) data = makeData!(double)(double.nan);
|
|
else
|
|
assert(0, "Type '" ~ name ~ "' has no default initializer");
|
|
|
|
assert(data.length == getSize, "size mismatch in defaultInit");
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
// Represents a normal class name.
|
|
class ObjectType : Type
|
|
{
|
|
final:
|
|
private:
|
|
// Class that we represent (set by resolve()). Variables of this
|
|
// type may point to objects of this class, or to objects of
|
|
// subclasses. We only use an index, since classes might be forward
|
|
// referenced.
|
|
CIndex clsIndex;
|
|
|
|
public:
|
|
|
|
this(Token t)
|
|
{
|
|
// Get the name and the line from the token
|
|
name = t.str;
|
|
loc = t.loc;
|
|
}
|
|
|
|
this(MonsterClass mc)
|
|
{
|
|
assert(mc !is null);
|
|
name = mc.name.str;
|
|
loc = mc.name.loc;
|
|
clsIndex = mc.gIndex;
|
|
}
|
|
|
|
MonsterClass getClass(Floc loc = Floc.init)
|
|
{
|
|
assert(clsIndex != 0);
|
|
|
|
if(!global.isLoaded(clsIndex))
|
|
fail("Cannot use " ~ name ~
|
|
": class not found or forward reference", loc);
|
|
|
|
return global.getClass(clsIndex);
|
|
}
|
|
|
|
override:
|
|
void validate(Floc loc)
|
|
{
|
|
// getClass does most of the error checking we need
|
|
auto mc = getClass(loc);
|
|
|
|
// check that we're not using a module as a type
|
|
if(mc.isModule)
|
|
fail("Cannot use module " ~ name ~ " as a type", loc);
|
|
}
|
|
|
|
char[] valToString(int[] data)
|
|
{
|
|
// Use the object's toString function
|
|
assert(data.length == 1);
|
|
if(data[0] == 0) return "(null object)";
|
|
auto mo = getMObject(cast(MIndex)data[0]);
|
|
return mo.toString();
|
|
}
|
|
|
|
int getSize() { return 1; }
|
|
bool isObject() { return true; }
|
|
int[] defaultInit() { return makeData!(int)(0); }
|
|
|
|
bool canCastCTime(Type to) { return false; }
|
|
|
|
// Used internally below, to get the class from another object type.
|
|
private MonsterClass getOther(Type other)
|
|
{
|
|
assert(other !is this);
|
|
assert(other.isObject);
|
|
auto ot = cast(ObjectType)other;
|
|
assert(ot !is null);
|
|
auto cls = ot.getClass();
|
|
assert(cls !is null);
|
|
assert(cls !is getClass());
|
|
return cls;
|
|
}
|
|
|
|
bool canCastTo(Type type)
|
|
{
|
|
assert(clsIndex != 0);
|
|
|
|
if(type.isString) return true;
|
|
|
|
if(type.isObject)
|
|
{
|
|
MonsterClass us = getClass();
|
|
MonsterClass other = getOther(type);
|
|
|
|
// Allow implicit downcasting if options.d says so.
|
|
static if(implicitDowncast)
|
|
{
|
|
if(other.childOf(us))
|
|
return true;
|
|
}
|
|
|
|
// We can always upcast implicitly
|
|
return other.parentOf(us);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool canCastToExplicit(Type type)
|
|
{
|
|
// We can explicitly cast to a child class, and let the VM
|
|
// handle any errors.
|
|
if(type.isObject)
|
|
{
|
|
auto us = getClass();
|
|
auto other = getOther(type);
|
|
|
|
return other.childOf(us);
|
|
}
|
|
}
|
|
|
|
void evalCastTo(Type to)
|
|
{
|
|
assert(clsIndex != 0);
|
|
assert(canCastTo(to) || canCastToExplicit(to));
|
|
|
|
if(to.isObject)
|
|
{
|
|
auto us = getClass();
|
|
auto other = getOther(to);
|
|
|
|
// Upcasting doesn't require any action
|
|
if(other.parentOf(us)) {}
|
|
|
|
// Downcasting (from parent to child) requires that VM
|
|
// checks the runtime type of the object.
|
|
else if(other.childOf(us))
|
|
// Use the global class index
|
|
tasm.downCast(other.getIndex());
|
|
|
|
// We should never get here
|
|
else assert(0);
|
|
|
|
return;
|
|
}
|
|
|
|
assert(to.isString);
|
|
tasm.castToString(tIndex);
|
|
}
|
|
|
|
// Members of objects are resolved in the class scope.
|
|
Scope getMemberScope()
|
|
{
|
|
assert(getClass !is null);
|
|
assert(getClass.sc !is null);
|
|
return getClass().sc;
|
|
}
|
|
|
|
// This is called when the type is defined, and it can forward
|
|
// reference classes that do not exist yet. The classes must exist
|
|
// by the time getClass() is called though, usually when the class
|
|
// body (not just the header) is being resolved.
|
|
void resolve(Scope sc)
|
|
{
|
|
// Insert the class into the global scope as a forward
|
|
// reference. Note that this means the class may not be part of
|
|
// any other package than 'global' when it is loaded later.
|
|
if(clsIndex == 0)
|
|
clsIndex = global.getForwardIndex(name);
|
|
|
|
assert(clsIndex != 0);
|
|
}
|
|
}
|
|
|
|
class ArrayType : Type
|
|
{
|
|
// What type is this an array of?
|
|
Type base;
|
|
|
|
static ArrayType str;
|
|
|
|
static ArrayType getString()
|
|
{
|
|
if(str is null)
|
|
str = new ArrayType(BasicType.getChar);
|
|
return str;
|
|
}
|
|
|
|
static ArrayType get(Type base)
|
|
{
|
|
// TODO: We can cache stuff here later
|
|
return new ArrayType(base);
|
|
}
|
|
|
|
this(Type btype)
|
|
{
|
|
base = btype;
|
|
name = base.name ~ "[]";
|
|
loc = base.loc;
|
|
}
|
|
|
|
override:
|
|
void validate(Floc loc) { assert(base !is null); base.validate(loc); }
|
|
int arrays() { return base.arrays() + 1; }
|
|
int getSize() { return 1; }
|
|
int[] defaultInit() { return makeData!(int)(0); }
|
|
Type getBase() { return base; }
|
|
|
|
Scope getMemberScope()
|
|
{
|
|
return ArrayProperties.singleton;
|
|
}
|
|
|
|
// All arrays can be cast to string
|
|
bool canCastTo(Type to)
|
|
{ return to.isString; }
|
|
|
|
void evalCastTo(Type to)
|
|
{
|
|
assert(to.isString);
|
|
tasm.castToString(tIndex);
|
|
}
|
|
|
|
int[] doCastCTime(int[] data, Type to)
|
|
{
|
|
assert(to.isString);
|
|
return [valToStringIndex(data)];
|
|
}
|
|
|
|
char[] valToString(int[] data)
|
|
{
|
|
assert(data.length == 1);
|
|
|
|
// Get the array reference
|
|
ArrayRef *arf = monster.vm.arrays.arrays.getRef(cast(AIndex)data[0]);
|
|
|
|
// Empty array?
|
|
if(arf.iarr.length == 0) return "[]";
|
|
|
|
assert(arf.elemSize == base.getSize);
|
|
assert(arf.iarr.length == arf.length * arf.elemSize);
|
|
|
|
if(isString) return format("\"%s\"", arf.carr);
|
|
|
|
char[] res = "[";
|
|
|
|
// For element of the array, run valToString on the appropriate
|
|
// data
|
|
for(int i; i<arf.iarr.length; i+=arf.elemSize)
|
|
{
|
|
if(i != 0) res ~= ',';
|
|
res ~= base.valToString(arf.iarr[i..i+arf.elemSize]);
|
|
}
|
|
return res ~ "]";
|
|
}
|
|
|
|
// We are a string (char[]) if the base type is char.
|
|
bool isString()
|
|
{
|
|
return base.isChar();
|
|
}
|
|
|
|
void resolve(Scope sc)
|
|
{
|
|
base.resolve(sc);
|
|
|
|
if(base.isReplacer())
|
|
base = base.getBase();
|
|
name = base.name ~ "[]";
|
|
}
|
|
}
|
|
|
|
// Type used for references to functions. Will later contain type
|
|
// information, but right now it's just used as an internal flag.
|
|
class FunctionType : InternalType
|
|
{
|
|
Function *func;
|
|
bool isMember;
|
|
|
|
this(Function *fn, bool isMemb)
|
|
{
|
|
func = fn;
|
|
assert(fn !is null);
|
|
isMember = isMemb;
|
|
|
|
name="Function";
|
|
}
|
|
|
|
override:
|
|
bool isFunc() { return true; }
|
|
}
|
|
|
|
class EnumType : Type
|
|
{
|
|
// Enum entries
|
|
EnumEntry[] entries;
|
|
EnumScope sc;
|
|
char[] initString = " (not set)";
|
|
|
|
// Lookup tables
|
|
EnumEntry* nameAA[char[]];
|
|
EnumEntry* valueAA[long];
|
|
|
|
// Fields
|
|
FieldDef fields[];
|
|
|
|
long
|
|
minVal = long.max,
|
|
maxVal = long.min;
|
|
|
|
Token nameTok;
|
|
|
|
EnumEntry *lookup(long val)
|
|
{
|
|
auto p = val in valueAA;
|
|
if(p is null)
|
|
return null;
|
|
return *p;
|
|
}
|
|
|
|
EnumEntry *lookup(char[] str)
|
|
{
|
|
auto p = str in nameAA;
|
|
if(p is null) return null;
|
|
return *p;
|
|
}
|
|
|
|
int findField(char[] str)
|
|
{
|
|
foreach(i, fd; fields)
|
|
if(fd.name.str == str)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
override:
|
|
|
|
bool isEnum() { return true; }
|
|
|
|
int[] defaultInit() { return [0]; }
|
|
int getSize() { return 1; }
|
|
|
|
void resolve(Scope last)
|
|
{
|
|
assert(sc is null, "resolve() called more than once");
|
|
|
|
initString = name ~ initString;
|
|
|
|
foreach(i, ref ent; entries)
|
|
{
|
|
// Make sure there are no naming conflicts.
|
|
last.clearId(ent.name);
|
|
|
|
// Assign an internal value to each entry
|
|
ent.index = i+1;
|
|
|
|
// Set the printed value to be "Enum.Name"
|
|
ent.stringValue = name ~ "." ~ ent.name.str;
|
|
|
|
// Create an AA for values, and one for the names. This is also
|
|
// where we check for duplicates in both.
|
|
if(ent.name.str in nameAA)
|
|
fail("Duplicate entry '" ~ ent.name.str ~ "' in enum", ent.name.loc);
|
|
if(ent.value in valueAA)
|
|
fail("Duplicate value " ~ .toString(ent.value) ~ " in enum", ent.name.loc);
|
|
nameAA[ent.name.str] = &ent;
|
|
valueAA[ent.value] = &ent;
|
|
|
|
if(ent.value > maxVal) maxVal = ent.value;
|
|
if(ent.value < minVal) minVal = ent.value;
|
|
}
|
|
|
|
// Create the scope
|
|
sc = new EnumScope(this);
|
|
|
|
// Check the fields
|
|
foreach(ref fd; fields)
|
|
{
|
|
last.clearId(fd.name);
|
|
if(fd.name.str in nameAA)
|
|
fail("Field name cannot match value name " ~ fd.name.str, fd.name.loc);
|
|
|
|
fd.type.resolve(last);
|
|
if(fd.type.isReplacer)
|
|
fd.type = fd.type.getBase();
|
|
|
|
fd.type.validate(fd.name.loc);
|
|
}
|
|
|
|
// Resolve and check field expressions.
|
|
foreach(ref ent; entries)
|
|
{
|
|
// Check number of expressions
|
|
if(ent.exp.length > fields.length)
|
|
fail(format("Too many fields in enum line (expected %s, found %s)",
|
|
fields.length, ent.exp.length),
|
|
ent.name.loc);
|
|
|
|
ent.fields.length = fields.length;
|
|
|
|
foreach(i, ref fe; ent.exp)
|
|
{
|
|
assert(fe !is null);
|
|
fe.resolve(last);
|
|
|
|
// Check the types
|
|
fields[i].type.typeCast(fe, format("field %s (%s)",
|
|
i+1, fields[i].name.str));
|
|
|
|
// And that they are all compile time expressions
|
|
if(!fe.isCTime)
|
|
fail("Cannot evaluate " ~ fe.toString ~ " at compile time", fe.loc);
|
|
}
|
|
|
|
// Finally, get the values
|
|
foreach(i, ref int[] data; ent.fields)
|
|
{
|
|
if(i < ent.exp.length)
|
|
data = ent.exp[i].evalCTime();
|
|
else
|
|
// Use the init value if no field is value is given
|
|
data = fields[i].type.defaultInit();
|
|
|
|
assert(data.length == fields[i].type.getSize);
|
|
}
|
|
|
|
// Clear the expression array since we don't need it anymore
|
|
ent.exp = null;
|
|
}
|
|
}
|
|
|
|
bool canCastTo(Type to)
|
|
{
|
|
// Can always cast to string
|
|
if(to.isString) return true;
|
|
|
|
// The value is a long, so we can always cast to types that long
|
|
// can be cast to.
|
|
if(BasicType.getLong().canCastOrEqual(to)) return true;
|
|
|
|
// Check each field from left to right. If the field can be cast
|
|
// to the given type, then it's ok.
|
|
foreach(f; fields)
|
|
if(f.type.canCastOrEqual(to))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void evalCastTo(Type to)
|
|
{
|
|
// Convert the enum name to a string
|
|
if(to.isString)
|
|
{
|
|
tasm.castToString(tIndex);
|
|
return;
|
|
}
|
|
|
|
auto lng = BasicType.getLong();
|
|
if(lng.canCastOrEqual(to))
|
|
{
|
|
// Get the value
|
|
tasm.getEnumValue(tIndex);
|
|
// Cast it if necessary
|
|
if(to != lng)
|
|
lng.evalCastTo(to);
|
|
return;
|
|
}
|
|
|
|
// Check the fields
|
|
foreach(i, f; fields)
|
|
if(f.type.canCastOrEqual(to))
|
|
{
|
|
// Get the field value from the enum
|
|
tasm.getEnumValue(tIndex, i);
|
|
|
|
// If the type doesn't match exactly, convert it.
|
|
if(f.type != to)
|
|
f.type.evalCastTo(to);
|
|
|
|
return;
|
|
}
|
|
|
|
assert(0);
|
|
}
|
|
|
|
int[] doCastCTime(int[] data, Type to)
|
|
{
|
|
if(to.isString)
|
|
return [valToStringIndex(data)];
|
|
|
|
// This code won't run yet, because the enum fields are
|
|
// properties and we haven't implemented ctime property reading
|
|
// yet. Leave this assert in here so that we remember to test it
|
|
// later.
|
|
assert(0, "finished, but not tested");
|
|
|
|
// Get the enum index
|
|
assert(data.length == 1);
|
|
int v = data[0];
|
|
|
|
// Check that we were not given a zero index
|
|
if(v-- == 0)
|
|
fail("Cannot get value of fields from an empty Enum variable.");
|
|
|
|
// Get the entry
|
|
assert(v >= 0 && v < entries.length);
|
|
auto ent = &entries[v];
|
|
|
|
auto lng = BasicType.getLong();
|
|
if(lng.canCastOrEqual(to))
|
|
{
|
|
// Get the value
|
|
int[] val = (cast(int*)&ent.value)[0..2];
|
|
// Cast it if necessary
|
|
if(to != lng)
|
|
val = lng.doCastCTime(val, to);
|
|
|
|
return val;
|
|
}
|
|
|
|
// Check the fields
|
|
foreach(i, f; fields)
|
|
if(f.type.canCastOrEqual(to))
|
|
{
|
|
// Get the field value from the enum
|
|
int[] val = ent.fields[i];
|
|
|
|
// If the type doesn't match exactly, convert it.
|
|
if(f.type != to)
|
|
val = f.type.doCastCTime(val, to);
|
|
|
|
return val;
|
|
}
|
|
|
|
assert(0);
|
|
}
|
|
|
|
// TODO: In this case, we could override valToStringIndex as well,
|
|
// and return a cached, constant string index to further optimize
|
|
// memory usage.
|
|
char[] valToString(int[] data)
|
|
{
|
|
assert(data.length == 1);
|
|
int v = data[0];
|
|
assert(v >= 0 && v <= entries.length);
|
|
|
|
// v == 0 means that no value is set - return a default string
|
|
// value
|
|
if(v == 0)
|
|
return initString;
|
|
|
|
else return entries[v-1].stringValue;
|
|
}
|
|
|
|
Scope getMemberScope()
|
|
{ return sc; }
|
|
}
|
|
|
|
class StructType : Type
|
|
{
|
|
private:
|
|
StructDeclaration sd;
|
|
|
|
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:
|
|
|
|
int size = -1;
|
|
StructScope sc; // Scope of the struct interior
|
|
int[] defInit;
|
|
bool set; // Have vars and defInit been set?
|
|
|
|
// Member variables
|
|
Variable*[] vars;
|
|
|
|
this(StructDeclaration sdp)
|
|
{
|
|
sd = sdp;
|
|
name = sd.name.str;
|
|
loc = sd.name.loc;
|
|
}
|
|
|
|
override:
|
|
// Calls validate for all member types
|
|
void validate(Floc loc)
|
|
{
|
|
foreach(v; vars)
|
|
v.type.validate(loc);
|
|
}
|
|
|
|
// Resolves member
|
|
void resolve(Scope sc) {}
|
|
|
|
bool isStruct() { return true; }
|
|
|
|
int getSize()
|
|
{
|
|
setup();
|
|
assert(size != -1);
|
|
return size;
|
|
}
|
|
|
|
int[] defaultInit()
|
|
{
|
|
setup();
|
|
assert(defInit.length == getSize);
|
|
return defInit;
|
|
}
|
|
|
|
Scope getMemberScope()
|
|
{ return GenericProperties.singleton; }
|
|
// Scope getMemberScope() { return sc; }
|
|
}
|
|
|
|
// A 'delayed lookup' type - can replace itself after resolve is
|
|
// called.
|
|
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 ids[];
|
|
|
|
public:
|
|
|
|
static bool canParse(TokenArray toks)
|
|
{
|
|
if(!isNext(toks, TT.Identifier)) return false;
|
|
return true;
|
|
}
|
|
|
|
void parse(ref TokenArray toks)
|
|
{
|
|
Token id;
|
|
reqNext(toks, TT.Identifier, id);
|
|
|
|
// Get the name and the line from the first token
|
|
name = id.str~"(replacer)";
|
|
loc = id.loc;
|
|
|
|
ids ~= id;
|
|
|
|
// Parse any following identifiers, separated by dots.
|
|
while(isNext(toks,TT.Dot))
|
|
{
|
|
reqNext(toks, TT.Identifier, id);
|
|
ids ~= id;
|
|
}
|
|
}
|
|
|
|
void resolve(Scope sc)
|
|
{
|
|
assert(ids.length >= 1);
|
|
|
|
// The top-most identifier is looked up in imported scopes
|
|
auto first = ids[0];
|
|
auto sl = sc.lookupImport(first);
|
|
if(sl.isImport)
|
|
sl = sl.imphold.lookup(first);
|
|
|
|
// The scope in which to resolve the class lookup.
|
|
Scope lastScope = sc;
|
|
|
|
// Loop through the list and look up each member.
|
|
foreach(int ind, idt; ids)
|
|
{
|
|
if(ind == 0) continue;
|
|
|
|
if(sl.isClass)
|
|
{
|
|
// Look up the next identifier in the class scope
|
|
assert(sl.mc !is null);
|
|
sl.mc.requireScope();
|
|
assert(sl.mc.sc !is null);
|
|
sl = sl.mc.sc.lookupClass(idt);
|
|
}
|
|
else if(sl.isPackage)
|
|
{
|
|
lastScope = sl.sc;
|
|
assert(sl.sc.isPackage);
|
|
sl = sl.sc.lookupClass(idt);
|
|
}
|
|
// Was anything found at all?
|
|
else if(!sl.isNone)
|
|
fail(sl.name.str ~ " (which is a " ~ LTypeName[sl.ltype]
|
|
~ ") does not have a type member called " ~ idt.str,
|
|
idt.loc);
|
|
else
|
|
fail("Unknown type " ~ sl.name.str, sl.name.loc);
|
|
}
|
|
|
|
// sl should now contain the lookup result of the last
|
|
// identifier in the list.
|
|
|
|
// Is it a type?
|
|
if(sl.isType)
|
|
// Great!
|
|
realType = sl.type;
|
|
|
|
// If not, maybe a class?
|
|
else if(sl.isClass)
|
|
{
|
|
// Splendid
|
|
sl.mc.requireScope();
|
|
realType = sl.mc.objType;
|
|
assert(realType !is null);
|
|
}
|
|
|
|
// Allow packages used as type names in some situations
|
|
else if(sl.isPackage)
|
|
realType = sl.sc.getPackage().type;
|
|
|
|
// Was anything found at all?
|
|
else if(!sl.isNone)
|
|
// Ouch, something was found that's not a type or class.
|
|
fail("Cannot use " ~ sl.name.str ~ " (which is a " ~
|
|
LTypeName[sl.ltype] ~ ") as a type!", sl.name.loc);
|
|
|
|
if(realType is null)
|
|
{
|
|
// Nothing was found. Assume it's a forward reference to a
|
|
// class. These are handled later on.
|
|
realType = new ObjectType(sl.name);
|
|
assert(lastScope !is null);
|
|
realType.resolve(lastScope);
|
|
}
|
|
|
|
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
|
|
{
|
|
static GenericType sing;
|
|
|
|
this() { name = "var"; }
|
|
|
|
static bool canParse(TokenArray toks)
|
|
{ return isNext(toks, TT.Var); }
|
|
|
|
static GenericType getSingleton()
|
|
{
|
|
if(sing is null)
|
|
sing = new GenericType;
|
|
|
|
return sing;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool isMeta() { return true; }
|
|
|
|
bool canCastTo(Type type)
|
|
{
|
|
return type.isString;
|
|
}
|
|
|
|
char[] valToString(int[] data)
|
|
{
|
|
assert(data.length == 0);
|
|
return base.name;
|
|
}
|
|
|
|
int[] cache;
|
|
|
|
int[] doCastCTime(int[] data, Type to)
|
|
{
|
|
assert(to.isString);
|
|
|
|
if(cache.length == 0)
|
|
cache = [valToStringIndex(data)];
|
|
|
|
return cache;
|
|
}
|
|
|
|
void evalCastTo(Type to)
|
|
{
|
|
assert(to.isString);
|
|
|
|
// 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; }
|
|
}
|