forked from teamnwah/openmw-tes3coop
953 lines
27 KiB
D
953 lines
27 KiB
D
|
/*
|
||
|
Monster - an advanced game scripting language
|
||
|
Copyright (C) 2007, 2008 Nicolay Korslund
|
||
|
Email: <korslund@gmail.com>
|
||
|
WWW: http://monster.snaptoad.com/
|
||
|
|
||
|
This file (types.d) is part of the Monster script language package.
|
||
|
|
||
|
Monster is distributed as free software: you can redistribute it
|
||
|
and/or modify it under the terms of the GNU General Public License
|
||
|
version 3, as published by the Free Software Foundation.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful, but
|
||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
version 3 along with this program. If not, see
|
||
|
http://www.gnu.org/licenses/ .
|
||
|
|
||
|
*/
|
||
|
|
||
|
module monster.compiler.types;
|
||
|
|
||
|
import monster.compiler.tokenizer;
|
||
|
import monster.compiler.scopes;
|
||
|
import monster.compiler.expression;
|
||
|
import monster.compiler.assembler;
|
||
|
import monster.compiler.properties;
|
||
|
import monster.compiler.block;
|
||
|
import monster.compiler.functions;
|
||
|
import monster.compiler.variables;
|
||
|
import monster.compiler.states;
|
||
|
import monster.vm.mclass;
|
||
|
import monster.vm.error;
|
||
|
|
||
|
import monster.minibos.stdio;
|
||
|
import monster.minibos.string;
|
||
|
|
||
|
/*
|
||
|
List of all type classes:
|
||
|
|
||
|
Type (abstract)
|
||
|
InternalType (abstract)
|
||
|
NullType (null expression)
|
||
|
BasicType (covers int, char, bool, float, void)
|
||
|
ObjectType
|
||
|
ArrayType
|
||
|
MetaType
|
||
|
|
||
|
*/
|
||
|
class TypeException : Exception
|
||
|
{
|
||
|
Type type1, type2;
|
||
|
|
||
|
this(Type t1, Type t2)
|
||
|
{
|
||
|
type1 = t1;
|
||
|
type2 = t2;
|
||
|
super("Unhandled TypeException on types " ~ type1.toString ~ " and " ~
|
||
|
type2.toString ~ ".");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// A class that represents a type. The Type class is abstract, and the
|
||
|
// different types are actually handled by various subclasses of Type
|
||
|
// (see below.) The static function identify() is used to figure out
|
||
|
// exactly which subclass to use.
|
||
|
abstract class Type : Block
|
||
|
{
|
||
|
// Can the given tokens possibly be parsed as a type? This is not
|
||
|
// meant as an extensive test to differentiate between types and
|
||
|
// non-types, it is more like a short-cut to get the type tokens out
|
||
|
// of the way. It should only be called from places where you
|
||
|
// require a type (and only in canParse(), since it removes the
|
||
|
// tokens.)
|
||
|
static bool canParseRem(ref TokenArray toks)
|
||
|
{
|
||
|
// TODO: Parse typeof(exp) here. We have to do a hack here, as
|
||
|
// we have no hope of parsing every expression in here. Instead
|
||
|
// we require the first ( and then remove every token until the
|
||
|
// matching ), allowing matching () pairs on the inside.
|
||
|
|
||
|
if(!isNext(toks, TT.Identifier)) return false;
|
||
|
|
||
|
while(isNext(toks,TT.LeftSquare))
|
||
|
if(!isNext(toks,TT.RightSquare))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Parse a type specifieer and return the correct class to handle it
|
||
|
// (fully parsed). Currently valid type formats are
|
||
|
//
|
||
|
// identifier - either a basic type (int, bool, etc) or a class name
|
||
|
// identifier[] - array
|
||
|
// identifier[][]... - arrays can be multi-dimensional
|
||
|
//
|
||
|
// static buffers, ie. int[10] are not allowed as types. The only
|
||
|
// place a type is allowed to take array expressions is after a new,
|
||
|
// eg. int[] i = new int[10], in that case you should set the second
|
||
|
// parameter to true. The expression array is stored in exps.
|
||
|
static Type identify(ref TokenArray toks, bool takeExpr, ref ExprArray exps)
|
||
|
{
|
||
|
assert(toks.length != 0);
|
||
|
|
||
|
// Model this after Exp.idSub or Code.identify
|
||
|
Type t = null;
|
||
|
|
||
|
// Find what kind of type this is and create an instance of the
|
||
|
// corresponding class.
|
||
|
if(BasicType.canParse(toks)) t = new BasicType();
|
||
|
else if(ObjectType.canParse(toks)) t = new ObjectType();
|
||
|
//else if(TypeofType.canParse(toks)) t = new TypeofType();
|
||
|
else fail("Cannot parse " ~ toks[0].str ~ " as a type", toks[0].loc);
|
||
|
|
||
|
// Parse the actual tokens with our new and shiny object.
|
||
|
t.parse(toks);
|
||
|
|
||
|
// Add arrays here afterwards by wrapping the previous type in
|
||
|
// an ArrayType.
|
||
|
exps = VarDeclaration.getArray(toks, takeExpr);
|
||
|
|
||
|
// Despite looking strange, this code is correct.
|
||
|
foreach(e; exps)
|
||
|
t = new ArrayType(t);
|
||
|
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
// Short version of the above, when expressions are not allowed.
|
||
|
static Type identify(ref TokenArray toks)
|
||
|
{
|
||
|
ExprArray exp;
|
||
|
return identify(toks, false, exp);
|
||
|
}
|
||
|
|
||
|
// The complete type name including specifiers, eg. "int[]".
|
||
|
char[] name;
|
||
|
|
||
|
// Used for easy checking
|
||
|
bool isInt() { return false; }
|
||
|
bool isUint() { return false; }
|
||
|
bool isLong() { return false; }
|
||
|
bool isUlong() { return false; }
|
||
|
bool isChar() { return false; }
|
||
|
bool isBool() { return false; }
|
||
|
bool isFloat() { return false; }
|
||
|
bool isDouble() { return false; }
|
||
|
bool isVoid() { return false; }
|
||
|
|
||
|
bool isString() { return false; }
|
||
|
|
||
|
bool isObject() { return false; }
|
||
|
bool isArray() { return arrays() != 0; }
|
||
|
|
||
|
bool isIntegral() { return isInt || isUint || isLong || isUlong; }
|
||
|
bool isFloating() { return isFloat || isDouble; }
|
||
|
|
||
|
// Numerical types allow arithmetic operators
|
||
|
bool isNumerical() { return isIntegral() || isFloating(); }
|
||
|
|
||
|
// Is this a meta-type? A meta-type is the type of expressions like
|
||
|
// 'int' and 'ClassName' - they themselves describe a type.
|
||
|
// Meta-types always have a base type accessible through getBase(),
|
||
|
// and a member scope similar to variables of the type itself.
|
||
|
bool isMeta() { return false; }
|
||
|
|
||
|
// Is this a legal type for variables? If this is false, you cannot
|
||
|
// create variables of this type, neither directly or indirectly
|
||
|
// (through automatic type inference.) This isn't currently used
|
||
|
// anywhere, but we will need it later when we implement automatic
|
||
|
// types.
|
||
|
bool isLegal() { return true; }
|
||
|
|
||
|
// Get base type (used for arrays and meta-types)
|
||
|
Type getBase() { assert(0, "Type " ~ toString() ~ " has no base type"); }
|
||
|
|
||
|
// Return the array dimension of this type. Eg. int[][] has two
|
||
|
// dimensions, int has zero.
|
||
|
int arrays() { return 0; }
|
||
|
|
||
|
// Return the number of ints needed to store one variable of this
|
||
|
// type.
|
||
|
int getSize();
|
||
|
|
||
|
// Get the scope for resolving members of this type. Examples:
|
||
|
// myObject.func() -> getMemberScope called for the type of myObject,
|
||
|
// returns the class scope, where func is resolved.
|
||
|
// array.length -> getMemberScope called for ArrayType, returns a
|
||
|
// special scope that contains the 'length' property
|
||
|
Scope getMemberScope()
|
||
|
{
|
||
|
// Returning null means this type does not have any members.
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Validate that this type actually exists. This is used to make
|
||
|
// sure that all forward references are resolved.
|
||
|
void validate() {}
|
||
|
|
||
|
// Check if this type is equivalent to the given D type
|
||
|
final bool isDType(TypeInfo ti)
|
||
|
{
|
||
|
char[] name = ti.toString;
|
||
|
name = name[name.rfind('.')+1..$];
|
||
|
|
||
|
switch(name)
|
||
|
{
|
||
|
case "int": return isInt();
|
||
|
case "uint": return isUint();
|
||
|
case "long": return isLong();
|
||
|
case "ulong": return isUlong();
|
||
|
case "float": return isFloat();
|
||
|
case "double": return isDouble();
|
||
|
case "bool": return isBool();
|
||
|
case "dchar": return isChar();
|
||
|
case "AIndex": return isArray();
|
||
|
case "MIndex": return isObject();
|
||
|
default:
|
||
|
assert(0, "illegal type in isDType(): " ~ name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Used by defaultInit as a shortcut for converting a variable to an
|
||
|
// int array.
|
||
|
static int[] makeData(T)(T val)
|
||
|
{
|
||
|
int data[];
|
||
|
if(T.sizeof == 4) data.length = 1;
|
||
|
else if(T.sizeof == 8) data.length = 2;
|
||
|
else assert(0, "Unsupported type size");
|
||
|
|
||
|
*(cast(T*)data.ptr) = val;
|
||
|
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
// Get the default initializer for this type. The assembler deals
|
||
|
// with data in terms of ints (4 byte chunks), so we return the data
|
||
|
// as an int[]. The default initializer should be an illegal value
|
||
|
// when possible (null pointers, nan, etc) to catch mistakenly
|
||
|
// uninitialized variables as quickly as possible. This will usually
|
||
|
// be the same init value as in D.
|
||
|
int[] defaultInit();
|
||
|
|
||
|
// Generate assembler code that pushes the default initializer on
|
||
|
// the stack. TODO: This should become a general function in the
|
||
|
// assembler to push any int[]. Pass it a type so it can check the
|
||
|
// size automatically as well.
|
||
|
final void pushInit()
|
||
|
{
|
||
|
int[] def = defaultInit();
|
||
|
assert(def.length == getSize, "default initializer is not the correct size");
|
||
|
setLine();
|
||
|
|
||
|
tasm.pushArray(def);
|
||
|
}
|
||
|
|
||
|
// Compare types
|
||
|
final int opEquals(Type t)
|
||
|
{
|
||
|
if(toString != t.toString) return 0;
|
||
|
|
||
|
// TODO: In our current system, if the name and array dimension
|
||
|
// match, we must be the same type. In the future we might have
|
||
|
// to add more tests here however. For example, two structs of
|
||
|
// the same name might be defined in different classes. The best
|
||
|
// solution is perhaps to always make sure the type name is
|
||
|
// unique.
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
final char[] toString() { return name; }
|
||
|
|
||
|
// Cast the expression orig to this type. Uses canCastTo to
|
||
|
// determine if a cast is possible.
|
||
|
final void typeCast(ref Expression orig)
|
||
|
{
|
||
|
if(orig.type == this) return;
|
||
|
|
||
|
// Replace the expression with a CastExpression. This acts as a
|
||
|
// wrapper that puts in the conversion code after expression is
|
||
|
// evaluated.
|
||
|
if(orig.type.canCastTo(this))
|
||
|
orig = new CastExpression(orig, this);
|
||
|
else
|
||
|
throw new TypeException(this, orig.type);
|
||
|
}
|
||
|
|
||
|
// Do compile-time type casting. Gets orig.evalCTime() and returns
|
||
|
// the converted result.
|
||
|
final int[] typeCastCTime(Expression orig)
|
||
|
{
|
||
|
int[] res = orig.evalCTime();
|
||
|
|
||
|
assert(res.length == orig.type.getSize);
|
||
|
|
||
|
if(orig.type.canCastTo(this))
|
||
|
res = orig.type.doCastCTime(res, this);
|
||
|
else
|
||
|
throw new TypeException(this, orig.type);
|
||
|
|
||
|
assert(res.length == getSize);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
// Can this type be cast to the parameter type? This function is not
|
||
|
// required to handle cases where the types are the same.
|
||
|
bool canCastTo(Type to)
|
||
|
{
|
||
|
return false; // By default we can't cast anything
|
||
|
}
|
||
|
|
||
|
// Returns true if the cast can be performed at compile time. This
|
||
|
// is usually true.
|
||
|
bool canCastCTime(Type to)
|
||
|
{ return canCastTo(to); }
|
||
|
|
||
|
// Returns true if the types are equal or if canCastTo returns true.
|
||
|
final bool canCastOrEqual(Type to)
|
||
|
{
|
||
|
return to == this || canCastTo(to);
|
||
|
}
|
||
|
|
||
|
// Do the cast in the assembler. Rename to compileCastTo, perhaps.
|
||
|
void evalCastTo(Type to)
|
||
|
{
|
||
|
assert(0, "evalCastTo not implemented for type " ~ toString);
|
||
|
}
|
||
|
|
||
|
// Do the cast in the compiler.
|
||
|
int[] doCastCTime(int[] data, Type to)
|
||
|
{
|
||
|
assert(0, "doCastCTime not implemented for type " ~ toString);
|
||
|
}
|
||
|
|
||
|
/* Cast two expressions to their common type, if any. Throw a
|
||
|
TypeException exception if not possible. This exception should be
|
||
|
caught elsewhere to give a more useful error message. Examples of
|
||
|
possible outcomes:
|
||
|
|
||
|
int, int -> does nothing
|
||
|
float, int -> converts the second paramter to float
|
||
|
int, float -> converts the first to float
|
||
|
bool, int -> throws an error
|
||
|
|
||
|
For various integer types, special rules apply. These are (for
|
||
|
the time being):
|
||
|
|
||
|
ulong, any -> ulong
|
||
|
long, any 32bit -> long
|
||
|
int, uint -> uint
|
||
|
|
||
|
Similar types (eg. uint, uint) will never convert types.
|
||
|
*/
|
||
|
static void castCommon(ref Expression e1, ref Expression e2)
|
||
|
{
|
||
|
Type t1 = e1.type;
|
||
|
Type t2 = e2.type;
|
||
|
|
||
|
if(t1 == t2) return;
|
||
|
|
||
|
Type common;
|
||
|
|
||
|
// Apply integral promotion rules first. TODO: Apply polysemous
|
||
|
// rules later.
|
||
|
if(t1.isIntegral && t2.isIntegral)
|
||
|
{
|
||
|
// ulong dominates all other types
|
||
|
if(t1.isUlong) common = t1;
|
||
|
else if(t2.isUlong) common = t2;
|
||
|
|
||
|
// long dominates over 32-bit values
|
||
|
else if(t1.isLong) common = t1;
|
||
|
else if(t2.isLong) common = t2;
|
||
|
|
||
|
// unsigned dominates over signed
|
||
|
else if(t1.isUint) common = t1;
|
||
|
else if(t2.isUint) common = t2;
|
||
|
else
|
||
|
{
|
||
|
assert(t1.isInt && t2.isInt, "unknown integral type encountered");
|
||
|
assert(0, "should never get here");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Find the common type
|
||
|
if(t1.canCastTo(t2)) common = t2; else
|
||
|
if(t2.canCastTo(t1)) common = t1;
|
||
|
else throw new TypeException(t1, t2);
|
||
|
}
|
||
|
|
||
|
// Wrap the expressions in CastExpression blocks if necessary.
|
||
|
common.typeCast(e1);
|
||
|
common.typeCast(e2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Internal types are types that are used internally by the compiler,
|
||
|
// eg. for type conversion or for special syntax. They can not be used
|
||
|
// for variables, neither directly nor indirectly.
|
||
|
abstract class InternalType : Type
|
||
|
{
|
||
|
final:
|
||
|
bool isLegal() { return false; }
|
||
|
int[] defaultInit() {assert(0);}
|
||
|
int getSize() { return 0; }
|
||
|
void parse(ref TokenArray toks) {assert(0);}
|
||
|
void resolve(Scope sc) {assert(0);}
|
||
|
}
|
||
|
|
||
|
// Handles the 'null' literal. This type is only used for
|
||
|
// conversions. You cannot declare variables of the nulltype.
|
||
|
class NullType : InternalType
|
||
|
{
|
||
|
this() { name = "null-type"; }
|
||
|
|
||
|
bool canCastTo(Type to)
|
||
|
{
|
||
|
return to.isArray || to.isObject;
|
||
|
}
|
||
|
|
||
|
void evalCastTo(Type to)
|
||
|
{
|
||
|
assert(to.getSize == 1);
|
||
|
|
||
|
// The value of null is always zero. There is no value on the
|
||
|
// stack to convert, so just push it.
|
||
|
tasm.push(0);
|
||
|
}
|
||
|
|
||
|
int[] doCastCTime(int[] data, Type to)
|
||
|
{
|
||
|
assert(to.getSize == 1);
|
||
|
assert(data.length == 0);
|
||
|
return [0];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Handles all the built-in types. These are: int, uint, long, ulong,
|
||
|
// float, double, bool, char and the "void" type, which is represented
|
||
|
// by an empty string. The void type is only allowed in special cases
|
||
|
// (currently only in function return types), and is not parsed
|
||
|
// normally through identify()/parse(). Instead it is created directly
|
||
|
// with the constructor.
|
||
|
class BasicType : Type
|
||
|
{
|
||
|
this() {}
|
||
|
|
||
|
// Create the given type directly without parsing. Use an empty
|
||
|
// string "" for the void type. This is the only way a void type can
|
||
|
// be created.
|
||
|
this(char[] tn)
|
||
|
{
|
||
|
if(!isBasic(tn))
|
||
|
fail("BasicType does not support type " ~ tn);
|
||
|
|
||
|
name = tn;
|
||
|
|
||
|
// Cache the class to save some overhead
|
||
|
store[tn] = this;
|
||
|
}
|
||
|
|
||
|
private static BasicType[char[]] store;
|
||
|
|
||
|
// Get a basic type of the given name. This will not allocate a new
|
||
|
// instance if another instance already exists.
|
||
|
static BasicType get(char[] tn)
|
||
|
{
|
||
|
if(tn in store) return store[tn];
|
||
|
|
||
|
return new BasicType(tn);
|
||
|
}
|
||
|
|
||
|
// Shortcuts
|
||
|
static BasicType getVoid() { return get(""); }
|
||
|
static BasicType getInt() { return get("int"); }
|
||
|
static BasicType getUint() { return get("uint"); }
|
||
|
static BasicType getLong() { return get("long"); }
|
||
|
static BasicType getUlong() { return get("ulong"); }
|
||
|
static BasicType getFloat() { return get("float"); }
|
||
|
static BasicType getDouble() { return get("double"); }
|
||
|
static BasicType getChar() { return get("char"); }
|
||
|
static BasicType getBool() { return get("bool"); }
|
||
|
|
||
|
static bool isBasic(char[] tn)
|
||
|
{
|
||
|
return (tn == "" || tn == "int" || tn == "float" || tn == "char" ||
|
||
|
tn == "bool" || tn == "uint" || tn == "long" ||
|
||
|
tn == "ulong" || tn == "double");
|
||
|
}
|
||
|
|
||
|
static bool canParse(TokenArray toks)
|
||
|
{
|
||
|
Token t;
|
||
|
if(!isNext(toks, TT.Identifier, t)) return false;
|
||
|
|
||
|
return isBasic(t.str);
|
||
|
}
|
||
|
|
||
|
override:
|
||
|
void parse(ref TokenArray toks)
|
||
|
{
|
||
|
Token t;
|
||
|
|
||
|
if(!isNext(toks, TT.Identifier, t) || !isBasic(t.str))
|
||
|
assert(0, "Internal error in BasicType.parse()");
|
||
|
|
||
|
// Get the name and the line from the token
|
||
|
name = t.str;
|
||
|
loc = t.loc;
|
||
|
}
|
||
|
|
||
|
bool isInt() { return name == "int"; }
|
||
|
bool isUint() { return name == "uint"; }
|
||
|
bool isLong() { return name == "long"; }
|
||
|
bool isUlong() { return name == "ulong"; }
|
||
|
bool isChar() { return name == "char"; }
|
||
|
bool isBool() { return name == "bool"; }
|
||
|
bool isFloat() { return name == "float"; }
|
||
|
bool isDouble() { return name == "double"; }
|
||
|
bool isVoid() { return name == ""; }
|
||
|
|
||
|
Scope getMemberScope()
|
||
|
{
|
||
|
if(isInt) return IntProperties.singleton;
|
||
|
if(isUint) return UintProperties.singleton;
|
||
|
if(isLong) return LongProperties.singleton;
|
||
|
if(isUlong) return UlongProperties.singleton;
|
||
|
if(isFloat) return FloatProperties.singleton;
|
||
|
if(isDouble) return DoubleProperties.singleton;
|
||
|
|
||
|
if(isChar || isBool)
|
||
|
return GenericProperties.singleton;
|
||
|
|
||
|
assert(isVoid);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// List the implisit conversions that are possible
|
||
|
bool canCastTo(Type to)
|
||
|
{
|
||
|
// We can convert between all integral types
|
||
|
if(to.isIntegral) return isIntegral;
|
||
|
|
||
|
// All numerical types can be converted to floating point
|
||
|
if(to.isFloating) return isNumerical;
|
||
|
|
||
|
// These types can be converted to strings.
|
||
|
if(to.isString)
|
||
|
return isNumerical || isChar || isBool;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool canCastCTime(Type to)
|
||
|
{
|
||
|
// We haven't implemented compile time string casting yet
|
||
|
if(to.isString) return false;
|
||
|
return canCastTo(to);
|
||
|
}
|
||
|
|
||
|
void evalCastTo(Type to)
|
||
|
{
|
||
|
assert(this != to);
|
||
|
assert(!isVoid);
|
||
|
|
||
|
int fromSize = getSize();
|
||
|
int toSize = to.getSize();
|
||
|
bool fromSign = isInt || isLong || isFloat || isBool;
|
||
|
|
||
|
if(to.isInt || to.isUint)
|
||
|
{
|
||
|
assert(isIntegral);
|
||
|
if(isLong || isUlong)
|
||
|
tasm.castLongToInt();
|
||
|
}
|
||
|
else if(to.isLong || to.isUlong)
|
||
|
{
|
||
|
if(isInt || isUint) tasm.castIntToLong(fromSign);
|
||
|
else assert(isUlong || isLong);
|
||
|
}
|
||
|
else if(to.isFloat || to.isDouble)
|
||
|
{
|
||
|
if(isIntegral)
|
||
|
tasm.castIntToFloat(this, to);
|
||
|
else if(isFloating)
|
||
|
tasm.castFloatToFloat(fromSize, toSize);
|
||
|
else assert(0);
|
||
|
}
|
||
|
else if(to.isString)
|
||
|
{
|
||
|
assert(!isVoid);
|
||
|
|
||
|
if(isIntegral)
|
||
|
tasm.castIntToString(this);
|
||
|
else if(isFloating)
|
||
|
tasm.castFloatToString(this);
|
||
|
else if(isBool) tasm.castBoolToString();
|
||
|
|
||
|
// Create an array from one element on the stack
|
||
|
else if(isChar) tasm.popToArray(1, 1);
|
||
|
else assert(0, name ~ " not done yet");
|
||
|
}
|
||
|
else
|
||
|
fail("Conversion " ~ toString ~ " to " ~ to.toString ~
|
||
|
" not implemented.");
|
||
|
}
|
||
|
|
||
|
int[] doCastCTime(int[] data, Type to)
|
||
|
{
|
||
|
assert(this != to);
|
||
|
assert(!isVoid);
|
||
|
|
||
|
int fromSize = getSize();
|
||
|
int toSize = to.getSize();
|
||
|
bool fromSign = isInt || isLong || isFloat || isBool;
|
||
|
|
||
|
assert(data.length == fromSize);
|
||
|
|
||
|
if(to.isInt || to.isUint)
|
||
|
{
|
||
|
assert(isIntegral);
|
||
|
data = data[0..1];
|
||
|
}
|
||
|
else if(to.isLong || to.isUlong)
|
||
|
{
|
||
|
if(isInt || isUint)
|
||
|
{
|
||
|
if(fromSign && to.isLong && data[0] < 0) data ~= -1;
|
||
|
else data ~= 0;
|
||
|
}
|
||
|
else assert(isUlong || isLong);
|
||
|
}
|
||
|
else if(to.isFloat)
|
||
|
{
|
||
|
assert(isNumerical);
|
||
|
|
||
|
float *fptr = cast(float*)data.ptr;
|
||
|
|
||
|
if(isInt) *fptr = data[0];
|
||
|
else if(isUint) *fptr = cast(uint)data[0];
|
||
|
else if(isLong) *fptr = *(cast(long*)data.ptr);
|
||
|
else if(isUlong) *fptr = *(cast(ulong*)data.ptr);
|
||
|
else if(isDouble) *fptr = *(cast(double*)data.ptr);
|
||
|
else assert(0);
|
||
|
data = data[0..1];
|
||
|
}
|
||
|
else if(to.isDouble)
|
||
|
{
|
||
|
assert(isNumerical);
|
||
|
|
||
|
if(data.length < 2) data.length = 2;
|
||
|
double *fptr = cast(double*)data.ptr;
|
||
|
|
||
|
if(isInt) *fptr = data[0];
|
||
|
else if(isUint) *fptr = cast(uint)data[0];
|
||
|
else if(isLong) *fptr = *(cast(long*)data.ptr);
|
||
|
else if(isUlong) *fptr = *(cast(ulong*)data.ptr);
|
||
|
else if(isFloat) *fptr = *(cast(float*)data.ptr);
|
||
|
else assert(0);
|
||
|
data = data[0..2];
|
||
|
}
|
||
|
else
|
||
|
fail("Compile time conversion " ~ toString ~ " to " ~ to.toString ~
|
||
|
" not implemented.");
|
||
|
|
||
|
assert(data.length == toSize);
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
int getSize()
|
||
|
{
|
||
|
if( isInt || isUint || isFloat || isChar || isBool )
|
||
|
return 1;
|
||
|
|
||
|
if( isLong || isUlong || isDouble )
|
||
|
return 2;
|
||
|
|
||
|
assert(0, "getSize() does not handle type '" ~ name ~ "'");
|
||
|
}
|
||
|
|
||
|
void resolve(Scope sc)
|
||
|
{
|
||
|
// Check that our given name is indeed valid
|
||
|
assert(isBasic(name));
|
||
|
}
|
||
|
|
||
|
int[] defaultInit()
|
||
|
{
|
||
|
int data[];
|
||
|
|
||
|
// Ints default to 0, bools to false
|
||
|
if(isInt || isUint || isBool) data = makeData!(int)(0);
|
||
|
// Ditto for double-size ints
|
||
|
else if(isLong || isUlong) data = makeData!(long)(0);
|
||
|
// Chars default to an illegal utf-32 value
|
||
|
else if(isChar) data = makeData!(int)(0x0000FFFF);
|
||
|
// Floats default to not a number
|
||
|
else if(isFloat) data = makeData!(float)(float.nan);
|
||
|
else if(isDouble) data = makeData!(double)(double.nan);
|
||
|
else
|
||
|
assert(0, "Type '" ~ name ~ "' has no default initializer");
|
||
|
|
||
|
assert(data.length == getSize, "size mismatch in defaultInit");
|
||
|
|
||
|
return data;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Represents a normal class name. The reason this is called
|
||
|
// "ObjectType" is because an actual variable (of this type) points to
|
||
|
// an object, not to a class. The meta-type ClassType should be used
|
||
|
// for variables that point to classes.
|
||
|
class ObjectType : Type
|
||
|
{
|
||
|
final:
|
||
|
private:
|
||
|
// Class that we represent (set by resolve()). Variables of this
|
||
|
// type may point to objects of this class, or to objects of
|
||
|
// subclasses. We only use an index, since classes might be forward
|
||
|
// referenced.
|
||
|
CIndex clsIndex;
|
||
|
|
||
|
public:
|
||
|
|
||
|
this() {}
|
||
|
this(MonsterClass mc)
|
||
|
{
|
||
|
assert(mc !is null);
|
||
|
name = mc.name.str;
|
||
|
loc = mc.name.loc;
|
||
|
clsIndex = mc.gIndex;
|
||
|
}
|
||
|
|
||
|
static bool canParse(TokenArray toks)
|
||
|
{
|
||
|
if(!isNext(toks, TT.Identifier)) return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
MonsterClass getClass()
|
||
|
{
|
||
|
assert(clsIndex != 0);
|
||
|
|
||
|
if(!global.isLoaded(clsIndex))
|
||
|
fail("Cannot use " ~ name ~
|
||
|
": class not found or forward reference", loc);
|
||
|
|
||
|
return global.getClass(clsIndex);
|
||
|
}
|
||
|
|
||
|
override:
|
||
|
// getClass does all the error checking we need
|
||
|
void validate() { getClass(); }
|
||
|
|
||
|
int getSize() { return 1; }
|
||
|
bool isObject() { return true; }
|
||
|
int[] defaultInit() { return makeData!(int)(0); }
|
||
|
|
||
|
bool canCastTo(Type type)
|
||
|
{
|
||
|
assert(clsIndex != 0);
|
||
|
|
||
|
if(type.isString) return true;
|
||
|
|
||
|
if(type.isObject)
|
||
|
{
|
||
|
auto ot = cast(ObjectType)type;
|
||
|
assert(ot !is null);
|
||
|
|
||
|
MonsterClass us = getClass();
|
||
|
MonsterClass other = ot.getClass();
|
||
|
|
||
|
assert(us != other);
|
||
|
|
||
|
// We can only upcast
|
||
|
return other.parentOf(us);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void evalCastTo(Type to)
|
||
|
{
|
||
|
assert(clsIndex != 0);
|
||
|
assert(canCastTo(to));
|
||
|
|
||
|
if(to.isObject)
|
||
|
{
|
||
|
auto tt = cast(ObjectType)to;
|
||
|
assert(tt !is null);
|
||
|
assert(clsIndex !is tt.clsIndex);
|
||
|
int cnum = tt.clsIndex;
|
||
|
|
||
|
// We have not decided what index to pass here yet. Think
|
||
|
// more about it when we implement full polymorphism.
|
||
|
assert(0, "not implemented");
|
||
|
tasm.upcast(cnum);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
assert(to.isString);
|
||
|
tasm.castObjToString();
|
||
|
}
|
||
|
|
||
|
// Members of objects are resolved in the class scope.
|
||
|
Scope getMemberScope()
|
||
|
{
|
||
|
return getClass().sc;
|
||
|
}
|
||
|
|
||
|
void parse(ref TokenArray toks)
|
||
|
{
|
||
|
Token t;
|
||
|
|
||
|
if(!isNext(toks, TT.Identifier, t))
|
||
|
assert(0, "Internal error in ObjectType.parse()");
|
||
|
|
||
|
// Get the name and the line from the token
|
||
|
name = t.str;
|
||
|
loc = t.loc;
|
||
|
}
|
||
|
|
||
|
// This is called when the type is defined, and it can forward
|
||
|
// reference classes that do not exist yet. The classes must exist
|
||
|
// by the time getClass() is called though, usually when class body
|
||
|
// (not just the header) is being resolved.
|
||
|
void resolve(Scope sc)
|
||
|
{
|
||
|
clsIndex = global.getForwardIndex(name);
|
||
|
assert(clsIndex != 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class ArrayType : Type
|
||
|
{
|
||
|
// What type is this an array of?
|
||
|
Type base;
|
||
|
|
||
|
this(Type btype)
|
||
|
{
|
||
|
base = btype;
|
||
|
name = base.name ~ "[]";
|
||
|
loc = base.loc;
|
||
|
}
|
||
|
|
||
|
override:
|
||
|
void validate() { assert(base !is null); base.validate(); }
|
||
|
int arrays() { return base.arrays() + 1; }
|
||
|
int getSize() { return 1; }
|
||
|
int[] defaultInit() { return makeData!(int)(0); }
|
||
|
Type getBase() { return base; }
|
||
|
|
||
|
Scope getMemberScope()
|
||
|
{
|
||
|
return ArrayProperties.singleton;
|
||
|
}
|
||
|
|
||
|
// We are a string (char[]) if the base type is char.
|
||
|
bool isString()
|
||
|
{
|
||
|
return base.isChar();
|
||
|
}
|
||
|
|
||
|
void parse(ref TokenArray toks)
|
||
|
{ assert(0, "array types aren't parsed"); }
|
||
|
|
||
|
void resolve(Scope sc)
|
||
|
{
|
||
|
base.resolve(sc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This type is given to type-names that are used as variables
|
||
|
// (ie. they are gramatically parsed by VariableExpr.) It's only used
|
||
|
// for expressions like int.max and p == int. Only basic types are
|
||
|
// supported. Classes are handled in a separate type.
|
||
|
class MetaType : InternalType
|
||
|
{
|
||
|
private:
|
||
|
static MetaType[char[]] store;
|
||
|
|
||
|
BasicType base;
|
||
|
|
||
|
public:
|
||
|
|
||
|
// Get a basic type of the given name. This will not allocate a new
|
||
|
// instance if another instance already exists.
|
||
|
static MetaType get(char[] tn)
|
||
|
{
|
||
|
if(tn in store) return store[tn];
|
||
|
|
||
|
return new MetaType(tn);
|
||
|
}
|
||
|
|
||
|
this(char[] baseType)
|
||
|
{
|
||
|
base = BasicType.get(baseType);
|
||
|
name = base.toString;
|
||
|
loc = base.loc;
|
||
|
|
||
|
store[name] = this;
|
||
|
}
|
||
|
|
||
|
// Return the scope belonging to the base type. This makes int.max
|
||
|
// work just like i.max.
|
||
|
Scope getMemberScope() { return base.getMemberScope(); }
|
||
|
|
||
|
Type getBase() { return base; }
|
||
|
bool isMeta() { return true; }
|
||
|
|
||
|
bool canCastTo(Type type)
|
||
|
{
|
||
|
return false;// type.isString;
|
||
|
}
|
||
|
|
||
|
void evalCastTo(Type to)
|
||
|
{
|
||
|
assert(to.isString);
|
||
|
// TODO: Fix static strings soon
|
||
|
assert(0, "not supported yet");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Types we might add later:
|
||
|
|
||
|
ClassType - on the form 'class MyClass', variable may refer to
|
||
|
MyClass or to any subclass of MyClass.
|
||
|
|
||
|
ListType - lists on the form { a; b; c } or similar
|
||
|
|
||
|
AAType - associative array (hash map)
|
||
|
|
||
|
TableType - something similar to Lua tables
|
||
|
|
||
|
StructType - structs
|
||
|
|
||
|
EnumType - enums and flags
|
||
|
|
||
|
FunctionType - pointer to a function with a given header. Since all
|
||
|
Monster functions are object members (methods), all
|
||
|
function pointers are in effect delegates.
|
||
|
|
||
|
*/
|